Skip to content

Commit 5fe8a17

Browse files
authored
Merge pull request #45 from zfi/master
Version 1.1.4
2 parents 777ef11 + 6f68268 commit 5fe8a17

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+1066
-95
lines changed

.gitignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
*.sqlite
33
*.log
44
venv/
5+
build.sh
6+
CloudSession-Pkg.tar.gz
57

68
#################
79
## NetBeans
@@ -16,3 +18,11 @@ nbactions.xml
1618
#################
1719

1820
.idea
21+
build
22+
copy-test
23+
deploy-test
24+
deploy2coppa
25+
dbupdate.sh
26+
/CloudSession-Templates.tar.gz
27+
/copy-test
28+
/deploy2coppa

Failures.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,11 +98,17 @@ def screen_name_already_in_use(screen_name):
9898
}, 500
9999

100100

101-
def rate_exceeded():
101+
def rate_exceeded(time):
102+
"""
103+
Service requested to frequently.
104+
105+
time - string representing the date and time the service will be available again
106+
"""
102107
logging.debug('Failures: Rate exceeded')
103108
return {
104109
'success': False,
105110
'message': 'Insufficient bucket tokens',
111+
'data': time,
106112
'code': 470
107113
}, 500
108114

app/AuthToken/controllers.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
# Import the database object from the main app module
2-
import json
32
import logging
43
import uuid
54
import datetime
6-
75
import Failures
6+
87
from app import db
98
from app.User import services as user_service
109

app/AuthToken/models.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
# We will define this inside /app/__init__.py in the next sections.
33
from app import db
44

5-
65
class AuthenticationToken(db.Model):
76
id = db.Column(db.BigInteger, primary_key=True)
87
id_user = db.Column(db.BigInteger, db.ForeignKey('user.id'))

app/Authenticate/controllers.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
# Import the database object from the main app module
22
import logging
3-
import uuid
4-
import datetime
5-
63
import Failures
4+
75
from app import db
86
from app.User import services as user_services
97
from app.RateLimiting import services as rate_limiting_services
@@ -66,7 +64,11 @@ def post(self):
6664
'email': user.email,
6765
'locale': user.locale,
6866
'screenname': user.screen_name,
69-
'authentication-source': user.auth_source
67+
'authentication-source': user.auth_source,
68+
'bdmonth': user.birth_month,
69+
'bdyear': user.birth_year,
70+
'parent-email': user.parent_email,
71+
'parent-email-source': user.parent_email_source
7072
}}
7173

7274
api.add_resource(AuthenticateLocalUser, '/local')

app/Email/services.py

Lines changed: 158 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,217 @@
1-
from app import mail, app, db
2-
1+
from app import mail, app
32
from os.path import expanduser, isfile
4-
5-
from flask.ext.mail import Message
3+
from flask_mail import Message
4+
from app.User.coppa import Coppa, SponsorType
65

76
import pystache
87
import logging
98

109

10+
"""
11+
TODO: System documentation goes here
12+
"""
13+
14+
1115
def send_email_template_for_user(id_user, template, server, **kwargs):
1216
from app.User.services import get_user
1317

14-
logging.info("Sending email to user: %s (%s)", id_user, template)
18+
# Get a copy of the user record
19+
logging.info("Checking for a valid user record for user ID: %s", id_user)
20+
user = get_user(id_user)
21+
22+
if user is None:
23+
logging.error("Cannot send email: Invalid user record")
24+
return False
25+
else:
26+
logging.info("Valid record found for user: %s", user.id)
27+
28+
logging.info("Sending email to user: %s using template: '%s'.", user.email, template)
1529

1630
params = {}
1731
for key, value in kwargs.items():
32+
logging.debug("Logging parameter %s = %s", key, value)
1833
params[key] = value
1934

20-
user = get_user(id_user)
21-
if user is None:
22-
return False
23-
35+
# The elements in the params array represent the data elements that are
36+
# available to the email templates.
2437
params['screenname'] = user.screen_name
38+
params['email'] = user.email
39+
params['registrant-email'] = user.email
40+
params['sponsoremail'] = user.parent_email
41+
params['blocklyprop-host'] = app.config['CLOUD_SESSION_PROPERTIES']['response.host']
42+
43+
# Default the recipient email address
44+
user_email = user.email
45+
coppa = Coppa()
46+
47+
# Send email to parent if user is under 13 years old
48+
if template == 'confirm' and coppa.is_coppa_covered(user.birth_month, user.birth_year):
49+
# Send email only to the sponsor address
50+
user_email = user.parent_email
51+
logging.info("COPPA account has a sponsor type of %s", user.parent_email_source)
52+
53+
if user.parent_email_source == SponsorType.TEACHER:
54+
# Teacher handles the account confirmation
55+
send_email_template_to_address(user_email, 'confirm-teacher', server, user.locale, params)
56+
elif user.parent_email_source == SponsorType.PARENT or\
57+
user.parent_email_source == SponsorType.GUARDIAN:
58+
# Parent handles the account confirmation
59+
send_email_template_to_address(user_email, 'confirm-parent', server, user.locale, params)
60+
else:
61+
logging.info("COPPA account %s has invalid sponsor type [%s]", user.id, user.parent_email_source)
62+
63+
return
64+
elif template == 'reset' and coppa.is_coppa_covered(user.birth_month, user.birth_year):
65+
# Send email only to the sponsor address
66+
logging.info("COPPA account has a sponsor type of %s", user.parent_email_source)
67+
68+
# Send password reset to student and parent
69+
send_email_template_to_address(user.email, 'reset-coppa', server, user.locale, params)
70+
send_email_template_to_address(user.parent_email, 'reset-coppa', server, user.locale, params)
71+
return
72+
else:
73+
# Registration not subject to COPPA regulations.
74+
#
75+
# Evaluate user wanting to use an alternate email address to register
76+
# the account.
77+
logging.info('Non-COPPA registration')
78+
if user.parent_email_source == SponsorType.INDIVIDUAL and user.parent_email:
79+
user_email = user.parent_email
80+
logging.info('Individual sponsor email %s being used', user_email)
81+
82+
if user.parent_email:
83+
user_email = user.parent_email
84+
logging.info('Sponsor email %s being used', user_email)
2585

26-
send_email_template_to_address(user.email, template, server, user.locale, params)
86+
send_email_template_to_address(user_email, template, server, user.locale, params)
87+
88+
return
2789

2890

2991
def send_email_template_to_address(recipient, template, server, locale, params=None, **kwargs):
30-
# Read templates
92+
logging.info("Preparing email template: %s for %s", template, recipient)
3193
params = params or {}
94+
95+
# Add any supplied arguments to the parameter dictionary
3296
for key, value in kwargs.items():
3397
params[key] = value
98+
3499
params['email'] = recipient
35100
params['locale'] = locale
36101

102+
# Create a URI-friendly version of the email addresses
103+
params['email-uri'] = _convert_email_uri(params['email'])
104+
logging.info("Email address %s converted to %s",
105+
params['email'],
106+
params['email-uri']
107+
)
108+
109+
params['registrant-email-uri'] = _convert_email_uri(params['registrant-email'])
110+
logging.info("Registrant email address %s converted to %s",
111+
params['registrant-email'],
112+
params['registrant-email-uri']
113+
)
114+
115+
params['sponsor-email-uri'] = _convert_email_uri(params['sponsoremail'])
116+
logging.info("Sponsor email address %s converted to %s",
117+
params['sponsoremail'],
118+
params['sponsor-email-uri']
119+
)
120+
121+
# Read templates
37122
(subject, plain, rich) = _read_templates(template, server, locale, params)
123+
# Add error checking here to detect any issues with parsing the template.
38124

125+
logging.info("Sending email to %s", params['email'])
39126
send_email(recipient, subject, plain, rich)
40127

41128

42129
def send_email(recipient, subject, email_text, rich_email_text=None):
130+
logging.info('Creating email message package')
43131
msg = Message(
44132
recipients=[recipient],
45133
subject=subject.rstrip(),
46134
body=email_text,
47135
html=rich_email_text,
48136
sender=app.config['DEFAULT_MAIL_SENDER']
49137
)
50-
mail.send(msg)
138+
139+
# Attempt to send the email
140+
try:
141+
logging.info('Sending email message to server')
142+
mail.send(msg)
143+
except Exception as ex:
144+
logging.error('Unable to send email')
145+
logging.error('Error message: %s', ex.message)
146+
return 1
147+
148+
logging.info('Email message was delivered to server')
149+
return 0
51150

52151

53152
def _read_templates(template, server, locale, params):
153+
logging.info("Loading header text for template: %s", template)
54154
header = _read_template(template, server, locale, 'header', params)
155+
156+
logging.info("Loading plain message text for template: %s", template)
55157
plain = _read_template(template, server, locale, 'plain', params)
158+
159+
logging.info("Loading rich message text for template: %s", template)
56160
rich = _read_template(template, server, locale, 'rich', params, True)
57161

58162
return header, plain, rich
59163

60164

61165
def _read_template(template, server, locale, part, params, none_if_missing=False):
166+
"""
167+
Render a mustache template.
168+
169+
:param template: Base template name
170+
:param server: Host server
171+
:param locale: Language designator
172+
:param part: Generic message type descriptor
173+
:param params: Text string to replace tags embedded within the template
174+
:param none_if_missing: Return 'none' if the requested template is not found
175+
176+
:return: Upon success, return a Renderer object. Return none or a general
177+
error message if the none_is_missing flag is false
178+
"""
62179
template_file = expanduser("~/templates/%s/%s/%s/%s.mustache" % (locale, template, server, part))
180+
63181
if isfile(template_file):
64182
logging.debug('Looking for template file: %s', template_file)
183+
65184
renderer = pystache.Renderer()
66-
rendered = renderer.render_path(template_file, params)
67-
#print(rendered)
185+
186+
logging.debug('Rendering the template file')
187+
try:
188+
rendered = renderer.render_path(template_file, params)
189+
except Exception as ex:
190+
logging.error('Unable to render template file %s', template_file)
191+
logging.error('Error message: %s', ex.message)
192+
return 'Template format error.'
193+
194+
logging.debug('Returning rendered template file.')
68195
return rendered
69196
else:
70-
logging.error('Looking for template file: %s, but the file is missing', template_file)
197+
logging.warn('Looking for template file: %s, but the file is missing', template_file)
71198
if none_if_missing:
72199
return None
73200
else:
74201
return 'Template missing'
202+
203+
204+
def _convert_email_uri(email):
205+
"""
206+
Evaluate email address and replace any plus signs that may appear in the
207+
portion of the address prior to the '@' with the literal '%2B'.
208+
209+
Standard web servers will convert any plus ('+') symbol to a space (' ')
210+
anywhere where they may appear in the URL. This will allow functions upstream
211+
to create a URI that contains an email address that, when submitted to a
212+
server, will not be replaced with a space character.
213+
"""
214+
if "+" in email:
215+
return email.replace("+", "%2B")
216+
else:
217+
return email

0 commit comments

Comments
 (0)