From d93ea216c51d6aab4d9304f675514ae7ae528bae Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Thu, 2 Feb 2017 09:35:27 -0800 Subject: [PATCH 01/53] Change characterization of a missing template file as a warning instead of an error. Added function documentation. --- app/Email/services.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/app/Email/services.py b/app/Email/services.py index 2fab71c..0f13575 100644 --- a/app/Email/services.py +++ b/app/Email/services.py @@ -59,15 +59,27 @@ def _read_templates(template, server, locale, params): def _read_template(template, server, locale, part, params, none_if_missing=False): + """ + Render a mustache template. + + :param template: Base template name + :param server: Host server + :param locale: Language designator + :param part: Generic message type descriptor + :param params: Text string to replace tags embedded within the template + :param none_if_missing: Return 'none' if the requested template is not found + + :return: Upon success, return a Renderer object. Return none or a general + error message if the none_is_missing flag is false + """ template_file = expanduser("~/templates/%s/%s/%s/%s.mustache" % (locale, template, server, part)) if isfile(template_file): logging.debug('Looking for template file: %s', template_file) renderer = pystache.Renderer() rendered = renderer.render_path(template_file, params) - #print(rendered) return rendered else: - logging.error('Looking for template file: %s, but the file is missing', template_file) + logging.warn('Looking for template file: %s, but the file is missing', template_file) if none_if_missing: return None else: From 947d00bb673e396d43e9811e107521a778e4fa96 Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Mon, 6 Feb 2017 17:24:33 -0800 Subject: [PATCH 02/53] Add artifacts from package operation to the .gitignore file. --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index c7771ae..47e079a 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,8 @@ *.sqlite *.log venv/ +build.sh +CloudSession-Pkg.tar.gz ################# ## NetBeans From c733908e918102c9404f9d5c4c5d34ca8c000cc0 Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Mon, 27 Feb 2017 10:53:54 -0800 Subject: [PATCH 03/53] Add example configuration file to project --- cloudsession.properties.example | 38 +++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 cloudsession.properties.example diff --git a/cloudsession.properties.example b/cloudsession.properties.example new file mode 100644 index 0000000..f2898f3 --- /dev/null +++ b/cloudsession.properties.example @@ -0,0 +1,38 @@ +# +# Sample Cloud Session configuration file +# + +# Database connection +database.url = jdbc:mysql://database.example.com:3306/cloudsession +database.username = database_user +database.password = database_user_password + +# SMTP server configuration +mail.host = smtp.example.com +mail.from = no_reply@example.com +mail.authenticated = true +mail.user = authenticated_username +mail.password = authenticated_user_password +mail.tls = true + +# The email notification system relies on a number of templates +# to produce the email messages sent to users. +email.template.path = /usr/share/tomcat7/templates + +# Rate limiting +# Starting number of compiles +bucket.compile.size = 100 + +# Number of tokens added per interval +bucket.compile.input = 50 + +# Token add interval (ms) +bucket.compile.freq = 500000 + +# Enable detailed statistics on applicaiton operation +metrics.console.enable = false + +# Bucket types are objects that can be applied to the rate-limiting service built +# into the server. Bucket types are arbitrary units. There can be more than one +# bucket type defined. +bucket.types = compile From 61df32f9bc345a1a9ee3a58bf5d54ee58cf3e0af Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Wed, 26 Apr 2017 19:09:27 -0700 Subject: [PATCH 04/53] Refactor minor details --- app/User/controllers.py | 17 ++++++++++++++--- app/__init__.py | 30 ++++++++++++++++-------------- requirements.txt | 1 + 3 files changed, 31 insertions(+), 17 deletions(-) diff --git a/app/User/controllers.py b/app/User/controllers.py index c4c8dbc..92e2c78 100644 --- a/app/User/controllers.py +++ b/app/User/controllers.py @@ -12,6 +12,7 @@ from app.User import services as user_service from models import User +# Define the endpoint prefix for user services user_app = Blueprint('user', __name__, url_prefix='/user') api = Api(user_app) @@ -72,7 +73,7 @@ def get(self, id_user): # Parse numbers try: id_user = int(id_user) - except: + except ValueError: return Failures.not_a_number('idUser', id_user) # Validate user exists, is validated and is not blocked @@ -133,6 +134,7 @@ class DoInfoChange(Resource): def post(self, id_user): screen_name = request.form.get('screenname') + # Validate required fields validation = Validation() validation.add_required_field('id-user', id_user) @@ -143,7 +145,7 @@ def post(self, id_user): # Parse numbers try: id_user = int(id_user) - except: + except ValueError: return Failures.not_a_number('idUser', id_user) # Validate user exists, is validated and is not blocked @@ -174,6 +176,7 @@ class DoLocaleChange(Resource): def post(self, id_user): locale = request.form.get('locale') + # Validate required fields validation = Validation() validation.add_required_field('id-user', id_user) @@ -184,7 +187,7 @@ def post(self, id_user): # Parse numbers try: id_user = int(id_user) - except: + except ValueError: return Failures.not_a_number('idUser', id_user) # Validate user exists, is validated and is not blocked @@ -206,9 +209,17 @@ def post(self, id_user): }} +# Supported endpoints +# Register a new user account api.add_resource(Register, '/register') + +# Retrieve details about an existing user account api.add_resource(GetUserById, '/id/') api.add_resource(GetUserByEmail, '/email/') api.add_resource(GetUserByScreenname, '/screenname/') + +# Update a user screen name api.add_resource(DoInfoChange, '/info/') + +# Update the local defined in the user account api.add_resource(DoLocaleChange, '/locale/') diff --git a/app/__init__.py b/app/__init__.py index 0ca2971..e90bda0 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,17 +1,19 @@ -# Import flask and template operators +""" +Cloud Session server application initialization + +""" # Import properties files utils import logging from ConfigParser import ConfigParser - -import os from FakeSecHead import FakeSecHead from os.path import expanduser, isfile # Import Flask -from flask import Flask, render_template +# from flask import Flask, render_template +from flask import Flask -# Import SQLAlchemy +# Import SQLAlchemy database mapper from flask.ext.sqlalchemy import SQLAlchemy # Import Mail @@ -20,6 +22,13 @@ # Define the WSGI application object from raven.contrib.flask import Sentry +from app.AuthToken.controllers import auth_token_app +from app.Authenticate.controllers import authenticate_app +from app.User.controllers import user_app +from app.LocalUser.controllers import local_user_app +from app.RateLimiting.controllers import rate_limiting_app +from app.OAuth.controllers import oauth_app + app = Flask(__name__) # Load basic configurations @@ -86,11 +95,10 @@ logging.info("No Sentry configuration") -app.config['SQLALCHEMY_DATABASE_URI'] = app.config['CLOUD_SESSION_PROPERTIES']['database.url'] - # Define the database object which is imported # by modules and controllers logging.info("Initializing database connection") +app.config['SQLALCHEMY_DATABASE_URI'] = app.config['CLOUD_SESSION_PROPERTIES']['database.url'] db = SQLAlchemy(app) app.config['MAIL_SERVER'] = app.config['CLOUD_SESSION_PROPERTIES']['mail.host'] @@ -101,6 +109,7 @@ app.config['MAIL_PORT'] = 25 else: app.config['MAIL_PORT'] = app.config['CLOUD_SESSION_PROPERTIES']['mail.port'] + app.config['MAIL_USE_TLS'] = app.config['CLOUD_SESSION_PROPERTIES']['mail.tls'] app.config['MAIL_USE_SSL'] = app.config['CLOUD_SESSION_PROPERTIES']['mail.ssl'] app.config['MAIL_DEBUG'] = app.config['CLOUD_SESSION_PROPERTIES']['mail.debug'] @@ -108,18 +117,11 @@ app.config['MAIL_PASSWORD'] = app.config['CLOUD_SESSION_PROPERTIES']['mail.password'] app.config['DEFAULT_MAIL_SENDER'] = app.config['CLOUD_SESSION_PROPERTIES']['mail.from'] -#app.debug = True logging.info("Initializing mail") mail = Mail(app) # -------------------------------------------- Services -------------------------------------------------------- logging.info("Initializing services") -from app.AuthToken.controllers import auth_token_app -from app.Authenticate.controllers import authenticate_app -from app.User.controllers import user_app -from app.LocalUser.controllers import local_user_app -from app.RateLimiting.controllers import rate_limiting_app -from app.OAuth.controllers import oauth_app app.register_blueprint(auth_token_app) app.register_blueprint(authenticate_app) diff --git a/requirements.txt b/requirements.txt index 9fd50b6..680ec6b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -19,3 +19,4 @@ SQLAlchemy==1.0.12 validate-email==1.3 Werkzeug==0.11.5 wheel==0.24.0 +validate_email==1.3 From bdf5f0fcd1d133c2db56b76935274c9313927b2a Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Thu, 27 Apr 2017 14:00:18 -0700 Subject: [PATCH 05/53] Add code for birthdate and parent email --- .gitignore | 2 ++ app/AuthToken/controllers.py | 3 +-- app/AuthToken/models.py | 1 - app/User/controllers.py | 43 +++++++++++++++++++++++++++++++----- app/User/models.py | 5 +++++ app/User/services.py | 15 +++++++++++-- app/__init__.py | 10 ++++++--- 7 files changed, 65 insertions(+), 14 deletions(-) diff --git a/.gitignore b/.gitignore index 47e079a..13d8b65 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,5 @@ nbactions.xml ################# .idea +/deploy-test.sh +deploy-test.sh diff --git a/app/AuthToken/controllers.py b/app/AuthToken/controllers.py index 5f0664a..0fb64fa 100644 --- a/app/AuthToken/controllers.py +++ b/app/AuthToken/controllers.py @@ -1,10 +1,9 @@ # Import the database object from the main app module -import json import logging import uuid import datetime - import Failures + from app import db from app.User import services as user_service diff --git a/app/AuthToken/models.py b/app/AuthToken/models.py index dd6e1d6..bea10aa 100644 --- a/app/AuthToken/models.py +++ b/app/AuthToken/models.py @@ -2,7 +2,6 @@ # We will define this inside /app/__init__.py in the next sections. from app import db - class AuthenticationToken(db.Model): id = db.Column(db.BigInteger, primary_key=True) id_user = db.Column(db.BigInteger, db.ForeignKey('user.id')) diff --git a/app/User/controllers.py b/app/User/controllers.py index 92e2c78..4d24cdf 100644 --- a/app/User/controllers.py +++ b/app/User/controllers.py @@ -28,6 +28,11 @@ def post(self): locale = request.form.get('locale') screen_name = request.form.get('screenname') + # COPPA support + birth_month = request.form.get('bdmonth') + birth_year = request.form.get('bdyear') + parent_email = request.form.get('parent-email') + # Validate required fields validation = Validation() validation.add_required_field('server', server) @@ -36,6 +41,16 @@ def post(self): validation.add_required_field('password-confirm', password_confirm) validation.add_required_field('locale', locale) validation.add_required_field('screenname', screen_name) + + # COPPA support + validation.add_required_field('bdmonth', birth_month) + validation.add_required_field('bdyear', birth_year) + if parent_email: + validation.check_email('parent-email', parent_email) + if not validation.is_valid(): + return validation.get_validation_response() + + # Verify user email address validation.check_email('email', email) if not validation.is_valid(): return validation.get_validation_response() @@ -56,7 +71,8 @@ def post(self): if not user_service.check_password_complexity(password): return Failures.password_complexity() - id_user = user_service.create_local_user(server, email, password, locale, screen_name) + id_user = user_service.create_local_user( + server, email, password, locale, screen_name, birth_month, birth_year, parent_email) user_service.send_email_confirm(id_user, server) db.session.commit() @@ -88,7 +104,10 @@ def get(self, id_user): 'email': user.email, 'locale': user.locale, 'screenname': user.screen_name, - 'authentication-source': user.auth_source + 'authentication-source': user.auth_source, + 'bdmonth': user.birth_month, + 'bdyear': user.birth_year, + 'parent-email': user.parent_email }} @@ -107,7 +126,10 @@ def get(self, email): 'email': user.email, 'locale': user.locale, 'screenname': user.screen_name, - 'authentication-source': user.auth_source + 'authentication-source': user.auth_source, + 'bdmonth': user.birth_month, + 'bdyear': user.birth_year, + 'parent-email': user.parent_email }} @@ -126,7 +148,10 @@ def get(self, screen_name): 'email': user.email, 'locale': user.locale, 'screenname': user.screen_name, - 'authentication-source': user.auth_source + 'authentication-source': user.auth_source, + 'bdmonth': user.birth_month, + 'bdyear': user.birth_year, + 'parent-email': user.parent_email }} @@ -168,7 +193,10 @@ def post(self, id_user): 'email': user.email, 'locale': user.locale, 'screenname': user.screen_name, - 'authentication-source': user.auth_source + 'authentication-source': user.auth_source, + 'bdmonth': user.birth_month, + 'bdyear': user.birth_year, + 'parent-email': user.parent_email }} @@ -205,7 +233,10 @@ def post(self, id_user): 'email': user.email, 'locale': user.locale, 'screenname': user.screen_name, - 'authentication-source': user.auth_source + 'authentication-source': user.auth_source, + 'bdmonth': user.birth_month, + 'bdyear': user.birth_year, + 'parent-email': user.parent_email }} diff --git a/app/User/models.py b/app/User/models.py index 08310b0..8fcfb31 100644 --- a/app/User/models.py +++ b/app/User/models.py @@ -12,6 +12,11 @@ class User(db.Model): confirmed = db.Column(db.Boolean) screen_name = db.Column(db.String(250)) + # COPPA support + birth_month = db.Column(db.INTEGER, nullable=False) + birth_year = db.Column(db.INTEGER, nullable=False) + parent_email = db.Column(db.String(250)) + def __init__(self): self.blocked = False self.confirmed = False diff --git a/app/User/services.py b/app/User/services.py index 1a71da8..2bc9458 100644 --- a/app/User/services.py +++ b/app/User/services.py @@ -39,7 +39,7 @@ def check_password_complexity(password): return 8 <= len(password) < 200 -def create_local_user(server, email, password, locale, screen_name): +def create_local_user(server, email, password, locale, screen_name, birth_month, birth_year, parent_email): salt, password_hash = get_password_hash(password) # Save user @@ -51,6 +51,11 @@ def create_local_user(server, email, password, locale, screen_name): user.password = password_hash user.salt = salt + #COPPA support + user.birth_month = birth_month + user.birth_year = birth_year + user.parent_email = parent_email + db.session.add(user) db.session.flush() db.session.refresh(user) @@ -58,7 +63,7 @@ def create_local_user(server, email, password, locale, screen_name): return user.id -def create_oauth_user(server, email, source, locale, screen_name): +def create_oauth_user(server, email, source, locale, screen_name, birth_month, birth_year, parent_email): # Save user user = User() user.email = email @@ -68,6 +73,12 @@ def create_oauth_user(server, email, source, locale, screen_name): user.confirmed = True user.blocked = False + # COPPA support + user.birth_month = birth_month + user.birth_year = birth_year + user.parent_email = parent_email + + # Add the user record db.session.add(user) db.session.flush() db.session.refresh(user) diff --git a/app/__init__.py b/app/__init__.py index e90bda0..74fe84a 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -67,8 +67,11 @@ 'bucket.email-confirm.freq': '1800000' } +logging.basicConfig(level=logging.DEBUG) + configfile = expanduser("~/cloudsession.properties") -print('Looking for config file: %s' % configfile) +logging.info('Looking for config file: %s', configfile) + if isfile(configfile): configs = ConfigParser(defaults) configs.readfp(FakeSecHead(open(configfile))) @@ -82,8 +85,6 @@ # -------------------------------------- Module initialization ------------------------------------------------- -logging.basicConfig(level=logging.DEBUG) - if app.config['CLOUD_SESSION_PROPERTIES']['sentry-dsn'] is not None: logging.info("Initializing Sentry") sentry = Sentry(app, @@ -101,6 +102,7 @@ app.config['SQLALCHEMY_DATABASE_URI'] = app.config['CLOUD_SESSION_PROPERTIES']['database.url'] db = SQLAlchemy(app) +logging.info("Configuring SMTP properties") app.config['MAIL_SERVER'] = app.config['CLOUD_SESSION_PROPERTIES']['mail.host'] if app.config['CLOUD_SESSION_PROPERTIES']['mail.port'] is None: if app.config['CLOUD_SESSION_PROPERTIES']['mail.tls']: @@ -110,6 +112,8 @@ else: app.config['MAIL_PORT'] = app.config['CLOUD_SESSION_PROPERTIES']['mail.port'] +logging.info("SMTP port: %s", app.config['MAIL_PORT'] ) + app.config['MAIL_USE_TLS'] = app.config['CLOUD_SESSION_PROPERTIES']['mail.tls'] app.config['MAIL_USE_SSL'] = app.config['CLOUD_SESSION_PROPERTIES']['mail.ssl'] app.config['MAIL_DEBUG'] = app.config['CLOUD_SESSION_PROPERTIES']['mail.debug'] From d1223e8753536476584c8b93815301cc060f5e13 Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Thu, 27 Apr 2017 14:49:48 -0700 Subject: [PATCH 06/53] Add application version number --- app/__init__.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index 74fe84a..201c103 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -22,14 +22,16 @@ # Define the WSGI application object from raven.contrib.flask import Sentry -from app.AuthToken.controllers import auth_token_app from app.Authenticate.controllers import authenticate_app +from app.AuthToken.controllers import auth_token_app from app.User.controllers import user_app from app.LocalUser.controllers import local_user_app from app.RateLimiting.controllers import rate_limiting_app from app.OAuth.controllers import oauth_app app = Flask(__name__) +version = "1.0.1" +db = None # Load basic configurations app.config.from_object('config') @@ -79,7 +81,9 @@ app_configs = {} for (key, value) in configs.items('section'): app_configs[key] = value + app.config['CLOUD_SESSION_PROPERTIES'] = app_configs + else: app.config['CLOUD_SESSION_PROPERTIES'] = defaults @@ -95,14 +99,14 @@ else: logging.info("No Sentry configuration") - # Define the database object which is imported # by modules and controllers -logging.info("Initializing database connection") +# logging.info("Initializing database connection") app.config['SQLALCHEMY_DATABASE_URI'] = app.config['CLOUD_SESSION_PROPERTIES']['database.url'] db = SQLAlchemy(app) -logging.info("Configuring SMTP properties") + +# logging.info("Configuring SMTP properties") app.config['MAIL_SERVER'] = app.config['CLOUD_SESSION_PROPERTIES']['mail.host'] if app.config['CLOUD_SESSION_PROPERTIES']['mail.port'] is None: if app.config['CLOUD_SESSION_PROPERTIES']['mail.tls']: From 45b4b4de653cfbd0b44a640597db20a99f599610 Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Fri, 28 Apr 2017 14:08:40 -0700 Subject: [PATCH 07/53] Add more COPPA fields --- app/Email/services.py | 10 ++++++++-- app/User/controllers.py | 23 +++++++++++++++++------ app/User/models.py | 1 + app/User/services.py | 33 +++++++++++++++++++++++++++++++-- app/__init__.py | 25 ++++++++++++++++--------- 5 files changed, 73 insertions(+), 19 deletions(-) diff --git a/app/Email/services.py b/app/Email/services.py index 0f13575..f61163f 100644 --- a/app/Email/services.py +++ b/app/Email/services.py @@ -9,7 +9,7 @@ def send_email_template_for_user(id_user, template, server, **kwargs): - from app.User.services import get_user + from app.User.services import get_user, is_coppa_covered logging.info("Sending email to user: %s (%s)", id_user, template) @@ -23,7 +23,13 @@ def send_email_template_for_user(id_user, template, server, **kwargs): params['screenname'] = user.screen_name - send_email_template_to_address(user.email, template, server, user.locale, params) + # Send email to parent if user is under 13 years old + if is_coppa_covered(user.birth_month, user.birth_year): + user_email = user.parent_email + else: + user_email = user.email + + send_email_template_to_address(user_email, template, server, user.locale, params) def send_email_template_to_address(recipient, template, server, locale, params=None, **kwargs): diff --git a/app/User/controllers.py b/app/User/controllers.py index 4d24cdf..11289f3 100644 --- a/app/User/controllers.py +++ b/app/User/controllers.py @@ -32,6 +32,7 @@ def post(self): birth_month = request.form.get('bdmonth') birth_year = request.form.get('bdyear') parent_email = request.form.get('parent-email') + parent_email_source = request.form.get('parent-email-source') # Validate required fields validation = Validation() @@ -71,10 +72,15 @@ def post(self): if not user_service.check_password_complexity(password): return Failures.password_complexity() + # Write user details to the database id_user = user_service.create_local_user( - server, email, password, locale, screen_name, birth_month, birth_year, parent_email) + server, email, password, locale, screen_name, + birth_month, birth_year, parent_email, parent_email_source) + + # Send a confirmation request email to user or parent user_service.send_email_confirm(id_user, server) + # Commit the database record db.session.commit() logging.info('User-controller: register success: %s', id_user) @@ -107,7 +113,8 @@ def get(self, id_user): 'authentication-source': user.auth_source, 'bdmonth': user.birth_month, 'bdyear': user.birth_year, - 'parent-email': user.parent_email + 'parent-email': user.parent_email, + 'parent-email-source': user.parent_email_source }} @@ -129,7 +136,8 @@ def get(self, email): 'authentication-source': user.auth_source, 'bdmonth': user.birth_month, 'bdyear': user.birth_year, - 'parent-email': user.parent_email + 'parent-email': user.parent_email, + 'parent-email-source': user.parent_email_source }} @@ -151,7 +159,8 @@ def get(self, screen_name): 'authentication-source': user.auth_source, 'bdmonth': user.birth_month, 'bdyear': user.birth_year, - 'parent-email': user.parent_email + 'parent-email': user.parent_email, + 'parent-email-source': user.parent_email_source }} @@ -196,7 +205,8 @@ def post(self, id_user): 'authentication-source': user.auth_source, 'bdmonth': user.birth_month, 'bdyear': user.birth_year, - 'parent-email': user.parent_email + 'parent-email': user.parent_email, + 'parent-email-source': user.parent_email_source }} @@ -236,7 +246,8 @@ def post(self, id_user): 'authentication-source': user.auth_source, 'bdmonth': user.birth_month, 'bdyear': user.birth_year, - 'parent-email': user.parent_email + 'parent-email': user.parent_email, + 'parent-email-source': user.parent_email_source }} diff --git a/app/User/models.py b/app/User/models.py index 8fcfb31..71ee9b2 100644 --- a/app/User/models.py +++ b/app/User/models.py @@ -16,6 +16,7 @@ class User(db.Model): birth_month = db.Column(db.INTEGER, nullable=False) birth_year = db.Column(db.INTEGER, nullable=False) parent_email = db.Column(db.String(250)) + parent_email_source = db.Column(db.INTEGER) def __init__(self): self.blocked = False diff --git a/app/User/services.py b/app/User/services.py index 2bc9458..dd24f39 100644 --- a/app/User/services.py +++ b/app/User/services.py @@ -39,7 +39,10 @@ def check_password_complexity(password): return 8 <= len(password) < 200 -def create_local_user(server, email, password, locale, screen_name, birth_month, birth_year, parent_email): +def create_local_user( + server, email, password, locale, screen_name, + birth_month, birth_year, parent_email, parent_email_source): + salt, password_hash = get_password_hash(password) # Save user @@ -55,6 +58,7 @@ def create_local_user(server, email, password, locale, screen_name, birth_month, user.birth_month = birth_month user.birth_year = birth_year user.parent_email = parent_email + user.parent_email_source = parent_email_source db.session.add(user) db.session.flush() @@ -63,7 +67,10 @@ def create_local_user(server, email, password, locale, screen_name, birth_month, return user.id -def create_oauth_user(server, email, source, locale, screen_name, birth_month, birth_year, parent_email): +def create_oauth_user( + server, email, source, locale, screen_name, + birth_month, birth_year, parent_email, parent_email_source): + # Save user user = User() user.email = email @@ -77,6 +84,7 @@ def create_oauth_user(server, email, source, locale, screen_name, birth_month, b user.birth_month = birth_month user.birth_year = birth_year user.parent_email = parent_email + user.parent_email_source = parent_email_source # Add the user record db.session.add(user) @@ -148,3 +156,24 @@ def send_password_reset(id_user, server): email_services.send_email_template_for_user(id_user, 'reset', server, token=token) return True, 0, 'Success' + +# Return true if the date is less than 13 years +def is_coppa_covered(month,year): + # This is the number of months a typical thirteen years old has been on planet Earth. + cap = 156 + + # This is the actual number of months a typical user has been on the same planet. + user_age = (year * 12) + month + + # Current year and month + current_month = datetime.now().month + current_year = datetime.now().year + + # This represents the number of months since the inception of AD + # Unless you want to count that first year as part of BC. + current_cap = (current_year * 12) + current_month + + if current_cap - user_age > cap: + return False + else + return True diff --git a/app/__init__.py b/app/__init__.py index 201c103..8ab180d 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -22,13 +22,6 @@ # Define the WSGI application object from raven.contrib.flask import Sentry -from app.Authenticate.controllers import authenticate_app -from app.AuthToken.controllers import auth_token_app -from app.User.controllers import user_app -from app.LocalUser.controllers import local_user_app -from app.RateLimiting.controllers import rate_limiting_app -from app.OAuth.controllers import oauth_app - app = Flask(__name__) version = "1.0.1" db = None @@ -116,8 +109,6 @@ else: app.config['MAIL_PORT'] = app.config['CLOUD_SESSION_PROPERTIES']['mail.port'] -logging.info("SMTP port: %s", app.config['MAIL_PORT'] ) - app.config['MAIL_USE_TLS'] = app.config['CLOUD_SESSION_PROPERTIES']['mail.tls'] app.config['MAIL_USE_SSL'] = app.config['CLOUD_SESSION_PROPERTIES']['mail.ssl'] app.config['MAIL_DEBUG'] = app.config['CLOUD_SESSION_PROPERTIES']['mail.debug'] @@ -125,12 +116,28 @@ app.config['MAIL_PASSWORD'] = app.config['CLOUD_SESSION_PROPERTIES']['mail.password'] app.config['DEFAULT_MAIL_SENDER'] = app.config['CLOUD_SESSION_PROPERTIES']['mail.from'] + logging.info("Initializing mail") +logging.info("SMTP port: %s", app.config['MAIL_PORT']) +logging.info("TLS: %s",app.config['MAIL_USE_TLS']) +logging.info("SSL: %s",app.config['MAIL_USE_SSL']) +logging.info("Sender: %s",app.config['DEFAULT_MAIL_SENDER']) + mail = Mail(app) # -------------------------------------------- Services -------------------------------------------------------- logging.info("Initializing services") +# All of these imports need the database +if db is not None: + from app.Authenticate.controllers import authenticate_app + from app.AuthToken.controllers import auth_token_app + from app.User.controllers import user_app + from app.LocalUser.controllers import local_user_app + from app.RateLimiting.controllers import rate_limiting_app + from app.OAuth.controllers import oauth_app + + app.register_blueprint(auth_token_app) app.register_blueprint(authenticate_app) app.register_blueprint(user_app) From 04f03fd6d83a160c6fe43e99490010c6ca0f94e9 Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Fri, 28 Apr 2017 16:06:46 -0700 Subject: [PATCH 08/53] Correct error in date selection --- app/User/services.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/User/services.py b/app/User/services.py index dd24f39..a63de3e 100644 --- a/app/User/services.py +++ b/app/User/services.py @@ -166,8 +166,8 @@ def is_coppa_covered(month,year): user_age = (year * 12) + month # Current year and month - current_month = datetime.now().month - current_year = datetime.now().year + current_month = datetime.date.today().month + current_year = datetime.date.today().year # This represents the number of months since the inception of AD # Unless you want to count that first year as part of BC. @@ -175,5 +175,5 @@ def is_coppa_covered(month,year): if current_cap - user_age > cap: return False - else + else: return True From 4a11ee560a5e6b8ca7052c4a44899e51ba865e17 Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Wed, 3 May 2017 09:30:56 -0700 Subject: [PATCH 09/53] Add trap for missing SMTP server --- .gitignore | 4 ++-- app/LocalUser/controllers.py | 17 ++++++++++++----- app/User/controllers.py | 2 +- app/User/services.py | 5 ++++- 4 files changed, 19 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index 13d8b65..1b5d2b6 100644 --- a/.gitignore +++ b/.gitignore @@ -18,5 +18,5 @@ nbactions.xml ################# .idea -/deploy-test.sh -deploy-test.sh +build +deploy-test diff --git a/app/LocalUser/controllers.py b/app/LocalUser/controllers.py index ba0a45e..8d51dec 100644 --- a/app/LocalUser/controllers.py +++ b/app/LocalUser/controllers.py @@ -95,11 +95,18 @@ def get(self, email): else: if code == 10: return Failures.rate_exceeded() - return { - 'success': False, - 'message': message, - 'code': 520 - } + elif code == 99: + return { + 'success': False, + 'message': message, + 'code': 540 + } + else: + return { + 'success': False, + 'message': message, + 'code': 520 + } class PasswordReset(Resource): diff --git a/app/User/controllers.py b/app/User/controllers.py index 11289f3..f754cf2 100644 --- a/app/User/controllers.py +++ b/app/User/controllers.py @@ -10,7 +10,7 @@ from Validation import Validation from app.User import services as user_service -from models import User +# from models import User # Define the endpoint prefix for user services user_app = Blueprint('user', __name__, url_prefix='/user') diff --git a/app/User/services.py b/app/User/services.py index a63de3e..78ba6ef 100644 --- a/app/User/services.py +++ b/app/User/services.py @@ -122,7 +122,10 @@ def send_email_confirm(id_user, server): confirm_token.validity = datetime.datetime.now() + datetime.timedelta(hours=token_validity_time) db.session.add(confirm_token) - email_services.send_email_template_for_user(id_user, 'confirm', server, token=token) + try: + email_services.send_email_template_for_user(id_user, 'confirm', server, token=token) + except Exception as ex: + return False, 99, 'Unable to contact SMTP server' return True, 0, 'Success' From 631edee85cf7c1f5f96dbac48411c81b7a37b7a0 Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Fri, 5 May 2017 09:44:16 -0700 Subject: [PATCH 10/53] Exclude local shell scripts --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 47e079a..8d902b6 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,5 @@ nbactions.xml ################# .idea +/build +/deploy-test From 8b6a76a1b01a131fc81061278f415bcd0b87e572 Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Fri, 5 May 2017 15:35:09 -0700 Subject: [PATCH 11/53] Add missing mustache templates to project. Update missing SMTP server error handler. --- app/User/controllers.py | 22 ++++++++++++------- app/User/services.py | 6 +++-- .../en/confirm/blocklyprop/header.mustache | 1 + .../en/confirm/blocklyprop/plain.mustache | 8 +++++++ .../en/reset/blocklyprop/header.mustache | 1 + templates/en/reset/blocklyprop/plain.mustache | 11 ++++++++++ .../en_US/confirm/blocklyprop/header.mustache | 1 + .../en_US/confirm/blocklyprop/plain.mustache | 8 +++++++ .../en_US/reset/blocklyprop/header.mustache | 1 + .../en_US/reset/blocklyprop/plain.mustache | 11 ++++++++++ 10 files changed, 60 insertions(+), 10 deletions(-) create mode 100644 templates/en/confirm/blocklyprop/header.mustache create mode 100644 templates/en/confirm/blocklyprop/plain.mustache create mode 100644 templates/en/reset/blocklyprop/header.mustache create mode 100644 templates/en/reset/blocklyprop/plain.mustache create mode 100644 templates/en_US/confirm/blocklyprop/header.mustache create mode 100644 templates/en_US/confirm/blocklyprop/plain.mustache create mode 100644 templates/en_US/reset/blocklyprop/header.mustache create mode 100644 templates/en_US/reset/blocklyprop/plain.mustache diff --git a/app/User/controllers.py b/app/User/controllers.py index f754cf2..769baf9 100644 --- a/app/User/controllers.py +++ b/app/User/controllers.py @@ -17,6 +17,7 @@ api = Api(user_app) +# Register a new user class Register(Resource): def post(self): @@ -78,15 +79,17 @@ def post(self): birth_month, birth_year, parent_email, parent_email_source) # Send a confirmation request email to user or parent - user_service.send_email_confirm(id_user, server) + (result, errno, mesg) = user_service.send_email_confirm(id_user, server) + if result: + # Commit the database record + db.session.commit() + logging.info('User-controller: register success: %s', id_user) - # Commit the database record - db.session.commit() - - logging.info('User-controller: register success: %s', id_user) - - # Create user - return {'success': True, 'user': id_user} + # Create user + return {'success': True, 'user': id_user} + else: + logging.error("Unable to register user. Error %s: %s", errno, mesg) + return {'success': False, 'user': 0} class GetUserById(Resource): @@ -252,6 +255,9 @@ def post(self, id_user): # Supported endpoints +# Note: The url_prefix is '/user'. All user endpoints are in the form +# of host:port/user/_service_ +# # Register a new user account api.add_resource(Register, '/register') diff --git a/app/User/services.py b/app/User/services.py index 78ba6ef..155c080 100644 --- a/app/User/services.py +++ b/app/User/services.py @@ -1,6 +1,6 @@ import hashlib import uuid - +import logging import datetime from app import db, app @@ -124,7 +124,9 @@ def send_email_confirm(id_user, server): try: email_services.send_email_template_for_user(id_user, 'confirm', server, token=token) - except Exception as ex: + except Exception as ex: + print("Exception {0}", ex.args) + logging.error("Unable to send email. Message is: %s", ex.message) return False, 99, 'Unable to contact SMTP server' return True, 0, 'Success' diff --git a/templates/en/confirm/blocklyprop/header.mustache b/templates/en/confirm/blocklyprop/header.mustache new file mode 100644 index 0000000..8d1f20a --- /dev/null +++ b/templates/en/confirm/blocklyprop/header.mustache @@ -0,0 +1 @@ +Please confirm your email address for BlocklyProp diff --git a/templates/en/confirm/blocklyprop/plain.mustache b/templates/en/confirm/blocklyprop/plain.mustache new file mode 100644 index 0000000..ff39fcb --- /dev/null +++ b/templates/en/confirm/blocklyprop/plain.mustache @@ -0,0 +1,8 @@ +Dear {{screenname}}, + +Please go to http://localhost:8080/blockly/confirm?locale={{locale}}&email={{email}}&token={{token}} to confirm your email address. + +If the url does not work, please go to http://localhost:8080/blockly/confirm and enter your email address and the token: {{token}} + + +The Parallax team diff --git a/templates/en/reset/blocklyprop/header.mustache b/templates/en/reset/blocklyprop/header.mustache new file mode 100644 index 0000000..630929b --- /dev/null +++ b/templates/en/reset/blocklyprop/header.mustache @@ -0,0 +1 @@ +Reset your BlocklyProp password diff --git a/templates/en/reset/blocklyprop/plain.mustache b/templates/en/reset/blocklyprop/plain.mustache new file mode 100644 index 0000000..9a5e5f5 --- /dev/null +++ b/templates/en/reset/blocklyprop/plain.mustache @@ -0,0 +1,11 @@ +Dear {{screenname}}, + +A request was made to reset your BlocklyProp account password. + +If this request was made by you, please go to http://localost:8080/blockly/reset?locale={{locale}}&email={{email}}&token={{token}} to reset your password now. + +If the url does not work, please go to http://localhost:8080/blockly/reset and use the token: {{token}} + +If you did not make this request, you may safely ignore this message. + +The Parallax team diff --git a/templates/en_US/confirm/blocklyprop/header.mustache b/templates/en_US/confirm/blocklyprop/header.mustache new file mode 100644 index 0000000..8d1f20a --- /dev/null +++ b/templates/en_US/confirm/blocklyprop/header.mustache @@ -0,0 +1 @@ +Please confirm your email address for BlocklyProp diff --git a/templates/en_US/confirm/blocklyprop/plain.mustache b/templates/en_US/confirm/blocklyprop/plain.mustache new file mode 100644 index 0000000..ff39fcb --- /dev/null +++ b/templates/en_US/confirm/blocklyprop/plain.mustache @@ -0,0 +1,8 @@ +Dear {{screenname}}, + +Please go to http://localhost:8080/blockly/confirm?locale={{locale}}&email={{email}}&token={{token}} to confirm your email address. + +If the url does not work, please go to http://localhost:8080/blockly/confirm and enter your email address and the token: {{token}} + + +The Parallax team diff --git a/templates/en_US/reset/blocklyprop/header.mustache b/templates/en_US/reset/blocklyprop/header.mustache new file mode 100644 index 0000000..630929b --- /dev/null +++ b/templates/en_US/reset/blocklyprop/header.mustache @@ -0,0 +1 @@ +Reset your BlocklyProp password diff --git a/templates/en_US/reset/blocklyprop/plain.mustache b/templates/en_US/reset/blocklyprop/plain.mustache new file mode 100644 index 0000000..59f709e --- /dev/null +++ b/templates/en_US/reset/blocklyprop/plain.mustache @@ -0,0 +1,11 @@ +Dear {{screenname}}, + +A request was made to reset your BlocklyProp account password. + +If this request was made by you, please go to http://localhost:8080/blockly/reset?locale={{locale}}&email={{email}}&token={{token}} to reset your password now. + +If the url does not work, please go to http://localhost:8080/blockly/reset and use the token: {{token}} + +If you did not make this request, you may safely ignore this message. + +The Parallax team From 53557d497c33f05e18bcf0e33cd5bad63325b2b5 Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Mon, 8 May 2017 16:02:31 -0700 Subject: [PATCH 12/53] Correct missing COPPA data block --- app/Authenticate/controllers.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/Authenticate/controllers.py b/app/Authenticate/controllers.py index 836517d..2ad1e6c 100644 --- a/app/Authenticate/controllers.py +++ b/app/Authenticate/controllers.py @@ -66,7 +66,11 @@ def post(self): 'email': user.email, 'locale': user.locale, 'screenname': user.screen_name, - 'authentication-source': user.auth_source + 'authentication-source': user.auth_source, + 'bdmonth': user.birth_month, + 'bdyear': user.birth_year, + 'parent-email': user.parent_email, + 'parent-email-source': user.parent_email_source }} api.add_resource(AuthenticateLocalUser, '/local') From 6621b410b0ccb05b5334cb326cc9fa915d3cb10c Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Thu, 11 May 2017 11:20:27 -0700 Subject: [PATCH 13/53] Add SponsorType enum to make code more readable --- app/User/services.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/User/services.py b/app/User/services.py index 155c080..a5bb9d9 100644 --- a/app/User/services.py +++ b/app/User/services.py @@ -11,6 +11,14 @@ from models import User, ConfirmToken, ResetToken +# Implementing an enum-like structure for the user sponsor email type +class SponsorType: + INDIVIDUAL = 0 + PARENT = 1 + GUARDIAN = 2 + TEACHER = 3 + + def get_password_hash(password): salt = str(uuid.uuid1()) password_hash = hashlib.sha256("%s:%s" % (password, salt)).hexdigest() From db7b859fdacbc9758d825d2a0d80ad0c8f8c59ae Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Thu, 11 May 2017 11:21:32 -0700 Subject: [PATCH 14/53] Add code to send registration confirmation email to the correct email address and with the correct email template --- app/Email/services.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/app/Email/services.py b/app/Email/services.py index f61163f..8e498c2 100644 --- a/app/Email/services.py +++ b/app/Email/services.py @@ -1,8 +1,7 @@ -from app import mail, app, db - +from app import mail, app from os.path import expanduser, isfile - from flask.ext.mail import Message +from app.User.services import SponsorType import pystache import logging @@ -24,8 +23,18 @@ def send_email_template_for_user(id_user, template, server, **kwargs): params['screenname'] = user.screen_name # Send email to parent if user is under 13 years old - if is_coppa_covered(user.birth_month, user.birth_year): - user_email = user.parent_email + if template == 'confirm': + if is_coppa_covered(user.birth_month, user.birth_year): + user_email = user.parent_email + + if user.parent_email_source == SponsorType.TEACHER: + # Teacher handles the account confirmation + send_email_template_to_address(user_email, 'confim_teacher', server, user.locale, params) + elif user.parent_email_source == SponsorType.PARENT or user.parent_email_source == SponsorType.GUARDIAN: + # Parent handles the account confirmation + send_email_template_to_address(user_email, 'confirm_parent', server, user.locale, params) + else: + logging.info("COPPA account %s has invalid sponsor type [%s]", id_user, user.parent_email_source) else: user_email = user.email From 39224fcf4ab5d0fba806852b4acc8872b00813f7 Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Thu, 11 May 2017 14:40:50 -0700 Subject: [PATCH 15/53] Add templates for teachers and parent emails. Moved SponsorType to email processing source file. --- .gitignore | 1 + app/Email/services.py | 16 ++++++++++++--- app/LocalUser/controllers.py | 4 +++- app/User/services.py | 9 +-------- .../blocklyprop/header.mustache | 1 + .../confim-teacher/blocklyprop/plain.mustache | 20 +++++++++++++++++++ .../en/confirm/blocklyprop/header.mustache | 2 +- .../en/confirm/blocklyprop/plain.mustache | 1 + 8 files changed, 41 insertions(+), 13 deletions(-) create mode 100644 templates/en/confim-teacher/blocklyprop/header.mustache create mode 100644 templates/en/confim-teacher/blocklyprop/plain.mustache diff --git a/.gitignore b/.gitignore index 1b5d2b6..2459a51 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,4 @@ nbactions.xml .idea build deploy-test +/CloudSession-Templates.tar.gz diff --git a/app/Email/services.py b/app/Email/services.py index 8e498c2..6eaeb70 100644 --- a/app/Email/services.py +++ b/app/Email/services.py @@ -1,11 +1,20 @@ from app import mail, app from os.path import expanduser, isfile from flask.ext.mail import Message -from app.User.services import SponsorType import pystache import logging +""" +""" + + +class SponsorType: + INDIVIDUAL=0 + PARENT=1 + GUARDIAN=2 + TEACHER=3 + def send_email_template_for_user(id_user, template, server, **kwargs): from app.User.services import get_user, is_coppa_covered @@ -26,13 +35,14 @@ def send_email_template_for_user(id_user, template, server, **kwargs): if template == 'confirm': if is_coppa_covered(user.birth_month, user.birth_year): user_email = user.parent_email + logging.info("COPPA account has a sponsor type of %s", user.parent_email_source) if user.parent_email_source == SponsorType.TEACHER: # Teacher handles the account confirmation - send_email_template_to_address(user_email, 'confim_teacher', server, user.locale, params) + send_email_template_to_address(user_email, 'confim-teacher', server, user.locale, params) elif user.parent_email_source == SponsorType.PARENT or user.parent_email_source == SponsorType.GUARDIAN: # Parent handles the account confirmation - send_email_template_to_address(user_email, 'confirm_parent', server, user.locale, params) + send_email_template_to_address(user_email, 'confirm-parent', server, user.locale, params) else: logging.info("COPPA account %s has invalid sponsor type [%s]", id_user, user.parent_email_source) else: diff --git a/app/LocalUser/controllers.py b/app/LocalUser/controllers.py index 8d51dec..6af2289 100644 --- a/app/LocalUser/controllers.py +++ b/app/LocalUser/controllers.py @@ -65,9 +65,11 @@ def post(self): class RequestConfirm(Resource): def get(self, email): - # Get values + # Get server URL server = request.headers.get('server') + logging.info("Requesting email confirmation for %s from server %s", email, server) + # Validate required fields validation = Validation() validation.add_required_field('email', email) diff --git a/app/User/services.py b/app/User/services.py index a5bb9d9..6045407 100644 --- a/app/User/services.py +++ b/app/User/services.py @@ -11,14 +11,6 @@ from models import User, ConfirmToken, ResetToken -# Implementing an enum-like structure for the user sponsor email type -class SponsorType: - INDIVIDUAL = 0 - PARENT = 1 - GUARDIAN = 2 - TEACHER = 3 - - def get_password_hash(password): salt = str(uuid.uuid1()) password_hash = hashlib.sha256("%s:%s" % (password, salt)).hexdigest() @@ -131,6 +123,7 @@ def send_email_confirm(id_user, server): db.session.add(confirm_token) try: + # Send an email to the user or user's responsible party to confirm the account request email_services.send_email_template_for_user(id_user, 'confirm', server, token=token) except Exception as ex: print("Exception {0}", ex.args) diff --git a/templates/en/confim-teacher/blocklyprop/header.mustache b/templates/en/confim-teacher/blocklyprop/header.mustache new file mode 100644 index 0000000..19c1a1c --- /dev/null +++ b/templates/en/confim-teacher/blocklyprop/header.mustache @@ -0,0 +1 @@ +{{! This is the email Subject line }}Please confirm your student's email address for BlocklyProp diff --git a/templates/en/confim-teacher/blocklyprop/plain.mustache b/templates/en/confim-teacher/blocklyprop/plain.mustache new file mode 100644 index 0000000..02ba7dc --- /dev/null +++ b/templates/en/confim-teacher/blocklyprop/plain.mustache @@ -0,0 +1,20 @@ +{{! This is the text body of the email}} +Hello, + +A student has created an account on the Parallax BlocklyProp web site under the +screen name {{screenname}}. When the account was created, your email address was +registered as the classroom instructor. If this is incorrect, please accept our +apologies. There is nothing more you need to do. The request will automatically expire. + +If this is your student, please confirm the registration by copying the link below into +your browser or by navigating to the second link and confirming the student's email address. + +Copy and paste into your browser +http://localhost:8080/blockly/confirm?locale={{locale}}&email={{email}}&token={{token}} to confirm your email address. + +If the above link is unable to complete your registration, please go to +http://localhost:8080/blockly/confirm and enter your email address and the token: {{token}} + +Regards, + +The Parallax team diff --git a/templates/en/confirm/blocklyprop/header.mustache b/templates/en/confirm/blocklyprop/header.mustache index 8d1f20a..ed52201 100644 --- a/templates/en/confirm/blocklyprop/header.mustache +++ b/templates/en/confirm/blocklyprop/header.mustache @@ -1 +1 @@ -Please confirm your email address for BlocklyProp +{{! This is the email Subject line }}Please confirm your email address for BlocklyProp diff --git a/templates/en/confirm/blocklyprop/plain.mustache b/templates/en/confirm/blocklyprop/plain.mustache index ff39fcb..023480c 100644 --- a/templates/en/confirm/blocklyprop/plain.mustache +++ b/templates/en/confirm/blocklyprop/plain.mustache @@ -1,3 +1,4 @@ +{{! This is the text body of the email}} Dear {{screenname}}, Please go to http://localhost:8080/blockly/confirm?locale={{locale}}&email={{email}}&token={{token}} to confirm your email address. From 3bfd977ae9ba14974fb98a370a3cfbc502e75c83 Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Fri, 12 May 2017 09:41:00 -0700 Subject: [PATCH 16/53] Create parent templates. Rename teacher template path --- .../blocklyprop/header.mustache | 1 + .../confirm-parent/blocklyprop/plain.mustache | 35 +++++++++++++++++++ .../blocklyprop/header.mustache | 0 .../blocklyprop/plain.mustache | 4 ++- 4 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 templates/en/confirm-parent/blocklyprop/header.mustache create mode 100644 templates/en/confirm-parent/blocklyprop/plain.mustache rename templates/en/{confim-teacher => confirm-teacher}/blocklyprop/header.mustache (100%) rename templates/en/{confim-teacher => confirm-teacher}/blocklyprop/plain.mustache (90%) diff --git a/templates/en/confirm-parent/blocklyprop/header.mustache b/templates/en/confirm-parent/blocklyprop/header.mustache new file mode 100644 index 0000000..6a65a8e --- /dev/null +++ b/templates/en/confirm-parent/blocklyprop/header.mustache @@ -0,0 +1 @@ +{{! This is the email Subject line }}New user registration confirmation request \ No newline at end of file diff --git a/templates/en/confirm-parent/blocklyprop/plain.mustache b/templates/en/confirm-parent/blocklyprop/plain.mustache new file mode 100644 index 0000000..e02f5fa --- /dev/null +++ b/templates/en/confirm-parent/blocklyprop/plain.mustache @@ -0,0 +1,35 @@ +{{! + This is the text body of the email new account notification to + a parent or guardian +}} +Hello, + +Someone, perhaps your child, has requested a new account on the Parallax +BlocklyProp web site (http://blockly.parallax.com) under the screen name +{{screenname}}. When the account was created, your email address was +received as the parent or guardian of the registrant. If this is incorrect, +please accept our apologies. There is nothing more you need to do. The +request will automatically expire. + +If this is your child, please confirm the registration by copying the link below into +your browser or by navigating to the second link and confirming the student's email address. + +Confirm account registration: +Copy and paste into your browser +http://localhost:8080/blockly/confirm?locale={{locale}}&email={{email}}&token={{token}} to confirm your email address. + +If the above link is unable to complete your registration, please go to +http://localhost:8080/blockly/confirm and enter your email address and the token: {{token}} + + +You may also elect to cancel the registration process immediately. To do +so, please copy and paste the link below. This will remove the pending registration +request and the account that was created during the registration process. + +[NEED A CANCEL LINK HERE] + + + +Regards, + +The Parallax team diff --git a/templates/en/confim-teacher/blocklyprop/header.mustache b/templates/en/confirm-teacher/blocklyprop/header.mustache similarity index 100% rename from templates/en/confim-teacher/blocklyprop/header.mustache rename to templates/en/confirm-teacher/blocklyprop/header.mustache diff --git a/templates/en/confim-teacher/blocklyprop/plain.mustache b/templates/en/confirm-teacher/blocklyprop/plain.mustache similarity index 90% rename from templates/en/confim-teacher/blocklyprop/plain.mustache rename to templates/en/confirm-teacher/blocklyprop/plain.mustache index 02ba7dc..d690344 100644 --- a/templates/en/confim-teacher/blocklyprop/plain.mustache +++ b/templates/en/confirm-teacher/blocklyprop/plain.mustache @@ -1,4 +1,6 @@ -{{! This is the text body of the email}} +{{! + This is the text body of the new account confirmation email directed to instructors +}} Hello, A student has created an account on the Parallax BlocklyProp web site under the From 9627ff969882384100880652caf086bd48cad282 Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Fri, 12 May 2017 09:42:45 -0700 Subject: [PATCH 17/53] Update code to handle COPPA compliant email templates --- app/Email/services.py | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/app/Email/services.py b/app/Email/services.py index 6eaeb70..34fe8a9 100644 --- a/app/Email/services.py +++ b/app/Email/services.py @@ -29,24 +29,27 @@ def send_email_template_for_user(id_user, template, server, **kwargs): if user is None: return False + # The elements in the params array represent the data elements that are + # available to the email templates. params['screenname'] = user.screen_name + params['email'] = user.email + params['sponsoremail'] = user.parent_email + + user_email = user.email # Send email to parent if user is under 13 years old - if template == 'confirm': - if is_coppa_covered(user.birth_month, user.birth_year): - user_email = user.parent_email - logging.info("COPPA account has a sponsor type of %s", user.parent_email_source) - - if user.parent_email_source == SponsorType.TEACHER: - # Teacher handles the account confirmation - send_email_template_to_address(user_email, 'confim-teacher', server, user.locale, params) - elif user.parent_email_source == SponsorType.PARENT or user.parent_email_source == SponsorType.GUARDIAN: - # Parent handles the account confirmation - send_email_template_to_address(user_email, 'confirm-parent', server, user.locale, params) - else: - logging.info("COPPA account %s has invalid sponsor type [%s]", id_user, user.parent_email_source) - else: - user_email = user.email + if template == 'confirm' and is_coppa_covered(user.birth_month, user.birth_year): + user_email = user.parent_email + logging.info("COPPA account has a sponsor type of %s", user.parent_email_source) + + if user.parent_email_source == SponsorType.TEACHER: + # Teacher handles the account confirmation + send_email_template_to_address(user_email, 'confirm-teacher', server, user.locale, params) + elif user.parent_email_source == SponsorType.PARENT or user.parent_email_source == SponsorType.GUARDIAN: + # Parent handles the account confirmation + send_email_template_to_address(user_email, 'confirm-parent', server, user.locale, params) + else: + logging.info("COPPA account %s has invalid sponsor type [%s]", id_user, user.parent_email_source) send_email_template_to_address(user_email, template, server, user.locale, params) From 5eb6e213b712a6cb9f38972d52988d769f680ab8 Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Fri, 12 May 2017 09:43:19 -0700 Subject: [PATCH 18/53] Add logging to help track code paths. --- app/LocalUser/controllers.py | 2 +- app/User/services.py | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/app/LocalUser/controllers.py b/app/LocalUser/controllers.py index 6af2289..e3ec51d 100644 --- a/app/LocalUser/controllers.py +++ b/app/LocalUser/controllers.py @@ -46,7 +46,7 @@ def post(self): confirm_token = ConfirmToken.query.filter_by(token=token).first() if confirm_token is None: - # Unkown token + # Unknown token return {'success': False, 'code': 510} if confirm_token.id_user != user.id: # Token is not for this user diff --git a/app/User/services.py b/app/User/services.py index 6045407..c388f64 100644 --- a/app/User/services.py +++ b/app/User/services.py @@ -95,16 +95,23 @@ def create_oauth_user( def send_email_confirm(id_user, server): + logging.info("Preparing new account confirmation email for user %s", id_user) + user = get_user(id_user) + if user is None: + logging.debug("Unknown user id: %s", id_user) return False, 1, 'User id not known' if user.confirmed: + logging.debug("User account %s has already been verified", id_user) return False, 2, 'Account already verified' if user.blocked: + logging.debug("User account %s has been blocked", id_user) return False, 3, 'Account Blocked' # check rate limiting if not rate_limiting_services.consume_tokens(id_user, 'email-confirm', 1): + logging.debug("Too many attempts to confirm account for user %s", id_user) return False, 10, 'Rate limiter exceeded' # Delete token if any exists From afd17dd4576e157e3126f02de0c582fb09917312 Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Fri, 12 May 2017 11:49:03 -0700 Subject: [PATCH 19/53] Complete draft email to teacher to register a student. --- app/Email/services.py | 19 ++++++++++++++++--- app/User/services.py | 5 +++-- .../confirm-parent/blocklyprop/plain.mustache | 2 +- .../blocklyprop/plain.mustache | 4 ++-- 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/app/Email/services.py b/app/Email/services.py index 34fe8a9..57357de 100644 --- a/app/Email/services.py +++ b/app/Email/services.py @@ -19,12 +19,14 @@ class SponsorType: def send_email_template_for_user(id_user, template, server, **kwargs): from app.User.services import get_user, is_coppa_covered - logging.info("Sending email to user: %s (%s)", id_user, template) + logging.info("Sending email to user: %s using template (%s)", id_user, template) params = {} for key, value in kwargs.items(): + logging.debug("Logging parameter %s = %s", key, value) params[key] = value + # Get a copy of the user record user = get_user(id_user) if user is None: return False @@ -33,12 +35,14 @@ def send_email_template_for_user(id_user, template, server, **kwargs): # available to the email templates. params['screenname'] = user.screen_name params['email'] = user.email + params['registrant-email'] = user.email params['sponsoremail'] = user.parent_email user_email = user.email # Send email to parent if user is under 13 years old if template == 'confirm' and is_coppa_covered(user.birth_month, user.birth_year): + # Send email only to the sponsor address user_email = user.parent_email logging.info("COPPA account has a sponsor type of %s", user.parent_email_source) @@ -51,23 +55,32 @@ def send_email_template_for_user(id_user, template, server, **kwargs): else: logging.info("COPPA account %s has invalid sponsor type [%s]", id_user, user.parent_email_source) - send_email_template_to_address(user_email, template, server, user.locale, params) + return + else: + # Registration not subject to COPPA regulations + send_email_template_to_address(user_email, template, server, user.locale, params) + return def send_email_template_to_address(recipient, template, server, locale, params=None, **kwargs): - # Read templates params = params or {} + + # Add any supplied arguments to the parameter dictionary for key, value in kwargs.items(): params[key] = value + params['email'] = recipient params['locale'] = locale + # Read templates (subject, plain, rich) = _read_templates(template, server, locale, params) + logging.info("Sending email to %s", recipient) send_email(recipient, subject, plain, rich) def send_email(recipient, subject, email_text, rich_email_text=None): + msg = Message( recipients=[recipient], subject=subject.rstrip(), diff --git a/app/User/services.py b/app/User/services.py index c388f64..d4c063f 100644 --- a/app/User/services.py +++ b/app/User/services.py @@ -130,11 +130,12 @@ def send_email_confirm(id_user, server): db.session.add(confirm_token) try: + logging.info("Sending account confirmation email to user: %s ", id_user) # Send an email to the user or user's responsible party to confirm the account request email_services.send_email_template_for_user(id_user, 'confirm', server, token=token) + logging.info("Completed email send process.") except Exception as ex: - print("Exception {0}", ex.args) - logging.error("Unable to send email. Message is: %s", ex.message) + logging.error("Error while sending email: %s", ex.message) return False, 99, 'Unable to contact SMTP server' return True, 0, 'Success' diff --git a/templates/en/confirm-parent/blocklyprop/plain.mustache b/templates/en/confirm-parent/blocklyprop/plain.mustache index e02f5fa..ea06899 100644 --- a/templates/en/confirm-parent/blocklyprop/plain.mustache +++ b/templates/en/confirm-parent/blocklyprop/plain.mustache @@ -16,7 +16,7 @@ your browser or by navigating to the second link and confirming the student's em Confirm account registration: Copy and paste into your browser -http://localhost:8080/blockly/confirm?locale={{locale}}&email={{email}}&token={{token}} to confirm your email address. +http://localhost:8080/blockly/confirm?locale={{locale}}&email={{registrant-email}}&token={{token}} to confirm your email address. If the above link is unable to complete your registration, please go to http://localhost:8080/blockly/confirm and enter your email address and the token: {{token}} diff --git a/templates/en/confirm-teacher/blocklyprop/plain.mustache b/templates/en/confirm-teacher/blocklyprop/plain.mustache index d690344..1e76d32 100644 --- a/templates/en/confirm-teacher/blocklyprop/plain.mustache +++ b/templates/en/confirm-teacher/blocklyprop/plain.mustache @@ -12,10 +12,10 @@ If this is your student, please confirm the registration by copying the link bel your browser or by navigating to the second link and confirming the student's email address. Copy and paste into your browser -http://localhost:8080/blockly/confirm?locale={{locale}}&email={{email}}&token={{token}} to confirm your email address. +http://localhost:8080/blockly/confirm?locale={{locale}}&email={{registrant-email}}&token={{token}} to confirm your email address. If the above link is unable to complete your registration, please go to -http://localhost:8080/blockly/confirm and enter your email address and the token: {{token}} +http://localhost:8080/blockly/confirm and enter your student's email address ({{registrant-email}}) and the token: {{token}} Regards, From 6fe37c1ef91b3d69f6e844dead51af9b17a7afe1 Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Tue, 16 May 2017 09:25:19 -0700 Subject: [PATCH 20/53] Updates to make the COPPA parent email work --- app/Authenticate/controllers.py | 4 +- app/Email/services.py | 36 ++++++++++-------- app/User/coppa.py | 38 +++++++++++++++++++ app/User/services.py | 29 +++----------- app/__init__.py | 2 + .../blocklyprop/header.mustache | 3 +- .../confirm-parent/blocklyprop/plain.mustache | 14 +++++++ .../blocklyprop/header.mustache | 3 +- 8 files changed, 85 insertions(+), 44 deletions(-) create mode 100644 app/User/coppa.py diff --git a/app/Authenticate/controllers.py b/app/Authenticate/controllers.py index 2ad1e6c..efc02a6 100644 --- a/app/Authenticate/controllers.py +++ b/app/Authenticate/controllers.py @@ -1,9 +1,7 @@ # Import the database object from the main app module import logging -import uuid -import datetime - import Failures + from app import db from app.User import services as user_services from app.RateLimiting import services as rate_limiting_services diff --git a/app/Email/services.py b/app/Email/services.py index 57357de..be15412 100644 --- a/app/Email/services.py +++ b/app/Email/services.py @@ -1,35 +1,37 @@ from app import mail, app from os.path import expanduser, isfile from flask.ext.mail import Message +from app.User.coppa import Coppa, SponsorType import pystache import logging + """ +TODO: System documentation goes here """ -class SponsorType: - INDIVIDUAL=0 - PARENT=1 - GUARDIAN=2 - TEACHER=3 +def send_email_template_for_user(id_user, template, server, **kwargs): + from app.User.services import get_user + # Get a copy of the user record + logging.info("Checking for a valid user record for user ID: %s", id_user) + user = get_user(id_user) -def send_email_template_for_user(id_user, template, server, **kwargs): - from app.User.services import get_user, is_coppa_covered + if user is None: + logging.error("Cannot send email: Invalid user record") + return False + else: + logging.info("Email template received user: %s", user.id) - logging.info("Sending email to user: %s using template (%s)", id_user, template) + logging.info("Sending email to user: %s using template (%s)", user.id, template) params = {} for key, value in kwargs.items(): logging.debug("Logging parameter %s = %s", key, value) params[key] = value - # Get a copy of the user record - user = get_user(id_user) - if user is None: - return False # The elements in the params array represent the data elements that are # available to the email templates. @@ -38,10 +40,12 @@ def send_email_template_for_user(id_user, template, server, **kwargs): params['registrant-email'] = user.email params['sponsoremail'] = user.parent_email + #Default the recipient email address user_email = user.email + coppa = Coppa() # Send email to parent if user is under 13 years old - if template == 'confirm' and is_coppa_covered(user.birth_month, user.birth_year): + if template == 'confirm' and coppa.is_coppa_covered(user.birth_month, user.birth_year): # Send email only to the sponsor address user_email = user.parent_email logging.info("COPPA account has a sponsor type of %s", user.parent_email_source) @@ -49,11 +53,12 @@ def send_email_template_for_user(id_user, template, server, **kwargs): if user.parent_email_source == SponsorType.TEACHER: # Teacher handles the account confirmation send_email_template_to_address(user_email, 'confirm-teacher', server, user.locale, params) - elif user.parent_email_source == SponsorType.PARENT or user.parent_email_source == SponsorType.GUARDIAN: + elif user.parent_email_source == SponsorType.PARENT or\ + user.parent_email_source == SponsorType.GUARDIAN: # Parent handles the account confirmation send_email_template_to_address(user_email, 'confirm-parent', server, user.locale, params) else: - logging.info("COPPA account %s has invalid sponsor type [%s]", id_user, user.parent_email_source) + logging.info("COPPA account %s has invalid sponsor type [%s]", user.id, user.parent_email_source) return else: @@ -62,6 +67,7 @@ def send_email_template_for_user(id_user, template, server, **kwargs): return + def send_email_template_to_address(recipient, template, server, locale, params=None, **kwargs): params = params or {} diff --git a/app/User/coppa.py b/app/User/coppa.py new file mode 100644 index 0000000..88fa09a --- /dev/null +++ b/app/User/coppa.py @@ -0,0 +1,38 @@ + +import datetime + +# Enumerate the sponsor types for COPPA eligible user accounts +class SponsorType: + INDIVIDUAL = 0 + PARENT = 1 + GUARDIAN = 2 + TEACHER = 3 + + def __init__(self): + pass + + +class Coppa: + def __init__(self): + pass + + # Return true if the date is less than 13 years + def is_coppa_covered(self, month, year): + # This is the number of months a typical thirteen years old has been on planet Earth. + cap = 156 + + # This is the actual number of months a typical user has been on the same planet. + user_age = (year * 12) + month + + # Current year and month + current_month = datetime.date.today().month + current_year = datetime.date.today().year + + # This represents the number of months since the inception of AD + # Unless you want to count that first year as part of BC. + current_cap = (current_year * 12) + current_month + + if current_cap - user_age > cap: + return False + else: + return True diff --git a/app/User/services.py b/app/User/services.py index d4c063f..d72d0be 100644 --- a/app/User/services.py +++ b/app/User/services.py @@ -11,6 +11,10 @@ from models import User, ConfirmToken, ResetToken +def get_user(id_user): + return User.query.get(id_user) + + def get_password_hash(password): salt = str(uuid.uuid1()) password_hash = hashlib.sha256("%s:%s" % (password, salt)).hexdigest() @@ -23,10 +27,6 @@ def check_password(id_user, password): return user.password == password_hash -def get_user(id_user): - return User.query.get(id_user) - - def get_user_by_email(email): return User.query.filter_by(email=email).first() @@ -96,6 +96,7 @@ def create_oauth_user( def send_email_confirm(id_user, server): logging.info("Preparing new account confirmation email for user %s", id_user) + logging.info("Account request received from server: %s", server) user = get_user(id_user) @@ -171,23 +172,3 @@ def send_password_reset(id_user, server): return True, 0, 'Success' -# Return true if the date is less than 13 years -def is_coppa_covered(month,year): - # This is the number of months a typical thirteen years old has been on planet Earth. - cap = 156 - - # This is the actual number of months a typical user has been on the same planet. - user_age = (year * 12) + month - - # Current year and month - current_month = datetime.date.today().month - current_year = datetime.date.today().year - - # This represents the number of months since the inception of AD - # Unless you want to count that first year as part of BC. - current_cap = (current_year * 12) + current_month - - if current_cap - user_age > cap: - return False - else: - return True diff --git a/app/__init__.py b/app/__init__.py index 8ab180d..3b71c6d 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -33,6 +33,8 @@ defaults = { 'database.url': 'mysql+mysqldb://cloudsession:cloudsession@localhost:3306/cloudsession', + 'request.host': 'http://localhost:8080/blockly', + 'sentry-dsn': None, 'mail.host': 'localhost', diff --git a/templates/en/confirm-parent/blocklyprop/header.mustache b/templates/en/confirm-parent/blocklyprop/header.mustache index 6a65a8e..3425542 100644 --- a/templates/en/confirm-parent/blocklyprop/header.mustache +++ b/templates/en/confirm-parent/blocklyprop/header.mustache @@ -1 +1,2 @@ -{{! This is the email Subject line }}New user registration confirmation request \ No newline at end of file +{{! This is the email Subject line }} +New user registration confirmation request \ No newline at end of file diff --git a/templates/en/confirm-parent/blocklyprop/plain.mustache b/templates/en/confirm-parent/blocklyprop/plain.mustache index ea06899..3840702 100644 --- a/templates/en/confirm-parent/blocklyprop/plain.mustache +++ b/templates/en/confirm-parent/blocklyprop/plain.mustache @@ -11,6 +11,20 @@ received as the parent or guardian of the registrant. If this is incorrect, please accept our apologies. There is nothing more you need to do. The request will automatically expire. +Why are we sending this? In the US, the federal Children's Online Privacy +Protection Act, known as COPPA, requires that we communicate with a parent +or guardian if the person registering an BlocklyProp account is under the +age of 13. The Act allows you to decline the registration request. If you +choose this option, we will immediately remove the registration information +and the associated account. If you choose to confirm the the request and +activate the account, you may close the account at any time by clicking on +the link provided below. This action will close the account and remove any +projects that are associated with the account. + +A full copy of our Child Privacy Policy is available online at: +http://localhost:8080/blockly/child-privacy-policy + + If this is your child, please confirm the registration by copying the link below into your browser or by navigating to the second link and confirming the student's email address. diff --git a/templates/en/confirm-teacher/blocklyprop/header.mustache b/templates/en/confirm-teacher/blocklyprop/header.mustache index 19c1a1c..7447513 100644 --- a/templates/en/confirm-teacher/blocklyprop/header.mustache +++ b/templates/en/confirm-teacher/blocklyprop/header.mustache @@ -1 +1,2 @@ -{{! This is the email Subject line }}Please confirm your student's email address for BlocklyProp +{{! This is the email Subject line }} +Please confirm your student's email address for BlocklyProp From 49aec572bccdb84fe6483f2311be21b71450d579 Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Tue, 16 May 2017 15:12:06 -0700 Subject: [PATCH 21/53] Bumping up the version number. 1.1.0 include support for US COPPA regulations --- app/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/__init__.py b/app/__init__.py index 3b71c6d..362949a 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -23,7 +23,7 @@ from raven.contrib.flask import Sentry app = Flask(__name__) -version = "1.0.1" +version = "1.1.0" db = None # Load basic configurations From 282c643dec08dba188a7b26961667f47a020f955 Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Fri, 19 May 2017 07:35:23 -0700 Subject: [PATCH 22/53] Add parent notification for US-EN language. Add paragraph to link to the Learn site --- .../confirm-parent/blocklyprop/plain.mustache | 6 ++ .../blocklyprop/header.mustache | 2 + .../confirm-parent/blocklyprop/plain.mustache | 55 +++++++++++++++++++ 3 files changed, 63 insertions(+) create mode 100644 templates/en_US/confirm-parent/blocklyprop/header.mustache create mode 100644 templates/en_US/confirm-parent/blocklyprop/plain.mustache diff --git a/templates/en/confirm-parent/blocklyprop/plain.mustache b/templates/en/confirm-parent/blocklyprop/plain.mustache index 3840702..4f4dde9 100644 --- a/templates/en/confirm-parent/blocklyprop/plain.mustache +++ b/templates/en/confirm-parent/blocklyprop/plain.mustache @@ -11,6 +11,12 @@ received as the parent or guardian of the registrant. If this is incorrect, please accept our apologies. There is nothing more you need to do. The request will automatically expire. +BlocklyProp is a free, online programming tool designed for education. See +Getting Started with BlocklyProp +http://learn.parallax.com/tutorials/language/blocklyprop/getting-started-blocklyprop +for more information. + + Why are we sending this? In the US, the federal Children's Online Privacy Protection Act, known as COPPA, requires that we communicate with a parent or guardian if the person registering an BlocklyProp account is under the diff --git a/templates/en_US/confirm-parent/blocklyprop/header.mustache b/templates/en_US/confirm-parent/blocklyprop/header.mustache new file mode 100644 index 0000000..3425542 --- /dev/null +++ b/templates/en_US/confirm-parent/blocklyprop/header.mustache @@ -0,0 +1,2 @@ +{{! This is the email Subject line }} +New user registration confirmation request \ No newline at end of file diff --git a/templates/en_US/confirm-parent/blocklyprop/plain.mustache b/templates/en_US/confirm-parent/blocklyprop/plain.mustache new file mode 100644 index 0000000..4f4dde9 --- /dev/null +++ b/templates/en_US/confirm-parent/blocklyprop/plain.mustache @@ -0,0 +1,55 @@ +{{! + This is the text body of the email new account notification to + a parent or guardian +}} +Hello, + +Someone, perhaps your child, has requested a new account on the Parallax +BlocklyProp web site (http://blockly.parallax.com) under the screen name +{{screenname}}. When the account was created, your email address was +received as the parent or guardian of the registrant. If this is incorrect, +please accept our apologies. There is nothing more you need to do. The +request will automatically expire. + +BlocklyProp is a free, online programming tool designed for education. See +Getting Started with BlocklyProp +http://learn.parallax.com/tutorials/language/blocklyprop/getting-started-blocklyprop +for more information. + + +Why are we sending this? In the US, the federal Children's Online Privacy +Protection Act, known as COPPA, requires that we communicate with a parent +or guardian if the person registering an BlocklyProp account is under the +age of 13. The Act allows you to decline the registration request. If you +choose this option, we will immediately remove the registration information +and the associated account. If you choose to confirm the the request and +activate the account, you may close the account at any time by clicking on +the link provided below. This action will close the account and remove any +projects that are associated with the account. + +A full copy of our Child Privacy Policy is available online at: +http://localhost:8080/blockly/child-privacy-policy + + +If this is your child, please confirm the registration by copying the link below into +your browser or by navigating to the second link and confirming the student's email address. + +Confirm account registration: +Copy and paste into your browser +http://localhost:8080/blockly/confirm?locale={{locale}}&email={{registrant-email}}&token={{token}} to confirm your email address. + +If the above link is unable to complete your registration, please go to +http://localhost:8080/blockly/confirm and enter your email address and the token: {{token}} + + +You may also elect to cancel the registration process immediately. To do +so, please copy and paste the link below. This will remove the pending registration +request and the account that was created during the registration process. + +[NEED A CANCEL LINK HERE] + + + +Regards, + +The Parallax team From ef71d2a8e846453064fa4d94d1a9575952f943fd Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Fri, 26 May 2017 11:15:35 -0700 Subject: [PATCH 23/53] Add documentation and model version number --- app/User/models.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/app/User/models.py b/app/User/models.py index 71ee9b2..9a7da45 100644 --- a/app/User/models.py +++ b/app/User/models.py @@ -2,6 +2,28 @@ class User(db.Model): + """ + User madel provides a direct mapping to the underlying + database. + + Version 1 deploys the following fields: + id (BigInteger) - Unique record identifier + email (String(250)) - User email address + password (String(100)) - User account password + salt (String(50)) - A hash used to encrypt password + auth_source (String(250)) - Identifier for system providing authentication + locale (String(50)) - User language + blocked (Boolean) - Flag to indicate account is disabled + confirmed (Boolean) - Flag to indicate account has been verified + screen_name (String(250)) - Unique user screen name + + Version 2 adds support for US COPPA compliance. The following fields were added: + birth_month (INTEGER) - User birth month + birth_year (INTEGER) - User birth year + parent_email (String(250)) - Sponsor email address + parent_email_source (INTEGER) - Classification of sponsor email address + + """ id = db.Column(db.BigInteger, primary_key=True) email = db.Column(db.String(250), unique=True) password = db.Column(db.String(100)) @@ -21,6 +43,7 @@ class User(db.Model): def __init__(self): self.blocked = False self.confirmed = False + self.version = 2 def __repr__(self): return '' % self.email From 81431441c23468ac80db3b5cedc6ba5ecd975a35 Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Fri, 26 May 2017 11:34:33 -0700 Subject: [PATCH 24/53] Hide build files --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 47e079a..2459a51 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,6 @@ nbactions.xml ################# .idea +build +deploy-test +/CloudSession-Templates.tar.gz From 642eebce9b93a538c34e1bde8e7bd0db664c6a0e Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Mon, 12 Jun 2017 15:40:23 -0700 Subject: [PATCH 25/53] Add template macro for blocklyprop host. Updated templates to use the new blocklyprop-host macro instead of localhost. --- app/Email/services.py | 2 +- app/__init__.py | 4 +++- .../confirm-parent/blocklyprop/plain.mustache | 6 ++--- .../blocklyprop/plain.mustache | 4 ++-- .../en/confirm/blocklyprop/plain.mustache | 4 ++-- templates/en/reset/blocklyprop/plain.mustache | 4 ++-- .../confirm-parent/blocklyprop/plain.mustache | 6 ++--- .../blocklyprop/header.mustache | 2 ++ .../blocklyprop/plain.mustache | 22 +++++++++++++++++++ .../en_US/confirm/blocklyprop/plain.mustache | 5 +++-- .../en_US/reset/blocklyprop/plain.mustache | 4 ++-- 11 files changed, 45 insertions(+), 18 deletions(-) create mode 100644 templates/en_US/confirm-teacher/blocklyprop/header.mustache create mode 100644 templates/en_US/confirm-teacher/blocklyprop/plain.mustache diff --git a/app/Email/services.py b/app/Email/services.py index be15412..061ebef 100644 --- a/app/Email/services.py +++ b/app/Email/services.py @@ -32,13 +32,13 @@ def send_email_template_for_user(id_user, template, server, **kwargs): logging.debug("Logging parameter %s = %s", key, value) params[key] = value - # The elements in the params array represent the data elements that are # available to the email templates. params['screenname'] = user.screen_name params['email'] = user.email params['registrant-email'] = user.email params['sponsoremail'] = user.parent_email + params['blocklyprop-host'] = app.config['CLOUD_SESSION_PROPERTIES']['response.host'] #Default the recipient email address user_email = user.email diff --git a/app/__init__.py b/app/__init__.py index 362949a..8091bf7 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -34,6 +34,7 @@ 'database.url': 'mysql+mysqldb://cloudsession:cloudsession@localhost:3306/cloudsession', 'request.host': 'http://localhost:8080/blockly', + 'response.host': 'localhost', 'sentry-dsn': None, @@ -57,7 +58,7 @@ 'bucket.password-reset.size': '2', 'bucket.password-reset.input': '1', - 'bucket.password-reset.freq': '1800000', + 'bucket.password-reset.freq': '600000', 'bucket.email-confirm.size': '2', 'bucket.email-confirm.input': '1', @@ -76,6 +77,7 @@ app_configs = {} for (key, value) in configs.items('section'): app_configs[key] = value + logging.info("Key:%s, Value:%s", key, value) app.config['CLOUD_SESSION_PROPERTIES'] = app_configs diff --git a/templates/en/confirm-parent/blocklyprop/plain.mustache b/templates/en/confirm-parent/blocklyprop/plain.mustache index 4f4dde9..a77a937 100644 --- a/templates/en/confirm-parent/blocklyprop/plain.mustache +++ b/templates/en/confirm-parent/blocklyprop/plain.mustache @@ -28,7 +28,7 @@ the link provided below. This action will close the account and remove any projects that are associated with the account. A full copy of our Child Privacy Policy is available online at: -http://localhost:8080/blockly/child-privacy-policy +http://{{blocklyprop-host}}:8080/blockly/child-privacy-policy If this is your child, please confirm the registration by copying the link below into @@ -36,10 +36,10 @@ your browser or by navigating to the second link and confirming the student's em Confirm account registration: Copy and paste into your browser -http://localhost:8080/blockly/confirm?locale={{locale}}&email={{registrant-email}}&token={{token}} to confirm your email address. +http://{{blocklyprop-host}}:8080/blockly/confirm?locale={{locale}}&email={{registrant-email}}&token={{token}} to confirm your email address. If the above link is unable to complete your registration, please go to -http://localhost:8080/blockly/confirm and enter your email address and the token: {{token}} +http://{{blocklyprop-host}}:8080/blockly/confirm and enter your email address and the token: {{token}} You may also elect to cancel the registration process immediately. To do diff --git a/templates/en/confirm-teacher/blocklyprop/plain.mustache b/templates/en/confirm-teacher/blocklyprop/plain.mustache index 1e76d32..65a651c 100644 --- a/templates/en/confirm-teacher/blocklyprop/plain.mustache +++ b/templates/en/confirm-teacher/blocklyprop/plain.mustache @@ -12,10 +12,10 @@ If this is your student, please confirm the registration by copying the link bel your browser or by navigating to the second link and confirming the student's email address. Copy and paste into your browser -http://localhost:8080/blockly/confirm?locale={{locale}}&email={{registrant-email}}&token={{token}} to confirm your email address. +http://{{blocklyprop-host}}:8080/blockly/confirm?locale={{locale}}&email={{registrant-email}}&token={{token}} to confirm your email address. If the above link is unable to complete your registration, please go to -http://localhost:8080/blockly/confirm and enter your student's email address ({{registrant-email}}) and the token: {{token}} +http://{{blocklyprop-host}}:8080/blockly/confirm and enter your student's email address ({{registrant-email}}) and the token: {{token}} Regards, diff --git a/templates/en/confirm/blocklyprop/plain.mustache b/templates/en/confirm/blocklyprop/plain.mustache index 023480c..9792de4 100644 --- a/templates/en/confirm/blocklyprop/plain.mustache +++ b/templates/en/confirm/blocklyprop/plain.mustache @@ -1,9 +1,9 @@ {{! This is the text body of the email}} Dear {{screenname}}, -Please go to http://localhost:8080/blockly/confirm?locale={{locale}}&email={{email}}&token={{token}} to confirm your email address. +Please go to http://{{blocklyprop-host}}:8080/blockly/confirm?locale={{locale}}&email={{email}}&token={{token}} to confirm your email address. -If the url does not work, please go to http://localhost:8080/blockly/confirm and enter your email address and the token: {{token}} +If the url does not work, please go to http://{{blocklyprop-host}}:8080/blockly/confirm and enter your email address and the token: {{token}} The Parallax team diff --git a/templates/en/reset/blocklyprop/plain.mustache b/templates/en/reset/blocklyprop/plain.mustache index 9a5e5f5..3e5166a 100644 --- a/templates/en/reset/blocklyprop/plain.mustache +++ b/templates/en/reset/blocklyprop/plain.mustache @@ -2,9 +2,9 @@ Dear {{screenname}}, A request was made to reset your BlocklyProp account password. -If this request was made by you, please go to http://localost:8080/blockly/reset?locale={{locale}}&email={{email}}&token={{token}} to reset your password now. +If this request was made by you, please go to http://{{blocklyprop-host}}:8080/blockly/reset?locale={{locale}}&email={{email}}&token={{token}} to reset your password now. -If the url does not work, please go to http://localhost:8080/blockly/reset and use the token: {{token}} +If the url does not work, please go to http://{{blocklyprop-host}}:8080/blockly/reset and use the token: {{token}} If you did not make this request, you may safely ignore this message. diff --git a/templates/en_US/confirm-parent/blocklyprop/plain.mustache b/templates/en_US/confirm-parent/blocklyprop/plain.mustache index 4f4dde9..a77a937 100644 --- a/templates/en_US/confirm-parent/blocklyprop/plain.mustache +++ b/templates/en_US/confirm-parent/blocklyprop/plain.mustache @@ -28,7 +28,7 @@ the link provided below. This action will close the account and remove any projects that are associated with the account. A full copy of our Child Privacy Policy is available online at: -http://localhost:8080/blockly/child-privacy-policy +http://{{blocklyprop-host}}:8080/blockly/child-privacy-policy If this is your child, please confirm the registration by copying the link below into @@ -36,10 +36,10 @@ your browser or by navigating to the second link and confirming the student's em Confirm account registration: Copy and paste into your browser -http://localhost:8080/blockly/confirm?locale={{locale}}&email={{registrant-email}}&token={{token}} to confirm your email address. +http://{{blocklyprop-host}}:8080/blockly/confirm?locale={{locale}}&email={{registrant-email}}&token={{token}} to confirm your email address. If the above link is unable to complete your registration, please go to -http://localhost:8080/blockly/confirm and enter your email address and the token: {{token}} +http://{{blocklyprop-host}}:8080/blockly/confirm and enter your email address and the token: {{token}} You may also elect to cancel the registration process immediately. To do diff --git a/templates/en_US/confirm-teacher/blocklyprop/header.mustache b/templates/en_US/confirm-teacher/blocklyprop/header.mustache new file mode 100644 index 0000000..7447513 --- /dev/null +++ b/templates/en_US/confirm-teacher/blocklyprop/header.mustache @@ -0,0 +1,2 @@ +{{! This is the email Subject line }} +Please confirm your student's email address for BlocklyProp diff --git a/templates/en_US/confirm-teacher/blocklyprop/plain.mustache b/templates/en_US/confirm-teacher/blocklyprop/plain.mustache new file mode 100644 index 0000000..65a651c --- /dev/null +++ b/templates/en_US/confirm-teacher/blocklyprop/plain.mustache @@ -0,0 +1,22 @@ +{{! + This is the text body of the new account confirmation email directed to instructors +}} +Hello, + +A student has created an account on the Parallax BlocklyProp web site under the +screen name {{screenname}}. When the account was created, your email address was +registered as the classroom instructor. If this is incorrect, please accept our +apologies. There is nothing more you need to do. The request will automatically expire. + +If this is your student, please confirm the registration by copying the link below into +your browser or by navigating to the second link and confirming the student's email address. + +Copy and paste into your browser +http://{{blocklyprop-host}}:8080/blockly/confirm?locale={{locale}}&email={{registrant-email}}&token={{token}} to confirm your email address. + +If the above link is unable to complete your registration, please go to +http://{{blocklyprop-host}}:8080/blockly/confirm and enter your student's email address ({{registrant-email}}) and the token: {{token}} + +Regards, + +The Parallax team diff --git a/templates/en_US/confirm/blocklyprop/plain.mustache b/templates/en_US/confirm/blocklyprop/plain.mustache index ff39fcb..9792de4 100644 --- a/templates/en_US/confirm/blocklyprop/plain.mustache +++ b/templates/en_US/confirm/blocklyprop/plain.mustache @@ -1,8 +1,9 @@ +{{! This is the text body of the email}} Dear {{screenname}}, -Please go to http://localhost:8080/blockly/confirm?locale={{locale}}&email={{email}}&token={{token}} to confirm your email address. +Please go to http://{{blocklyprop-host}}:8080/blockly/confirm?locale={{locale}}&email={{email}}&token={{token}} to confirm your email address. -If the url does not work, please go to http://localhost:8080/blockly/confirm and enter your email address and the token: {{token}} +If the url does not work, please go to http://{{blocklyprop-host}}:8080/blockly/confirm and enter your email address and the token: {{token}} The Parallax team diff --git a/templates/en_US/reset/blocklyprop/plain.mustache b/templates/en_US/reset/blocklyprop/plain.mustache index 59f709e..3e5166a 100644 --- a/templates/en_US/reset/blocklyprop/plain.mustache +++ b/templates/en_US/reset/blocklyprop/plain.mustache @@ -2,9 +2,9 @@ Dear {{screenname}}, A request was made to reset your BlocklyProp account password. -If this request was made by you, please go to http://localhost:8080/blockly/reset?locale={{locale}}&email={{email}}&token={{token}} to reset your password now. +If this request was made by you, please go to http://{{blocklyprop-host}}:8080/blockly/reset?locale={{locale}}&email={{email}}&token={{token}} to reset your password now. -If the url does not work, please go to http://localhost:8080/blockly/reset and use the token: {{token}} +If the url does not work, please go to http://{{blocklyprop-host}}:8080/blockly/reset and use the token: {{token}} If you did not make this request, you may safely ignore this message. From cabbadc5e88e5ae203963380170992ea3362a02b Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Tue, 20 Jun 2017 07:17:01 -0700 Subject: [PATCH 26/53] Add to configuration file documentation --- cloudsession.properties.example | 43 ++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/cloudsession.properties.example b/cloudsession.properties.example index f2898f3..0ff000d 100644 --- a/cloudsession.properties.example +++ b/cloudsession.properties.example @@ -9,17 +9,56 @@ database.password = database_user_password # SMTP server configuration mail.host = smtp.example.com +mail.port = 25 mail.from = no_reply@example.com mail.authenticated = true mail.user = authenticated_username mail.password = authenticated_user_password +mail.ssl = false mail.tls = true # The email notification system relies on a number of templates # to produce the email messages sent to users. email.template.path = /usr/share/tomcat7/templates +# When a new account confirmation email is created, a security +# token is created and attached to the confirmation request. +# The token has a default lifespan of 12 hours. The default +# can be adjusted here. +# ---------------------------------------------------------- +confirm-token-validity-hours = 48 + + +# When a password reset request has been received, a security +# token is created and attached to an email sent to the user. +# The token has a default lifespan of 12 hours. The default +# value can be adjusted with this setting. +# ----------------------------------------------------------- +reset-token-validity-hours = 4 + +# Bucket types are objects that can be applied to the rate-limiting service built +# into the server. Bucket types are arbitrary units. There can be more than one +# bucket type defined. +# --------------------------------------------------------------------------------- +bucket.types = compile + # Rate limiting +# Various buckets can be defined to limit the use of specific +# system resources and access to specific system features. +# Each bucket has three characteristics; size, input and +# frequency. These are defined as: +# +# size = Sets the number of time the access or feature can +# be used before the system stops listening to that +# user's requests for access or service. +# +# input = Sets the number of additional tokens are available +# at each interval as defined in 'freq'. +# +# freq = Set the interval, in milliseconds, that system will +# wait until adding tokens as set in 'input'. +# --------------------------------------------------------------- + # Starting number of compiles bucket.compile.size = 100 @@ -32,7 +71,3 @@ bucket.compile.freq = 500000 # Enable detailed statistics on applicaiton operation metrics.console.enable = false -# Bucket types are objects that can be applied to the rate-limiting service built -# into the server. Bucket types are arbitrary units. There can be more than one -# bucket type defined. -bucket.types = compile From 4c2d481ea753e7009ee54b1a5b0fc36fe1f8e53f Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Tue, 20 Jun 2017 07:19:27 -0700 Subject: [PATCH 27/53] Add deployment scripts to ignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 2459a51..fa45eca 100644 --- a/.gitignore +++ b/.gitignore @@ -19,5 +19,7 @@ nbactions.xml .idea build +copy-test deploy-test +deploy2coppa /CloudSession-Templates.tar.gz From 4ff17d7f005612b2c72a24e6f5fa0746a5e71102 Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Tue, 20 Jun 2017 11:40:16 -0700 Subject: [PATCH 28/53] Update templates to use new email aliases for studdent and teacher. Add code to handle password reset email for Coppa restricted users. --- app/Email/services.py | 8 ++++++++ .../en/confirm-parent/blocklyprop/plain.mustache | 6 +++--- .../en/confirm-teacher/blocklyprop/plain.mustache | 4 ++-- templates/en/confirm/blocklyprop/plain.mustache | 4 ++-- .../en/reset-coppa/blocklyprop/header.mustache | 1 + .../en/reset-coppa/blocklyprop/plain.mustache | 15 +++++++++++++++ templates/en/reset/blocklyprop/plain.mustache | 8 ++++---- .../confirm-parent/blocklyprop/plain.mustache | 6 +++--- .../confirm-teacher/blocklyprop/plain.mustache | 4 ++-- .../en_US/confirm/blocklyprop/plain.mustache | 4 ++-- .../en_US/reset-coppa/blocklyprop/header.mustache | 1 + .../en_US/reset-coppa/blocklyprop/plain.mustache | 15 +++++++++++++++ templates/en_US/reset/blocklyprop/plain.mustache | 8 ++++---- 13 files changed, 62 insertions(+), 22 deletions(-) create mode 100644 templates/en/reset-coppa/blocklyprop/header.mustache create mode 100644 templates/en/reset-coppa/blocklyprop/plain.mustache create mode 100644 templates/en_US/reset-coppa/blocklyprop/header.mustache create mode 100644 templates/en_US/reset-coppa/blocklyprop/plain.mustache diff --git a/app/Email/services.py b/app/Email/services.py index 061ebef..e4ef094 100644 --- a/app/Email/services.py +++ b/app/Email/services.py @@ -60,6 +60,14 @@ def send_email_template_for_user(id_user, template, server, **kwargs): else: logging.info("COPPA account %s has invalid sponsor type [%s]", user.id, user.parent_email_source) + return + elif template == 'reset' and coppa.is_coppa_covered(user.birth_month, user.birth_year): + # Send email only to the sponsor address + logging.info("COPPA account has a sponsor type of %s", user.parent_email_source) + + # Send password reset to student and parent + send_email_template_to_address(user.email, 'reset-coppa', server, user.locale, params) + send_email_template_to_address(user.parent_email, 'reset-coppa', server, user.locale, params) return else: # Registration not subject to COPPA regulations diff --git a/templates/en/confirm-parent/blocklyprop/plain.mustache b/templates/en/confirm-parent/blocklyprop/plain.mustache index a77a937..b01e33f 100644 --- a/templates/en/confirm-parent/blocklyprop/plain.mustache +++ b/templates/en/confirm-parent/blocklyprop/plain.mustache @@ -28,7 +28,7 @@ the link provided below. This action will close the account and remove any projects that are associated with the account. A full copy of our Child Privacy Policy is available online at: -http://{{blocklyprop-host}}:8080/blockly/child-privacy-policy +http://{{blocklyprop-host}}/blockly/child-privacy-policy If this is your child, please confirm the registration by copying the link below into @@ -36,10 +36,10 @@ your browser or by navigating to the second link and confirming the student's em Confirm account registration: Copy and paste into your browser -http://{{blocklyprop-host}}:8080/blockly/confirm?locale={{locale}}&email={{registrant-email}}&token={{token}} to confirm your email address. +http://{{blocklyprop-host}}/blockly/confirm?locale={{locale}}&email={{registrant-email}}&token={{token}} to confirm your email address. If the above link is unable to complete your registration, please go to -http://{{blocklyprop-host}}:8080/blockly/confirm and enter your email address and the token: {{token}} +http://{{blocklyprop-host}}/blockly/confirm and enter your email address and the token: {{token}} You may also elect to cancel the registration process immediately. To do diff --git a/templates/en/confirm-teacher/blocklyprop/plain.mustache b/templates/en/confirm-teacher/blocklyprop/plain.mustache index 65a651c..78a1f1b 100644 --- a/templates/en/confirm-teacher/blocklyprop/plain.mustache +++ b/templates/en/confirm-teacher/blocklyprop/plain.mustache @@ -12,10 +12,10 @@ If this is your student, please confirm the registration by copying the link bel your browser or by navigating to the second link and confirming the student's email address. Copy and paste into your browser -http://{{blocklyprop-host}}:8080/blockly/confirm?locale={{locale}}&email={{registrant-email}}&token={{token}} to confirm your email address. +http://{{blocklyprop-host}}/blockly/confirm?locale={{locale}}&email={{registrant-email}}&token={{token}} to confirm your email address. If the above link is unable to complete your registration, please go to -http://{{blocklyprop-host}}:8080/blockly/confirm and enter your student's email address ({{registrant-email}}) and the token: {{token}} +http://{{blocklyprop-host}}/blockly/confirm and enter your student's email address ({{registrant-email}}) and the token: {{token}} Regards, diff --git a/templates/en/confirm/blocklyprop/plain.mustache b/templates/en/confirm/blocklyprop/plain.mustache index 9792de4..e39fe63 100644 --- a/templates/en/confirm/blocklyprop/plain.mustache +++ b/templates/en/confirm/blocklyprop/plain.mustache @@ -1,9 +1,9 @@ {{! This is the text body of the email}} Dear {{screenname}}, -Please go to http://{{blocklyprop-host}}:8080/blockly/confirm?locale={{locale}}&email={{email}}&token={{token}} to confirm your email address. +Please go to http://{{blocklyprop-host}}/blockly/confirm?locale={{locale}}&email={{email}}&token={{token}} to confirm your email address. -If the url does not work, please go to http://{{blocklyprop-host}}:8080/blockly/confirm and enter your email address and the token: {{token}} +If the url does not work, please go to http://{{blocklyprop-host}}/blockly/confirm and enter your email address and the token: {{token}} The Parallax team diff --git a/templates/en/reset-coppa/blocklyprop/header.mustache b/templates/en/reset-coppa/blocklyprop/header.mustache new file mode 100644 index 0000000..630929b --- /dev/null +++ b/templates/en/reset-coppa/blocklyprop/header.mustache @@ -0,0 +1 @@ +Reset your BlocklyProp password diff --git a/templates/en/reset-coppa/blocklyprop/plain.mustache b/templates/en/reset-coppa/blocklyprop/plain.mustache new file mode 100644 index 0000000..4b05bee --- /dev/null +++ b/templates/en/reset-coppa/blocklyprop/plain.mustache @@ -0,0 +1,15 @@ +{{! + This is the text body of the email that is sent to an account holder + to reset the password associated with the account. +}} +Hello, + +The BlocklyProp web site has received a request to reset the password for user '{{screenname}}'. + +If this request was made by you, please go to http://{{blocklyprop-host}}/blockly/reset?locale={{locale}}&email={{registrant-email}}&token={{token}} to reset your password now. + +If the url does not work, please go to http://{{blocklyprop-host}}/blockly/reset and use the token: {{token}} + +If you did not make this request, you may safely ignore this message. + +The Parallax team diff --git a/templates/en/reset/blocklyprop/plain.mustache b/templates/en/reset/blocklyprop/plain.mustache index 3e5166a..f9d2518 100644 --- a/templates/en/reset/blocklyprop/plain.mustache +++ b/templates/en/reset/blocklyprop/plain.mustache @@ -1,10 +1,10 @@ -Dear {{screenname}}, +Hello, -A request was made to reset your BlocklyProp account password. +The BlocklyProp web site has received a request to reset the password for user '{{screenname}}'. -If this request was made by you, please go to http://{{blocklyprop-host}}:8080/blockly/reset?locale={{locale}}&email={{email}}&token={{token}} to reset your password now. +If this request was made by you, please go to http://{{blocklyprop-host}}/blockly/reset?locale={{locale}}&email={{registrant-email}}&token={{token}} to reset your password now. -If the url does not work, please go to http://{{blocklyprop-host}}:8080/blockly/reset and use the token: {{token}} +If the url does not work, please go to http://{{blocklyprop-host}}/blockly/reset and use the token: {{token}} If you did not make this request, you may safely ignore this message. diff --git a/templates/en_US/confirm-parent/blocklyprop/plain.mustache b/templates/en_US/confirm-parent/blocklyprop/plain.mustache index a77a937..b01e33f 100644 --- a/templates/en_US/confirm-parent/blocklyprop/plain.mustache +++ b/templates/en_US/confirm-parent/blocklyprop/plain.mustache @@ -28,7 +28,7 @@ the link provided below. This action will close the account and remove any projects that are associated with the account. A full copy of our Child Privacy Policy is available online at: -http://{{blocklyprop-host}}:8080/blockly/child-privacy-policy +http://{{blocklyprop-host}}/blockly/child-privacy-policy If this is your child, please confirm the registration by copying the link below into @@ -36,10 +36,10 @@ your browser or by navigating to the second link and confirming the student's em Confirm account registration: Copy and paste into your browser -http://{{blocklyprop-host}}:8080/blockly/confirm?locale={{locale}}&email={{registrant-email}}&token={{token}} to confirm your email address. +http://{{blocklyprop-host}}/blockly/confirm?locale={{locale}}&email={{registrant-email}}&token={{token}} to confirm your email address. If the above link is unable to complete your registration, please go to -http://{{blocklyprop-host}}:8080/blockly/confirm and enter your email address and the token: {{token}} +http://{{blocklyprop-host}}/blockly/confirm and enter your email address and the token: {{token}} You may also elect to cancel the registration process immediately. To do diff --git a/templates/en_US/confirm-teacher/blocklyprop/plain.mustache b/templates/en_US/confirm-teacher/blocklyprop/plain.mustache index 65a651c..78a1f1b 100644 --- a/templates/en_US/confirm-teacher/blocklyprop/plain.mustache +++ b/templates/en_US/confirm-teacher/blocklyprop/plain.mustache @@ -12,10 +12,10 @@ If this is your student, please confirm the registration by copying the link bel your browser or by navigating to the second link and confirming the student's email address. Copy and paste into your browser -http://{{blocklyprop-host}}:8080/blockly/confirm?locale={{locale}}&email={{registrant-email}}&token={{token}} to confirm your email address. +http://{{blocklyprop-host}}/blockly/confirm?locale={{locale}}&email={{registrant-email}}&token={{token}} to confirm your email address. If the above link is unable to complete your registration, please go to -http://{{blocklyprop-host}}:8080/blockly/confirm and enter your student's email address ({{registrant-email}}) and the token: {{token}} +http://{{blocklyprop-host}}/blockly/confirm and enter your student's email address ({{registrant-email}}) and the token: {{token}} Regards, diff --git a/templates/en_US/confirm/blocklyprop/plain.mustache b/templates/en_US/confirm/blocklyprop/plain.mustache index 9792de4..e39fe63 100644 --- a/templates/en_US/confirm/blocklyprop/plain.mustache +++ b/templates/en_US/confirm/blocklyprop/plain.mustache @@ -1,9 +1,9 @@ {{! This is the text body of the email}} Dear {{screenname}}, -Please go to http://{{blocklyprop-host}}:8080/blockly/confirm?locale={{locale}}&email={{email}}&token={{token}} to confirm your email address. +Please go to http://{{blocklyprop-host}}/blockly/confirm?locale={{locale}}&email={{email}}&token={{token}} to confirm your email address. -If the url does not work, please go to http://{{blocklyprop-host}}:8080/blockly/confirm and enter your email address and the token: {{token}} +If the url does not work, please go to http://{{blocklyprop-host}}/blockly/confirm and enter your email address and the token: {{token}} The Parallax team diff --git a/templates/en_US/reset-coppa/blocklyprop/header.mustache b/templates/en_US/reset-coppa/blocklyprop/header.mustache new file mode 100644 index 0000000..630929b --- /dev/null +++ b/templates/en_US/reset-coppa/blocklyprop/header.mustache @@ -0,0 +1 @@ +Reset your BlocklyProp password diff --git a/templates/en_US/reset-coppa/blocklyprop/plain.mustache b/templates/en_US/reset-coppa/blocklyprop/plain.mustache new file mode 100644 index 0000000..4b05bee --- /dev/null +++ b/templates/en_US/reset-coppa/blocklyprop/plain.mustache @@ -0,0 +1,15 @@ +{{! + This is the text body of the email that is sent to an account holder + to reset the password associated with the account. +}} +Hello, + +The BlocklyProp web site has received a request to reset the password for user '{{screenname}}'. + +If this request was made by you, please go to http://{{blocklyprop-host}}/blockly/reset?locale={{locale}}&email={{registrant-email}}&token={{token}} to reset your password now. + +If the url does not work, please go to http://{{blocklyprop-host}}/blockly/reset and use the token: {{token}} + +If you did not make this request, you may safely ignore this message. + +The Parallax team diff --git a/templates/en_US/reset/blocklyprop/plain.mustache b/templates/en_US/reset/blocklyprop/plain.mustache index 3e5166a..f9d2518 100644 --- a/templates/en_US/reset/blocklyprop/plain.mustache +++ b/templates/en_US/reset/blocklyprop/plain.mustache @@ -1,10 +1,10 @@ -Dear {{screenname}}, +Hello, -A request was made to reset your BlocklyProp account password. +The BlocklyProp web site has received a request to reset the password for user '{{screenname}}'. -If this request was made by you, please go to http://{{blocklyprop-host}}:8080/blockly/reset?locale={{locale}}&email={{email}}&token={{token}} to reset your password now. +If this request was made by you, please go to http://{{blocklyprop-host}}/blockly/reset?locale={{locale}}&email={{registrant-email}}&token={{token}} to reset your password now. -If the url does not work, please go to http://{{blocklyprop-host}}:8080/blockly/reset and use the token: {{token}} +If the url does not work, please go to http://{{blocklyprop-host}}/blockly/reset and use the token: {{token}} If you did not make this request, you may safely ignore this message. From d9573867481961f7b061bf1cf650bc81c7eb0acd Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Tue, 20 Jun 2017 13:29:09 -0700 Subject: [PATCH 29/53] Update email template to note seven day registration expiration. --- templates/en/confirm-parent/blocklyprop/plain.mustache | 8 +------- templates/en_US/confirm-parent/blocklyprop/plain.mustache | 6 +----- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/templates/en/confirm-parent/blocklyprop/plain.mustache b/templates/en/confirm-parent/blocklyprop/plain.mustache index b01e33f..2defe7f 100644 --- a/templates/en/confirm-parent/blocklyprop/plain.mustache +++ b/templates/en/confirm-parent/blocklyprop/plain.mustache @@ -41,13 +41,7 @@ http://{{blocklyprop-host}}/blockly/confirm?locale={{locale}}&email={{registrant If the above link is unable to complete your registration, please go to http://{{blocklyprop-host}}/blockly/confirm and enter your email address and the token: {{token}} - -You may also elect to cancel the registration process immediately. To do -so, please copy and paste the link below. This will remove the pending registration -request and the account that was created during the registration process. - -[NEED A CANCEL LINK HERE] - +This registration request will automatically expire in 7 days. If you do not wish your child to use this site, you can simply allow the registration to expire. Regards, diff --git a/templates/en_US/confirm-parent/blocklyprop/plain.mustache b/templates/en_US/confirm-parent/blocklyprop/plain.mustache index b01e33f..eabe9c1 100644 --- a/templates/en_US/confirm-parent/blocklyprop/plain.mustache +++ b/templates/en_US/confirm-parent/blocklyprop/plain.mustache @@ -42,11 +42,7 @@ If the above link is unable to complete your registration, please go to http://{{blocklyprop-host}}/blockly/confirm and enter your email address and the token: {{token}} -You may also elect to cancel the registration process immediately. To do -so, please copy and paste the link below. This will remove the pending registration -request and the account that was created during the registration process. - -[NEED A CANCEL LINK HERE] +This registration request will automatically expire in 7 days. If you do not wish your child to use this site, you can simply allow the registration to expire. From 8a53392a29f69a4a74491f778edd1c5cdcc322dc Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Tue, 20 Jun 2017 14:00:44 -0700 Subject: [PATCH 30/53] Add database migration scripts. 0001 and 0002 were moved from the BlocklyProp Server project. --- database/patches/0001-add-user-coach.sql | 4 +++ database/patches/0002-add-coppa-support.sql | 31 +++++++++++++++++++ database/patches/0003-add-user-timestamps.sql | 6 ++++ 3 files changed, 41 insertions(+) create mode 100644 database/patches/0001-add-user-coach.sql create mode 100644 database/patches/0002-add-coppa-support.sql create mode 100644 database/patches/0003-add-user-timestamps.sql diff --git a/database/patches/0001-add-user-coach.sql b/database/patches/0001-add-user-coach.sql new file mode 100644 index 0000000..7e44c66 --- /dev/null +++ b/database/patches/0001-add-user-coach.sql @@ -0,0 +1,4 @@ +/* + * Add coach email address field to support email cc option. + */ +ALTER TABLE user ADD COLUMN coach_email VARCHAR(250) AFTER screen_name; diff --git a/database/patches/0002-add-coppa-support.sql b/database/patches/0002-add-coppa-support.sql new file mode 100644 index 0000000..5e76365 --- /dev/null +++ b/database/patches/0002-add-coppa-support.sql @@ -0,0 +1,31 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ + +/** + * Add fields to user table to support US COPPA compliance + * + * Author: Jim Ewald + * Created: Apr 27, 2017A + * + * birth_month - is a range of [1 - 12] + * birth_year - is a range from [1930 - current year] + * parent_email - is used to register a child under the ae of 13. This is the + * email address of the parent, guardian or instructor that is + * creating the account on behalf of the child + * + * parent_email_source - is a integer designator to characterize the parent + * email adress noted above. Current options are: + * 0 - undefined + * 1 - child's parent + * 2 - child's guardian + * 3 - child's instructor or teacher + */ + +ALTER TABLE cloudsession.user ADD birth_month INT NOT NULL; +ALTER TABLE cloudsession.user ADD birth_year INT NOT NULL; +ALTER TABLE cloudsession.user ADD parent_email VARCHAR(250) NULL; +ALTER TABLE cloudsession.user ADD parent_email_source INT DEFAULT 0 NULL; +ALTER TABLE cloudsession.user DROP coach_email; diff --git a/database/patches/0003-add-user-timestamps.sql b/database/patches/0003-add-user-timestamps.sql new file mode 100644 index 0000000..9b5f23f --- /dev/null +++ b/database/patches/0003-add-user-timestamps.sql @@ -0,0 +1,6 @@ +/* + * Add datestamps to the user record to track when the record was created + * and when it was last modified. + */ +ALTER TABLE cloudsession.user ADD create_date DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL; +ALTER TABLE cloudsession.user ADD last_update DATETIME NULL ON UPDATE CURRENT_TIMESTAMP; From 27e8c52155a43ced63136c75ee5e1d08bd110802 Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Tue, 20 Jun 2017 14:07:22 -0700 Subject: [PATCH 31/53] Add base database schema to project --- database/cloudsession-schema.sql | 132 +++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 database/cloudsession-schema.sql diff --git a/database/cloudsession-schema.sql b/database/cloudsession-schema.sql new file mode 100644 index 0000000..0addcfb --- /dev/null +++ b/database/cloudsession-schema.sql @@ -0,0 +1,132 @@ +/* + * Base Cloud Session database schema. + */ + +-- MySQL dump 10.13 Distrib 5.6.24, for Win64 (x86_64) +-- +-- Host: localhost Database: cloudsession +-- ------------------------------------------------------ +-- Server version 5.7.7-rc-log + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- +-- Table structure for table `authentication_token` +-- +DROP TABLE IF EXISTS `authentication_token`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `authentication_token` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `id_user` bigint(20) DEFAULT NULL, + `browser` varchar(200) DEFAULT NULL, + `server` varchar(1000) DEFAULT NULL, + `ip_address` varchar(200) DEFAULT NULL, + `validity` datetime DEFAULT NULL, + `token` varchar(200) DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `token` (`token`), + KEY `id_user` (`id_user`), + CONSTRAINT `authentication_token_ibfk_1` FOREIGN KEY (`id_user`) REFERENCES `user` (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `bucket` +-- + +DROP TABLE IF EXISTS `bucket`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `bucket` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `id_user` bigint(20) DEFAULT NULL, + `type` varchar(200) DEFAULT NULL, + `content` int(11) DEFAULT NULL, + `timestamp` datetime DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `user_type_unique` (`id_user`,`type`), + CONSTRAINT `bucket_ibfk_1` FOREIGN KEY (`id_user`) REFERENCES `user` (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `confirmtoken` +-- + + +DROP TABLE IF EXISTS `confirm_token`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `confirm_token` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `id_user` bigint(20) DEFAULT NULL, + `validity` datetime DEFAULT NULL, + `token` varchar(200) DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `id_user` (`id_user`), + UNIQUE KEY `token` (`token`), + CONSTRAINT `confirm_token_ibfk_1` FOREIGN KEY (`id_user`) REFERENCES `user` (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `resettoken` +-- + +DROP TABLE IF EXISTS `reset_token`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `reset_token` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `id_user` bigint(20) DEFAULT NULL, + `validity` datetime DEFAULT NULL, + `token` varchar(200) DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `id_user` (`id_user`), + UNIQUE KEY `token` (`token`), + CONSTRAINT `reset_token_ibfk_1` FOREIGN KEY (`id_user`) REFERENCES `user` (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `user` +-- + +DROP TABLE IF EXISTS `user`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `user` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `email` varchar(250) DEFAULT NULL, + `password` varchar(100) DEFAULT NULL, + `salt` varchar(50) DEFAULT NULL, + `auth_source` varchar(250) DEFAULT NULL, + `locale` varchar(50) DEFAULT NULL, + `blocked` tinyint(1) DEFAULT NULL, + `confirmed` tinyint(1) DEFAULT NULL, + `screen_name` varchar(250) DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `email` (`email`) +) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; + +-- Dump completed on 2015-08-18 20:24:57 From 5562ce65e2894081e6080a604b3806a661931398 Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Wed, 21 Jun 2017 08:12:37 -0700 Subject: [PATCH 32/53] Update email message sent to parent of registering child. --- .../confirm-parent/blocklyprop/plain.mustache | 39 ++++-------------- .../confirm-parent/blocklyprop/plain.mustache | 41 ++++--------------- 2 files changed, 18 insertions(+), 62 deletions(-) diff --git a/templates/en/confirm-parent/blocklyprop/plain.mustache b/templates/en/confirm-parent/blocklyprop/plain.mustache index 2defe7f..6c5b224 100644 --- a/templates/en/confirm-parent/blocklyprop/plain.mustache +++ b/templates/en/confirm-parent/blocklyprop/plain.mustache @@ -2,48 +2,27 @@ This is the text body of the email new account notification to a parent or guardian }} -Hello, - -Someone, perhaps your child, has requested a new account on the Parallax -BlocklyProp web site (http://blockly.parallax.com) under the screen name -{{screenname}}. When the account was created, your email address was -received as the parent or guardian of the registrant. If this is incorrect, -please accept our apologies. There is nothing more you need to do. The -request will automatically expire. - -BlocklyProp is a free, online programming tool designed for education. See -Getting Started with BlocklyProp -http://learn.parallax.com/tutorials/language/blocklyprop/getting-started-blocklyprop -for more information. +Hello, -Why are we sending this? In the US, the federal Children's Online Privacy -Protection Act, known as COPPA, requires that we communicate with a parent -or guardian if the person registering an BlocklyProp account is under the -age of 13. The Act allows you to decline the registration request. If you -choose this option, we will immediately remove the registration information -and the associated account. If you choose to confirm the the request and -activate the account, you may close the account at any time by clicking on -the link provided below. This action will close the account and remove any -projects that are associated with the account. +A person under age 13 has requested a new account on the Parallax BlocklyProp web site http://{{blocklyprop-host}}) under the screen name {{screenname}}. In the request, your email address was provided as the parent or guardian of the requester. -A full copy of our Child Privacy Policy is available online at: -http://{{blocklyprop-host}}/blockly/child-privacy-policy +BlocklyProp is a free, online programming tool designed for education. See Getting Started with BlocklyProp for more information. +Why are we sending this? In the US, the federal Children's Online Privacy Protection Act (COPPA) requires that we communicate with a parent or guardian of any person under age 13 that requests a BlocklyProp account. A full copy of our Child Privacy Policy is available online at: http://{{blocklyprop-host}}/blockly/privacy-policy -If this is your child, please confirm the registration by copying the link below into -your browser or by navigating to the second link and confirming the student's email address. +To complete your child’s account registration, copy and paste this link into your browser to confirm your email address: -Confirm account registration: -Copy and paste into your browser http://{{blocklyprop-host}}/blockly/confirm?locale={{locale}}&email={{registrant-email}}&token={{token}} to confirm your email address. -If the above link is unable to complete your registration, please go to +If the above link is unable to complete the registration, please go to http://{{blocklyprop-host}}/blockly/confirm and enter your email address and the token: {{token}} -This registration request will automatically expire in 7 days. If you do not wish your child to use this site, you can simply allow the registration to expire. +If you do NOT want to complete your child’s account registration, you need do nothing more; this confirmation request will automatically expire in 7 days and the account will not be created. +If you do complete your child’s account registration, you may close the account in the future. Email a request for closure and your child’s screen name to blocklyadmin@parallax.com. We will then close the account and remove any projects that are associated with the account. Regards, The Parallax team + diff --git a/templates/en_US/confirm-parent/blocklyprop/plain.mustache b/templates/en_US/confirm-parent/blocklyprop/plain.mustache index eabe9c1..6c5b224 100644 --- a/templates/en_US/confirm-parent/blocklyprop/plain.mustache +++ b/templates/en_US/confirm-parent/blocklyprop/plain.mustache @@ -2,50 +2,27 @@ This is the text body of the email new account notification to a parent or guardian }} -Hello, - -Someone, perhaps your child, has requested a new account on the Parallax -BlocklyProp web site (http://blockly.parallax.com) under the screen name -{{screenname}}. When the account was created, your email address was -received as the parent or guardian of the registrant. If this is incorrect, -please accept our apologies. There is nothing more you need to do. The -request will automatically expire. - -BlocklyProp is a free, online programming tool designed for education. See -Getting Started with BlocklyProp -http://learn.parallax.com/tutorials/language/blocklyprop/getting-started-blocklyprop -for more information. +Hello, -Why are we sending this? In the US, the federal Children's Online Privacy -Protection Act, known as COPPA, requires that we communicate with a parent -or guardian if the person registering an BlocklyProp account is under the -age of 13. The Act allows you to decline the registration request. If you -choose this option, we will immediately remove the registration information -and the associated account. If you choose to confirm the the request and -activate the account, you may close the account at any time by clicking on -the link provided below. This action will close the account and remove any -projects that are associated with the account. +A person under age 13 has requested a new account on the Parallax BlocklyProp web site http://{{blocklyprop-host}}) under the screen name {{screenname}}. In the request, your email address was provided as the parent or guardian of the requester. -A full copy of our Child Privacy Policy is available online at: -http://{{blocklyprop-host}}/blockly/child-privacy-policy +BlocklyProp is a free, online programming tool designed for education. See Getting Started with BlocklyProp for more information. +Why are we sending this? In the US, the federal Children's Online Privacy Protection Act (COPPA) requires that we communicate with a parent or guardian of any person under age 13 that requests a BlocklyProp account. A full copy of our Child Privacy Policy is available online at: http://{{blocklyprop-host}}/blockly/privacy-policy -If this is your child, please confirm the registration by copying the link below into -your browser or by navigating to the second link and confirming the student's email address. +To complete your child’s account registration, copy and paste this link into your browser to confirm your email address: -Confirm account registration: -Copy and paste into your browser http://{{blocklyprop-host}}/blockly/confirm?locale={{locale}}&email={{registrant-email}}&token={{token}} to confirm your email address. -If the above link is unable to complete your registration, please go to +If the above link is unable to complete the registration, please go to http://{{blocklyprop-host}}/blockly/confirm and enter your email address and the token: {{token}} +If you do NOT want to complete your child’s account registration, you need do nothing more; this confirmation request will automatically expire in 7 days and the account will not be created. -This registration request will automatically expire in 7 days. If you do not wish your child to use this site, you can simply allow the registration to expire. - - +If you do complete your child’s account registration, you may close the account in the future. Email a request for closure and your child’s screen name to blocklyadmin@parallax.com. We will then close the account and remove any projects that are associated with the account. Regards, The Parallax team + From 6c60d2d41b417b887ee3de579d731005c73c014f Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Wed, 21 Jun 2017 10:59:08 -0700 Subject: [PATCH 33/53] Update message text. Add logging to capture a failed template parse. --- app/Email/services.py | 24 ++++++++++++++++--- .../confirm-parent/blocklyprop/plain.mustache | 19 +++++++++------ .../confirm-parent/blocklyprop/plain.mustache | 19 +++++++++------ 3 files changed, 45 insertions(+), 17 deletions(-) diff --git a/app/Email/services.py b/app/Email/services.py index e4ef094..54d5df9 100644 --- a/app/Email/services.py +++ b/app/Email/services.py @@ -23,9 +23,9 @@ def send_email_template_for_user(id_user, template, server, **kwargs): logging.error("Cannot send email: Invalid user record") return False else: - logging.info("Email template received user: %s", user.id) + logging.info("Valid record found for user: %s", user.id) - logging.info("Sending email to user: %s using template (%s)", user.id, template) + logging.info("Sending email to user: %s using template: '%s'.", user.id, template) params = {} for key, value in kwargs.items(): @@ -77,6 +77,7 @@ def send_email_template_for_user(id_user, template, server, **kwargs): def send_email_template_to_address(recipient, template, server, locale, params=None, **kwargs): + logging.info("Preparing email template: %s", template) params = params or {} # Add any supplied arguments to the parameter dictionary @@ -88,6 +89,7 @@ def send_email_template_to_address(recipient, template, server, locale, params=N # Read templates (subject, plain, rich) = _read_templates(template, server, locale, params) + # Add error checking here to detect any issues with parsing the template. logging.info("Sending email to %s", recipient) send_email(recipient, subject, plain, rich) @@ -106,8 +108,13 @@ def send_email(recipient, subject, email_text, rich_email_text=None): def _read_templates(template, server, locale, params): + logging.info("Loading header text for template: %s", template) header = _read_template(template, server, locale, 'header', params) + + logging.info("Loading plain message text for template: %s", template) plain = _read_template(template, server, locale, 'plain', params) + + logging.info("Loading rich message text for template: %s", template) rich = _read_template(template, server, locale, 'rich', params, True) return header, plain, rich @@ -128,10 +135,21 @@ def _read_template(template, server, locale, part, params, none_if_missing=False error message if the none_is_missing flag is false """ template_file = expanduser("~/templates/%s/%s/%s/%s.mustache" % (locale, template, server, part)) + if isfile(template_file): logging.debug('Looking for template file: %s', template_file) + renderer = pystache.Renderer() - rendered = renderer.render_path(template_file, params) + + logging.debug('Rendering the template file') + try: + rendered = renderer.render_path(template_file, params) + except Exception as ex: + logging.error('Unable to render template file %s', template_file) + logging.error('Error message: %s', ex.message) + return 'Template format error.' + + logging.debug('Returning rendered template file.') return rendered else: logging.warn('Looking for template file: %s, but the file is missing', template_file) diff --git a/templates/en/confirm-parent/blocklyprop/plain.mustache b/templates/en/confirm-parent/blocklyprop/plain.mustache index 6c5b224..ff72128 100644 --- a/templates/en/confirm-parent/blocklyprop/plain.mustache +++ b/templates/en/confirm-parent/blocklyprop/plain.mustache @@ -1,26 +1,31 @@ {{! This is the text body of the email new account notification to a parent or guardian -}} + Tags used in this template: + blocklyprop-host + locale + registrant-email + screenname + token +}} Hello, -A person under age 13 has requested a new account on the Parallax BlocklyProp web site http://{{blocklyprop-host}}) under the screen name {{screenname}}. In the request, your email address was provided as the parent or guardian of the requester. +A person under age 13 has requested a new account on the Parallax BlocklyProp web site http://{{blocklyprop-host}} under the screen name {{screenname}}. In the request, your email address was provided as the parent or guardian of the requester. BlocklyProp is a free, online programming tool designed for education. See Getting Started with BlocklyProp for more information. Why are we sending this? In the US, the federal Children's Online Privacy Protection Act (COPPA) requires that we communicate with a parent or guardian of any person under age 13 that requests a BlocklyProp account. A full copy of our Child Privacy Policy is available online at: http://{{blocklyprop-host}}/blockly/privacy-policy -To complete your child’s account registration, copy and paste this link into your browser to confirm your email address: +To complete your child's account registration, please copy and past this link into your browser to confirm your email address: http://{{blocklyprop-host}}/blockly/confirm?locale={{locale}}&email={{registrant-email}}&token={{token}} to confirm your email address. -If the above link is unable to complete the registration, please go to -http://{{blocklyprop-host}}/blockly/confirm and enter your email address and the token: {{token}} +If the above link is unable to complete the registration, please go to http://{{blocklyprop-host}}/blockly/confirm and enter your email address and the token: {{token}} -If you do NOT want to complete your child’s account registration, you need do nothing more; this confirmation request will automatically expire in 7 days and the account will not be created. +If you do NOT want to complete your child's account registration, you need do nothing more; this confirmation request will automatically expire in 7 days and the account will not be created. -If you do complete your child’s account registration, you may close the account in the future. Email a request for closure and your child’s screen name to blocklyadmin@parallax.com. We will then close the account and remove any projects that are associated with the account. +If you do complete your child's account registration, you may close the account in the future. Email a request for closure and your child's screen name to blocklyadmin@parallax.com. We will confirm your request and then close the account and remove any projects that are associated with the account. Regards, diff --git a/templates/en_US/confirm-parent/blocklyprop/plain.mustache b/templates/en_US/confirm-parent/blocklyprop/plain.mustache index 6c5b224..ff72128 100644 --- a/templates/en_US/confirm-parent/blocklyprop/plain.mustache +++ b/templates/en_US/confirm-parent/blocklyprop/plain.mustache @@ -1,26 +1,31 @@ {{! This is the text body of the email new account notification to a parent or guardian -}} + Tags used in this template: + blocklyprop-host + locale + registrant-email + screenname + token +}} Hello, -A person under age 13 has requested a new account on the Parallax BlocklyProp web site http://{{blocklyprop-host}}) under the screen name {{screenname}}. In the request, your email address was provided as the parent or guardian of the requester. +A person under age 13 has requested a new account on the Parallax BlocklyProp web site http://{{blocklyprop-host}} under the screen name {{screenname}}. In the request, your email address was provided as the parent or guardian of the requester. BlocklyProp is a free, online programming tool designed for education. See Getting Started with BlocklyProp for more information. Why are we sending this? In the US, the federal Children's Online Privacy Protection Act (COPPA) requires that we communicate with a parent or guardian of any person under age 13 that requests a BlocklyProp account. A full copy of our Child Privacy Policy is available online at: http://{{blocklyprop-host}}/blockly/privacy-policy -To complete your child’s account registration, copy and paste this link into your browser to confirm your email address: +To complete your child's account registration, please copy and past this link into your browser to confirm your email address: http://{{blocklyprop-host}}/blockly/confirm?locale={{locale}}&email={{registrant-email}}&token={{token}} to confirm your email address. -If the above link is unable to complete the registration, please go to -http://{{blocklyprop-host}}/blockly/confirm and enter your email address and the token: {{token}} +If the above link is unable to complete the registration, please go to http://{{blocklyprop-host}}/blockly/confirm and enter your email address and the token: {{token}} -If you do NOT want to complete your child’s account registration, you need do nothing more; this confirmation request will automatically expire in 7 days and the account will not be created. +If you do NOT want to complete your child's account registration, you need do nothing more; this confirmation request will automatically expire in 7 days and the account will not be created. -If you do complete your child’s account registration, you may close the account in the future. Email a request for closure and your child’s screen name to blocklyadmin@parallax.com. We will then close the account and remove any projects that are associated with the account. +If you do complete your child's account registration, you may close the account in the future. Email a request for closure and your child's screen name to blocklyadmin@parallax.com. We will confirm your request and then close the account and remove any projects that are associated with the account. Regards, From 372e40d8c90af68e5b6fcff42858824af90f79e5 Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Wed, 21 Jun 2017 11:10:00 -0700 Subject: [PATCH 34/53] Bumping version to 1.1.1 --- app/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/__init__.py b/app/__init__.py index 8091bf7..89be21b 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -23,7 +23,7 @@ from raven.contrib.flask import Sentry app = Flask(__name__) -version = "1.1.0" +version = "1.1.1" db = None # Load basic configurations From 59394bcd275ab047db9620db4ad6eefa4fe1eeab Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Tue, 27 Jun 2017 11:18:21 -0700 Subject: [PATCH 35/53] Add code to return a timestamp to indicate whent he next token will be available --- Failures.py | 3 ++- app/RateLimiting/controllers.py | 5 +++-- app/RateLimiting/services.py | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Failures.py b/Failures.py index d12ed9f..1437af5 100644 --- a/Failures.py +++ b/Failures.py @@ -98,11 +98,12 @@ def screen_name_already_in_use(screen_name): }, 500 -def rate_exceeded(): +def rate_exceeded(time): logging.debug('Failures: Rate exceeded') return { 'success': False, 'message': 'Insufficient bucket tokens', + 'data': time, 'code': 470 }, 500 diff --git a/app/RateLimiting/controllers.py b/app/RateLimiting/controllers.py index 2e7b972..21417cd 100644 --- a/app/RateLimiting/controllers.py +++ b/app/RateLimiting/controllers.py @@ -47,9 +47,10 @@ def get(self, bucket_type, id_user): if bucket_type not in bucket_types: return Failures.unknown_bucket_type(bucket_type) - if not rate_limiting_services.consume_tokens(user.id, bucket_type, 1): + result, time = rate_limiting_services.consume_tokens(user.id, bucket_type, 1) + if not result: db.session.commit() - return Failures.rate_exceeded() + return Failures.rate_exceeded(time) db.session.commit() diff --git a/app/RateLimiting/services.py b/app/RateLimiting/services.py index 8507ced..c8801c3 100644 --- a/app/RateLimiting/services.py +++ b/app/RateLimiting/services.py @@ -36,11 +36,11 @@ def consume_tokens(id_user, bucket_type, token_count): milliseconds_till_enough = (token_count - old_bucket_content) * bucket_stream_frequency date_when_enough = bucket.timestamp + datetime.timedelta(milliseconds=milliseconds_till_enough) # Log and return or throw error - return False + return False, date_when_enough bucket.content = bucket.content - token_count bucket.timestamp = datetime.datetime.now() - return True + return True, bucket.timestamp def has_sufficient_tokens(id_user, bucket_type, token_count): From 53361bf359b032d715a084fc3b99fb861449d1a1 Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Wed, 28 Jun 2017 09:31:34 -0700 Subject: [PATCH 36/53] Correct timestamp issue in error 470 handler --- Failures.py | 5 +++++ app/RateLimiting/controllers.py | 16 ++++++++++++---- app/RateLimiting/services.py | 4 ++-- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/Failures.py b/Failures.py index 1437af5..c97b29c 100644 --- a/Failures.py +++ b/Failures.py @@ -99,6 +99,11 @@ def screen_name_already_in_use(screen_name): def rate_exceeded(time): + """ + Service requested to frequently. + + time - string representing the date and time the service will be available again + """ logging.debug('Failures: Rate exceeded') return { 'success': False, diff --git a/app/RateLimiting/controllers.py b/app/RateLimiting/controllers.py index 21417cd..47a0bdc 100644 --- a/app/RateLimiting/controllers.py +++ b/app/RateLimiting/controllers.py @@ -1,4 +1,5 @@ import logging +import time import Failures from app import db, app @@ -44,13 +45,16 @@ def get(self, bucket_type, id_user): return Failures.email_not_confirmed() bucket_types = app.config['CLOUD_SESSION_PROPERTIES']['bucket.types'].split(',') + if bucket_type not in bucket_types: return Failures.unknown_bucket_type(bucket_type) - result, time = rate_limiting_services.consume_tokens(user.id, bucket_type, 1) + # Decrement a token count + result, next_time = rate_limiting_services.consume_tokens(user.id, bucket_type, 1) + if not result: db.session.commit() - return Failures.rate_exceeded(time) + return Failures.rate_exceeded(time.strptime(next_time, '%c')) db.session.commit() @@ -83,6 +87,7 @@ def get(self, bucket_type, id_user, count): # Validate user exists, is validated and is not blocked user = user_services.get_user(id_user) + if user is None: return Failures.unknown_user_id(id_user) if user.blocked: @@ -91,12 +96,15 @@ def get(self, bucket_type, id_user, count): return Failures.email_not_confirmed() bucket_types = app.config['CLOUD_SESSION_PROPERTIES']['bucket.types'].split(',') + if bucket_type not in bucket_types: return Failures.unknown_bucket_type(bucket_type) - if not rate_limiting_services.consume_tokens(user.id, bucket_type, 1): + result, next_time = rate_limiting_services.consume_tokens(user.id, bucket_type, 1) + + if not result: db.session.commit() - return Failures.rate_exceeded() + return Failures.rate_exceeded(time.strptime(next_time, '%c')) db.session.commit() diff --git a/app/RateLimiting/services.py b/app/RateLimiting/services.py index c8801c3..ec75319 100644 --- a/app/RateLimiting/services.py +++ b/app/RateLimiting/services.py @@ -40,7 +40,7 @@ def consume_tokens(id_user, bucket_type, token_count): bucket.content = bucket.content - token_count bucket.timestamp = datetime.datetime.now() - return True, bucket.timestamp + return True, bucket.timestamp def has_sufficient_tokens(id_user, bucket_type, token_count): @@ -70,6 +70,6 @@ def has_sufficient_tokens(id_user, bucket_type, token_count): milliseconds_till_enough = (token_count - old_bucket_content) * bucket_stream_frequency date_when_enough = bucket.timestamp + datetime.timedelta(milliseconds=milliseconds_till_enough) # Log and return or throw error - return False + return False, date_when_enough return True From f6e5d553a09f38b8cdbfd5f33e86050366c2fd5a Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Wed, 28 Jun 2017 09:50:25 -0700 Subject: [PATCH 37/53] Correct datetime string formatting error. Bump patch level --- app/RateLimiting/controllers.py | 5 ++--- app/__init__.py | 4 +++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/app/RateLimiting/controllers.py b/app/RateLimiting/controllers.py index 47a0bdc..989e183 100644 --- a/app/RateLimiting/controllers.py +++ b/app/RateLimiting/controllers.py @@ -1,5 +1,4 @@ import logging -import time import Failures from app import db, app @@ -54,7 +53,7 @@ def get(self, bucket_type, id_user): if not result: db.session.commit() - return Failures.rate_exceeded(time.strptime(next_time, '%c')) + return Failures.rate_exceeded(next_time.strftime("%Y-%m-%d %H:%M:%S")) db.session.commit() @@ -104,7 +103,7 @@ def get(self, bucket_type, id_user, count): if not result: db.session.commit() - return Failures.rate_exceeded(time.strptime(next_time, '%c')) + return Failures.rate_exceeded(next_time.strftime("%Y-%m-%d %H:%M:%S")) db.session.commit() diff --git a/app/__init__.py b/app/__init__.py index 89be21b..2608dea 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -23,7 +23,9 @@ from raven.contrib.flask import Sentry app = Flask(__name__) -version = "1.1.1" + +# Application version (major,minor,patch-level) +version = "1.1.2" db = None # Load basic configurations From 8c275a69bdd73baaed50faf194248d4219d9f0f2 Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Fri, 14 Jul 2017 09:30:54 -0700 Subject: [PATCH 38/53] Add database update script to exclusion list --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index fa45eca..3b5e2b3 100644 --- a/.gitignore +++ b/.gitignore @@ -22,4 +22,5 @@ build copy-test deploy-test deploy2coppa +dbupdate.sh /CloudSession-Templates.tar.gz From 3cbdf7fb004301d31171fff8c4b35f688a4be061 Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Mon, 24 Jul 2017 12:59:21 -0700 Subject: [PATCH 39/53] Create script to dump cloudsession database. Create patch to change character set and collation to utf8 --- .../0004-update-cs-default-char-col.sql | 79 +++++++++++++++++++ database/tools/dumpdb.sh | 8 ++ 2 files changed, 87 insertions(+) create mode 100644 database/patches/0004-update-cs-default-char-col.sql create mode 100755 database/tools/dumpdb.sh diff --git a/database/patches/0004-update-cs-default-char-col.sql b/database/patches/0004-update-cs-default-char-col.sql new file mode 100644 index 0000000..e9a0084 --- /dev/null +++ b/database/patches/0004-update-cs-default-char-col.sql @@ -0,0 +1,79 @@ +/* +Script: 0004-update-cs-default-char-col.sql + +This script corrects an issue in the cloud session database +where the default character set and collation were set to +'latin1' and 'latin1_swedish_ci'. These settings should be +'utf8' and 'utf8_general_ci'. + +This script updates the character set and collation settings +on the cloudsession database, all cloudsession tables and +affected columns within each of these tables. + */ + +# Select the target database +USE cloudsession + +# Set the database defaults +# This also sets the collation for individual table columns +ALTER DATABASE cloudsession CHARACTER SET utf8 COLLATE utf8_general_ci; + +# Update the authentication_token table +SET foreign_key_checks = 0; +# Default table settings +ALTER TABLE cloudsession.authentication_token DEFAULT CHARACTER SET utf8; + +# Column settings +ALTER TABLE cloudsession.authentication_token MODIFY browser VARCHAR(200) CHARACTER SET utf8; +ALTER TABLE cloudsession.authentication_token MODIFY server VARCHAR(1000) CHARACTER SET utf8; +ALTER TABLE cloudsession.authentication_token MODIFY ip_address VARCHAR(200) CHARACTER SET utf8; +ALTER TABLE cloudsession.authentication_token MODIFY token VARCHAR(200) CHARACTER SET utf8; + +SET foreign_key_checks = 1; + + +# Update the bucket table +SET foreign_key_checks = 0; +ALTER TABLE cloudsession.bucket DEFAULT CHARACTER SET utf8; + +# Reset fields +ALTER TABLE cloudsession.bucket MODIFY type VARCHAR(200) CHARACTER SET utf8; + +SET foreign_key_checks = 1; + + +# Update the confirm_token table +SET foreign_key_checks = 0; +ALTER TABLE cloudsession.confirm_token DEFAULT CHARACTER SET utf8; + +# Reset fields +ALTER TABLE cloudsession.confirm_token MODIFY token VARCHAR(200) CHARACTER SET utf8; + +SET foreign_key_checks = 1; + + +# Update the reset_token table +SET foreign_key_checks = 0; +ALTER TABLE cloudsession.reset_token DEFAULT CHARACTER SET utf8; + +# Reset fields +ALTER TABLE cloudsession.reset_token MODIFY token VARCHAR(200) CHARACTER SET utf8; + +SET foreign_key_checks = 1; + + +# Update the user table +SET foreign_key_checks = 0; +ALTER TABLE cloudsession.user DEFAULT CHARACTER SET utf8; + +# Reset fields +ALTER TABLE cloudsession.user MODIFY email VARCHAR(250) CHARACTER SET utf8; +ALTER TABLE cloudsession.user MODIFY password VARCHAR(100) CHARACTER SET utf8; +ALTER TABLE cloudsession.user MODIFY salt VARCHAR(50) CHARACTER SET utf8; +ALTER TABLE cloudsession.user MODIFY auth_source VARCHAR(250) CHARACTER SET utf8; +ALTER TABLE cloudsession.user MODIFY locale VARCHAR(50) CHARACTER SET utf8; +ALTER TABLE cloudsession.user MODIFY screen_name VARCHAR(250) CHARACTER SET utf8; +ALTER TABLE cloudsession.user MODIFY parent_email VARCHAR(250) CHARACTER SET utf8; + +SET foreign_key_checks = 1; + diff --git a/database/tools/dumpdb.sh b/database/tools/dumpdb.sh new file mode 100755 index 0000000..cc2a24e --- /dev/null +++ b/database/tools/dumpdb.sh @@ -0,0 +1,8 @@ +#!/bin/bash +# +# Dump the local cloudsession database to a .sql file +# + +echo 'Enter the MySQL user account password below' +mysqldump --single-transaction -u blocklydb -p cloudsession > cs-backup.sql + From 16a3ccc34151d97cd13b228791a09e933dc60573 Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Tue, 5 Dec 2017 10:34:44 -0800 Subject: [PATCH 40/53] Update script to correct SQL errors --- database/patches/0004-update-cs-default-char-col.sql | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/database/patches/0004-update-cs-default-char-col.sql b/database/patches/0004-update-cs-default-char-col.sql index e9a0084..bcfd383 100644 --- a/database/patches/0004-update-cs-default-char-col.sql +++ b/database/patches/0004-update-cs-default-char-col.sql @@ -12,12 +12,16 @@ affected columns within each of these tables. */ # Select the target database -USE cloudsession +USE cloudsession; # Set the database defaults # This also sets the collation for individual table columns +;ALTER DATABASE blocklyprop CHARACTER SET utf8 COLLATE utf8_general_ci; ALTER DATABASE cloudsession CHARACTER SET utf8 COLLATE utf8_general_ci; +# latin1_general_ci +# ALTER DATABASE cloudsession CHARACTER SET latin1 COLLATE latin1_general_ci; + # Update the authentication_token table SET foreign_key_checks = 0; # Default table settings From 1d14097440ca4834d5b0f881d6fab8aabaef1f73 Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Thu, 7 Dec 2017 10:32:21 -0800 Subject: [PATCH 41/53] Add support for individual or alternate email confirmation address --- .gitignore | 2 ++ app/Email/services.py | 8 +++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 2459a51..a3496dd 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,5 @@ nbactions.xml build deploy-test /CloudSession-Templates.tar.gz +/copy-test +/deploy2coppa diff --git a/app/Email/services.py b/app/Email/services.py index be15412..96d568b 100644 --- a/app/Email/services.py +++ b/app/Email/services.py @@ -62,7 +62,13 @@ def send_email_template_for_user(id_user, template, server, **kwargs): return else: - # Registration not subject to COPPA regulations + # Registration not subject to COPPA regulations. + # + # Evaluate user wanting to use an alternate email address to register + # the account. + if user.parent_email_source == SponsorType.INDIVIDUAL or user.parent_email: + user_email = user.parent_email + send_email_template_to_address(user_email, template, server, user.locale, params) return From 4d8c75b3c30dd3cced44cbe5401d381c32f35297 Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Thu, 7 Dec 2017 11:32:52 -0800 Subject: [PATCH 42/53] Update to support alternate email confirmation address --- app/Email/services.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/Email/services.py b/app/Email/services.py index 96d568b..1d44c8b 100644 --- a/app/Email/services.py +++ b/app/Email/services.py @@ -32,7 +32,6 @@ def send_email_template_for_user(id_user, template, server, **kwargs): logging.debug("Logging parameter %s = %s", key, value) params[key] = value - # The elements in the params array represent the data elements that are # available to the email templates. params['screenname'] = user.screen_name @@ -40,7 +39,7 @@ def send_email_template_for_user(id_user, template, server, **kwargs): params['registrant-email'] = user.email params['sponsoremail'] = user.parent_email - #Default the recipient email address + # Default the recipient email address user_email = user.email coppa = Coppa() From 9e6f7f164d08d6717c32bae7a6d03f9c58714e3b Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Thu, 7 Dec 2017 11:34:31 -0800 Subject: [PATCH 43/53] Correct error where template used wrong email address in URL --- templates/en/confirm/blocklyprop/plain.mustache | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/en/confirm/blocklyprop/plain.mustache b/templates/en/confirm/blocklyprop/plain.mustache index 023480c..294f76c 100644 --- a/templates/en/confirm/blocklyprop/plain.mustache +++ b/templates/en/confirm/blocklyprop/plain.mustache @@ -1,9 +1,9 @@ {{! This is the text body of the email}} Dear {{screenname}}, -Please go to http://localhost:8080/blockly/confirm?locale={{locale}}&email={{email}}&token={{token}} to confirm your email address. +Please go to http://localhost:8080/blockly/confirm?locale={{locale}}&email={{registrant-email}}&token={{token}} to confirm your email address. -If the url does not work, please go to http://localhost:8080/blockly/confirm and enter your email address and the token: {{token}} +If the url does not work, please go to http://localhost:8080/blockly/confirm, then enter your email address and the token: {{token}} The Parallax team From 43b18e03b5af30bace49057651ca422dffceb236 Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Thu, 7 Dec 2017 11:54:54 -0800 Subject: [PATCH 44/53] Corrent error where localhost was hard-coded into templates. --- templates/en/confirm-parent/blocklyprop/plain.mustache | 6 +++--- templates/en/confirm-teacher/blocklyprop/plain.mustache | 4 ++-- templates/en/confirm/blocklyprop/plain.mustache | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/templates/en/confirm-parent/blocklyprop/plain.mustache b/templates/en/confirm-parent/blocklyprop/plain.mustache index 3840702..13f3172 100644 --- a/templates/en/confirm-parent/blocklyprop/plain.mustache +++ b/templates/en/confirm-parent/blocklyprop/plain.mustache @@ -22,7 +22,7 @@ the link provided below. This action will close the account and remove any projects that are associated with the account. A full copy of our Child Privacy Policy is available online at: -http://localhost:8080/blockly/child-privacy-policy +http://{{blocklyprop-host}}//blockly/child-privacy-policy If this is your child, please confirm the registration by copying the link below into @@ -30,10 +30,10 @@ your browser or by navigating to the second link and confirming the student's em Confirm account registration: Copy and paste into your browser -http://localhost:8080/blockly/confirm?locale={{locale}}&email={{registrant-email}}&token={{token}} to confirm your email address. +http://{{blocklyprop-host}}//blockly/confirm?locale={{locale}}&email={{registrant-email}}&token={{token}} to confirm your email address. If the above link is unable to complete your registration, please go to -http://localhost:8080/blockly/confirm and enter your email address and the token: {{token}} +http://{{blocklyprop-host}}//blockly/confirm and enter your email address and the token: {{token}} You may also elect to cancel the registration process immediately. To do diff --git a/templates/en/confirm-teacher/blocklyprop/plain.mustache b/templates/en/confirm-teacher/blocklyprop/plain.mustache index 1e76d32..7d10691 100644 --- a/templates/en/confirm-teacher/blocklyprop/plain.mustache +++ b/templates/en/confirm-teacher/blocklyprop/plain.mustache @@ -12,10 +12,10 @@ If this is your student, please confirm the registration by copying the link bel your browser or by navigating to the second link and confirming the student's email address. Copy and paste into your browser -http://localhost:8080/blockly/confirm?locale={{locale}}&email={{registrant-email}}&token={{token}} to confirm your email address. +http://{{blocklyprop-host}}//blockly/confirm?locale={{locale}}&email={{registrant-email}}&token={{token}} to confirm your email address. If the above link is unable to complete your registration, please go to -http://localhost:8080/blockly/confirm and enter your student's email address ({{registrant-email}}) and the token: {{token}} +http://{{blocklyprop-host}}//blockly/confirm and enter your student's email address ({{registrant-email}}) and the token: {{token}} Regards, diff --git a/templates/en/confirm/blocklyprop/plain.mustache b/templates/en/confirm/blocklyprop/plain.mustache index 294f76c..d414bf0 100644 --- a/templates/en/confirm/blocklyprop/plain.mustache +++ b/templates/en/confirm/blocklyprop/plain.mustache @@ -1,9 +1,9 @@ {{! This is the text body of the email}} Dear {{screenname}}, -Please go to http://localhost:8080/blockly/confirm?locale={{locale}}&email={{registrant-email}}&token={{token}} to confirm your email address. +Please go to http://{{blocklyprop-host}}//blockly/confirm?locale={{locale}}&email={{registrant-email}}&token={{token}} to confirm your email address. -If the url does not work, please go to http://localhost:8080/blockly/confirm, then enter your email address and the token: {{token}} +If the url does not work, please go to http://{{blocklyprop-host}}//blockly/confirm, then enter your email address ({{email}}), and the token: {{token}} The Parallax team From 618d644a1fbb13c5f0dcbd5f6d36009236beb726 Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Thu, 7 Dec 2017 12:07:44 -0800 Subject: [PATCH 45/53] Correct local merge error --- .../confirm-parent/blocklyprop/plain.mustache | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/templates/en/confirm-parent/blocklyprop/plain.mustache b/templates/en/confirm-parent/blocklyprop/plain.mustache index 9ddd228..8c7de6c 100644 --- a/templates/en/confirm-parent/blocklyprop/plain.mustache +++ b/templates/en/confirm-parent/blocklyprop/plain.mustache @@ -11,13 +11,10 @@ }} Hello, -<<<<<<< HEAD -A person under age 13 has requested a new account on the Parallax BlocklyProp web site http://{{blocklyprop-host}} under the screen name {{screenname}}. In the request, your email address was provided as the parent or guardian of the requester. -======= Someone, perhaps your child, has requested a new account on the Parallax BlocklyProp web site (http://blockly.parallax.com) under the screen name {{screenname}}. When the account was created, your email address was -received as the parent or guardian of the registrant. If this is incorrect, +submitted as the parent or guardian of the registrant. If this is incorrect, please accept our apologies. There is nothing more you need to do. The request will automatically expire. @@ -32,12 +29,11 @@ the link provided below. This action will close the account and remove any projects that are associated with the account. A full copy of our Child Privacy Policy is available online at: -http://{{blocklyprop-host}}//blockly/child-privacy-policy ->>>>>>> 1.1 +http://{{blocklyprop-host}}/blockly/child-privacy-policy -BlocklyProp is a free, online programming tool designed for education. See Getting Started with BlocklyProp for more information. +BlocklyProp is a free, online programming tool designed for education. See +Getting Started with BlocklyProp for more information. -Why are we sending this? In the US, the federal Children's Online Privacy Protection Act (COPPA) requires that we communicate with a parent or guardian of any person under age 13 that requests a BlocklyProp account. A full copy of our Child Privacy Policy is available online at: http://{{blocklyprop-host}}/blockly/privacy-policy Confirm account registration: Copy and paste into your browser @@ -46,11 +42,17 @@ http://{{blocklyprop-host}}/blockly/confirm?locale={{locale}}&email={{registrant If the above link is unable to complete your registration, please go to http://{{blocklyprop-host}}/blockly/confirm and enter your email address and the token: {{token}} -If the above link is unable to complete the registration, please go to http://{{blocklyprop-host}}/blockly/confirm and enter your email address and the token: {{token}} +If the above link is unable to complete the registration, please go to +http://{{blocklyprop-host}}/blockly/confirm and enter your email address and the token: {{token}} -If you do NOT want to complete your child's account registration, you need do nothing more; this confirmation request will automatically expire in 7 days and the account will not be created. +If you do NOT want to complete your child's account registration, you need do +nothing more; this confirmation request will automatically expire in 7 days and +the account will not be created. -If you do complete your child's account registration, you may close the account in the future. Email a request for closure and your child's screen name to blocklyadmin@parallax.com. We will confirm your request and then close the account and remove any projects that are associated with the account. +If you do complete your child's account registration, you may close the account +in the future. Email a request for closure and your child's screen name to +blocklyadmin@parallax.com. We will confirm your request and then close the account +and remove any projects that are associated with the account. Regards, From 8fb885729afae0d08eecba533737029c6f3f87c1 Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Fri, 15 Dec 2017 11:09:51 -0800 Subject: [PATCH 46/53] Add documentation to user account creation process --- app/LocalUser/controllers.py | 32 ++++++++++++++++++++++++++++++-- app/__init__.py | 10 +++++++++- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/app/LocalUser/controllers.py b/app/LocalUser/controllers.py index e3ec51d..4757bbc 100644 --- a/app/LocalUser/controllers.py +++ b/app/LocalUser/controllers.py @@ -18,11 +18,26 @@ class DoConfirm(Resource): + """ + Confirm and activate a user account. + + Args: + None + + Returns: + A JSON document with the key 'success' set to True if the operation + is successful. Otherwise the key 'success' is set to False and the + field 'code' is set to the HTTP error code that represents a specific + reason when the account confirmation was rejected. + + Raises: + None + """ def post(self): # Get values - email = request.form.get('email') - token = request.form.get('token') + email = request.form.get('email') # User account email address + token = request.form.get('token') # Token assigned to account during account registration # Validate required fields validation = Validation() @@ -52,9 +67,13 @@ def post(self): # Token is not for this user return {'success': False, 'code': 510} + # Set user account status to 'Confirmed' user.confirmed = True + # Delete the account confirmation token; it is no longer required db.session.delete(confirm_token) + + # Commit the user account changes db.session.commit() logging.info('LocalUser-controller: DoConfirm: success: %s', user.id) @@ -63,6 +82,15 @@ def post(self): class RequestConfirm(Resource): + """ + Send account confirmation request email to user + + Args: + param1: User account email address + + Returns: + JSON document detailing the success or failure of the request. + """ def get(self, email): # Get server URL diff --git a/app/__init__.py b/app/__init__.py index 2608dea..c5acf7f 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -25,7 +25,15 @@ app = Flask(__name__) # Application version (major,minor,patch-level) -version = "1.1.2" +version = "1.1.3" + +""" +Change Log + +1.1.3 Added documentation around the user account registration process. + +""" + db = None # Load basic configurations From 89bf0de0154186cf2a287cbadfe0f89c0d29f9a7 Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Fri, 29 Dec 2017 15:37:19 -0800 Subject: [PATCH 47/53] Minor script updates that do not affect historical schema progression --- database/create_cloudsession_database.sql | 7 ++ .../create_cloudsession_tables.sql | 103 +++++++----------- database/patches/0001-add-user-coach.sql | 3 +- .../0004-update-cs-default-char-col.sql | 1 - 4 files changed, 48 insertions(+), 66 deletions(-) create mode 100644 database/create_cloudsession_database.sql rename cloudsession-schema.sql => database/create_cloudsession_tables.sql (50%) diff --git a/database/create_cloudsession_database.sql b/database/create_cloudsession_database.sql new file mode 100644 index 0000000..b404f2f --- /dev/null +++ b/database/create_cloudsession_database.sql @@ -0,0 +1,7 @@ +/* + * Create the cloudsession database + */ + +CREATE DATABASE IF NOT EXISTS `cloudsession` + CHARACTER SET = utf8 + COLLATE = utf8_general_ci; diff --git a/cloudsession-schema.sql b/database/create_cloudsession_tables.sql similarity index 50% rename from cloudsession-schema.sql rename to database/create_cloudsession_tables.sql index f4f7c00..fb72912 100644 --- a/cloudsession-schema.sql +++ b/database/create_cloudsession_tables.sql @@ -1,26 +1,36 @@ --- MySQL dump 10.13 Distrib 5.6.24, for Win64 (x86_64) +/* + * Base Cloud Session database schema. + */ + +USE cloudsession; + + +-- +-- Table structure for table `user` -- --- Host: localhost Database: cloudsession --- ------------------------------------------------------ --- Server version 5.7.7-rc-log -/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; -/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; -/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; -/*!40101 SET NAMES utf8 */; -/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; -/*!40103 SET TIME_ZONE='+00:00' */; -/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; -/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; -/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; -/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; +DROP TABLE IF EXISTS `user`; +CREATE TABLE `user` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `email` varchar(250) DEFAULT NULL, + `password` varchar(100) DEFAULT NULL, + `salt` varchar(50) DEFAULT NULL, + `auth_source` varchar(250) DEFAULT NULL, + `locale` varchar(50) DEFAULT NULL, + `blocked` tinyint(1) DEFAULT NULL, + `confirmed` tinyint(1) DEFAULT NULL, + `screen_name` varchar(250) DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `email` (`email`) +) ENGINE=InnoDB + AUTO_INCREMENT=0 + DEFAULT CHARSET=utf8; + -- -- Table structure for table `authentication_token` -- DROP TABLE IF EXISTS `authentication_token`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; CREATE TABLE `authentication_token` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `id_user` bigint(20) DEFAULT NULL, @@ -33,16 +43,15 @@ CREATE TABLE `authentication_token` ( UNIQUE KEY `token` (`token`), KEY `id_user` (`id_user`), CONSTRAINT `authentication_token_ibfk_1` FOREIGN KEY (`id_user`) REFERENCES `user` (`id`) -) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; +) ENGINE=InnoDB + AUTO_INCREMENT=0 + DEFAULT CHARSET=utf8; -- -- Table structure for table `bucket` -- DROP TABLE IF EXISTS `bucket`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; CREATE TABLE `bucket` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `id_user` bigint(20) DEFAULT NULL, @@ -52,17 +61,15 @@ CREATE TABLE `bucket` ( PRIMARY KEY (`id`), UNIQUE KEY `user_type_unique` (`id_user`,`type`), CONSTRAINT `bucket_ibfk_1` FOREIGN KEY (`id_user`) REFERENCES `user` (`id`) -) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; +) ENGINE=InnoDB + AUTO_INCREMENT=0 + DEFAULT CHARSET=utf8; -- -- Table structure for table `confirmtoken` -- - DROP TABLE IF EXISTS `confirm_token`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; CREATE TABLE `confirm_token` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `id_user` bigint(20) DEFAULT NULL, @@ -72,16 +79,16 @@ CREATE TABLE `confirm_token` ( UNIQUE KEY `id_user` (`id_user`), UNIQUE KEY `token` (`token`), CONSTRAINT `confirm_token_ibfk_1` FOREIGN KEY (`id_user`) REFERENCES `user` (`id`) -) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; +) ENGINE=InnoDB + AUTO_INCREMENT=0 + DEFAULT CHARSET=utf8; + -- -- Table structure for table `resettoken` -- DROP TABLE IF EXISTS `reset_token`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; CREATE TABLE `reset_token` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `id_user` bigint(20) DEFAULT NULL, @@ -91,38 +98,6 @@ CREATE TABLE `reset_token` ( UNIQUE KEY `id_user` (`id_user`), UNIQUE KEY `token` (`token`), CONSTRAINT `reset_token_ibfk_1` FOREIGN KEY (`id_user`) REFERENCES `user` (`id`) -) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `user` --- - -DROP TABLE IF EXISTS `user`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `user` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT, - `email` varchar(250) DEFAULT NULL, - `password` varchar(100) DEFAULT NULL, - `salt` varchar(50) DEFAULT NULL, - `auth_source` varchar(250) DEFAULT NULL, - `locale` varchar(50) DEFAULT NULL, - `blocked` tinyint(1) DEFAULT NULL, - `confirmed` tinyint(1) DEFAULT NULL, - `screen_name` varchar(250) DEFAULT NULL, - PRIMARY KEY (`id`), - UNIQUE KEY `email` (`email`) -) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; -/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; - -/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; -/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; -/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; -/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; -/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; -/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; -/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; - --- Dump completed on 2015-08-18 20:24:57 +) ENGINE=InnoDB + AUTO_INCREMENT=0 + DEFAULT CHARSET=latin1; diff --git a/database/patches/0001-add-user-coach.sql b/database/patches/0001-add-user-coach.sql index 7e44c66..8c1b1a6 100644 --- a/database/patches/0001-add-user-coach.sql +++ b/database/patches/0001-add-user-coach.sql @@ -1,4 +1,5 @@ /* * Add coach email address field to support email cc option. */ -ALTER TABLE user ADD COLUMN coach_email VARCHAR(250) AFTER screen_name; +USE cloudsession; +ALTER TABLE cloudsession.user ADD COLUMN coach_email VARCHAR(250) AFTER screen_name; diff --git a/database/patches/0004-update-cs-default-char-col.sql b/database/patches/0004-update-cs-default-char-col.sql index bcfd383..9f22a5f 100644 --- a/database/patches/0004-update-cs-default-char-col.sql +++ b/database/patches/0004-update-cs-default-char-col.sql @@ -16,7 +16,6 @@ USE cloudsession; # Set the database defaults # This also sets the collation for individual table columns -;ALTER DATABASE blocklyprop CHARACTER SET utf8 COLLATE utf8_general_ci; ALTER DATABASE cloudsession CHARACTER SET utf8 COLLATE utf8_general_ci; # latin1_general_ci From fff339a16893612aa5d17eddd95271c4036285f5 Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Fri, 5 Jan 2018 03:55:00 -0800 Subject: [PATCH 48/53] Add try-catch block in email send to trap exceptions from the flask email package. Add logging to document things that might go wrong. --- app/Email/services.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/app/Email/services.py b/app/Email/services.py index 3cb6ca1..142959a 100644 --- a/app/Email/services.py +++ b/app/Email/services.py @@ -102,7 +102,7 @@ def send_email_template_to_address(recipient, template, server, locale, params=N def send_email(recipient, subject, email_text, rich_email_text=None): - + logging.info('Creating email message package') msg = Message( recipients=[recipient], subject=subject.rstrip(), @@ -110,7 +110,18 @@ def send_email(recipient, subject, email_text, rich_email_text=None): html=rich_email_text, sender=app.config['DEFAULT_MAIL_SENDER'] ) - mail.send(msg) + + # Attempt to send the email + try: + logging.info('Sending email message to server') + mail.send(msg) + except Exception as ex: + logging.error('Unable to send email') + logging.error('Error message: %s', ex.message) + return 1 + + logging.info('Email message was delivered to server') + return 0 def _read_templates(template, server, locale, params): From 0fa74af95b4be5568680f9fe15cc4493e3b6d8c8 Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Fri, 5 Jan 2018 05:24:08 -0800 Subject: [PATCH 49/53] Correct logic error that was using alternate email address even if it was empty. --- app/Email/services.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/app/Email/services.py b/app/Email/services.py index 142959a..2c951dd 100644 --- a/app/Email/services.py +++ b/app/Email/services.py @@ -25,7 +25,7 @@ def send_email_template_for_user(id_user, template, server, **kwargs): else: logging.info("Valid record found for user: %s", user.id) - logging.info("Sending email to user: %s using template: '%s'.", user.id, template) + logging.info("Sending email to user: %s using template: '%s'.", user.email, template) params = {} for key, value in kwargs.items(): @@ -74,8 +74,14 @@ def send_email_template_for_user(id_user, template, server, **kwargs): # # Evaluate user wanting to use an alternate email address to register # the account. - if user.parent_email_source == SponsorType.INDIVIDUAL or user.parent_email: + logging.info('Non-COPPA registration') + if user.parent_email_source == SponsorType.INDIVIDUAL and user.parent_email: user_email = user.parent_email + logging.info('Individual sponsor email %s being used', user_email) + + if user.parent_email: + user_email = user.parent_email + logging.info('Sponsor email %s being used', user_email) send_email_template_to_address(user_email, template, server, user.locale, params) @@ -83,7 +89,7 @@ def send_email_template_for_user(id_user, template, server, **kwargs): def send_email_template_to_address(recipient, template, server, locale, params=None, **kwargs): - logging.info("Preparing email template: %s", template) + logging.info("Preparing email template: %s for %s", template, recipient) params = params or {} # Add any supplied arguments to the parameter dictionary @@ -97,7 +103,7 @@ def send_email_template_to_address(recipient, template, server, locale, params=N (subject, plain, rich) = _read_templates(template, server, locale, params) # Add error checking here to detect any issues with parsing the template. - logging.info("Sending email to %s", recipient) + logging.info("Sending email to %s", params['email']) send_email(recipient, subject, plain, rich) From 512f43f3753c4fd2c2f1b19951723eed58abf705 Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Fri, 5 Jan 2018 15:22:50 -0800 Subject: [PATCH 50/53] Update packages to supported versions. Update logging to less verbose on startup. --- requirements.txt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/requirements.txt b/requirements.txt index 680ec6b..302ce2b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,22 +1,22 @@ aniso8601==1.1.0 blinker==1.4 contextlib2==0.5.1 -Flask==0.10.1 +Flask==0.12.2 Flask-Mail==0.9.1 Flask-MySQLdb==0.2.0 -Flask-RESTful==0.3.5 -Flask-SQLAlchemy==2.1 +Flask-RESTful==0.3.6 +Flask-SQLAlchemy==2.3.2 itsdangerous==0.24 Jinja2==2.8 MarkupSafe==0.23 -mysqlclient==1.3.7 +mysqlclient==1.3.12 pystache==0.5.4 python-dateutil==2.5.2 pytz==2016.3 -raven==5.12.0 +raven==6.4.0 six==1.10.0 -SQLAlchemy==1.0.12 -validate-email==1.3 +# SQLAlchemy==1.0.12 +SQLAlchemy==1.2.0 Werkzeug==0.11.5 wheel==0.24.0 validate_email==1.3 From ffdf2e30bfcd3c1fde393105abc0810732bdc71d Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Fri, 5 Jan 2018 15:23:12 -0800 Subject: [PATCH 51/53] Update packages to supported versions. Update logging to less verbose on startup. --- app/Email/services.py | 2 +- app/__init__.py | 27 ++++++++++++++++++--------- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/app/Email/services.py b/app/Email/services.py index 2c951dd..12e7e26 100644 --- a/app/Email/services.py +++ b/app/Email/services.py @@ -1,6 +1,6 @@ from app import mail, app from os.path import expanduser, isfile -from flask.ext.mail import Message +from flask_mail import Message from app.User.coppa import Coppa, SponsorType import pystache diff --git a/app/__init__.py b/app/__init__.py index c5acf7f..584a5c7 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -10,14 +10,13 @@ from os.path import expanduser, isfile # Import Flask -# from flask import Flask, render_template from flask import Flask # Import SQLAlchemy database mapper -from flask.ext.sqlalchemy import SQLAlchemy +from flask_sqlalchemy import SQLAlchemy # Import Mail -from flask.ext.mail import Mail +from flask_mail import Mail # Define the WSGI application object from raven.contrib.flask import Sentry @@ -76,6 +75,8 @@ } logging.basicConfig(level=logging.DEBUG) +logging.info('Log level set to %s', 'DEBUG') +logging.info('Starting Cloud Session Service v%s', version) configfile = expanduser("~/cloudsession.properties") logging.info('Looking for config file: %s', configfile) @@ -85,17 +86,18 @@ configs.readfp(FakeSecHead(open(configfile))) app_configs = {} + logging.debug('Configuration Key Settings') for (key, value) in configs.items('section'): app_configs[key] = value - logging.info("Key:%s, Value:%s", key, value) + logging.debug("Key:%s, Value:%s", key, value) app.config['CLOUD_SESSION_PROPERTIES'] = app_configs else: app.config['CLOUD_SESSION_PROPERTIES'] = defaults + logging.warn('WARNING: Using application defaults.') - -# -------------------------------------- Module initialization ------------------------------------------------- +# ---------- Init Sentry Module ---------- if app.config['CLOUD_SESSION_PROPERTIES']['sentry-dsn'] is not None: logging.info("Initializing Sentry") sentry = Sentry(app, @@ -106,20 +108,27 @@ else: logging.info("No Sentry configuration") +# ---------- Init database package ---------- # Define the database object which is imported # by modules and controllers -# logging.info("Initializing database connection") +# -------------------------------------------- app.config['SQLALCHEMY_DATABASE_URI'] = app.config['CLOUD_SESSION_PROPERTIES']['database.url'] +logging.debug("Initializing database connection: %s", app.config['SQLALCHEMY_DATABASE_URI']) + db = SQLAlchemy(app) -# logging.info("Configuring SMTP properties") app.config['MAIL_SERVER'] = app.config['CLOUD_SESSION_PROPERTIES']['mail.host'] +logging.debug("Configuring SMTP properties for %s", app.config['MAIL_SERVER']) + +# Set the correct server port for encrypted vs unencrypted communications if app.config['CLOUD_SESSION_PROPERTIES']['mail.port'] is None: if app.config['CLOUD_SESSION_PROPERTIES']['mail.tls']: app.config['MAIL_PORT'] = 587 else: app.config['MAIL_PORT'] = 25 + + logging.info("Email server default port set to port %s", app.config['MAIL_PORT']) else: app.config['MAIL_PORT'] = app.config['CLOUD_SESSION_PROPERTIES']['mail.port'] @@ -139,7 +148,7 @@ mail = Mail(app) -# -------------------------------------------- Services -------------------------------------------------------- +# ---------- Services ---------- logging.info("Initializing services") # All of these imports need the database From c5463a5346fa6cb2a5a9fa4c9112fe4d4a2e4361 Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Fri, 5 Jan 2018 16:03:31 -0800 Subject: [PATCH 52/53] Remove extraneous '/' from URL embedded in the templates. --- templates/en/confirm/blocklyprop/plain.mustache | 4 ++-- templates/en_US/confirm/blocklyprop/plain.mustache | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/templates/en/confirm/blocklyprop/plain.mustache b/templates/en/confirm/blocklyprop/plain.mustache index d414bf0..3be82f1 100644 --- a/templates/en/confirm/blocklyprop/plain.mustache +++ b/templates/en/confirm/blocklyprop/plain.mustache @@ -1,9 +1,9 @@ {{! This is the text body of the email}} Dear {{screenname}}, -Please go to http://{{blocklyprop-host}}//blockly/confirm?locale={{locale}}&email={{registrant-email}}&token={{token}} to confirm your email address. +Please go to http://{{blocklyprop-host}}/blockly/confirm?locale={{locale}}&email={{registrant-email}}&token={{token}} to confirm your email address. -If the url does not work, please go to http://{{blocklyprop-host}}//blockly/confirm, then enter your email address ({{email}}), and the token: {{token}} +If the url does not work, please go to http://{{blocklyprop-host}}/blockly/confirm, then enter your email address ({{email}}), and the supplied token: {{token}} The Parallax team diff --git a/templates/en_US/confirm/blocklyprop/plain.mustache b/templates/en_US/confirm/blocklyprop/plain.mustache index e39fe63..bc28514 100644 --- a/templates/en_US/confirm/blocklyprop/plain.mustache +++ b/templates/en_US/confirm/blocklyprop/plain.mustache @@ -3,7 +3,6 @@ Dear {{screenname}}, Please go to http://{{blocklyprop-host}}/blockly/confirm?locale={{locale}}&email={{email}}&token={{token}} to confirm your email address. -If the url does not work, please go to http://{{blocklyprop-host}}/blockly/confirm and enter your email address and the token: {{token}} - +If the url does not work, please go to http://{{blocklyprop-host}}/blockly/confirm and enter your email address and the supplied token: {{token}} The Parallax team From 8e9b6b231545731f74c28cdf3ea17b286d526803 Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Tue, 16 Jan 2018 15:53:52 -0800 Subject: [PATCH 53/53] Add template variables to suplement plus sign in some email addresses --- app/Email/services.py | 35 ++++++++++++ app/__init__.py | 6 ++- .../confirm-parent/blocklyprop/plain.mustache | 5 +- .../blocklyprop/plain.mustache | 4 +- .../en/confirm/blocklyprop/plain.mustache | 4 +- .../en/reset-coppa/blocklyprop/plain.mustache | 2 +- templates/en/reset/blocklyprop/plain.mustache | 2 +- .../confirm-parent/blocklyprop/plain.mustache | 54 +++++++++++++------ .../blocklyprop/plain.mustache | 4 +- .../en_US/confirm/blocklyprop/plain.mustache | 5 +- .../reset-coppa/blocklyprop/plain.mustache | 2 +- .../en_US/reset/blocklyprop/plain.mustache | 2 +- 12 files changed, 93 insertions(+), 32 deletions(-) diff --git a/app/Email/services.py b/app/Email/services.py index 12e7e26..8ec3d4b 100644 --- a/app/Email/services.py +++ b/app/Email/services.py @@ -99,6 +99,25 @@ def send_email_template_to_address(recipient, template, server, locale, params=N params['email'] = recipient params['locale'] = locale + # Create a URI-friendly version of the email addresses + params['email-uri'] = _convert_email_uri(params['email']) + logging.info("Email address %s converted to %s", + params['email'], + params['email-uri'] + ) + + params['registrant-email-uri'] = _convert_email_uri(params['registrant-email']) + logging.info("Registrant email address %s converted to %s", + params['registrant-email'], + params['registrant-email-uri'] + ) + + params['sponsor-email-uri'] = _convert_email_uri(params['sponsoremail']) + logging.info("Sponsor email address %s converted to %s", + params['sponsoremail'], + params['sponsor-email-uri'] + ) + # Read templates (subject, plain, rich) = _read_templates(template, server, locale, params) # Add error checking here to detect any issues with parsing the template. @@ -180,3 +199,19 @@ def _read_template(template, server, locale, part, params, none_if_missing=False return None else: return 'Template missing' + + +def _convert_email_uri(email): + """ + Evaluate email address and replace any plus signs that may appear in the + portion of the address prior to the '@' with the literal '%2B'. + + Standard web servers will convert any plus ('+') symbol to a space (' ') + anywhere where they may appear in the URL. This will allow functions upstream + to create a URI that contains an email address that, when submitted to a + server, will not be replaced with a space character. + """ + if "+" in email: + return email.replace("+", "%2B") + else: + return email diff --git a/app/__init__.py b/app/__init__.py index 584a5c7..693d67d 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -24,11 +24,15 @@ app = Flask(__name__) # Application version (major,minor,patch-level) -version = "1.1.3" +version = "1.1.4" """ Change Log +1.1.4 Add code to convert plus signs located the the username portion + of an email address to a '%2B'when the email address is embedded + in a URL. + 1.1.3 Added documentation around the user account registration process. """ diff --git a/templates/en/confirm-parent/blocklyprop/plain.mustache b/templates/en/confirm-parent/blocklyprop/plain.mustache index 8c7de6c..48f6dff 100644 --- a/templates/en/confirm-parent/blocklyprop/plain.mustache +++ b/templates/en/confirm-parent/blocklyprop/plain.mustache @@ -37,10 +37,7 @@ Getting Started with BlocklyProp for more information. Confirm account registration: Copy and paste into your browser -http://{{blocklyprop-host}}/blockly/confirm?locale={{locale}}&email={{registrant-email}}&token={{token}} to confirm your email address. - -If the above link is unable to complete your registration, please go to -http://{{blocklyprop-host}}/blockly/confirm and enter your email address and the token: {{token}} +http://{{blocklyprop-host}}/blockly/confirm?locale={{locale}}&email={{registrant-email-uri}}&token={{token}} to confirm your email address. If the above link is unable to complete the registration, please go to http://{{blocklyprop-host}}/blockly/confirm and enter your email address and the token: {{token}} diff --git a/templates/en/confirm-teacher/blocklyprop/plain.mustache b/templates/en/confirm-teacher/blocklyprop/plain.mustache index 78a1f1b..fbe4f7c 100644 --- a/templates/en/confirm-teacher/blocklyprop/plain.mustache +++ b/templates/en/confirm-teacher/blocklyprop/plain.mustache @@ -12,10 +12,10 @@ If this is your student, please confirm the registration by copying the link bel your browser or by navigating to the second link and confirming the student's email address. Copy and paste into your browser -http://{{blocklyprop-host}}/blockly/confirm?locale={{locale}}&email={{registrant-email}}&token={{token}} to confirm your email address. +http://{{blocklyprop-host}}/blockly/confirm?locale={{locale}}&email={{registrant-email-uri}}&token={{token}} to confirm your email address. If the above link is unable to complete your registration, please go to -http://{{blocklyprop-host}}/blockly/confirm and enter your student's email address ({{registrant-email}}) and the token: {{token}} +http://{{blocklyprop-host}}/blockly/confirm and enter your student's email address ({{registrant-email-uri}}) and the token: {{token}} Regards, diff --git a/templates/en/confirm/blocklyprop/plain.mustache b/templates/en/confirm/blocklyprop/plain.mustache index 3be82f1..dde6f54 100644 --- a/templates/en/confirm/blocklyprop/plain.mustache +++ b/templates/en/confirm/blocklyprop/plain.mustache @@ -1,9 +1,9 @@ {{! This is the text body of the email}} Dear {{screenname}}, -Please go to http://{{blocklyprop-host}}/blockly/confirm?locale={{locale}}&email={{registrant-email}}&token={{token}} to confirm your email address. +Please go to http://{{blocklyprop-host}}/blockly/confirm?locale={{locale}}&email={{registrant-email-uri}}&token={{token}} to confirm your email address. -If the url does not work, please go to http://{{blocklyprop-host}}/blockly/confirm, then enter your email address ({{email}}), and the supplied token: {{token}} +If the url does not work, please go to http://{{blocklyprop-host}}/blockly/confirm, then enter your email address ({{email-uri}}), and the supplied token: {{token}} The Parallax team diff --git a/templates/en/reset-coppa/blocklyprop/plain.mustache b/templates/en/reset-coppa/blocklyprop/plain.mustache index 4b05bee..946c325 100644 --- a/templates/en/reset-coppa/blocklyprop/plain.mustache +++ b/templates/en/reset-coppa/blocklyprop/plain.mustache @@ -6,7 +6,7 @@ Hello, The BlocklyProp web site has received a request to reset the password for user '{{screenname}}'. -If this request was made by you, please go to http://{{blocklyprop-host}}/blockly/reset?locale={{locale}}&email={{registrant-email}}&token={{token}} to reset your password now. +If this request was made by you, please go to http://{{blocklyprop-host}}/blockly/reset?locale={{locale}}&email={{registrant-email-uri}}&token={{token}} to reset your password now. If the url does not work, please go to http://{{blocklyprop-host}}/blockly/reset and use the token: {{token}} diff --git a/templates/en/reset/blocklyprop/plain.mustache b/templates/en/reset/blocklyprop/plain.mustache index f9d2518..7c88411 100644 --- a/templates/en/reset/blocklyprop/plain.mustache +++ b/templates/en/reset/blocklyprop/plain.mustache @@ -2,7 +2,7 @@ Hello, The BlocklyProp web site has received a request to reset the password for user '{{screenname}}'. -If this request was made by you, please go to http://{{blocklyprop-host}}/blockly/reset?locale={{locale}}&email={{registrant-email}}&token={{token}} to reset your password now. +If this request was made by you, please go to http://{{blocklyprop-host}}/blockly/reset?locale={{locale}}&email={{registrant-email-uri}}&token={{token}} to reset your password now. If the url does not work, please go to http://{{blocklyprop-host}}/blockly/reset and use the token: {{token}} diff --git a/templates/en_US/confirm-parent/blocklyprop/plain.mustache b/templates/en_US/confirm-parent/blocklyprop/plain.mustache index ff72128..48f6dff 100644 --- a/templates/en_US/confirm-parent/blocklyprop/plain.mustache +++ b/templates/en_US/confirm-parent/blocklyprop/plain.mustache @@ -11,21 +11,45 @@ }} Hello, -A person under age 13 has requested a new account on the Parallax BlocklyProp web site http://{{blocklyprop-host}} under the screen name {{screenname}}. In the request, your email address was provided as the parent or guardian of the requester. - -BlocklyProp is a free, online programming tool designed for education. See Getting Started with BlocklyProp for more information. - -Why are we sending this? In the US, the federal Children's Online Privacy Protection Act (COPPA) requires that we communicate with a parent or guardian of any person under age 13 that requests a BlocklyProp account. A full copy of our Child Privacy Policy is available online at: http://{{blocklyprop-host}}/blockly/privacy-policy - -To complete your child's account registration, please copy and past this link into your browser to confirm your email address: - -http://{{blocklyprop-host}}/blockly/confirm?locale={{locale}}&email={{registrant-email}}&token={{token}} to confirm your email address. - -If the above link is unable to complete the registration, please go to http://{{blocklyprop-host}}/blockly/confirm and enter your email address and the token: {{token}} - -If you do NOT want to complete your child's account registration, you need do nothing more; this confirmation request will automatically expire in 7 days and the account will not be created. - -If you do complete your child's account registration, you may close the account in the future. Email a request for closure and your child's screen name to blocklyadmin@parallax.com. We will confirm your request and then close the account and remove any projects that are associated with the account. +Someone, perhaps your child, has requested a new account on the Parallax +BlocklyProp web site (http://blockly.parallax.com) under the screen name +{{screenname}}. When the account was created, your email address was +submitted as the parent or guardian of the registrant. If this is incorrect, +please accept our apologies. There is nothing more you need to do. The +request will automatically expire. + +Why are we sending this? In the US, the federal Children's Online Privacy +Protection Act, known as COPPA, requires that we communicate with a parent +or guardian if the person registering an BlocklyProp account is under the +age of 13. The Act allows you to decline the registration request. If you +choose this option, we will immediately remove the registration information +and the associated account. If you choose to confirm the the request and +activate the account, you may close the account at any time by clicking on +the link provided below. This action will close the account and remove any +projects that are associated with the account. + +A full copy of our Child Privacy Policy is available online at: +http://{{blocklyprop-host}}/blockly/child-privacy-policy + +BlocklyProp is a free, online programming tool designed for education. See +Getting Started with BlocklyProp for more information. + + +Confirm account registration: +Copy and paste into your browser +http://{{blocklyprop-host}}/blockly/confirm?locale={{locale}}&email={{registrant-email-uri}}&token={{token}} to confirm your email address. + +If the above link is unable to complete the registration, please go to +http://{{blocklyprop-host}}/blockly/confirm and enter your email address and the token: {{token}} + +If you do NOT want to complete your child's account registration, you need do +nothing more; this confirmation request will automatically expire in 7 days and +the account will not be created. + +If you do complete your child's account registration, you may close the account +in the future. Email a request for closure and your child's screen name to +blocklyadmin@parallax.com. We will confirm your request and then close the account +and remove any projects that are associated with the account. Regards, diff --git a/templates/en_US/confirm-teacher/blocklyprop/plain.mustache b/templates/en_US/confirm-teacher/blocklyprop/plain.mustache index 78a1f1b..fbe4f7c 100644 --- a/templates/en_US/confirm-teacher/blocklyprop/plain.mustache +++ b/templates/en_US/confirm-teacher/blocklyprop/plain.mustache @@ -12,10 +12,10 @@ If this is your student, please confirm the registration by copying the link bel your browser or by navigating to the second link and confirming the student's email address. Copy and paste into your browser -http://{{blocklyprop-host}}/blockly/confirm?locale={{locale}}&email={{registrant-email}}&token={{token}} to confirm your email address. +http://{{blocklyprop-host}}/blockly/confirm?locale={{locale}}&email={{registrant-email-uri}}&token={{token}} to confirm your email address. If the above link is unable to complete your registration, please go to -http://{{blocklyprop-host}}/blockly/confirm and enter your student's email address ({{registrant-email}}) and the token: {{token}} +http://{{blocklyprop-host}}/blockly/confirm and enter your student's email address ({{registrant-email-uri}}) and the token: {{token}} Regards, diff --git a/templates/en_US/confirm/blocklyprop/plain.mustache b/templates/en_US/confirm/blocklyprop/plain.mustache index bc28514..dde6f54 100644 --- a/templates/en_US/confirm/blocklyprop/plain.mustache +++ b/templates/en_US/confirm/blocklyprop/plain.mustache @@ -1,8 +1,9 @@ {{! This is the text body of the email}} Dear {{screenname}}, -Please go to http://{{blocklyprop-host}}/blockly/confirm?locale={{locale}}&email={{email}}&token={{token}} to confirm your email address. +Please go to http://{{blocklyprop-host}}/blockly/confirm?locale={{locale}}&email={{registrant-email-uri}}&token={{token}} to confirm your email address. + +If the url does not work, please go to http://{{blocklyprop-host}}/blockly/confirm, then enter your email address ({{email-uri}}), and the supplied token: {{token}} -If the url does not work, please go to http://{{blocklyprop-host}}/blockly/confirm and enter your email address and the supplied token: {{token}} The Parallax team diff --git a/templates/en_US/reset-coppa/blocklyprop/plain.mustache b/templates/en_US/reset-coppa/blocklyprop/plain.mustache index 4b05bee..946c325 100644 --- a/templates/en_US/reset-coppa/blocklyprop/plain.mustache +++ b/templates/en_US/reset-coppa/blocklyprop/plain.mustache @@ -6,7 +6,7 @@ Hello, The BlocklyProp web site has received a request to reset the password for user '{{screenname}}'. -If this request was made by you, please go to http://{{blocklyprop-host}}/blockly/reset?locale={{locale}}&email={{registrant-email}}&token={{token}} to reset your password now. +If this request was made by you, please go to http://{{blocklyprop-host}}/blockly/reset?locale={{locale}}&email={{registrant-email-uri}}&token={{token}} to reset your password now. If the url does not work, please go to http://{{blocklyprop-host}}/blockly/reset and use the token: {{token}} diff --git a/templates/en_US/reset/blocklyprop/plain.mustache b/templates/en_US/reset/blocklyprop/plain.mustache index f9d2518..7c88411 100644 --- a/templates/en_US/reset/blocklyprop/plain.mustache +++ b/templates/en_US/reset/blocklyprop/plain.mustache @@ -2,7 +2,7 @@ Hello, The BlocklyProp web site has received a request to reset the password for user '{{screenname}}'. -If this request was made by you, please go to http://{{blocklyprop-host}}/blockly/reset?locale={{locale}}&email={{registrant-email}}&token={{token}} to reset your password now. +If this request was made by you, please go to http://{{blocklyprop-host}}/blockly/reset?locale={{locale}}&email={{registrant-email-uri}}&token={{token}} to reset your password now. If the url does not work, please go to http://{{blocklyprop-host}}/blockly/reset and use the token: {{token}}