This repository has been archived by the owner on Feb 8, 2018. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 308
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #4034 from gratipay/identity-form
Identity form
- Loading branch information
Showing
14 changed files
with
533 additions
and
154 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
{{ _("Identity Viewed") }} | ||
[---] text/html | ||
{{ _( "This is a transactional email to let you know that {a_viewer}{viewer}{_a} viewed your identity information for {a_country}{country_name}{_a} on Gratipay." | ||
, viewer=viewer | ||
, country_name=country_name | ||
, a_viewer=('<a href="https://gratipay.com/~{}/">'|safe).format(viewer) | ||
, a_country=('<a href="https://gratipay.com/about/me/identities/{}">'|safe).format(country_code) | ||
, _a='</a>'|safe | ||
) }} | ||
[---] text/plain | ||
{{ _( "This is a transactional email to let you know that {viewer} viewed your identity information for {country_name} on Gratipay." | ||
, viewer=viewer | ||
, country_name=country_name | ||
) }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
Gratipay.countryChooser = {} | ||
|
||
Gratipay.countryChooser.init = function() { | ||
$('.open-country-chooser').click(Gratipay.countryChooser.open); | ||
$('.close-country-chooser').click(Gratipay.countryChooser.close); | ||
$('#grayout').click(Gratipay.countryChooser.close); | ||
}; | ||
|
||
Gratipay.countryChooser.open = function() { | ||
$('.open-country-chooser').blur(); | ||
$('#grayout').show() | ||
$('#country-chooser').show(); | ||
}; | ||
|
||
Gratipay.countryChooser.close = function() { | ||
$('#country-chooser').hide() | ||
$('#grayout').hide() | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
#identities { | ||
padding-top: 20px; | ||
|
||
.card { | ||
height: 96px; | ||
width: 46%; | ||
float: left; | ||
margin: 0 8% 8% 0; | ||
padding: 20px; | ||
position: relative; | ||
@include border-radius(5px); | ||
@include box-shadow(0, 0, 10px, $black); | ||
|
||
&:nth-child(even) { | ||
margin-right: 0; | ||
} | ||
|
||
&.verified { | ||
background-image: radial-gradient( circle at 36px 60px | ||
, lighten($green, 55%) 0% | ||
, lighten($green, 30%) 100% | ||
); | ||
color: $green; | ||
} | ||
|
||
&.unverified { | ||
color: $red; | ||
} | ||
|
||
&.add { | ||
background: $lightest-gray; | ||
color: $medium-gray; | ||
border-style: dashed; | ||
@include box-shadow(0,0,0); | ||
border: 2px dashed $darker-gray; | ||
|
||
&:hover { | ||
color: $black; | ||
background-image: radial-gradient( ellipse farthest-corner at 50% 50% | ||
, $white 0% | ||
, $lightest-gray 50% | ||
); | ||
} | ||
} | ||
|
||
img { | ||
position: absolute; | ||
bottom: 20px; | ||
left: 20px; | ||
} | ||
|
||
h2 { | ||
white-space: nowrap; | ||
margin: 0; | ||
padding: 0; | ||
overflow: hidden; | ||
text-overflow: ellipsis; | ||
} | ||
|
||
.status { | ||
position: absolute; | ||
bottom: 20px; | ||
right: 20px; | ||
font: normal 9px $Mono; | ||
} | ||
} | ||
|
||
#country-chooser { | ||
z-index: 1001; | ||
display: none; | ||
background: white; | ||
@include border-radius(5px); | ||
@include box-shadow(0, 0, 10px, $black); | ||
position: fixed; | ||
width: 240px; | ||
height: 50vh; | ||
top: 25%; | ||
left: 50%; | ||
margin-left: -120px; | ||
|
||
header { | ||
position: absolute; | ||
top: 0; | ||
left: 0; | ||
z-index: 1; | ||
width: 240px; | ||
height: 36px; | ||
background: white; | ||
border-bottom: 1px solid $black; | ||
@include border-radius(5px 5px 0 0); | ||
@include box-shadow(0, 0, 10px, $black); | ||
|
||
h2 { | ||
margin: 0; | ||
padding: 12px 20px 0; | ||
} | ||
button { | ||
position: absolute; | ||
top: 5px; | ||
right: 20px; | ||
} | ||
} | ||
|
||
section { | ||
overflow: auto; | ||
height: 50vh; | ||
z-index: 0; | ||
margin-top: 36px; | ||
background: $lightest-gray; | ||
@include border-radius(0 0 5px 5px); | ||
@include box-shadow(0, 0, 10px, $black); | ||
|
||
a { | ||
padding: 5px 20px; | ||
display: block; | ||
img { | ||
margin: 0 5px -10px 0; | ||
} | ||
span { | ||
font: bold 18px/18px $Ideal; | ||
} | ||
white-space: nowrap; | ||
overflow: hidden; | ||
text-overflow: ellipsis; | ||
} | ||
} | ||
} | ||
|
||
#grayout { | ||
width: 100vw; | ||
height: 100vh; | ||
position: fixed; | ||
top: 0; | ||
left: 0; | ||
background: transparentize($black, 0.3); | ||
z-index: 1000; | ||
display: none; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
from __future__ import absolute_import, division, print_function, unicode_literals | ||
|
||
from gratipay.models.country import Country | ||
from gratipay.models.participant import Participant | ||
from gratipay.testing.emails import EmailHarness | ||
|
||
|
||
class Tests(EmailHarness): | ||
|
||
def setUp(self): | ||
super(Tests, self).setUp() | ||
self.make_participant('alice', claimed_time='now', is_admin=True) | ||
self.make_participant('whit537', id=1451, email_address='[email protected]', | ||
claimed_time='now', is_admin=True) | ||
self.make_participant('bob', claimed_time='now', email_address='[email protected]') | ||
self.verify('bob', 'TT') | ||
|
||
def identify(self, username, *codes): | ||
participant = Participant.from_username(username) | ||
for code in codes: | ||
country_id = Country.from_code(code).id | ||
participant.store_identity_info(country_id, 'nothing-enforced', {}) | ||
return participant | ||
|
||
def verify(self, username, *codes): | ||
participant = Participant.from_username(username) | ||
for code in codes: | ||
country_id = Country.from_code(code).id | ||
participant.store_identity_info(country_id, 'nothing-enforced', {}) | ||
participant.set_identity_verification(country_id, True) | ||
return participant | ||
|
||
|
||
# il - identities listing | ||
|
||
def test_il_is_403_for_anon(self): | ||
assert self.client.GxT('/~bob/identities/').code == 403 | ||
|
||
def test_il_is_403_for_non_admin(self): | ||
assert self.client.GxT('/~bob/identities/').code == 403 | ||
|
||
def test_il_is_200_for_self(self): | ||
assert self.client.GET('/~bob/identities/', auth_as='bob').code == 200 | ||
|
||
def test_il_is_200_for_admin(self): | ||
assert self.client.GET('/~bob/identities/', auth_as='alice').code == 200 | ||
|
||
|
||
# ip - identity page | ||
|
||
def test_ip_disallows_methods(self): | ||
assert self.client.hxt('HEAD', '/~bob/identities/TT').code == 405 | ||
|
||
def test_ip_is_403_for_anon(self): | ||
assert self.client.GxT('/~bob/identities/TT').code == 403 | ||
|
||
def test_ip_is_403_for_non_admin(self): | ||
assert self.client.GxT('/~bob/identities/TT').code == 403 | ||
|
||
def test_ip_is_200_for_self(self): | ||
assert self.client.GET('/~bob/identities/TT', auth_as='bob').code == 200 | ||
|
||
def test_ip_is_403_for_most_admins(self): | ||
assert self.client.GxT('/~bob/identities/TT', auth_as='alice').code == 403 | ||
|
||
def test_ip_is_200_for_whit537_yikes_O_O(self): | ||
assert self.client.GET('/~bob/identities/TT', auth_as='whit537').code == 200 | ||
|
||
def test_ip_notifies_participant_when_whit537_views(self): | ||
self.client.GET('/~bob/identities/TT', auth_as='whit537') | ||
assert 'whit537 viewed your identity' in self.get_last_email()['body_text'] | ||
|
||
def test_ip_is_404_for_unknown_code(self): | ||
assert self.client.GxT('/~bob/identities/XX', auth_as='bob').code == 404 | ||
|
||
def test_ip_is_302_if_no_verified_email(self): | ||
response = self.client.GxT('/~alice/identities/TT', auth_as='alice') | ||
assert response.code == 302 | ||
assert response.headers['Location'] == '/about/me/emails/' | ||
|
||
|
||
def test_ip_is_200_for_third_identity(self): | ||
self.verify('bob', 'TT', 'US') | ||
assert self.client.GET('/~bob/identities/US', auth_as='bob').code == 200 | ||
|
||
def test_ip_is_302_for_fourth_identity(self): | ||
self.verify('bob', 'TT', 'US', 'GB') | ||
assert self.client.GxT('/~bob/identities/CA', auth_as='bob').code == 302 | ||
|
||
def test_ip_is_302_for_fifth_identities(self): | ||
self.verify('bob', 'TT', 'US', 'GB', 'GH') | ||
assert self.client.GxT('/~bob/identities/CA', auth_as='bob').code == 302 | ||
|
||
def test_but_ip_always_loads_for_own_identity(self): | ||
self.verify('bob', 'TT', 'US', 'GB', 'GH') | ||
assert self.client.GET('/~bob/identities/TT', auth_as='bob').code == 200 | ||
|
||
def test_ip_always_loads_for_own_identity_even_if_unverified(self): | ||
self.verify('bob', 'US', 'GB', 'GH') | ||
self.identify('bob', 'TT') | ||
assert self.client.GET('/~bob/identities/TT', auth_as='bob').code == 200 | ||
|
||
|
||
def test_ip_removes_identity(self): | ||
bob = self.verify('bob', 'TT') | ||
assert len(bob.list_identity_metadata()) == 1 | ||
data = {'action': 'remove'} | ||
assert self.client.PxST('/~bob/identities/TT', auth_as='bob', data=data).code == 302 | ||
assert len(bob.list_identity_metadata()) == 0 | ||
|
||
def test_ip_stores_identity(self): | ||
bob = Participant.from_username('bob') | ||
assert len(bob.list_identity_metadata()) == 1 | ||
data = { 'id_type': '' | ||
, 'id_number': '' | ||
, 'legal_name': 'Bobsworth B. Bobbleton, IV' | ||
, 'dob': '' | ||
, 'address_1': '' | ||
, 'address_2': '' | ||
, 'city': '' | ||
, 'region': '' | ||
, 'postcode': '' | ||
, 'action': 'store' | ||
} | ||
assert self.client.PxST('/~bob/identities/US', auth_as='bob', data=data).code == 302 | ||
assert len(bob.list_identity_metadata()) == 2 | ||
info = bob.retrieve_identity_info(Country.from_code('US').id) | ||
assert info['legal_name'] == 'Bobsworth B. Bobbleton, IV' | ||
|
||
def test_ip_validates_action(self): | ||
bob = Participant.from_username('bob') | ||
assert len(bob.list_identity_metadata()) == 1 | ||
data = {'action': 'cheese'} | ||
assert self.client.PxST('/~bob/identities/TT', auth_as='bob', data=data).code == 400 | ||
assert len(bob.list_identity_metadata()) == 1 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.