Merge pull request #45 from DanielGrams/issue/44-tos

Issue/44 tos
This commit is contained in:
Daniel Grams 2020-12-20 16:27:11 +01:00 committed by GitHub
commit f1cb72186f
13 changed files with 129 additions and 171 deletions

View File

@ -68,13 +68,6 @@ Create `.env` file in the root directory or pass as environment variables.
| MAIL_SERVER | " |
| MAIL_USERNAME | " |
### Login with Google via OAuth
| Variable | Function |
| --- | --- |
| GOOGLE_OAUTH_CLIENT_ID | Client Id |
| GOOGLE_OAUTH_CLIENT_SECRET | Secret |
### Resolve addresses with Google Maps
| Variable | Function |

View File

@ -23,10 +23,6 @@ app.config["SECURITY_RECOVERABLE"] = True
app.config["SECURITY_CHANGEABLE"] = True
app.config["SECURITY_EMAIL_SENDER"] = os.getenv("MAIL_DEFAULT_SENDER")
app.config["LANGUAGES"] = ["en", "de"]
app.config["GOOGLE_OAUTH_CLIENT_ID"] = os.getenv("GOOGLE_OAUTH_CLIENT_ID")
app.config["GOOGLE_OAUTH_CLIENT_SECRET"] = os.getenv("GOOGLE_OAUTH_CLIENT_SECRET")
app.config["OAUTHLIB_INSECURE_TRANSPORT"] = True
app.config["OAUTHLIB_RELAX_TOKEN_SCOPE"] = True
# Generate a nice key using secrets.token_urlsafe()
app.config["SECRET_KEY"] = os.environ.get(
@ -86,14 +82,10 @@ app.json_encoder = DateTimeEncoder
# Setup Flask-Security
from project.models import User, Role
from project.forms.security import ExtendedRegisterForm
user_datastore = SQLAlchemySessionUserDatastore(db.session, User, Role)
security = Security(app, user_datastore)
# OAuth
from project.oauth import blueprint
app.register_blueprint(blueprint, url_prefix="/login")
security = Security(app, user_datastore, register_form=ExtendedRegisterForm)
from project import i10n
from project import jinja_filters

View File

@ -1,8 +1,10 @@
from flask import url_for
from flask_babelex import lazy_gettext
from flask_wtf import FlaskForm
from flask_wtf.file import FileField, FileAllowed
from wtforms import StringField, BooleanField, HiddenField
from wtforms.validators import Optional
from markupsafe import Markup
import re
import base64
@ -62,6 +64,24 @@ class Base64ImageForm(BaseImageForm):
obj.encoding_format = None
def get_accept_tos_markup():
tos_open = '<a href="%s">' % url_for("tos")
tos_close = "</a>"
privacy_open = '<a href="%s">' % url_for("privacy")
privacy_close = "</a>"
return Markup(
lazy_gettext(
"I read and accept %(tos_open)sTerms of Service%(tos_close)s and %(privacy_open)sPrivacy%(privacy_close)s.",
tos_open=tos_open,
tos_close=tos_close,
privacy_open=privacy_open,
privacy_close=privacy_close,
)
)
event_rating_choices = [
(0, lazy_gettext("0 (Little relevant)")),
(10, "1"),

View File

@ -14,7 +14,7 @@ from project.models import (
EventRejectionReason,
Image,
)
from project.forms.common import Base64ImageForm
from project.forms.common import Base64ImageForm, get_accept_tos_markup
from project.forms.widgets import CustomDateTimeField, TagSelectField
@ -88,15 +88,14 @@ class CreateEventSuggestionForm(FlaskForm):
"We recommend uploading a photo for the event. It looks a lot more, but of course it works without it."
),
)
accept_tos = BooleanField(
lazy_gettext(
"I confirm that I have clarified all information (text, images, etc.) that I upload into the system with regard to their rights of use and declare that they may be passed on."
),
validators=[DataRequired()],
)
accept_tos = BooleanField(validators=[DataRequired()])
submit = SubmitField(lazy_gettext("Create event suggestion"))
def __init__(self, **kwargs):
super(CreateEventSuggestionForm, self).__init__(**kwargs)
self._fields["accept_tos"].label.text = get_accept_tos_markup()
def populate_obj(self, obj):
for name, field in self._fields.items():
if name == "photo" and not obj.photo:

12
project/forms/security.py Normal file
View File

@ -0,0 +1,12 @@
from flask_security.forms import RegisterForm
from wtforms import BooleanField
from wtforms.validators import DataRequired
from project.forms.common import get_accept_tos_markup
class ExtendedRegisterForm(RegisterForm):
accept_tos = BooleanField(validators=[DataRequired()])
def __init__(self, *args, **kwargs):
super(ExtendedRegisterForm, self).__init__(*args, **kwargs)
self._fields["accept_tos"].label.text = get_accept_tos_markup()

View File

@ -1,67 +0,0 @@
from flask import flash
from flask_security import current_user, login_user
from flask_dance.contrib.google import make_google_blueprint
from flask_dance.consumer import oauth_authorized, oauth_error
from flask_dance.consumer.storage.sqla import SQLAlchemyStorage
from project.models import OAuth
from project import db, user_datastore
from flask_babelex import gettext
blueprint = make_google_blueprint(
scope=["profile", "email"],
storage=SQLAlchemyStorage(OAuth, db.session, user=current_user),
)
# create/login local user on successful OAuth login
@oauth_authorized.connect_via(blueprint)
def google_logged_in(blueprint, token): # pragma: no cover
if not token:
flash("Failed to log in.", category="error")
return False
resp = blueprint.session.get("/oauth2/v1/userinfo")
if not resp.ok:
msg = "Failed to fetch user info."
flash(msg, category="error")
return False
info = resp.json()
user_id = info["id"]
# Find this OAuth token in the database, or create it
oauth = OAuth.query.filter_by(
provider=blueprint.name, provider_user_id=user_id
).first()
if oauth is None:
oauth = OAuth(provider=blueprint.name, provider_user_id=user_id, token=token)
if oauth.user:
login_user(oauth.user, authn_via=["google"])
user_datastore.commit()
flash(gettext("Successfully signed in."), "success")
else:
# Create a new local user account for this user
user = user_datastore.create_user(email=info["email"])
# Associate the new local user account with the OAuth token
oauth.user = user
# Save and commit our database models
db.session.add_all([user, oauth])
db.session.commit()
# Log in the new local user account
login_user(user, authn_via=["google"])
user_datastore.commit()
flash(gettext("Successfully signed in."), "success")
# Disable Flask-Dance's default behavior for saving the OAuth token
return False
# notify on OAuth provider error
@oauth_error.connect_via(blueprint)
def google_error(blueprint, message, response): # pragma: no cover
msg = "OAuth error from {name}! message={message} response={response}".format(
name=blueprint.name, message=message, response=response
)
flash(msg, category="error")

View File

@ -438,10 +438,6 @@
{% endmacro %}
{% macro render_google_sign_in_button() %}
<a href="{{ url_for('google.login') }}" class="btn btn-google" role="button"><i class="fab fa-google mr-2"></i> {{ _('Sign in with Google') }}</a>
{% endmacro %}
{% macro render_google_place_autocomplete_header(location_only = False) %}
<script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key={{ "dev" | env_override('GOOGLE_MAPS_API_KEY') }}&libraries=places"></script>
<script>

View File

@ -1,5 +1,5 @@
{% extends "layout.html" %}
{% from "_macros.html" import render_google_sign_in_button, render_field_with_errors, render_field, render_field_errors %}
{% from "_macros.html" import render_field_with_errors, render_field, render_field_errors %}
{% block content %}
@ -24,9 +24,6 @@
<div class="my-2">
<a href="{{ url_for_security('register') }}" class="btn btn-dark"><i class="fa fa-user-plus mr-2"></i> {{ _('Register for free') }}</a>
</div>
<div class="my-2">
{{ render_google_sign_in_button() }}
</div>
</div>
</div>

View File

@ -1,5 +1,5 @@
{% extends "layout.html" %}
{% from "_macros.html" import render_google_sign_in_button, render_field_with_errors, render_field %}
{% from "_macros.html" import render_field_with_errors, render_field %}
{% block content %}
@ -11,11 +11,13 @@
{% if register_user_form.password_confirm %}
{{ render_field_with_errors(register_user_form.password_confirm) }}
{% endif %}
<div class="form-group form-check">
{{ register_user_form.accept_tos(class="form-check-input")|safe }}
{{ register_user_form.accept_tos.label(class="form-check-label") }}
</div>
{{ render_field(register_user_form.submit) }}
</form>
<hr class="my-4">
{{ render_google_sign_in_button() }}
{% endblock %}

View File

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2020-12-12 13:52+0100\n"
"POT-Creation-Date: 2020-12-20 15:15+0100\n"
"PO-Revision-Date: 2020-06-07 18:51+0200\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: de\n"
@ -114,10 +114,6 @@ msgstr "Verifiziert"
msgid "EventReviewStatus.rejected"
msgstr "Abgelehnt"
#: project/oauth.py:42 project/oauth.py:55
msgid "Successfully signed in."
msgstr "Erfolgreich eingeloggt."
#: project/utils.py:5
msgid "Event_"
msgstr "Event_"
@ -127,24 +123,24 @@ msgid "."
msgstr "."
#: project/forms/admin.py:8 project/templates/layout.html:139
#: project/views/root.py:24
#: project/views/root.py:25
msgid "Terms of service"
msgstr "Nutzungsbedingungen"
#: project/forms/admin.py:9 project/templates/layout.html:143
#: project/views/root.py:31
#: project/views/root.py:33
msgid "Legal notice"
msgstr "Impressum"
#: project/forms/admin.py:10 project/templates/_macros.html:972
#: project/forms/admin.py:10 project/templates/_macros.html:968
#: project/templates/layout.html:147
#: project/templates/widget/event_suggestion/create.html:171
#: project/views/root.py:38
#: project/views/root.py:41
msgid "Contact"
msgstr "Kontakt"
#: project/forms/admin.py:11 project/templates/layout.html:151
#: project/views/root.py:45
#: project/views/root.py:49
msgid "Privacy"
msgstr "Datenschutz"
@ -295,79 +291,89 @@ msgstr "Mitglied löschen"
msgid "Update member"
msgstr "Mitglied aktualisieren"
#: project/forms/common.py:12
#: project/forms/common.py:14
msgid "Copyright text"
msgstr "Copyright Text"
#: project/forms/common.py:18
#: project/forms/common.py:20
msgid "File"
msgstr "Datei"
#: project/forms/common.py:19
#: project/forms/common.py:21
msgid "Images only!"
msgstr "Nur Bilder!"
#: project/forms/common.py:22
#: project/forms/common.py:24
msgid "Delete image"
msgstr "Bild löschen"
#: project/forms/common.py:66
#: project/forms/common.py:75
#, python-format
msgid ""
"I read and accept %(tos_open)sTerms of Service%(tos_close)s and "
"%(privacy_open)sPrivacy%(privacy_close)s."
msgstr ""
"Ich habe die %(tos_open)sNutzungsbedingungen%(tos_close)s und die "
"%(privacy_open)sDatenschutzerklärung%(privacy_close)s gelesen und "
"akzeptiere diese."
#: project/forms/common.py:83
msgid "0 (Little relevant)"
msgstr "0 (Wenig relevant)"
#: project/forms/common.py:76
#: project/forms/common.py:93
msgid "10 (Highlight)"
msgstr "10 (Highlight)"
#: project/forms/common.py:80
#: project/forms/common.py:97
msgid "Monday"
msgstr "Montag"
#: project/forms/common.py:81
#: project/forms/common.py:98
msgid "Tueday"
msgstr "Dienstag"
#: project/forms/common.py:82
#: project/forms/common.py:99
msgid "Wednesday"
msgstr "Mittwoch"
#: project/forms/common.py:83
#: project/forms/common.py:100
msgid "Thursday"
msgstr "Donnerstag"
#: project/forms/common.py:84
#: project/forms/common.py:101
msgid "Friday"
msgstr "Freitag"
#: project/forms/common.py:85
#: project/forms/common.py:102
msgid "Saturday"
msgstr "Samstag"
#: project/forms/common.py:86
#: project/forms/common.py:103
msgid "Sunday"
msgstr "Sonntag"
#: project/forms/common.py:90
#: project/forms/common.py:107
msgid "500 m"
msgstr "500 m"
#: project/forms/common.py:91
#: project/forms/common.py:108
msgid "5 km"
msgstr "5 km"
#: project/forms/common.py:92
#: project/forms/common.py:109
msgid "10 km"
msgstr "10 km"
#: project/forms/common.py:93
#: project/forms/common.py:110
msgid "20 km"
msgstr "20 km"
#: project/forms/common.py:94
#: project/forms/common.py:111
msgid "50 km"
msgstr "50 km"
#: project/forms/common.py:95
#: project/forms/common.py:112
msgid "100 km"
msgstr "100 km"
@ -564,7 +570,7 @@ msgstr "Neu angesetzt"
msgid "Update event"
msgstr "Veranstaltung aktualisieren"
#: project/forms/event.py:230 project/templates/_macros.html:918
#: project/forms/event.py:230 project/templates/_macros.html:914
#: project/templates/event/delete.html:6
msgid "Delete event"
msgstr "Veranstaltung löschen"
@ -687,39 +693,29 @@ msgstr ""
"Wir empfehlen dir, ein Foto für die Veranstaltung hochzuladen. Es macht "
"schon deutlich mehr her, aber es geht natürlich auch ohne."
#: project/forms/event_suggestion.py:92
msgid ""
"I confirm that I have clarified all information (text, images, etc.) that"
" I upload into the system with regard to their rights of use and declare "
"that they may be passed on."
msgstr ""
"Ich bestätige dass ich alle Informationen (Text, Bild, etc.), die ich in "
"das System hochlade, hinsichtlich ihrer Nutzungsrechte abgeklärt habe und"
" erkläre, dass diese weitergegeben werden dürfen."
#: project/forms/event_suggestion.py:98 project/templates/example.html:10
#: project/forms/event_suggestion.py:93 project/templates/example.html:10
#: project/templates/widget/event_suggestion/create.html:4
#: project/templates/widget/event_suggestion/create.html:124
msgid "Create event suggestion"
msgstr "Veranstaltung vorschlagen"
#: project/forms/event_suggestion.py:116 project/forms/reference_request.py:45
#: project/forms/event_suggestion.py:115 project/forms/reference_request.py:45
msgid "Rejection reason"
msgstr "Ablehnungsgrund"
#: project/forms/event_suggestion.py:122
#: project/forms/event_suggestion.py:121
msgid "EventRejectionReason.duplicate"
msgstr "Duplikat"
#: project/forms/event_suggestion.py:126
#: project/forms/event_suggestion.py:125
msgid "EventRejectionReason.untrustworthy"
msgstr "Unseriös"
#: project/forms/event_suggestion.py:130
#: project/forms/event_suggestion.py:129
msgid "EventRejectionReason.illegal"
msgstr "Unzulässig"
#: project/forms/event_suggestion.py:135
#: project/forms/event_suggestion.py:134
#: project/templates/event_suggestion/reject.html:9
#: project/templates/event_suggestion/review.html:22
msgid "Reject event suggestion"
@ -769,7 +765,7 @@ msgstr "Anfrage speichern"
msgid "Delete request"
msgstr "Anfrage löschen"
#: project/forms/reference_request.py:26 project/templates/_macros.html:983
#: project/forms/reference_request.py:26 project/templates/_macros.html:979
#: project/templates/event_suggestion/review_status.html:18
#: project/templates/reference_request/review_status.html:12
msgid "Review status"
@ -812,7 +808,7 @@ msgid "This field is required"
msgstr "Dieses Feld ist erforderlich"
#: project/templates/_macros.html:116 project/templates/_macros.html:322
#: project/templates/_macros.html:329 project/templates/_macros.html:614
#: project/templates/_macros.html:329 project/templates/_macros.html:610
msgid "Date"
msgstr "Datum"
@ -849,15 +845,11 @@ msgstr "%(count)d Termine"
msgid "Show directions"
msgstr "Anreise planen"
#: project/templates/_macros.html:442
msgid "Sign in with Google"
msgstr "Mit Google anmelden"
#: project/templates/_macros.html:502
#: project/templates/_macros.html:498
msgid "Search location on Google"
msgstr "Ort bei Google suchen"
#: project/templates/_macros.html:565 project/templates/_macros.html:567
#: project/templates/_macros.html:561 project/templates/_macros.html:563
#: project/templates/event_date/list.html:270
#: project/templates/widget/event_suggestion/create.html:160
#: project/templates/widget/event_suggestion/create.html:185
@ -868,7 +860,7 @@ msgstr "Ort bei Google suchen"
msgid "Previous"
msgstr "Zurück"
#: project/templates/_macros.html:570 project/templates/_macros.html:572
#: project/templates/_macros.html:566 project/templates/_macros.html:568
#: project/templates/event_date/list.html:271
#: project/templates/widget/event_suggestion/create.html:161
#: project/templates/widget/event_suggestion/create.html:186
@ -878,50 +870,50 @@ msgstr "Zurück"
msgid "Next"
msgstr "Weiter"
#: project/templates/_macros.html:637
#: project/templates/_macros.html:633
msgid "Radius"
msgstr "Umkreis"
#: project/templates/_macros.html:832
#: project/templates/_macros.html:828
msgid "Edit image"
msgstr "Bild bearbeiten"
#: project/templates/_macros.html:853
#: project/templates/_macros.html:849
msgid "Close"
msgstr "Schließen"
#: project/templates/_macros.html:854
#: project/templates/_macros.html:850
msgid "Okay"
msgstr "OK"
#: project/templates/_macros.html:863 project/templates/_macros.html:865
#: project/templates/_macros.html:859 project/templates/_macros.html:861
msgid "Choose image file"
msgstr "Bild-Datei auswählen"
#: project/templates/_macros.html:913
#: project/templates/_macros.html:909
msgid "Actions"
msgstr "Aktionen"
#: project/templates/_macros.html:917
#: project/templates/_macros.html:913
msgid "Edit event"
msgstr "Veranstaltung bearbeiten"
#: project/templates/_macros.html:921
#: project/templates/_macros.html:917
msgid "Duplicate event"
msgstr "Veranstaltung duplizieren"
#: project/templates/_macros.html:924
#: project/templates/_macros.html:920
#: project/templates/manage/references_incoming.html:10
msgid "Reference event"
msgstr "Veranstaltung empfehlen"
#: project/templates/_macros.html:927
#: project/templates/_macros.html:923
#: project/templates/manage/reference_requests_outgoing.html:10
#: project/templates/manage/references_outgoing.html:10
msgid "Empfehlung anfragen"
msgstr "Empfehlung anfragen"
#: project/templates/_macros.html:950
#: project/templates/_macros.html:946
msgid "Event suggestion"
msgstr "Veranstaltungsvorschlag"
@ -1555,3 +1547,24 @@ msgstr "Neue Veranstaltung zu prüfen"
#~ msgid "Register"
#~ msgstr ""
#~ msgid "Successfully signed in."
#~ msgstr "Erfolgreich eingeloggt."
#~ msgid "Sign in with Google"
#~ msgstr "Mit Google anmelden"
#~ msgid ""
#~ "I confirm that I have clarified "
#~ "all information (text, images, etc.) "
#~ "that I upload into the system with"
#~ " regard to their rights of use "
#~ "and declare that they may be "
#~ "passed on."
#~ msgstr ""
#~ "Ich bestätige dass ich alle "
#~ "Informationen (Text, Bild, etc.), die "
#~ "ich in das System hochlade, hinsichtlich"
#~ " ihrer Nutzungsrechte abgeklärt habe und"
#~ " erkläre, dass diese weitergegeben werden"
#~ " dürfen."

View File

@ -9,7 +9,7 @@ blinker==1.4
certifi==2020.12.5
cffi==1.14.4
cfgv==3.2.0
chardet==4.0.0
chardet==3.0.4
click==7.1.2
colour==0.1.5
coverage==5.3

View File

@ -20,6 +20,7 @@ class UtilActions(object):
"email": email,
"password": password,
"password_confirm": password,
"accept_tos": "y",
"csrf_token": self.get_csrf(response),
"submit": "Register",
},