Skip to content

Commit

Permalink
Merge pull request #23 from getyoti/SDK-282-IsAgeVerifiedHelper
Browse files Browse the repository at this point in the history
[SDK-282]: Added IsAgeVerified helper
  • Loading branch information
echarrod authored Mar 29, 2018
2 parents f3a256d + 96fd3e9 commit 68de001
Show file tree
Hide file tree
Showing 11 changed files with 128 additions and 31 deletions.
17 changes: 11 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,8 @@ Here is an example of how this works:

```python
client = Client(YOTI_CLIENT_SDK_ID, YOTI_KEY_FILE_PATH)
user_profile = client.get_activity_details(token).user_profile
activity_details = client.get_activity_details(token)
user_profile = activity_details.user_profile

user_id = user_profile.get('user_id')
selfie = user_profile.get('selfie')
Expand All @@ -126,9 +127,12 @@ family_name = user_profile.get('family_name')
full_name = user_profile.get('full_name')
phone_number = user_profile.get('phone_number')
date_of_birth = user_profile.get('date_of_birth')
is_age_verified = user_profile.get('is_age_verified')
postal_address = user_profile.get('postal_address')
gender = user_profile.get('gender')
nationality = user_profile.get('nationality')

base64_selfie_uri = activity_details.get('base64_selfie_uri')
```

## AML Integration
Expand Down Expand Up @@ -238,30 +242,31 @@ Plugins for both Django and Flask are in the `plugins/` dir. Their purpose is to

## Running the Tests

Run your project but please make sure you have all the correct requirements:
Running the tests is done by the following process, ensuring you are using Python 3.0+:

1. Install dependencies: `pip install -r requirements.txt`
2. Install the SDK: `python setup.py develop`
3. Execute in the main project dir: `py.test`
1. Install the SDK: `python setup.py develop`
1. Execute in the main project dir: `py.test`

For information on testing with multiple Python versions, see [VERSION-SUPPORT.md](/VERSION-SUPPORT.md)

## API Coverage

* Activity Details
* [X] User ID `user_id`
* [X] Profile
* [X] Profile `user_profile`
* [X] Photo `selfie`
* [X] Given Names `given_names`
* [X] Family Name `family_name`
* [X] Full Name `full_name`
* [X] Mobile Number `phone_number`
* [X] Email Address `email_address`
* [X] Age / Date of Birth `date_of_birth`
* [X] Age / Verify Condition `age_[over|under]:[1-999]`
* [X] Age / Is Age Verified `is_age_verified`
* [X] Address `postal_address`
* [X] Gender `gender`
* [X] Nationality `nationality`
* [X] Base64 Selfie URI `base64_selfie_uri`

## Support

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,10 @@ <h3><a href="/">Home</a></h3>
</tr>
{% endif %}

{% if age_verified %}
{% if is_age_verified is not None %}
<tr>
<td>Age Verified:</td>
<td>{{age_verified}}</td>
<td>{{is_age_verified}}</td>
</tr>
{% endif %}

Expand Down
3 changes: 0 additions & 3 deletions examples/yoti_example_django/yoti_example/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,6 @@ def get(self, request, *args, **kwargs):
context = activity_details.user_profile
context['base64_selfie_uri'] = getattr(activity_details, 'base64_selfie_uri')

# This key uses the format: age_[over|under]:[1-999] and is dynamically
# generated based on the dashboard attribute Age / Verify Condition
context['age_verified'] = context.get('age_over:18')
selfie = context.get('selfie')
if selfie is not None:
self.save_image(selfie)
Expand Down
5 changes: 1 addition & 4 deletions examples/yoti_example_flask/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,6 @@ def auth():
user_profile = activity_details.user_profile
user_profile['base64_selfie_uri'] = getattr(activity_details, 'base64_selfie_uri')

# This key uses the format: age_[over|under]:[1-999] and is dynamically
# generated based on the dashboard attribute Age / Verify Condition
user_profile['age_verified'] = user_profile.get('age_over:18')
selfie = user_profile.get('selfie')
if selfie is not None:
save_image(selfie)
Expand All @@ -49,4 +46,4 @@ def auth():


if __name__ == '__main__':
app.run(host="0.0.0.0", debug=True)
app.run(host="0.0.0.0")
4 changes: 2 additions & 2 deletions examples/yoti_example_flask/templates/profile.html
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,10 @@ <h3><a href="/">Home</a></h3>
</tr>
{% endif %}

{% if age_verified %}
{% if is_age_verified is defined %}
<tr>
<td>Age Verified:</td>
<td>{{age_verified}}</td>
<td>{{is_age_verified}}</td>
</tr>
{% endif %}

Expand Down
6 changes: 2 additions & 4 deletions plugins/django_yoti/django_yoti/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from django.test import TestCase, Client, RequestFactory

from yoti_python_sdk.activity_details import ActivityDetails
from yoti_python_sdk.tests.conftest import successful_receipt, failure_receipt
from ..views import profile


Expand Down Expand Up @@ -31,10 +32,7 @@ def test_profile_not_logged_in(self):
self.assertEqual(response.url, '/login/')

def test_profile_outcome_is_failure(self):
receipt = {'remember_me_id': 'some_id',
'sharing_outcome': 'FAILURE'}
activity_details = ActivityDetails(receipt, None)

activity_details = ActivityDetails(failure_receipt(), None)
request = self.factory.get('/profile/')
self._update_session(request, activity_details=dict(activity_details))
response = profile(request)
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
from setuptools import setup, find_packages

VERSION = '2.1.0'
VERSION = '2.2.0'
long_description = 'This package contains the tools you need to quickly ' \
'integrate your Python back-end with Yoti, so that your ' \
'users can share their identity details with your ' \
Expand Down
33 changes: 27 additions & 6 deletions yoti_python_sdk/activity_details.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
# -*- coding: utf-8 -*-
from yoti_python_sdk import config
from yoti_python_sdk.protobuf.v1.protobuf import Protobuf


class ActivityDetails:
def __init__(self, receipt, decrypted_profile=None):
self.decrypted_profile = decrypted_profile
self.user_profile = {}
self.base64_selfie_uri = ''
self.base64_selfie_uri = None

if decrypted_profile and hasattr(decrypted_profile, 'attributes'):
for field in decrypted_profile.attributes:
Expand All @@ -15,15 +16,35 @@ def __init__(self, receipt, decrypted_profile=None):
field.content_type
)
self.user_profile[field.name] = value
if field.name == 'selfie':
self.base64_selfie_uri = Protobuf().image_uri_based_on_content_type(
field.value,
field.content_type
)

self.try_parse_selfie_field(field)
self.try_parse_age_verified_field(field)

self.user_id = receipt['remember_me_id']
self.outcome = receipt['sharing_outcome']

def try_parse_selfie_field(self, field):
if field.name == 'selfie':
self.base64_selfie_uri = Protobuf().image_uri_based_on_content_type(
field.value,
field.content_type
)

def try_parse_age_verified_field(self, field):
if field.name.startswith(config.ATTRIBUTE_AGE_OVER) or field.name.startswith(config.ATTRIBUTE_AGE_UNDER):
is_age_verified = Protobuf().value_based_on_content_type(
field.value,
field.content_type
)
if is_age_verified == 'true':
self.user_profile['is_age_verified'] = True
return
if is_age_verified == 'false':
self.user_profile['is_age_verified'] = False
return
else:
raise TypeError("age_verified_field unable to be parsed")

def __iter__(self):
yield 'user_id', self.user_id
yield 'outcome', self.outcome
Expand Down
2 changes: 2 additions & 0 deletions yoti_python_sdk/config.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
# -*- coding: utf-8 -*-
SDK_IDENTIFIER = "Python"
ATTRIBUTE_AGE_OVER = "age_over:"
ATTRIBUTE_AGE_UNDER = "age_under:"
23 changes: 20 additions & 3 deletions yoti_python_sdk/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
from os.path import dirname, join, abspath

import pytest
import io
from os.path import dirname, join, abspath

from yoti_python_sdk import Client
from yoti_python_sdk.crypto import Crypto
Expand Down Expand Up @@ -39,6 +39,23 @@ def decrypted_request_token():
'470c-48e5-8ceb-25cf86674ba4'


@pytest.fixture(scope='module')
def user_id():
return 'some_id'


@pytest.fixture(scope='module')
def successful_receipt():
return {'remember_me_id': user_id(),
'sharing_outcome': 'SUCCESS'}


@pytest.fixture(scope='module')
def failure_receipt():
return {'remember_me_id': user_id(),
'sharing_outcome': 'FAILURE'}


@pytest.fixture(scope='module')
def x_yoti_auth_key():
with open(AUTH_KEY_FILE_PATH, 'r') as auth_key_file:
Expand All @@ -53,5 +70,5 @@ def x_yoti_auth_digest_get():

@pytest.fixture(scope='module')
def x_yoti_auth_digest_post():
with open(AUTH_DIGEST_POST_FILE_PATH, 'r') as auth_digest_file:
with io.open(AUTH_DIGEST_POST_FILE_PATH, mode='r', encoding='utf-8') as auth_digest_file:
return auth_digest_file.read()
60 changes: 60 additions & 0 deletions yoti_python_sdk/tests/test_activity_details.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# -*- coding: utf-8 -*-
import pytest

from yoti_python_sdk.activity_details import ActivityDetails
from yoti_python_sdk.protobuf.v1.protobuf import Protobuf
from yoti_python_sdk.tests.conftest import successful_receipt


def create_selfie_field(activity_details):
activity_details.field = lambda: None
activity_details.field.name = "selfie"
activity_details.field.value = "base64(ง •̀_•́)ง"
activity_details.field.content_type = Protobuf.CT_STRING


def create_age_verified_field(activity_details, over, encoded_string_verified_value, age):
activity_details.field = lambda: None
activity_details.field.name = "age_over:{0}".format(age) if over is True else "age_under:".format(age)
activity_details.field.value = encoded_string_verified_value
activity_details.field.content_type = Protobuf.CT_STRING


def test_try_parse_selfie_field_valid_selfie():
activity_details = ActivityDetails(successful_receipt())
create_selfie_field(activity_details)

ActivityDetails.try_parse_selfie_field(activity_details, activity_details.field)
assert activity_details.base64_selfie_uri is not None


def test_try_parse_age_verified_both_missing_returns_null():
activity_details = ActivityDetails(successful_receipt())
create_selfie_field(activity_details)

ActivityDetails.try_parse_age_verified_field(activity_details, activity_details.field)
assert not activity_details.user_profile


def test_try_parse_age_verified_field_age_over():
activity_details = ActivityDetails(successful_receipt())
create_age_verified_field(activity_details, True, "true".encode(), 18)

ActivityDetails.try_parse_age_verified_field(activity_details, activity_details.field)
assert activity_details.user_profile['is_age_verified'] is True


def test_try_parse_age_verified_field_age_under():
activity_details = ActivityDetails(successful_receipt())
create_age_verified_field(activity_details, False, "false".encode(), 55)

ActivityDetails.try_parse_age_verified_field(activity_details, activity_details.field)
assert activity_details.user_profile['is_age_verified'] is False


def test_try_parse_age_verified_field_non_bool_value_throws_error():
activity_details = ActivityDetails(successful_receipt())
create_age_verified_field(activity_details, True, "18".encode(), 18)

with pytest.raises(TypeError):
ActivityDetails.try_parse_age_verified_field(activity_details, activity_details.field)

0 comments on commit 68de001

Please sign in to comment.