Browse Source

Presun z predchoziho repo

master
Martin Rejman 2 years ago
commit
c1093a54fe
  1. 18
      .gitignore
  2. 45
      install.sh
  3. 2
      src/.gitignore
  4. 32
      src/Dockerfile
  5. 22
      src/deploy/nginx-sample/nalodeni.sample.conf
  6. 1
      src/deploy/nginx/.gitignore
  7. 0
      src/deploy/nginx/.keep
  8. 27
      src/docker-compose.sample.yml
  9. 16
      src/docker-entrypoint.sh
  10. 0
      src/keycloak_oidc/__init__.py
  11. 57
      src/keycloak_oidc/auth.py
  12. 13
      src/keycloak_oidc/exceptions.py
  13. 29
      src/keycloak_oidc/middleware.py
  14. 14
      src/keycloak_oidc/urls.py
  15. 337
      src/keycloak_oidc/views.py
  16. 0
      src/main/__init__.py
  17. 160
      src/main/settings.py
  18. 208
      src/main/settings_global.py
  19. 57
      src/main/settings_local.distrib.py
  20. 96
      src/main/templates/base.html
  21. 38
      src/main/urls.py
  22. 16
      src/main/wsgi.py
  23. 15
      src/manage.py
  24. 0
      src/nalodeni/__init__.py
  25. 262
      src/nalodeni/auth.py
  26. 122
      src/nalodeni/fixtures/nalodeni_interestregion.json
  27. 75
      src/nalodeni/fixtures/nalodeni_interestregion.src.yaml
  28. 114
      src/nalodeni/fixtures/nalodeni_skills.json
  29. 70
      src/nalodeni/fixtures/nalodeni_skills.src.yaml
  30. 170
      src/nalodeni/fixtures/nalodeni_topics.json
  31. 108
      src/nalodeni/fixtures/nalodeni_topics.src.yaml
  32. 203
      src/nalodeni/forms.py
  33. 76
      src/nalodeni/migrations/0001_initial.py
  34. 17
      src/nalodeni/migrations/0002_remove_appuser_postcode.py
  35. 18
      src/nalodeni/migrations/0003_appuser_district.py
  36. 18
      src/nalodeni/migrations/0004_appuser_postcode.py
  37. 18
      src/nalodeni/migrations/0005_appuser_emailtoken.py
  38. 17
      src/nalodeni/migrations/0006_auto_20180312_0112.py
  39. 35
      src/nalodeni/migrations/0007_auto_20180312_0213.py
  40. 18
      src/nalodeni/migrations/0008_appuser_city.py
  41. 22
      src/nalodeni/migrations/0009_auto_20180323_1145.py
  42. 18
      src/nalodeni/migrations/0010_auto_20180323_1214.py
  43. 27
      src/nalodeni/migrations/0011_auto_20180323_1312.py
  44. 23
      src/nalodeni/migrations/0012_auto_20180325_1851.py
  45. 28
      src/nalodeni/migrations/0013_auto_20180325_2109.py
  46. 18
      src/nalodeni/migrations/0014_auto_20180325_2110.py
  47. 23
      src/nalodeni/migrations/0015_auto_20180325_2326.py
  48. 18
      src/nalodeni/migrations/0016_appuser_interestedin.py
  49. 28
      src/nalodeni/migrations/0017_auto_20180410_1144.py
  50. 23
      src/nalodeni/migrations/0018_auto_20180410_1547.py
  51. 28
      src/nalodeni/migrations/0019_auto_20180617_1104.py
  52. 33
      src/nalodeni/migrations/0020_auto_20180617_1146.py
  53. 28
      src/nalodeni/migrations/0021_auto_20180617_1310.py
  54. 18
      src/nalodeni/migrations/0022_appuser_email_contact_token.py
  55. 19
      src/nalodeni/migrations/0023_appuser_ts_for_ldap_sync.py
  56. 18
      src/nalodeni/migrations/0024_appregemail_data_consent.py
  57. 18
      src/nalodeni/migrations/0025_appregemail_dc_stamp.py
  58. 18
      src/nalodeni/migrations/0026_appuser_dc_stamp.py
  59. 75
      src/nalodeni/migrations/0027_auto_20180625_0314.py
  60. 32
      src/nalodeni/migrations/0028_auto_20180707_1508.py
  61. 19
      src/nalodeni/migrations/0029_appuser_createdstamp.py
  62. 19
      src/nalodeni/migrations/0030_appregemail_createdstamp.py
  63. 60
      src/nalodeni/migrations/0031_auto_20180815_0639.py
  64. 22
      src/nalodeni/migrations/0032_auto_20180818_1728.py
  65. 18
      src/nalodeni/migrations/0033_auto_20180818_1729.py
  66. 81
      src/nalodeni/migrations/0034_auto_20180818_1730.py
  67. 18
      src/nalodeni/migrations/0035_newsletter_is_del.py
  68. 18
      src/nalodeni/migrations/0036_newsletter_kind.py
  69. 18
      src/nalodeni/migrations/0037_auto_20180818_1934.py
  70. 18
      src/nalodeni/migrations/0038_newsmsg_is_del.py
  71. 18
      src/nalodeni/migrations/0039_auto_20180819_0221.py
  72. 43
      src/nalodeni/migrations/0040_auto_20180819_0222.py
  73. 22
      src/nalodeni/migrations/0041_auto_20180819_0254.py
  74. 18
      src/nalodeni/migrations/0042_auto_20180819_0255.py
  75. 18
      src/nalodeni/migrations/0043_auto_20180819_0257.py
  76. 18
      src/nalodeni/migrations/0044_auto_20180819_0847.py
  77. 23
      src/nalodeni/migrations/0045_auto_20180819_0848.py
  78. 18
      src/nalodeni/migrations/0046_auto_20180819_0900.py
  79. 18
      src/nalodeni/migrations/0047_newsletter_recipients.py
  80. 25
      src/nalodeni/migrations/0048_auto_20180819_1025.py
  81. 28
      src/nalodeni/migrations/0049_auto_20181123_1607.py
  82. 22
      src/nalodeni/migrations/0050_auto_20190220_2358.py
  83. 23
      src/nalodeni/migrations/0051_auto_20190221_0035.py
  84. 34
      src/nalodeni/migrations/0052_auto_20190303_1151.py
  85. 23
      src/nalodeni/migrations/0053_auto_20190303_1207.py
  86. 0
      src/nalodeni/migrations/__init__.py
  87. 23
      src/nalodeni/migrations/does-not-work/0002_auto_20180228_1631.py
  88. 17
      src/nalodeni/migrations/works/0002_remove_appuser_postcode.py
  89. 18
      src/nalodeni/migrations/works/0003_appuser_district.py
  90. 18
      src/nalodeni/migrations/works/0004_appuser_postcode.py
  91. 936
      src/nalodeni/models.py
  92. 569
      src/nalodeni/news.py
  93. 197
      src/nalodeni/people.py
  94. 8
      src/nalodeni/static/css/main.css
  95. 13
      src/nalodeni/static/css/main_local.css
  96. BIN
      src/nalodeni/static/fonts/BebasNeue/Bebas Neue - SIL Open Font License 1.1.pdf
  97. BIN
      src/nalodeni/static/fonts/BebasNeue/BebasNeue-Bold.ttf
  98. BIN
      src/nalodeni/static/fonts/BebasNeue/BebasNeue-Book.ttf
  99. BIN
      src/nalodeni/static/fonts/BebasNeue/BebasNeue-Light.ttf
  100. BIN
      src/nalodeni/static/fonts/BebasNeue/BebasNeue-Regular.ttf

18
.gitignore

@ -0,0 +1,18 @@
# git ls-files --others --exclude-from=.git/info/exclude
# Lines that start with '#' are comments.
# For a project mostly in C, the following would be a good set of
# exclude patterns (uncomment them if you want to use them):
# *.[oa]
# *~
local_python
lopy
static_files
log_files
venv
*.sw[op]
*.pyc
settings_local.py
docker-compose.yml

45
install.sh

@ -0,0 +1,45 @@
virtualenv -p python3.5 venv
source ./venv/bin/activate
pip install Django==2.0.3 # 2.0.3
# s binarne distribuovanou libssl
pip install psycopg2
pip install django-statici18n # preklad Javascript souboru
# nahrada za SUDS, zalozeno na lxml
#pip install zeep
pip install bleach
##
# Keycloak SSO
##
#pip install git+https://github.com/jhuapl-boss/django-oidc.git
#pip install git+https://github.com/jhuapl-boss/drf-oidc-auth.git
#pip install git+https://github.com/jhuapl-boss/boss-oidc.git
pip install -U django-auth-oidc pyjwkest
#pip install -U service_identity
#pip install -U openid_connect
##
# Database setup
##
psql <<SQL
CREATE USER piratinalodeni;
CREATE DATABASE piratinalodeni OWNER=piratinalodeni ENCODING='UTF8' LC_COLLATE='cs_CZ.UTF-8' template template0;
SQL
##
# App Setup
##
./src/manage.py makemigrations nalodeni
./src/manage.py migrate

2
src/.gitignore

@ -0,0 +1,2 @@
nalodeni.env

32
src/Dockerfile

@ -0,0 +1,32 @@
FROM debian:stretch
#FROM python:3
EXPOSE 8000
WORKDIR /
RUN apt-get update
RUN apt-get install -y python-virtualenv python-pip
RUN virtualenv -p python3 venv
# ponechano kvuli vyuziti cache
#RUN bash -c 'source /venv/bin/activate; pip install -r pip-requirements.txt'
RUN bash -c 'source /venv/bin/activate; pip install Django==2.0.3 psycopg2==2.7.4 django-statici18n==1.7.0 django-auth-oidc==0.4.5 pyjwkest==1.4.0 bleach==2.1.4 django-anymail==3.0 html2text==2018.1.9'
ADD . /nalodeni/src/
#RUN bash -c 'touch /nalodeni/src/main/settings_local.py' # this file needs to exist
#RUN adduser --disabled-login --quiet --gecos nalodeni nalodeni
#RUN chown -R nalodeni:nalodeni /nalodeni/
#RUN chmod u+x /nalodeni/src/docker-entrypoint.sh
RUN bash -c 'adduser --disabled-login --quiet --gecos nalodeni nalodeni && \
chmod -R o+r /nalodeni/ && \
chown -R nalodeni:nalodeni /nalodeni/src/static_files && \
chmod o+x /nalodeni/src/docker-entrypoint.sh && \
touch /nalodeni/src/main/settings_local.py '
USER nalodeni
ENTRYPOINT /nalodeni/src/docker-entrypoint.sh

22
src/deploy/nginx-sample/nalodeni.sample.conf

@ -0,0 +1,22 @@
upstream nalodeni.pirati.io {
ip_hash;
server nalodeni:8000;
}
server {
server_name nalodeni.pirati.io;
listen 8001;
location / {
proxy_pass http://nalodeni.pirati.io/;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location /static/ {
alias /static_nalodeni/;
}
}

1
src/deploy/nginx/.gitignore

@ -0,0 +1 @@
nalodeni.conf

0
src/deploy/nginx/.keep

27
src/docker-compose.sample.yml

@ -0,0 +1,27 @@
version: '2'
services:
nginx:
image: nginx:latest
container_name: nalodeni-nginx
ports:
- "80:8001"
volumes:
- ./deploy/nginx:/etc/nginx/conf.d
- vol_static_nalodeni:/static_nalodeni
depends_on:
- nalodeni
nalodeni:
build: .
container_name: nalodeni
command: bash -c "/nalodeni/src/docker-entrypoint.sh"
volumes:
- vol_static_nalodeni:/nalodeni/src/static_files
ports:
- "8000"
environment:
- NALODENI_DEBUG=off
- NALODENI_DEBUG_LOCAL=off
volumes:
vol_static_nalodeni:

16
src/docker-entrypoint.sh

@ -0,0 +1,16 @@
#!/bin/bash
trap "echo TRAPed signal" HUP INT QUIT KILL TERM
source /venv/bin/activate
cd /nalodeni/src
python manage.py migrate
python manage.py collectstatic --noinput
python manage.py loaddata nalodeni_newsletter nalodeni_topics nalodeni_skills
PYTHONIOENCODING=utf-8 python manage.py runserver 0.0.0.0:8000

0
src/keycloak_oidc/__init__.py

57
src/keycloak_oidc/auth.py

@ -0,0 +1,57 @@
import os
import requests
from django.conf import settings
from openid_connect import connect, connect_url
from openid_connect._oidc import OpenIDClient, TokenResponse
class OpenIDClientCustom(OpenIDClient):
def refresh_session(self, refresh_token):
r = requests.post(self.token_endpoint, auth=self.auth, data=dict(
grant_type="refresh_token",
refresh_token=refresh_token
), headers={'Accept': 'application/json'})
if r.status_code != 200:
#r.raise_for_status()
return None
resp = TokenResponse(r.json(), self)
if "scope" in resp._data:
resp.scope = set(self.translate_scope_out(set(resp._data["scope"].split(" "))))
if not hasattr(resp, "scope") or "openid" in resp.scope:
resp.id = self.get_id(resp)
return resp
server = None
def get_server_orig():
global server
if server is not None:
return server
AUTH_URL = os.environ.get("AUTH_URL")
if AUTH_URL:
server = connect_url(AUTH_URL)
else:
server = connect(settings.AUTH_SERVER, settings.AUTH_CLIENT_ID,
settings.AUTH_CLIENT_SECRET,
getattr(settings, 'AUTH_PROTOCOL', None))
return server
def get_server():
global server
if server is not None:
return server
server = OpenIDClientCustom( settings.AUTH_SERVER, settings.AUTH_CLIENT_ID,
settings.AUTH_CLIENT_SECRET)
return server

13
src/keycloak_oidc/exceptions.py

@ -0,0 +1,13 @@
# encoding: utf8
class MissingSsoInfo(Exception):
pass
class EmailVersusIdentityMismatch(Exception):
pass
class UsernameAlreadyTaken(Exception):
pass
class EmailAlreadyTaken(Exception):
pass

29
src/keycloak_oidc/middleware.py

@ -0,0 +1,29 @@
# -*- encoding: utf-8 -*-
import time
import keycloak_oidc.views
class KeycloakSessionRefreshMiddleware(object):
def __init__(self, get_response):
self.get_response = get_response
# One-time configuration and initialization.
def __call__(self, request):
# Code to be executed for each request before
# the view (and later middleware) are called.
refresh_expires_at = request.session.get('refresh_expires_at',None)
#print("refresh_expires_at", refresh_expires_at, time.time())
if refresh_expires_at is not None:
if refresh_expires_at - time.time() < 5*60: # 5 minutes before expiration
rslt = keycloak_oidc.views.renew_session(request)
if rslt != 0:
# renew not successful
return keycloak_oidc.views.logout(request, multiSessionFound=False)
return self.get_response(request)

14
src/keycloak_oidc/urls.py

@ -0,0 +1,14 @@
from django.conf.urls import url
from . import views
app_name = 'keycloak_oidc'
urlpatterns = [
url(r'^$', views.login, name='login-base'),
url(r'^login/$', views.login, name='login'),
url(r'^login/(?P<idp_hint>[a-z]+)/$', views.login, name='login_idp'),
url(r'^done/$', views.callback, name='login-done'),
url(r'^logout/$', views.logout, name='logout', kwargs={'ssoLogout':True}),
url(r'^logoutLocal/$', views.logout, name='logout'),
url(r'^account/$', views.account, name='account'),
url(r'^refresh_session/$', views.refresh_session, name='refresh-session'),
]

337
src/keycloak_oidc/views.py

@ -0,0 +1,337 @@
# encoding: utf8
from importlib import import_module
import logging
from jwkest.jwt import JWT
from requests.exceptions import HTTPError
from django.contrib import auth
from django.contrib import messages
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType
from django.shortcuts import redirect, resolve_url
from django.urls import reverse
from django.utils.http import is_safe_url
from django.conf import settings as appSettings
from . import auth as _auth
from . import exceptions as sso_exc
logger = logging.getLogger(__name__)
try:
LOGIN_REDIRECT_URL = appSettings.LOGIN_REDIRECT_URL
except AttributeError:
LOGIN_REDIRECT_URL = '/'
try:
LOGOUT_REDIRECT_URL = appSettings.LOGOUT_REDIRECT_URL
except AttributeError:
LOGOUT_REDIRECT_URL = '/sso/logout/'
if LOGOUT_REDIRECT_URL is None:
LOGOUT_REDIRECT_URL = '/sso/logout/'
try:
AUTH_SCOPE = appSettings.AUTH_SCOPE
except AttributeError:
AUTH_SCOPE = ('openid',)
try:
AUTH_AVAIL_IDP = appSettings.AUTH_AVAIL_IDP
except AttributeError:
AUTH_AVAIL_IDP = []
try:
GET_USER_FUNCTION = appSettings.AUTH_GET_USER_FUNCTION
except AttributeError:
GET_USER_FUNCTION = 'keycloak_oidc:get_user_by_username'
try:
LOGOUT_REDIRECT_VIEW = appSettings.AUTH_LOGOUT_REDIRECT_VIEW
except AttributeError:
LOGOUT_REDIRECT_VIEW = 'nalodeni:about'
try:
LOGIN_ROLE_REQUIRED = appSettings.AUTH_LOGIN_ROLE_REQUIRED
except AttributeError:
LOGIN_ROLE_REQUIRED = None
try:
LOGIN_ROLE_REQUIRED_ANY = appSettings.AUTH_LOGIN_ROLE_REQUIRED_ANY
except AttributeError:
LOGIN_ROLE_REQUIRED_ANY = False
jwt_singleton = JWT()
def _import_object(path, def_name):
try:
mod, cls = path.split(':', 1)
except ValueError:
mod = path
cls = def_name
return getattr(import_module(mod), cls)
get_user = _import_object(GET_USER_FUNCTION, 'get_user')
def login(request, idp_hint=None):
return_path = request.GET.get(auth.REDIRECT_FIELD_NAME, "/")
if idp_hint and idp_hint not in AUTH_AVAIL_IDP:
idp_hint = None
try:
srv = _auth.get_server()
except Exception as e:
messages.error(request, "Bohužel nastaly potíže s centrálním přihlášením uživatele.")
logger.error('Chyba SSO login: %s' % e)
return redirect(request.build_absolute_uri("/"))
return redirect(_auth.get_server().authorize(
redirect_uri = request.build_absolute_uri(reverse("keycloak_oidc:login-done")),
state = return_path,
scope = AUTH_SCOPE,
) + "&kc_locale=" + appSettings.AUTH_SSO_LOCALE + (
('&kc_idp_hint=' + idp_hint) if idp_hint is not None else ""
)
)
def renew_session(request, returnHttp=False):
"""
Refresh the session using a refresh_token.
"""
reftoken = request.session.get('refresh_token', None)
if reftoken is None:
return -3
#print("Refresh token IAT:", jwt_singleton.unpack(reftoken).payload()['iat'])
#print(jwt_singleton.unpack(reftoken).payload())
try:
srv = _auth.get_server()
except Exception as e:
messages.error(request,"Bohužel nastaly potíže s centrálním SSO serverem.")
logger.error('Chyba SSO renew: %s' % e)
if returnHttp:
return redirect(request.build_absolute_uri("/"))
else:
return -1
new_token = srv.refresh_session(reftoken)
if new_token is None:
if returnHttp:
return redirect(request.build_absolute_uri("/"))
else:
return -2
rt = new_token._data['refresh_token']
#print("Refresh token NEW IAT:", jwt_singleton.unpack(rt).payload()['iat'])
rt_payload = jwt_singleton.unpack(rt).payload()
# save the new refresh token
request.session['refresh_token'] = rt
request.session['refresh_expires_at'] = rt_payload['exp']
if returnHttp:
return redirect(request.build_absolute_uri("/"))
else:
return 0
def callback(request):
return_path = request.GET.get("state",'/')
sso_code = request.GET.get('code','')
if sso_code == "" and return_path == "":
messages.error(request, "Chyba při přihlášení na centrálním serveru SSO.")
return redirect(request.build_absolute_uri("/"))
else:
try:
res = _auth.get_server().request_token(
redirect_uri = request.build_absolute_uri(reverse("keycloak_oidc:login-done")),
code = sso_code,
)
except HTTPError as e:
messages.error(request, "Přihlášení uživatele se nezdařilo. Chyba komunikace s centrálním serverem přihlášení.")
logger.error('Chyba SSO callback: %s' % e)
return redirect(request.build_absolute_uri("/"))
#print(res)
#print(dir(res))
#print(res.id_token)
#print(res.access_token)
id_token_data = jwt_singleton.unpack(res.id_token).payload()
reftoken = jwt_singleton.unpack(res._data['refresh_token']).payload()
# Get the user from local DB
#
try:
user = get_user(id_token_data)
user.backend = 'django.contrib.auth.backends.ModelBackend'
except sso_exc.MissingSsoInfo as e:
messages.error(request, "Účet na Pirátské identitě není řádně vyplněn. %s" % e )
messages.info(request,"Zkontrolujte údaje na <a href='%s' target='_blank'>auth.pirati.cz</a>." % (appSettings.AUTH_SERVER + "account/"), extra_tags="safe")
logger.error('SSO account incomplete 1: %s' % e)
return redirect(request.build_absolute_uri("/"))
except sso_exc.EmailVersusIdentityMismatch as e:
messages.error(request, "Účet v nalodění a na Pirátské identitě nesouhlasí.")
messages.info(request,"Obraťte se na technický odbor Pirátů.")
logger.error('SSO account email vs. identity mismatch: %s' % e)
return redirect(request.build_absolute_uri("/"))
except sso_exc.UsernameAlreadyTaken as e:
messages.error(request, "%s" % e)
messages.info(request,'Pokud se jedná o vaše uživatelské jméno,'
+ 'obraťte se na technický odbor Pirátů.')
logger.error('Username already taken in Nalodeni: %s' % e)
return redirect(request.build_absolute_uri("/"))
except sso_exc.EmailAlreadyTaken as e:
messages.error(request, "%s" % e)
messages.info(request,'Pokud se jedná o váš email,'
+ 'obraťte se na technický odbor Pirátů.')
logger.error('Email already taken in Nalodeni: %s' % e)
return redirect(request.build_absolute_uri("/"))
# Process the user roles, if any ( taken from bossoidc.backend )
#
jwt = jwt_singleton.unpack(res.access_token).payload()
#print("access_token:", jwt_singleton.unpack(res.access_token).payload())
#print("id_token:", jwt_singleton.unpack(res.id_token).payload())
#### if 'realm_access' in jwt: # Session logins and Bearer tokens from password Grant Types
#### roles = jwt['realm_access']['roles']
#### else:
#### roles = []
resource_roles = []
if 'resource_access' in jwt:
if jwt['azp'] in jwt['resource_access']:
if 'roles' in jwt['resource_access'][jwt['azp']]:
resource_roles = jwt['resource_access'][jwt['azp']]['roles']
if (LOGIN_ROLE_REQUIRED_ANY and len(resource_roles) == 0) or (LOGIN_ROLE_REQUIRED is not None and LOGIN_ROLE_REQUIRED not in resource_roles):
messages.error(request, "Váš pirátský účet nemá přidělené potřebné přístupové role. Přístup odmítnut.")
logger.error('SSO account %s: no roles assigned' % (user.ssoUid,))
return redirect(request.build_absolute_uri("/"))
# Update user information
#
if 'given_name' in jwt and 'family_name' in jwt:
user.first_name = jwt['given_name']
user.last_name = jwt['family_name']
else:
messages.error(request, "Účet na Pirátské identitě není řádně vyplněn.")
messages.info(request,"Zkontrolujte údaje na <a href='%s' target='_blank'>auth.pirati.cz</a>." % (appSettings.AUTH_SERVER + "account/"), extra_tags="safe")
logger.error('SSO account incomplete 2: %s' % e)
return redirect(request.build_absolute_uri("/"))
user.loginSession = jwt['session_state']
user.save()
# Authenticate the user
#
auth.login(request, user)
request.session['openid_token'] = res.id_token
request.session['openid'] = id_token_data
request.session['refresh_token'] = res._data['refresh_token']
request.session['refresh_expires_at'] = reftoken['exp']
request.session['loginSession'] = user.loginSession
##
# Set client-system roles based on SSO roles
#
# After setting permissions, the User object should be reloaded,
# but we do a redirect, so it is fine.
##
#print(roles)
#print(resource_roles)
user.user_permissions.clear()
request.session['site_perms'] = []
# prepend "sso_" not to mix with other perms
for rr in resource_roles:
perm = 'sso_'+rr
request.session['site_perms'].append(perm)
# tell the User to calculate needed permissions
request.session['spc'] = user.do_site_perms_calc(request.session['site_perms'])
# tell us, what roles do we have
if appSettings.DEBUG:
print("Roles assigned:", request.session['site_perms'])
print("Roles calculated:", request.session['spc'])
url_is_safe = is_safe_url(
url = return_path,
host = request.get_host(),
allowed_hosts = set(request.get_host()),
require_https = request.is_secure(),
)
if not url_is_safe:
return redirect(resolve_url(LOGIN_REDIRECT_URL))
return redirect(return_path)
def logout(request, multiSessionFound=False, ssoLogout=False):
id_token = request.session.get('openid_token', None)
if request.user.is_authenticated:
if not multiSessionFound:
# reset the DB session, because this is
# a user requested logout
request.user.loginSession = None
request.user.save()
request.session['loginSession'] = None
auth.logout(request)
if ssoLogout:
try:
srv = _auth.get_server()
except Exception as e:
messages.error(request,"Bohužel nastaly potíže s centrálním odhlášením uživatele.")
logger.error('Chyba SSO logout: %s' % e)
return redirect(request.build_absolute_uri("/"))
if _auth.get_server().end_session_endpoint and id_token is not None:
messages.info(request,"Byli jste odhlášení z aplikace i Centrální identity.")
return redirect(_auth.get_server().end_session(
post_logout_redirect_uri = request.build_absolute_uri(LOGOUT_REDIRECT_URL),
state = '',
id_token_hint = id_token,
))
else:
if id_token:
messages.info(request,"Byli jste odhlášení z aplikace. Vaše centrální identita na <a href='%s' target='_blank'>auth.pirati.cz</a> zůstává přihlášena." % (appSettings.AUTH_SERVER + "account/"), extra_tags="safe")
else:
messages.info(request,"Byli jste odhlášení z aplikace.")
return redirect(LOGOUT_REDIRECT_VIEW)
def account(request):
return redirect(
request.build_absolute_uri(appSettings.AUTH_SERVER+"account/")
+ "?kc_locale=" + appSettings.AUTH_SSO_LOCALE
)
def refresh_session(request):
"""
Does a keycloak session refresh via middleware.
"""
resp = { 'exp' : request.session.get('refresh_expires_at', None) }
return HttpResponse(json.dumps(resp), content_type="application/json")

0
src/main/__init__.py

160
src/main/settings.py

@ -0,0 +1,160 @@
# -*- encoding:utf-8 -*-
"""
Main settings file for one domain.
Domain: .CZ
"""
import os
from .settings_global import *
from .settings_local import *
##
# Load ENV variables with prefix NALODENI_ to UPDATE_CONFIGS
#
# Docker deployment needs setting to be supplied externally.
#
# ENV_APPROVED_UPDATES contains known variables we can set,
# the tuple contains "path" to the setting.
ENV_APPROVED_UPDATES = {
'AUTH_SERVER' : ('AUTH_SERVER',),
'AUTH_CLIENT_ID' : ('AUTH_CLIENT_ID',),
'AUTH_CLIENT_SECRET' : ('AUTH_CLIENT_SECRET',),
'AUTH_AVAIL_IDP' : ('AUTH_AVAIL_IDP',),
'HTTP_PROTOCOL' : ('HTTP_PROTOCOL',),
'BASE_DOMAIN' : ('BASE_DOMAIN',),
'BASE_SUBDOMAIN' : ('BASE_SUBDOMAIN',),
'BASE_PORT' : ('BASE_PORT',),
'PSQL_USER' : ('DATABASES','default','USER'),
'PSQL_PASSWORD' : ('DATABASES','default','PASSWORD'),
'PSQL_DBNAME' : ('DATABASES','default','NAME'),
'PSQL_HOST' : ('DATABASES','default','HOST'),
'PSQL_PORT' : ('DATABASES','default','PORT'),
'APP_REG_LIMIT_HARD' : ('APP_REG_LIMIT_HARD', ),
'APP_REG_LIMIT_SOFT' : ('APP_REG_LIMIT_SOFT', ),
'TOKEN_VALID_SEC' : ('TOKEN_VALID_SEC', ),
'EMAIL_BACKEND' : ('EMAIL_BACKEND',),
'EMAIL_HOST' : ('EMAIL_HOST',),
'EMAIL_PORT' : ('EMAIL_PORT',),
'EMAIL_HOST_USER' : ('EMAIL_HOST_USER',),
'EMAIL_HOST_PASSWORD' : ('EMAIL_HOST_PASSWORD',),
'EMAIL_USE_TLS' : ('EMAIL_USE_TLS',),
'EMAIL_USE_SSL' : ('EMAIL_USE_SSL',),
'EMAIL_SSL_CERTFILE' : ('EMAIL_SSL_CERTFILE',),
'EMAIL_SSL_KEYFILE' : ('EMAIL_SSL_KEYFILE',),
'DEBUG' : ('DEBUG',),
'DEBUG_LOCAL' : ('DEBUG_LOCAL',),
}
for evk in os.environ:
if evk[0:9] == "NALODENI_":
var_key = evk[9:]
if var_key in ENV_APPROVED_UPDATES:
val_type = os.environ[evk][0:2]
val = str(os.environ[evk][2:])
if val_type == "s-":
val = (val, ) # tuple
elif val_type == "b-":
val = (val == 'on', ) # tuple
elif val_type == "a-":
# nested tuple, one gets eaten by + operator
val = ( tuple(val.split(',')) ,)
else:
print("Wrong ENV value for '%s', skipping" % evk)
continue
if val == "":
val = (None, )
UPDATE_CONFIGS.append( ENV_APPROVED_UPDATES[var_key] + val )
##
# Update configs from settings_local.UPDATE_CONFIGS
#
# Each item is a list-like path to the setting to be
# updated, the last value of the list is the value of
# the setting updated. E.g.:
# ('CACHES', 'default', 'KEY_PREFIX', 'cz:'),
# is the same as
# CACHES['default']['KEY_PREFIX'] = 'cz:'
# written directly in the settings file.
#
# Use array references to update the existing array.
# [:-2] used to preserve the last array-like link, so
# we can update the value there, and not the local variable.
for item in UPDATE_CONFIGS:
p = vars()
for i in item[:-2]:
if not i in p:
p[i] = {}
p = p[i]
p[item[-2]]=item[-1]
#
##
## ##
# DO NOT EDIT BELOW !!! #
## ##
##
# SECURITY - do not change.
##
#CSRF_USE_SESSIONS = False
CSRF_COOKIE_HTTPONLY = True
CSRF_COOKIE_SECURE = (HTTP_PROTOCOL == 'https') # fix to HTTPS when available
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SECURE = (HTTP_PROTOCOL == 'https') # fix to HTTPS when available
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', HTTP_PROTOCOL)
##
# Domain setup
##
BASE_URL = HTTP_PROTOCOL + '://' + BASE_SUBDOMAIN + BASE_DOMAIN + ((':' + BASE_PORT) if BASE_PORT != "" else "")
SESSION_COOKIE_DOMAIN= BASE_SUBDOMAIN + BASE_DOMAIN
CSRF_COOKIE_DOMAIN=BASE_SUBDOMAIN + BASE_DOMAIN
CSRF_TRUSTED_ORIGINS = [ SESSION_COOKIE_DOMAIN , ]
ALLOWED_HOSTS = [ BASE_SUBDOMAIN + BASE_DOMAIN, ]
##
# Internationalization, https://docs.djangoproject.com/en/1.11/topics/i18n/
##
LANGUAGE_CODE = 'cs'
TIME_ZONE = 'Europe/Prague'
##
# User model
##
AUTH_USER_MODEL = "nalodeni.AppUser"
##
# Logger config
##
LOG_FILES = os.path.join(BASE_DIR, 'log_files')
LOG_INCOMING_REQUESTS_FILE = os.path.join(
LOG_FILES, 'incoming_requests_%s.log' % BASE_DOMAIN
)
##
# Media files path setup
##
MEDIA_ROOT = os.path.join(BASE_DIR,'media_files')
##
# Debugging settings
##
if DEBUG_LOCAL:
ALLOWED_HOSTS += [
'localhost',
]
CSRF_COOKIE_DOMAIN = "localhost"
SESSION_COOKIE_DOMAIN= CSRF_COOKIE_DOMAIN

208
src/main/settings_global.py

@ -0,0 +1,208 @@
"""
Django settings for main project.
Generated by 'django-admin startproject' using Django 2.0.2.
For more information on this file, see
https://docs.djangoproject.com/en/2.0/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/2.0/ref/settings/
"""
import os
# Used to update config values _after_ importing settings_local
UPDATE_CONFIGS = []
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '-replace-this-in-install-settings-3243v432'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False
DEBUG_LOCAL=False
DEBUG_PROPAGATE_EXCEPTIONS = True
LOG_INCOMING_REQUESTS = False
ALLOWED_HOSTS = []
HTTP_PROTOCOL = 'https'
BASE_DOMAIN = "localhost"
BASE_SUBDOMAIN = ""
BASE_PORT = ""
# Application definition
INSTALLED_APPS = [
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
#'statici18n', # e.g. for javascript translation
'keycloak_oidc',
'anymail',
'nalodeni',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'keycloak_oidc.middleware.KeycloakSessionRefreshMiddleware', # added
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.middleware.gzip.GZipMiddleware',
]
ROOT_URLCONF = 'main.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
#'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'django.template.context_processors.i18n', # for statici18n
],
},
},
]
WSGI_APPLICATION = 'main.wsgi.application'
# Database
# https://docs.djangoproject.com/en/2.0/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': '',
'USER': '',
'PASSWORD': '',
'HOST': '', # Set to empty string for localhost.
'PORT': '', # Set to empty string for default.
}
}
# Specify in settings
AUTH_USER_MODEL = None
# Other auth backends
AUTHENTICATION_BACKENDS = [
'django.contrib.auth.backends.ModelBackend',
'nalodeni.auth.EmailTokenAuthBackend',
]
# Password validation
# https://docs.djangoproject.com/en/2.0/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
# {
# 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
# },
# {
# 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
# },
# {
# 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
# },
]
# Internationalization
# https://docs.djangoproject.com/en/2.0/topics/i18n/
LANGUAGE_CODE = 'cs'
TIME_ZONE = 'Europe/Prague'
USE_I18N = True
USE_L10N = True
USE_TZ = False
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.0/howto/static-files/
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR,'static_files')
##
# Locale, translation files
##
LOCALE_PATHS = [
BASE_DIR + "/locale",
]
##
# Email setup
##
EMAIL_HOST="localhost"
EMAIL_PORT=25
#DEFAULT_FROM_EMAIL = 'nalodeni@pirati.cz'
ANYMAIL = {
"MAILGUN_API_KEY": "",
"MAILGUN_SENDER_DOMAIN": '',
"MAILGUN_API_URL": 'https://api.eu.mailgun.net/v3',
}
#EMAIL_BACKEND = "anymail.backends.mailgun.EmailBackend"
##
# SingleSignOn
#
# AUTH_SCOPE - list of scopes to request from the auth server
# AUTH_GET_USER_FUNCTION - name of a function that takes the user info dict,
# and returns an user object representing that user
##
AUTH_SERVER = "https://pttest1.kouzelnakrabicka.cz/auth/realms/pirati/"
AUTH_CLIENT_ID = "" # view setting_local.py
AUTH_CLIENT_SECRET = "" # view setting_local.py
AUTH_SCOPE = ['openid',]
AUTH_GET_USER_FUNCTION = 'nalodeni.models:get_user_by_keycloak_email'
AUTH_SSO_LOCALE = 'cs'
AUTH_AVAIL_IDP = []
#LOGIN_REDIRECT_URL = '/sso/login'
#LOGOUT_REDIRECT_URL = '/sso/logout'
##
# Email registration
##
APP_REG_LIMIT_HARD = 50
APP_REG_LIMIT_SOFT = 0
TOKEN_VALID_SEC = 30*60
##
# Defaults for settings_local variables
##

57
src/main/settings_local.distrib.py

@ -0,0 +1,57 @@
# -*- encoding:utf-8 -*-
"""
Application local settings:
- database
- server connections
"""
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '5k@dbpq_3+t7g+ylt5h1*ox79lnwp-qijry3y60^1r_9q*m5b('
##
# Database setup
##
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql', # Add 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
'NAME': 'mainApp', # Or path to database file if using sqlite3.
'USER': 'user', # Not used with sqlite3.
'PASSWORD': '', # Not used with sqlite3.
'HOST': '', # Set to empty string for localhost. Not used with sqlite3.
'PORT': '', # Set to empty string for default. Not used with sqlite3.
}
}
##
# Email setup
##
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
ANYMAIL = {
"MAILGUN_API_KEY": "",
"MAILGUN_SENDER_DOMAIN": '',
"MAILGUN_API_URL": 'https://api.eu.mailgun.net/v3',
"FROM_NAME" : "",
"FROM_EMAIL" : "",
"PUBLIC_TO_EMAIL" : "",
}
##
# Update config array with these values
##
UPDATE_CONFIGS = [
]
UPDATE_CONFIGS = [
#('CACHES', 'default', 'LOCATION', '/var/run/memcached/memcached.sock'),
('CACHES', 'default', 'KEY_PREFIX', 'mainApp:'),
('AUTH_CLIENT_ID','mainApp'),
('AUTH_CLIENT_SECRET',''),
('AUTH_SSO_LOCALE', 'cs'),
('AUTH_AVAIL_IDP',('facebook',)), # tuple
]
LOG_INCOMING_REQUESTS = False
DEBUG_LOCAL=False

96
src/main/templates/base.html

@ -0,0 +1,96 @@
<!doctype html>
<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7" lang=""> <![endif]-->
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8" lang=""> <![endif]-->
<!--[if IE 8]> <html class="no-js lt-ie9" lang=""> <![endif]-->
<!--[if gt IE 8]><!--> <html class="no-js" lang=""> <!--<![endif]-->
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title></title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="apple-touch-icon" href="apple-touch-icon.png">
<link rel="stylesheet" href="static/css/bootstrap.min.css">
<style>
body {
padding-top: 50px;
padding-bottom: 20px;
}
</style>
<link rel="stylesheet" href="static/css/bootstrap-theme.min.css">
<link rel="stylesheet" href="static/css/main.css">
<script src="static/js/vendor/modernizr-2.8.3-respond-1.4.2.min.js"></script>
</head>
<body>
<!--[if lt IE 8]>
<p class="browserupgrade">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p>
<![endif]-->
<nav class="navbar navbar-inverse navbar-fixed-top" role="navigation">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">Project name</a>
</div>
<div id="navbar" class="navbar-collapse collapse">
<form class="navbar-form navbar-right" role="form">
<div class="form-group">
<input type="text" placeholder="Email" class="form-control">
</div>
<div class="form-group">
<input type="password" placeholder="Password" class="form-control">
</div>
<button type="submit" class="btn btn-success">Sign in</button>
</form>
</div><!--/.navbar-collapse -->
</div>
</nav>
<!-- Main jumbotron for a primary marketing message or call to action -->
<div class="jumbotron">
<div class="container">
<h1>Hello, world!</h1>
<p>This is a template for a simple marketing or informational website. It includes a large callout called a jumbotron and three supporting pieces of content. Use it as a starting point to create something more unique.</p>
<p><a class="btn btn-primary btn-lg" href="#" role="button">Learn more &raquo;</a></p>
</div>
</div>
<div class="container">
<!-- Example row of columns -->
<div class="row">
<div class="col-md-4">
<h2>Heading</h2>
<p>Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. Donec sed odio dui. </p>
<p><a class="btn btn-default" href="#" role="button">View details &raquo;</a></p>
</div>
<div class="col-md-4">
<h2>Heading</h2>
<p>Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. Donec sed odio dui. </p>
<p><a class="btn btn-default" href="#" role="button">View details &raquo;</a></p>
</div>
<div class="col-md-4">
<h2>Heading</h2>
<p>Donec sed odio dui. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Vestibulum id ligula porta felis euismod semper. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus.</p>
<p><a class="btn btn-default" href="#" role="button">View details &raquo;</a></p>
</div>
</div>
<hr>
<footer>
<p>&copy; Company 2015</p>
</footer>
</div> <!-- /container -->
<script>window.jQuery || document.write('<script src="static/js/vendor/jquery-1.11.2.min.js"><\/script>')</script>
<script src="static/js/vendor/bootstrap.min.js"></script>
<script src="static/js/main.js"></script>
</body>
</html>

38
src/main/urls.py

@ -0,0 +1,38 @@
"""main URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/2.0/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import include, path, re_path
import nalodeni.urls
urlpatterns = [
#path('admin/', admin.site.urls),
path('', include(nalodeni.urls)),
# SSO
path('sso/', include('keycloak_oidc.urls')),
]
# Javascript translations
from django.views.i18n import JavaScriptCatalog
urlpatterns += [
path('jsi18n/nalodeni/',
JavaScriptCatalog.as_view(packages=['nalodeni']),
name='js-cat-website'),
]

16
src/main/wsgi.py

@ -0,0 +1,16 @@
"""
WSGI config for main project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/2.0/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "main.settings")
application = get_wsgi_application()

15
src/manage.py

@ -0,0 +1,15 @@
#!/usr/bin/env python
import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "main.settings")
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)

0
src/nalodeni/__init__.py

262
src/nalodeni/auth.py

@ -0,0 +1,262 @@
# -*- encoding: utf-8 -*-
import hashlib
from datetime import datetime
import logging
from django.contrib.auth import get_user_model
from django.utils.crypto import get_random_string
from django.core.mail import send_mail
from django.core.validators import validate_email
from django.core.exceptions import ValidationError
from django.conf import settings as appSettings
from . import models
# Get an instance of a logger
logger = logging.getLogger(__name__)
# Our user model
USER_MODEL = get_user_model()
class EmailTokenAuthBackend:
"""
Provides authorization via email workflow.
The user is verified by a token previously sent by email.
"""
def get_user(self, user_id):
try:
return USER_MODEL.objects.get(pk=user_id)
except USER_MODEL.DoesNotExist:
return None
def authenticate(self, request, emailToken=None):
if emailToken is None:
return None
# do we have a user with this valid token ?
try:
u = USER_MODEL.objects.get(emailToken=emailToken)
if u.etStamp and (datetime.now() - u.etStamp).total_seconds() < int(appSettings.TOKEN_VALID_SEC):
u.emailToken = None
u.etStamp = None
u.save()
return u
else:
u.emailToken = None
u.etStamp = None
u.save()
raise ValidationError("Přihlašovací odkaz vypršel, nechte si poslat nový.")
except USER_MODEL.DoesNotExist:
pass
# is this a first-time login, so the user has to be created ?
rslt = models.AppRegEmail.objects.filter(emailToken=emailToken)
if len(rslt) == 0:
return None
elif len(rslt) == 1:
reg = rslt[0]
if reg.etStamp and (datetime.now() - reg.etStamp).total_seconds() < int(appSettings.TOKEN_VALID_SEC):
rsltUsers = models.AppUser.objects.filter(username=reg.email)
if len(rsltUsers) != 0 :
raise ValidationError("Uživatelské jméno je již obsazeno.")
rsltUsers = models.AppUser.objects.filter(email=reg.email)
if len(rsltUsers) != 0 :
raise ValidationError("E-mailová adresa je již obsazena.")
# token valid, create the user
u = USER_MODEL()
u.username = reg.email
u.email = reg.email
u.postcode = reg.postcode
u.kind = reg.kind
u.interestedIn = reg.interestedIn
u.userform = reg.userform
u.dc_stamp = datetime.now()
u.save()
# remove the approved email from registration
reg.userform = None
reg.save()
reg.delete()
return u
else:
raise ValidationError("Registrační odkaz vypršel, nechte si poslat nový.")
else:
# multiple tokens, which should not happen
raise Exception("Multiple records with the same token.")
return None
def sendLoginToken(user):
"""
Generate and send a token to the user.
"""
emailToken = get_random_string(120)
user.emailToken = emailToken
user.etStamp = datetime.now()
user.save()
emailSubj = 'Piráti - nalodění - přihlašovací odkaz'
emailBody = """\
Dobrý den,
níže zasíláme přihlašovací odkaz do aplikace Pirátů "Nalodění":
{baseUrl}/prihlaseni/?t={emailToken}
Přihlásíte se kliknutím na odkaz, nebo jeho překopírováním do prohlížeče internetových stránek.
Odkaz je možné použít pouze jednou, v případě potřeby si nechte zaslat nový odkaz.
S pozdravem
Piráti
"""
send_mail(
emailSubj,
emailBody.format(
emailToken=emailToken,
baseUrl=appSettings.BASE_URL),
"nalodeni@pirati.cz",
[user.email], # email to ...
fail_silently=False,
)
def sendRegisterToken(f_email):
"""
Generate and send a token to the registered email.
"""
# Check for user non-existence with this email
f_email = f_email.strip().lower()
rslt = models.AppUser.objects.filter(email__iexact=f_email)
if len(rslt) == 0:
pass
elif len(rslt) == 1:
# send login token instead
return sendLoginToken(rslt[0])
else:
logger.error("More AppUser objects with the same email.")
return
# check registrations
rslt = models.AppRegEmail.objects.filter(email__iexact=f_email)
if len(rslt) == 1:
rt = rslt[0]
elif len(rslt) == 0:
rt = models.AppRegEmail()
rt.email = f_email
else:
logger.error("More AppRegEmail objects with the same email.")
return
sendRegisterTokenReg(rt)
def sendRegisterTokenReg(rt):
""" Send registration email to an already existing AppRegEmail instance 'rt'. """
# create token
emailToken = get_random_string(120)
rt.emailToken = emailToken
rt.etStamp = datetime.now()
rt.save()
emailSubj = 'Piráti - nalodění - registrační odkaz'
emailBody = """\
Dobrý den,
níže Vám zasíláme registrační odkaz do aplikace Pirátů "Nalodění":
{baseUrl}/prihlaseni/?t={emailToken}
V registraci pokračujte kliknutím na odkaz, nebo jeho překopírováním do prohlížeče internetových stránek.
Odkaz je možné použít pouze jednou, v případě potřeby si nechte zaslat nový odkaz.
S pozdravem
Piráti
"""
send_mail(
emailSubj,
emailBody.format(
emailToken=emailToken,
baseUrl=appSettings.BASE_URL),
"nalodeni@pirati.cz",
[rt.email], # email to ...
fail_silently=False,
)
def sendEmailContactVerificationToken(user):
""" Send validation email to user.email_contact. """
# The wanted new value, save it here so we do not accidentally change it,
# or someone else through the user-object.
ecw = user.email_contact
if ecw is None or ecw == "":
raise ValidationError("Chybí kontaktní email.")
# ValidationError se odchytava vyse
validate_email(ecw)
# create token
rand_str = get_random_string(120)
user.email_contact_token = "%s-%s" % ( int(datetime.now().timestamp()), rand_str )
user.email_contact_verified = False
user.save()
sha256hash = hashlib.sha256()
sha256hash.update(rand_str.encode('utf-8'))
sha256hash.update(ecw.encode('utf-8'))
emailToken = sha256hash.hexdigest()
emailSubj = 'Piráti - nalodění - ověření kontaktního emailu'
emailBody = """\
Dobrý den,
níže Vám zasíláme odkaz pro ověření platnosti kontaktní e-mailové adresy.
Změna adresy na {email_contact} byla vyžádána v aplikace Pirátů "Nalodění".
Adresu ověříte přihlášením se do aplikace Nalodění
(https://nalodeni.pirati.cz/prihlaseni/) a následným kliknutím na odkaz:
{baseUrl}/ja-pirat/profil/?t={emailToken}
Pokud jste tuto změnu nežádali, můžete tuto zprávu smazat.
S pozdravem
Piráti
"""
send_mail(
emailSubj,
emailBody.format(
email_contact = ecw,
emailToken=emailToken,
baseUrl=appSettings.BASE_URL),
"nalodeni@pirati.cz",
[ecw], # email to ...
fail_silently=False,
)
return True
#TODO:: osetreni transakci (pro pripad utoku typu DDoS)

122
src/nalodeni/fixtures/nalodeni_interestregion.json

@ -0,0 +1,122 @@
[
{
"model": "nalodeni.interestregion",
"pk": 1,
"fields": {
"name": "Celostátní",
"tag": "news-republic"
}
},
{
"model": "nalodeni.interestregion",
"pk": 3,
"fields": {
"name": "Hlavní město Praha",
"tag": "news-region-pha"
}
},
{
"model": "nalodeni.interestregion",
"pk": 4,
"fields": {
"name": "Jihočeský kraj",
"tag": "news-region-jhc"
}
},
{
"model": "nalodeni.interestregion",
"pk": 5,
"fields": {
"name": "Jihomoravský kraj",
"tag": "news-region-jhm"
<