From 61df32f9bc345a1a9ee3a58bf5d54ee58cf3e0af Mon Sep 17 00:00:00 2001 From: Jim Ewald Date: Wed, 26 Apr 2017 19:09:27 -0700 Subject: [PATCH 01/34] 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 02/34] 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 03/34] 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 04/34] 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 05/34] 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 06/34] 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 07/34] 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 08/34] 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 09/34] 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 10/34] 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 11/34] 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 12/34] 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 13/34] 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 14/34] 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 15/34] 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 16/34] 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 17/34] 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 18/34] 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 19/34] 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 20/34] 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 21/34] 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 22/34] 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 23/34] 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 24/34] 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 25/34] 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 26/34] 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 27/34] 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 28/34] 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 29/34] 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 30/34] 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 31/34] 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 32/34] 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 33/34] 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 34/34] 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