Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add feature to block user address #35

Merged
merged 5 commits into from
Apr 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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()
Loading