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

Commit

Permalink
implement has_verified_identity
Browse files Browse the repository at this point in the history
Conflicts:
	sql/branch.sql
  • Loading branch information
chadwhitacre committed May 11, 2016
1 parent ea90746 commit 0600a21
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 0 deletions.
26 changes: 26 additions & 0 deletions gratipay/models/participant/mixins/identity.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ class IdentityMixin(object):
"""

#: ``True`` if the participant has at least one verified identity on file,
#: ``False`` otherwise. This attribute is read-only. It is updated with
#: :py:meth:`set_identity_verification` and :py:meth:`clear_identity`.

has_verified_identity = False

def store_identity_info(self, country_id, schema_name, info):
"""Store the participant's national identity information for a given country.
Expand Down Expand Up @@ -219,6 +225,7 @@ def set_identity_verification(self, country_id, is_verified):
)

add_event(cursor, 'participant', payload)
self._update_has_verified_identity(cursor)


def clear_identity(self, country_id):
Expand All @@ -243,6 +250,25 @@ def clear_identity(self, country_id):
, action='clear identity'
)
add_event(cursor, 'participant', payload)
self._update_has_verified_identity(cursor)


def _update_has_verified_identity(self, cursor):
has_verified_identity = cursor.one("""
WITH verified_identities AS
( SELECT *
FROM participant_identities
WHERE participant_id=%(participant_id)s
AND is_verified
)
UPDATE participants
SET has_verified_identity=(SELECT count(*) FROM verified_identities) > 0
WHERE id=%(participant_id)s
RETURNING has_verified_identity
""", dict(participant_id=self.id))
self.set_attributes(has_verified_identity=has_verified_identity)


# Rekeying
Expand Down
3 changes: 3 additions & 0 deletions sql/branch.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-- participants.has_verified_identity

ALTER TABLE participants ADD COLUMN has_verified_identity bool NOT NULL DEFAULT false;
93 changes: 93 additions & 0 deletions tests/py/test_participant_identities.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@

from cryptography.fernet import InvalidToken
from gratipay.testing import Harness
from gratipay.models.participant import Participant
from gratipay.models.participant.mixins import identity, Identity
from gratipay.models.participant.mixins.identity import _validate_info, rekey
from gratipay.models.participant.mixins.identity import ParticipantIdentityInfoInvalid
from gratipay.models.participant.mixins.identity import ParticipantIdentitySchemaUnknown
from gratipay.security.crypto import EncryptingPacker, Fernet
from postgres.orm import ReadOnly
from psycopg2 import IntegrityError
from pytest import raises

Expand Down Expand Up @@ -250,6 +252,97 @@ def test_ci_still_logs_an_event_when_noop(self):
self.assert_events(self.crusher.id, [None], [self.TT], ['clear identity'])


# hvi - has_verified_identity

def test_hvi_defaults_to_false(self):
assert self.crusher.has_verified_identity is False

def test_hvi_is_read_only(self):
with raises(ReadOnly):
self.crusher.has_verified_identity = True

def test_hvi_becomes_true_when_an_identity_is_verified(self):
self.crusher.store_identity_info(self.TT, 'nothing-enforced', {})
self.crusher.set_identity_verification(self.TT, True)
assert self.crusher.has_verified_identity
assert Participant.from_username('crusher').has_verified_identity

def test_hvi_becomes_false_when_the_identity_is_unverified(self):
self.crusher.store_identity_info(self.TT, 'nothing-enforced', {})
self.crusher.set_identity_verification(self.TT, True)
self.crusher.set_identity_verification(self.TT, False)
assert not self.crusher.has_verified_identity
assert not Participant.from_username('crusher').has_verified_identity

def test_hvi_stays_true_when_a_secondary_identity_is_verified(self):
self.crusher.store_identity_info(self.US, 'nothing-enforced', {})
self.crusher.set_identity_verification(self.US, True)
self.crusher.store_identity_info(self.TT, 'nothing-enforced', {})
self.crusher.set_identity_verification(self.TT, True)
assert self.crusher.has_verified_identity
assert Participant.from_username('crusher').has_verified_identity

def test_hvi_stays_true_when_the_secondary_identity_is_unverified(self):
self.crusher.store_identity_info(self.US, 'nothing-enforced', {})
self.crusher.set_identity_verification(self.US, True)
self.crusher.store_identity_info(self.TT, 'nothing-enforced', {})
self.crusher.set_identity_verification(self.TT, True)
self.crusher.set_identity_verification(self.TT, False)
assert self.crusher.has_verified_identity
assert Participant.from_username('crusher').has_verified_identity

def test_hvi_goes_back_to_false_when_both_are_unverified(self):
self.crusher.store_identity_info(self.US, 'nothing-enforced', {})
self.crusher.store_identity_info(self.TT, 'nothing-enforced', {})
self.crusher.set_identity_verification(self.TT, True)
self.crusher.set_identity_verification(self.US, True)
self.crusher.set_identity_verification(self.TT, False)
self.crusher.set_identity_verification(self.US, False)
assert not self.crusher.has_verified_identity
assert not Participant.from_username('crusher').has_verified_identity

def test_hvi_changes_are_scoped_to_a_participant(self):
self.crusher.store_identity_info(self.US, 'nothing-enforced', {})

bruiser = self.make_participant('bruiser', email_address='[email protected]')
bruiser.store_identity_info(self.US, 'nothing-enforced', {})

self.crusher.set_identity_verification(self.US, True)

assert self.crusher.has_verified_identity
assert Participant.from_username('crusher').has_verified_identity
assert not bruiser.has_verified_identity
assert not Participant.from_username('bruiser').has_verified_identity

def test_hvi_resets_when_identity_is_cleared(self):
self.crusher.store_identity_info(self.TT, 'nothing-enforced', {})
self.crusher.set_identity_verification(self.TT, True)
self.crusher.clear_identity(self.TT)
assert not self.crusher.has_verified_identity
assert not Participant.from_username('crusher').has_verified_identity

def test_hvi_doesnt_reset_when_penultimate_identity_is_cleared(self):
self.crusher.store_identity_info(self.US, 'nothing-enforced', {})
self.crusher.set_identity_verification(self.US, True)
self.crusher.store_identity_info(self.TT, 'nothing-enforced', {})
self.crusher.set_identity_verification(self.TT, True)
self.crusher.set_identity_verification(self.TT, False)
self.crusher.clear_identity(self.TT)
assert self.crusher.has_verified_identity
assert Participant.from_username('crusher').has_verified_identity

def test_hvi_does_reset_when_both_identities_are_cleared(self):
self.crusher.store_identity_info(self.US, 'nothing-enforced', {})
self.crusher.store_identity_info(self.TT, 'nothing-enforced', {})
self.crusher.set_identity_verification(self.US, True)
self.crusher.set_identity_verification(self.TT, True)
self.crusher.set_identity_verification(self.TT, False)
self.crusher.set_identity_verification(self.US, False)
self.crusher.clear_identity(self.TT)
assert not self.crusher.has_verified_identity
assert not Participant.from_username('crusher').has_verified_identity


# fine - fail_if_no_email

def test_fine_fails_if_no_email(self):
Expand Down

0 comments on commit 0600a21

Please sign in to comment.