Skip to content
This repository has been archived by the owner on Feb 8, 2018. It is now read-only.

Commit

Permalink
Refaktor
Browse files Browse the repository at this point in the history
  • Loading branch information
chadwhitacre committed Apr 3, 2017
1 parent c0c9ad5 commit 566c176
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 49 deletions.
92 changes: 62 additions & 30 deletions gratipay/models/participant/email.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,50 +221,82 @@ def _set_primary_email(self, email, cursor):
""", dict(email=email, username=self.username))


def verify_email(self, email, nonce):
def finish_email_verification(self, email, nonce):
if '' in (email, nonce):
return VERIFICATION_MISSING
r = self.get_email(email)
if r is None:
return VERIFICATION_FAILED
if r.verified:
assert r.nonce is None # and therefore, order of conditions matters
return VERIFICATION_REDUNDANT
if not constant_time_compare(r.nonce, nonce):
return VERIFICATION_FAILED
if (utcnow() - r.verification_start) > EMAIL_HASH_TIMEOUT:
return VERIFICATION_EXPIRED
try:
self.finish_email_verification(email, nonce)
except IntegrityError:
return VERIFICATION_STYMIED
return VERIFICATION_SUCCEEDED
with self.db.get_cursor() as c:
r = self.get_email(email, c)
if r is None:
return VERIFICATION_FAILED
packages = self.get_packages(c, nonce)
if r.verified and not packages:
assert r.nonce is None # and therefore, order of conditions matters
return VERIFICATION_REDUNDANT
if not constant_time_compare(r.nonce, nonce):
return VERIFICATION_FAILED
if (utcnow() - r.verification_start) > EMAIL_HASH_TIMEOUT:
return VERIFICATION_EXPIRED
try:
self.save_email_address(c, email)
self.finish_package_claims(c, *packages)
except IntegrityError:
return VERIFICATION_STYMIED
return VERIFICATION_SUCCEEDED


def get_packages(self, cursor, nonce):
"""Given a nonce, return :py:class:`Package` objects associated with it.
"""
return cursor.all("""
SELECT *
FROM packages p
JOIN claims c
ON p.id = c.package_id
WHERE c.nonce=%s
""", (nonce,))


def finish_email_verification(self, email, nonce):
"""Given an email address and nonce, modify the database.
def save_email_address(self, cursor, address):
"""Given an email address, modify the database.
"""
with self.db.get_cursor() as c:
c.run("""
UPDATE emails
SET verified=true, verification_end=now(), nonce=NULL
WHERE participant_id=%s
AND address=%s
AND verified IS NULL
""", (self.id, email))
if not self.email_address:
self.set_primary_email(email, c)
cursor.run("""
UPDATE emails
SET verified=true, verification_end=now(), nonce=NULL
WHERE participant_id=%s
AND address=%s
AND verified IS NULL
""", (self.id, address))
if not self.email_address:
self.set_primary_email(address, cursor)


def finish_package_claims(self, cursor, *packages):
"""Create stub projects if needed and associate them with the packages.
"""
for package in packages:

# get/create a project for the package
#project = None

# set ownership of the project

# associate the two
#package.team_id = project.id

# log an event

pass


def get_email(self, email, cursor=None):
def get_email(self, address, cursor=None):
"""Return a record for a single email address on file for this participant.
"""
return (cursor or self.db).one("""
SELECT *
FROM emails
WHERE participant_id=%s
AND address=%s
""", (self.id, email))
""", (self.id, address))


def get_emails(self):
Expand Down
2 changes: 2 additions & 0 deletions sql/branch.sql
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ BEGIN;
, UNIQUE(nonce, package_id)
);

ALTER TABLE packages ADD COLUMN team_id bigint REFERENCES teams(id) ON DELETE RESTRICT;

END;
63 changes: 49 additions & 14 deletions tests/py/test_email.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def setUp(self):
def add(self, participant, address, _flush=False):
participant.start_email_verification(address)
nonce = participant.get_email(address).nonce
r = participant.verify_email(address, nonce)
r = participant.finish_email_verification(address, nonce)
assert r == _email.VERIFICATION_SUCCEEDED
if _flush:
self.app.email_queue.flush()
Expand All @@ -52,7 +52,7 @@ def hit_email_spt(self, action, address, user='alice', package_ids=[], should_fa
response.render_body({'_': lambda a: a})
return response

def verify_email(self, email, nonce, username='alice', should_fail=False):
def finish_email_verification(self, email, nonce, username='alice', should_fail=False):
# Email address is encoded in url.
url = '/~%s/emails/verify.html?email2=%s&nonce=%s'
url %= (username, encode_for_querystring(email), nonce)
Expand All @@ -62,7 +62,7 @@ def verify_email(self, email, nonce, username='alice', should_fail=False):
def verify_and_change_email(self, old_email, new_email, username='alice', _flush=True):
self.hit_email_spt('add-email', old_email)
nonce = P(username).get_email(old_email).nonce
self.verify_email(old_email, nonce)
self.finish_email_verification(old_email, nonce)
self.hit_email_spt('add-email', new_email)
if _flush:
self.app.email_queue.flush()
Expand Down Expand Up @@ -129,15 +129,15 @@ def test_post_too_quickly_is_400(self):
assert 'too quickly' in response.body

def test_verify_email_without_adding_email(self):
response = self.verify_email('', 'sample-nonce')
response = self.finish_email_verification('', 'sample-nonce')
assert 'Bad Info' in response.body

def test_verify_email_wrong_nonce(self):
self.hit_email_spt('add-email', '[email protected]')
nonce = 'fake-nonce'
r = self.alice.verify_email('[email protected]', nonce)
r = self.alice.finish_email_verification('[email protected]', nonce)
assert r == _email.VERIFICATION_FAILED
self.verify_email('[email protected]', nonce)
self.finish_email_verification('[email protected]', nonce)
expected = None
actual = P('alice').email_address
assert expected == actual
Expand All @@ -146,8 +146,8 @@ def test_verify_email_a_second_time_returns_redundant(self):
address = '[email protected]'
self.hit_email_spt('add-email', address)
nonce = self.alice.get_email(address).nonce
r = self.alice.verify_email(address, nonce)
r = self.alice.verify_email(address, nonce)
r = self.alice.finish_email_verification(address, nonce)
r = self.alice.finish_email_verification(address, nonce)
assert r == _email.VERIFICATION_REDUNDANT

def test_verify_email_expired_nonce(self):
Expand All @@ -159,15 +159,15 @@ def test_verify_email_expired_nonce(self):
WHERE participant_id = %s;
""", (self.alice.id,))
nonce = self.alice.get_email(address).nonce
r = self.alice.verify_email(address, nonce)
r = self.alice.finish_email_verification(address, nonce)
assert r == _email.VERIFICATION_EXPIRED
actual = P('alice').email_address
assert actual == None

def test_verify_email(self):
def test_finish_email_verification(self):
self.hit_email_spt('add-email', '[email protected]')
nonce = self.alice.get_email('[email protected]').nonce
self.verify_email('[email protected]', nonce)
self.finish_email_verification('[email protected]', nonce)
expected = '[email protected]'
actual = P('alice').email_address
assert expected == actual
Expand Down Expand Up @@ -197,7 +197,7 @@ def test_get_emails(self):
def test_verify_email_after_update(self):
self.verify_and_change_email('[email protected]', '[email protected]')
nonce = self.alice.get_email('[email protected]').nonce
self.verify_email('[email protected]', nonce)
self.finish_email_verification('[email protected]', nonce)
expected = '[email protected]'
actual = P('alice').email_address
assert expected == actual
Expand Down Expand Up @@ -296,7 +296,7 @@ def test_cannot_update_email_to_already_verified(self):
with self.assertRaises(EmailTaken):
bob.start_email_verification('[email protected]')
nonce = bob.get_email('[email protected]').nonce
bob.verify_email('[email protected]', nonce)
bob.finish_email_verification('[email protected]', nonce)

email_alice = P('alice').email_address
assert email_alice == '[email protected]'
Expand Down Expand Up @@ -592,7 +592,7 @@ def check(self, *package_names, **kw):
def preverify(self, address='[email protected]'):
self.alice.start_email_verification(address)
nonce = self.alice.get_email(address).nonce
self.alice.verify_email(address, nonce)
self.alice.finish_email_verification(address, nonce)


class VerificationMessage(VerificationBase):
Expand Down Expand Up @@ -677,3 +677,38 @@ def test_sends_notice_for_unverified_address_and_multiple_packages(self):
html, text = self.check('foo', 'bar')
assert ' connecting <b>[email protected]</b> and 2 npm packages ' in html
assert ' connecting [email protected] and 2 npm packages ' in text


class FinishEmailVerification(VerificationBase):

def start(self, address, *package_names):
packages = [self.make_package(name=name, emails=[address]) for name in package_names]
self.alice.start_email_verification(address, *packages)
return self.alice.get_email(address).nonce

def test_handles_new_address(self):
address = '[email protected]'
assert self.alice.email_address is None
self.alice.finish_email_verification(address, self.start(address))
assert self.alice.email_address == P('alice').email_address == address

def test_handles_verified_address_and_no_packages(self):
raise NotImplementedError # should error

def test_handles_verified_address_and_one_package(self):
self.preverify()
address = '[email protected]'
assert self.alice.email_address == address
self.alice.finish_email_verification(address, self.start(address, 'foo'))
assert self.alice.email_address == P('alice').email_address == address
raise NotImplementedError # assert we connect the package

def test_handles_verified_address_and_multiple_packages(self):
self.preverify()
raise NotImplementedError

def test_handles_unverified_address_and_one_package(self):
raise NotImplementedError

def test_handles_unverified_address_and_multiple_packages(self):
raise NotImplementedError
11 changes: 7 additions & 4 deletions tests/py/test_take_over.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,8 @@ def test_take_over_is_fine_with_identity_info_on_primary(self):
TT = self.db.one("SELECT id FROM countries WHERE code='TT'")
alice = self.make_participant('alice')
alice.start_email_verification('[email protected]')
alice.verify_email('[email protected]', alice.get_email('[email protected]').nonce)
nonce = alice.get_email('[email protected]').nonce
alice.finish_email_verification('[email protected]', nonce)
alice.store_identity_info(TT, 'nothing-enforced', {})

bob_github = self.make_elsewhere('github', 2, 'bob')
Expand All @@ -169,7 +170,7 @@ def test_take_over_fails_if_secondary_has_identity_info(self):
bob_github = self.make_elsewhere('github', 2, 'bob')
bob = bob_github.opt_in('bob')[0].participant
bob.start_email_verification('[email protected]')
bob.verify_email('[email protected]', bob.get_email('[email protected]').nonce)
bob.finish_email_verification('[email protected]', bob.get_email('[email protected]').nonce)
bob.store_identity_info(TT, 'nothing-enforced', {})

pytest.raises(WontTakeOverWithIdentities, alice.take_over, bob_github)
Expand All @@ -187,11 +188,13 @@ def test_email_addresses_merging(self):
alice.start_email_verification('[email protected]')
alice.start_email_verification('[email protected]')
alice.start_email_verification('[email protected]')
alice.verify_email('[email protected]', alice.get_email('[email protected]').nonce)
nonce = alice.get_email('[email protected]').nonce
alice.finish_email_verification('[email protected]', nonce)
bob_github = self.make_elsewhere('github', 2, 'bob')
bob = bob_github.opt_in('bob')[0].participant
bob.start_email_verification('[email protected]')
bob.verify_email('[email protected]', bob.get_email('[email protected]').nonce)
nonce = bob.get_email('[email protected]').nonce
bob.finish_email_verification('[email protected]', nonce)
bob.start_email_verification('[email protected]')
bob.start_email_verification('[email protected]')
alice.take_over(bob_github, have_confirmation=True)
Expand Down
2 changes: 1 addition & 1 deletion www/~/%username/emails/verify.html.spt
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ if participant == user.participant:
else:
email_address = decode_from_querystring(request.qs.get('email2', ''), default='')
nonce = request.qs.get('nonce', '')
result = participant.verify_email(email_address, nonce)
result = participant.finish_email_verification(email_address, nonce)
if not participant.email_lang:
participant.set_email_lang(request.headers.get("Accept-Language"))

Expand Down

0 comments on commit 566c176

Please sign in to comment.