Moves functions around and handles insertion better

This commit is contained in:
Oleg Silkin 2019-07-22 08:15:54 -04:00
parent 16f7a6efa3
commit bbd106a80a
3 changed files with 77 additions and 52 deletions

View file

@ -1,20 +1,20 @@
# cython: language_level=3 # cython: language_level=3
import logging import logging
import time
import asyncio import asyncio
from aiohttp import web from aiohttp import web
from aiojobs.aiohttp import atomic from aiojobs.aiohttp import atomic
from asyncio import coroutine
from misc import clean_input_params from src.misc import clean_input_params
from src.database import get_claim_comments from src.database import get_claim_comments
from src.database import get_comments_by_id, get_comment_ids from src.database import get_comments_by_id, get_comment_ids
from src.database import get_channel_id_from_comment_id from src.database import get_channel_id_from_comment_id
from src.database import obtain_connection from src.database import obtain_connection
from src.database import delete_comment_by_id from src.misc import is_valid_base_comment
from src.writes import create_comment_or_error from src.misc import is_valid_credential_input
from src.misc import is_authentic_delete_signal
from src.misc import make_error from src.misc import make_error
from writes import delete_comment_if_authorized, write_comment
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -44,26 +44,12 @@ def handle_get_comments_by_id(app, kwargs):
return get_comments_by_id(conn, **kwargs) return get_comments_by_id(conn, **kwargs)
async def write_comment(app, comment):
return await coroutine(create_comment_or_error)(app['writer'], **comment)
async def delete_comment(app, comment_id):
return await coroutine(delete_comment_by_id)(app['writer'], comment_id)
async def handle_create_comment(app, params): async def handle_create_comment(app, params):
job = await app['comment_scheduler'].spawn(write_comment(app, params)) if is_valid_base_comment(**params) and is_valid_credential_input(**params):
return await job.wait() job = await app['comment_scheduler'].spawn(write_comment(app, params))
return await job.wait()
else:
async def delete_comment_if_authorized(app, comment_id, channel_name, channel_id, signature): raise ValueError('base comment is invalid')
authorized = await is_authentic_delete_signal(app, comment_id, channel_name, channel_id, signature)
if not authorized:
return {'deleted': False}
job = await app['comment_scheduler'].spawn(delete_comment(app, comment_id))
return {'deleted': await job.wait()}
async def handle_delete_comment(app, params): async def handle_delete_comment(app, params):

View file

@ -31,7 +31,7 @@ ERRORS = {
} }
def make_error(error, exc: Exception = None) -> dict: def make_error(error, exc=None) -> dict:
body = ERRORS[error] if error in ERRORS else ERRORS['INTERNAL'] body = ERRORS[error] if error in ERRORS else ERRORS['INTERNAL']
try: try:
if exc: if exc:
@ -42,18 +42,7 @@ def make_error(error, exc: Exception = None) -> dict:
return body return body
def channel_matches_pattern(channel_id: str, channel_name: str): async def resolve_channel_claim(app, channel_id, channel_name):
assert channel_id and channel_name
assert type(channel_id) is str and type(channel_name) is str
assert re.fullmatch(
'^@(?:(?![\x00-\x08\x0b\x0c\x0e-\x1f\x23-\x26'
'\x2f\x3a\x3d\x3f-\x40\uFFFE-\U0000FFFF]).){1,255}$',
channel_name
)
assert re.fullmatch('[a-z0-9]{40}', channel_id)
async def resolve_channel_claim(app: dict, channel_id: str, channel_name: str):
lbry_url = f'lbry://{channel_name}#{channel_id}' lbry_url = f'lbry://{channel_name}#{channel_id}'
resolve_body = { resolve_body = {
'method': 'resolve', 'method': 'resolve',
@ -73,6 +62,24 @@ async def resolve_channel_claim(app: dict, channel_id: str, channel_name: str):
raise ValueError('claim resolution yields error', {'error': resp['error']}) raise ValueError('claim resolution yields error', {'error': resp['error']})
def get_encoded_signature(signature):
signature = signature.encode() if type(signature) is str else signature
r = int(signature[:int(len(signature) / 2)], 16)
s = int(signature[int(len(signature) / 2):], 16)
return ecdsa.util.sigencode_der(r, s, len(signature) * 4)
def channel_matches_pattern_or_error(channel_id, channel_name):
assert channel_id and channel_name
assert re.fullmatch(
'^@(?:(?![\x00-\x08\x0b\x0c\x0e-\x1f\x23-\x26'
'\x2f\x3a\x3d\x3f-\x40\uFFFE-\U0000FFFF]).){1,255}$',
channel_name
)
assert re.fullmatch('([a-f0-9]|[A-F0-9]){40}', channel_id)
return True
def is_signature_valid(encoded_signature, signature_digest, public_key_bytes): def is_signature_valid(encoded_signature, signature_digest, public_key_bytes):
try: try:
public_key = load_der_public_key(public_key_bytes, default_backend()) public_key = load_der_public_key(public_key_bytes, default_backend())
@ -83,18 +90,29 @@ def is_signature_valid(encoded_signature, signature_digest, public_key_bytes):
return False return False
def get_encoded_signature(signature): def is_valid_base_comment(comment, claim_id, parent_id=None, **kwargs):
signature = signature.encode() if type(signature) is str else signature try:
r = int(signature[:int(len(signature) / 2)], 16) assert 0 < len(comment) <= 2000
s = int(signature[int(len(signature) / 2):], 16) assert (parent_id is None) or (0 < len(parent_id) <= 2000)
return ecdsa.util.sigencode_der(r, s, len(signature) * 4) assert re.fullmatch('[a-z0-9]{40}', claim_id)
except Exception:
def validate_base_comment(comment: str, claim_id: str, **kwargs): return False
assert 0 < len(comment) <= 2000 return True
assert re.fullmatch('[a-z0-9]{40}', claim_id)
async def is_authentic_delete_signal(app, comment_id: str, channel_name: str, channel_id: str, signature: str): def is_valid_credential_input(channel_id=None, channel_name=None, signature=None, signing_ts=None, **kwargs):
if channel_name or channel_name or signature or signing_ts:
try:
assert channel_matches_pattern_or_error(channel_id, channel_name)
if signature or signing_ts:
assert len(signature) == 128
assert signing_ts.isalnum()
except Exception:
return False
return True
async def is_authentic_delete_signal(app, comment_id, channel_name, channel_id, signature):
claim = await resolve_channel_claim(app, channel_id, channel_name) claim = await resolve_channel_claim(app, channel_id, channel_name)
if claim: if claim:
public_key = claim['value']['public_key'] public_key = claim['value']['public_key']

View file

@ -1,10 +1,15 @@
import logging import logging
import sqlite3 import sqlite3
from asyncio import coroutine
from database import delete_comment_by_id
from misc import is_authentic_delete_signal
from src.database import get_comment_or_none from src.database import get_comment_or_none
from src.database import insert_comment from src.database import insert_comment
from src.database import insert_channel from src.database import insert_channel
from src.misc import channel_matches_pattern from src.misc import channel_matches_pattern_or_error
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -12,7 +17,6 @@ logger = logging.getLogger(__name__)
def create_comment_or_error(conn, comment, claim_id, channel_id=None, channel_name=None, def create_comment_or_error(conn, comment, claim_id, channel_id=None, channel_name=None,
signature=None, signing_ts=None, parent_id=None) -> dict: signature=None, signing_ts=None, parent_id=None) -> dict:
if channel_id or channel_name or signature or signing_ts: if channel_id or channel_name or signature or signing_ts:
# validate_signature(signature, signing_ts, comment, channel_name, channel_id)
insert_channel_or_error(conn, channel_name, channel_id) insert_channel_or_error(conn, channel_name, channel_id)
comment_id = insert_comment( comment_id = insert_comment(
conn=conn, comment=comment, claim_id=claim_id, channel_id=channel_id, conn=conn, comment=comment, claim_id=claim_id, channel_id=channel_id,
@ -23,8 +27,25 @@ def create_comment_or_error(conn, comment, claim_id, channel_id=None, channel_na
def insert_channel_or_error(conn: sqlite3.Connection, channel_name: str, channel_id: str): def insert_channel_or_error(conn: sqlite3.Connection, channel_name: str, channel_id: str):
try: try:
channel_matches_pattern(channel_id, channel_name) channel_matches_pattern_or_error(channel_id, channel_name)
insert_channel(conn, channel_name, channel_id) insert_channel(conn, channel_name, channel_id)
except AssertionError as ae: except AssertionError:
logger.exception('Invalid channel values given: %s', ae) logger.exception('Invalid channel values given')
raise ValueError('Received invalid values for channel_id or channel_name') raise ValueError('Received invalid values for channel_id or channel_name')
async def delete_comment(app, comment_id):
return await coroutine(delete_comment_by_id)(app['writer'], comment_id)
async def delete_comment_if_authorized(app, comment_id, channel_name, channel_id, signature):
authorized = await is_authentic_delete_signal(app, comment_id, channel_name, channel_id, signature)
if not authorized:
return {'deleted': False}
job = await app['comment_scheduler'].spawn(delete_comment(app, comment_id))
return {'deleted': await job.wait()}
async def write_comment(app, comment):
return await coroutine(create_comment_or_error)(app['writer'], **comment)