Skip to content

Commit

Permalink
Merge pull request #35 from gnosischain/dev
Browse files Browse the repository at this point in the history
Add feature to block user address
  • Loading branch information
giacomognosis authored Apr 10, 2024
2 parents fc2e3fb + 3014ca5 commit 9936614
Show file tree
Hide file tree
Showing 10 changed files with 124 additions and 10 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ cd api
python3 -m unittest discover -p 'test_*.py'
```

Run specific test case:

```
cd api
python3 -m unittest tests.test_api.TestAPI.test_ask_route_blocked_users
```

### Run Flake8 and isort

```
Expand Down
4 changes: 3 additions & 1 deletion api/api/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
from flask_cors import CORS
from flask_migrate import Migrate

from .manage import create_access_keys_cmd, create_enabled_token_cmd
from .manage import (block_user_cmd, create_access_keys_cmd,
create_enabled_token_cmd)
from .routes import apiv1
from .services import Web3Singleton
from .services.database import db
Expand Down Expand Up @@ -39,6 +40,7 @@ def create_app():
# Add cli commands
app.cli.add_command(create_access_keys_cmd)
app.cli.add_command(create_enabled_token_cmd)
app.cli.add_command(block_user_cmd)

with app.app_context():
db.init_app(app)
Expand Down
24 changes: 23 additions & 1 deletion api/api/manage.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from flask.cli import with_appcontext

from .services import Web3Singleton
from .services.database import AccessKey, AccessKeyConfig, Token
from .services.database import AccessKey, AccessKeyConfig, BlockedUsers, Token
from .utils import generate_access_key


Expand Down Expand Up @@ -62,3 +62,25 @@ def create_enabled_token_cmd(name, chain_id, address, max_amount_day, type):
token.save()

logging.info('Token created successfully')


@click.command(name='block_user')
@click.argument('address')
@with_appcontext
def block_user_cmd(address):
w3 = Web3Singleton(
current_app.config['FAUCET_RPC_URL'],
current_app.config['FAUCET_PRIVATE_KEY']
)

# check if Token already exists
check_user = BlockedUsers.get_by_address(address)

if check_user:
raise Exception('User %s already blocked' % address)

blocked_user = BlockedUsers()
blocked_user.address = w3.to_checksum_address(address)
blocked_user.save()

logging.info('User blocked successfully')
17 changes: 15 additions & 2 deletions api/api/services/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from datetime import datetime

from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import MetaData
from sqlalchemy import MetaData, func

from api.const import (DEFAULT_ERC20_MAX_AMOUNT_PER_DAY,
DEFAULT_NATIVE_MAX_AMOUNT_PER_DAY, FaucetRequestType)
Expand Down Expand Up @@ -93,7 +93,7 @@ def enabled_tokens(cls):
@classmethod
def get_by_address(cls, address):
return cls.query.filter_by(address=address).first()

@classmethod
def get_by_address_and_chain_id(cls, address, chain_id):
return cls.query.filter_by(address=address,
Expand Down Expand Up @@ -196,3 +196,16 @@ def get_amount_sum_by_access_key_and_token(cls,
access_key_id=access_key_id,
token=token_address
).first().amount


class BlockedUsers(BaseModel):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
address = db.Column(db.String(42), nullable=False)
created = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
updated = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)

__tablename__ = "blocked_users"

@classmethod
def get_by_address(cls, address):
return cls.query.filter(func.lower(cls.address) == func.lower(address)).first()
18 changes: 17 additions & 1 deletion api/api/services/validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from api.const import TokenType

from .captcha import captcha_verify
from .database import AccessKeyConfig, Token, Transaction
from .database import AccessKeyConfig, BlockedUsers, Token, Transaction
from .rate_limit import Strategy


Expand All @@ -18,6 +18,7 @@ class AskEndpointValidator:
'UNSUPPORTED_CHAIN': 'chainId: %s is not supported. Supported chainId: %s',
'INVALID_RECIPIENT': 'recipient: A valid recipient address must be specified',
'INVALID_RECIPIENT_ITSELF': 'recipient: address cant\'t be the Faucet address itself',
'BLOCKED_RECIPIENT': 'Recipient address is blocked',
'REQUIRED_AMOUNT': 'amount: is required',
'AMOUNT_ZERO': 'amount: must be greater than 0',
'INVALID_TOKEN_ADDRESS': 'tokenAddress: A valid token address must be specified',
Expand All @@ -32,6 +33,10 @@ def __init__(self, request_data, validate_captcha, access_key=None, *args, **kwa
self.errors = []

def validate(self):
self.blocked_user_validation()
if len(self.errors) > 0:
return False

self.data_validation()
if len(self.errors) > 0:
return False
Expand Down Expand Up @@ -59,6 +64,17 @@ def validate(self):
return False
return True

def blocked_user_validation(self):
recipient = self.request_data.get('recipient', None)
# Run validation on blocked users only if `recipient` is available.
# Let next validation steps do the rest.
if recipient:
# check if recipient in blocked_users, return 403
user = BlockedUsers.get_by_address(recipient)
if user:
self.errors.append(self.messages['BLOCKED_RECIPIENT'])
self.http_return_code = 403

def data_validation(self):
if self.request_data.get('chainId') != current_app.config['FAUCET_CHAIN_ID']:
self.errors.append(self.messages['UNSUPPORTED_CHAIN'] % (
Expand Down
3 changes: 1 addition & 2 deletions api/migrations/versions/4cacf36b2356_.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,11 @@
Create Date: 2024-03-09 11:37:03.009350
"""
from alembic import op
import sqlalchemy as sa
from alembic import op

from api.services.database import flask_db_convention


# revision identifiers, used by Alembic.
revision = '4cacf36b2356'
down_revision = '022497197c7a'
Expand Down
33 changes: 33 additions & 0 deletions api/migrations/versions/fc63d8242f0d_.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"""empty message
Revision ID: fc63d8242f0d
Revises: 4cacf36b2356
Create Date: 2024-04-10 10:25:15.572753
"""
import sqlalchemy as sa
from alembic import op

# revision identifiers, used by Alembic.
revision = 'fc63d8242f0d'
down_revision = '4cacf36b2356'
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('blocked_users',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('address', sa.String(length=42), nullable=False),
sa.Column('created', sa.DateTime(), nullable=False),
sa.Column('updated', sa.DateTime(), nullable=False),
sa.PrimaryKeyConstraint('id', name=op.f('pk_blocked_users'))
)
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('blocked_users')
# ### end Alembic commands ###
1 change: 0 additions & 1 deletion api/scripts/sample_cli_request.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import requests


ASK_API_ENDPOINT = 'https://api.faucet.dev.gnosisdev.com/api/v1/cli/ask'
ACCESS_KEY_ID = '__ACCESS_KEY_ID__'
ACCESS_KEY_SECRET = '__ACCESS_KEY_SECRET__'
Expand Down
2 changes: 1 addition & 1 deletion api/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def _mock(self, env_variables=None):
]
if env_variables:
self.patchers.append(mock.patch.dict(os.environ, env_variables))

for p in self.patchers:
p.start()

Expand Down
25 changes: 24 additions & 1 deletion api/tests/test_api.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import unittest

from api.const import ZERO_ADDRESS
from api.services.database import Transaction
from api.services.database import BlockedUsers, Transaction

from .conftest import BaseTest, api_prefix
# from mock import patch
Expand Down Expand Up @@ -130,6 +130,29 @@ def test_ask_route_token_transaction(self):
self.assertEqual(response.get_json().get('transactionHash'),
transaction.hash)

def test_ask_route_blocked_users(self):
response = self.client.post(api_prefix + '/ask', json={
'captcha': CAPTCHA_TEST_RESPONSE_TOKEN,
'chainId': FAUCET_CHAIN_ID,
'amount': DEFAULT_ERC20_MAX_AMOUNT_PER_DAY,
'recipient': ZERO_ADDRESS,
'tokenAddress': ERC20_TOKEN_ADDRESS
})
self.assertEqual(response.status_code, 200)

# Add recipient to BlockedUsers
blocked_user = BlockedUsers(address=ZERO_ADDRESS)
blocked_user.save()

response = self.client.post(api_prefix + '/ask', json={
'captcha': CAPTCHA_TEST_RESPONSE_TOKEN,
'chainId': FAUCET_CHAIN_ID,
'amount': DEFAULT_ERC20_MAX_AMOUNT_PER_DAY,
'recipient': ZERO_ADDRESS,
'tokenAddress': ERC20_TOKEN_ADDRESS
})
self.assertEqual(response.status_code, 403)


if __name__ == '__main__':
unittest.main()

0 comments on commit 9936614

Please sign in to comment.