diff --git a/app/app/settings.py b/app/app/settings.py index e783a0a5bb1..428b000667a 100644 --- a/app/app/settings.py +++ b/app/app/settings.py @@ -120,7 +120,8 @@ 'event_ethdenver2019', 'inbox', 'feeswapper', - 'oauth2_provider' + 'oauth2_provider', + 'compliance' ] MIDDLEWARE = [ diff --git a/app/compliance/__init__.py b/app/compliance/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/app/compliance/admin.py b/app/compliance/admin.py new file mode 100644 index 00000000000..2a8d4ca20f5 --- /dev/null +++ b/app/compliance/admin.py @@ -0,0 +1,11 @@ +from django.contrib import admin + +from .models import Country, Entity + + +class GeneralAdmin(admin.ModelAdmin): + ordering = ['-id'] + list_display = ['created_on', '__str__'] + +admin.site.register(Country, GeneralAdmin) +admin.site.register(Entity, GeneralAdmin) diff --git a/app/compliance/apps.py b/app/compliance/apps.py new file mode 100644 index 00000000000..82773aa54d0 --- /dev/null +++ b/app/compliance/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class ComplianceConfig(AppConfig): + name = 'compliance' diff --git a/app/compliance/management/__init__.py b/app/compliance/management/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/app/compliance/management/commands/__init__.py b/app/compliance/management/commands/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/app/compliance/management/commands/pull_compliance_list.py b/app/compliance/management/commands/pull_compliance_list.py new file mode 100644 index 00000000000..9186fbde89a --- /dev/null +++ b/app/compliance/management/commands/pull_compliance_list.py @@ -0,0 +1,72 @@ +''' + Copyright (C) 2019 Gitcoin Core + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +''' + +import urllib.request +import xml.etree as etree +import xml.etree.ElementTree as ET + +from django.core.management.base import BaseCommand +from django.db import transaction + +from compliance.models import Country, Entity + + +def insert_countries(): + # clear existing table + Country.objects.all().delete() + + # pull data + countries = 'Balkans, Belarus, Burma, Cote D\'Ivoire , Cuba, Democratic Republic of Congo, Iran, Iraq, Liberia, North Korea, Sudan, Syria, Zimbabwe'.split(',') + + # insert data + for country in countries: + Country.objects.create(name=country) + + +def insert_entities(): + # clear existing table + Entity.objects.all().delete() + + # pull data + url = 'https://www.treasury.gov/ofac/downloads/consolidated/consolidated.xml' + response = urllib.request.urlopen(url).read() + tree = ET.fromstring(response) + + # insert data + for ele in tree: + try: + response = {} + keys = ['firstName', 'lastName', 'sdnType', 'city', 'country', 'program', 'stateOrProvince', 'uid'] + for key in keys: + elements = ele.findall('{http://tempuri.org/sdnList.xsd}' + f'{key}') + element = elements[0].text if len(elements) else '' + response[key] = element + response['fullName'] = (response.get('firstName', '') + ' ' + response.get('lastName', '')).strip() + Entity.objects.create(**response) + except Exception as e: + print(e) + + +class Command(BaseCommand): + + help = 'syncs compliance info from remote server' + + def handle(self, *args, **options): + with transaction.atomic(): + insert_countries() + insert_entities() diff --git a/app/compliance/migrations/0001_initial.py b/app/compliance/migrations/0001_initial.py new file mode 100644 index 00000000000..3e2e3df7352 --- /dev/null +++ b/app/compliance/migrations/0001_initial.py @@ -0,0 +1,47 @@ +# Generated by Django 2.2.4 on 2019-12-05 20:47 + +from django.db import migrations, models +import economy.models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Country', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_on', models.DateTimeField(db_index=True, default=economy.models.get_time)), + ('modified_on', models.DateTimeField(default=economy.models.get_time)), + ('name', models.CharField(max_length=500)), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='Entity', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_on', models.DateTimeField(db_index=True, default=economy.models.get_time)), + ('modified_on', models.DateTimeField(default=economy.models.get_time)), + ('firstName', models.CharField(max_length=500)), + ('lastName', models.CharField(max_length=500)), + ('fullName', models.CharField(max_length=500)), + ('sdnType', models.CharField(max_length=500)), + ('city', models.CharField(max_length=500)), + ('country', models.CharField(max_length=500)), + ('program', models.CharField(max_length=500)), + ('stateOrProvince', models.CharField(max_length=500)), + ('uid', models.CharField(max_length=25)), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/app/compliance/migrations/__init__.py b/app/compliance/migrations/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/app/compliance/models.py b/app/compliance/models.py new file mode 100644 index 00000000000..7cee697a148 --- /dev/null +++ b/app/compliance/models.py @@ -0,0 +1,27 @@ +from django.db import models + +from economy.models import SuperModel + + +class Entity(SuperModel): + firstName = models.CharField(max_length=500) + lastName = models.CharField(max_length=500) + fullName = models.CharField(max_length=500) + sdnType = models.CharField(max_length=500) + city = models.CharField(max_length=500) + country = models.CharField(max_length=500) + program = models.CharField(max_length=500) + stateOrProvince = models.CharField(max_length=500) + uid = models.CharField(max_length=25) + + def __str__(self): + """Return the string representation of this obj.""" + return f'{self.fullName}' + + +class Country(SuperModel): + name = models.CharField(max_length=500) + + def __str__(self): + """Return the string representation of this obj.""" + return f'{self.name}' diff --git a/app/compliance/tests.py b/app/compliance/tests.py new file mode 100644 index 00000000000..7ce503c2dd9 --- /dev/null +++ b/app/compliance/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/app/compliance/views.py b/app/compliance/views.py new file mode 100644 index 00000000000..91ea44a218f --- /dev/null +++ b/app/compliance/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/app/dashboard/utils.py b/app/dashboard/utils.py index 4574c399836..882d328e4d9 100644 --- a/app/dashboard/utils.py +++ b/app/dashboard/utils.py @@ -30,6 +30,7 @@ import ipfshttpclient import requests from app.utils import sync_profile +from compliance.models import Country, Entity from dashboard.helpers import UnsupportedSchemaException, normalize_url, process_bounty_changes, process_bounty_details from dashboard.models import Activity, BlockedUser, Bounty, Profile, UserAction from eth_utils import to_checksum_address @@ -801,7 +802,33 @@ def get_tx_status(txid, network, created_on): def is_blocked(handle): - return BlockedUser.objects.filter(handle__iexact=handle, active=True).exists() + # check admin block list + is_on_blocked_list = BlockedUser.objects.filter(handle__iexact=handle, active=True).exists() + if is_on_blocked_list: + return True + + # check banned country list + profiles = Profile.objects.filter(handle__iexact=handle) + if profiles.exists(): + profile = profiles.first() + last_login = profile.actions.filter(action='Login').order_by('pk').last() + if last_login: + last_login_country = last_login.location_data.get('country_name') + if last_login_country: + is_on_banned_countries = Country.objects.filter(name=last_login_country) + if is_on_banned_countries: + return True + + # check banned entity list + if profile.user: + first_name = profile.user.first_name + last_name = profile.user.last_name + full_name = '{first_name} {last_name}' + is_on_banned_user_list = Entity.objects.filter(fullName__icontains=full_name) + if is_on_banned_user_list: + return True + + return False def get_nonce(network, address): diff --git a/scripts/crontab b/scripts/crontab index f656b10618e..c1176ec0483 100644 --- a/scripts/crontab +++ b/scripts/crontab @@ -37,6 +37,7 @@ PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/us */8 * * * * cd gitcoin/coin; bash scripts/run_management_command.bash send_tips_for_bounty_fulfiller >> /var/log/gitcoin/send_tips_for_bounty_fulfiller.log 2>&1 35 1 * * * cd gitcoin/coin; bash scripts/run_management_command.bash update_popularity >> /var/log/gitcoin/update_popularity.log 2>&1 */7 * * * * cd gitcoin/coin; bash scripts/run_management_command_if_not_already_running.bash update_tx_status >> /var/log/gitcoin/update_tx_status.log 2>&1 +1 3 * * * cd gitcoin/coin; bash scripts/run_management_command_if_not_already_running.bash pull_compliance_list >> /var/log/gitcoin/pull_compliance_list.log 2>&1 ## REPORTING 1 1 1 * * cd gitcoin/coin; bash scripts/run_management_command.bash activity_report $(date --date='-1 month' +%Y/%m/%d) $(date +%Y/%m/%d) >> /var/log/gitcoin/activity_report.log 2>&1