diff --git a/base/izzylib/__init__.py b/base/izzylib/__init__.py index ae08e51..e440d40 100644 --- a/base/izzylib/__init__.py +++ b/base/izzylib/__init__.py @@ -17,7 +17,7 @@ izzylog = logging.logger['IzzyLib'] from .path import Path from .dotdict import DotDict, LowerDotDict, DefaultDotDict, MultiDotDict, JsonEncoder from .misc import * -from .cache import LruCache, TtlCache +from .cache import CacheDecorator, LruCache, TtlCache from .connection import Connection from .http_urllib_client import HttpUrllibClient, HttpUrllibResponse diff --git a/base/izzylib/cache.py b/base/izzylib/cache.py index 905711c..aec9ffe 100644 --- a/base/izzylib/cache.py +++ b/base/izzylib/cache.py @@ -1,8 +1,9 @@ -import re +import json, re from datetime import datetime from collections import OrderedDict -from functools import wraps +from functools import update_wrapper +from hashlib import sha1 from . import DotDict @@ -43,17 +44,24 @@ def parse_ttl(ttl): class BaseCache(OrderedDict): _get = OrderedDict.get + _items = OrderedDict.items def __init__(self, maxsize=1024, ttl=None): self.ttl = parse_ttl(ttl) self.maxsize = maxsize self.set = self.store + self.deco = lambda *args: CacheDecorator(self, *args) + def __str__(self): - data = ', '.join([f'{k}="{v["data"]}"' for k,v in self.items()]) + data = ', '.join([f'{k}="{v}"' for k,v in self.items()]) return f'BaseCache({data})' + def items(self): + return [[k, v.data] for k,v in self._items()] + + def get(self, key): while len(self) >= self.maxsize and self.maxsize > 0: self.popitem(last=False) @@ -109,24 +117,29 @@ class BaseCache(OrderedDict): self[key]['timestamp'] = timestamp + self.ttl self.move_to_end(key) - return self[key].data + return item.data - ## Was gonna use this for db stuff, but I need to plan it out better - def decorator(function, key, arg=0): - @wraps(function) - def wrapper(*args, **kwargs): - cached = self.fetch(key) +## This doesn't work for some reason +def CacheDecorator(cache): + def decorator(func): + def wrapper(cls, *args, **kwargs): + key = sha1(json.dumps(args).encode() + json.dumps(kwargs).encode()).hexdigest() + cached = cache.fetch(key) + print(cached) - if cached: + if cached != None: + print('Returning cached value:', cache) return cached - result = function(*args, **kwargs) + result = func(cls, *args, **kwargs) - self.store(key, args[arg] if type(arg) == int else kwargs[arg]) + cache.store(key, result) return result + return wrapper + return decorator class TtlCache(BaseCache): diff --git a/base/izzylib/misc.py b/base/izzylib/misc.py index c75dbb1..b4de428 100644 --- a/base/izzylib/misc.py +++ b/base/izzylib/misc.py @@ -95,7 +95,7 @@ def catch_kb_interrupt(function, *args, **kwargs): ''' try: - function(*args, **kwargs) + return function(*args, **kwargs) except KeyboardInterrupt: izzylog.verbose('Bye! UvU') diff --git a/hasher/izzylib/hasher/__init__.py b/hasher/izzylib/hasher/__init__.py new file mode 100644 index 0000000..114a4b9 --- /dev/null +++ b/hasher/izzylib/hasher/__init__.py @@ -0,0 +1 @@ +from .hasher import PasswordHasher diff --git a/http_server/izzylib/http_server/hasher.py b/hasher/izzylib/hasher/hasher.py similarity index 98% rename from http_server/izzylib/http_server/hasher.py rename to hasher/izzylib/hasher/hasher.py index a708dae..92ff922 100644 --- a/http_server/izzylib/http_server/hasher.py +++ b/hasher/izzylib/hasher/hasher.py @@ -54,7 +54,7 @@ class PasswordHasher: def hash(self, password: str): - return super().hash(password) + return self.hasher.hash(password) def verify(self, passhash: str, password: str): diff --git a/http_server/setup.py b/hasher/setup.py similarity index 86% rename from http_server/setup.py rename to hasher/setup.py index 02fdcb4..35ac1d7 100644 --- a/http_server/setup.py +++ b/hasher/setup.py @@ -3,18 +3,14 @@ from setuptools import setup, find_namespace_packages requires = [ - 'argon2-cffi==20.1.0', - 'pillow==8.2.0', - 'pycryptodome==3.10.1', - 'sanic==21.2.4', - 'Sanic-Cors==1.0.0' + 'argon2-cffi==20.1.0' ] setup( - name="IzzyLib HTTP Server", + name="IzzyLib Password Hasher", version='0.6.0', - packages=find_namespace_packages(include=['izzylib.http_server']), + packages=find_namespace_packages(include=['izzylib.hasher']), python_requires='>=3.7.0', install_requires=requires, include_package_data=False, diff --git a/http_server/izzylib/http_server/__init__.py b/http_server/izzylib/http_server/__init__.py deleted file mode 100644 index a501f67..0000000 --- a/http_server/izzylib/http_server/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from .hasher import PasswordHasher -from .server import HttpServer, HttpServerRequest, HttpServerResponse diff --git a/http_server/izzylib/http_server/server.py b/http_server/izzylib/http_server/server.py deleted file mode 100644 index 5e4ca53..0000000 --- a/http_server/izzylib/http_server/server.py +++ /dev/null @@ -1,331 +0,0 @@ -# probably gonna remove this since I'll be using my asgi framework -import multiprocessing, sanic, signal, traceback -import logging as pylog - -from jinja2.exceptions import TemplateNotFound -from multidict import CIMultiDict -from multiprocessing import cpu_count, current_process -from sanic.views import HTTPMethodView -from urllib.parse import parse_qsl, urlparse - -from . import http, izzylog -from .misc import DotDict, DefaultDict, LowerDotDict -from .template import Template - - -log_path_ignore = [ - '/media', - '/static' -] - -log_ext_ignore = [ - 'js', 'ttf', 'woff2', - 'ac3', 'aiff', 'flac', 'm4a', 'mp3', 'ogg', 'wav', 'wma', - 'apng', 'ico', 'jpeg', 'jpg', 'png', 'svg', - 'divx', 'mov', 'mp4', 'webm', 'wmv' -] - - -class HttpServer(sanic.Sanic): - def __init__(self, name='sanic', host='0.0.0.0', port='4080', **kwargs): - self.host = host - self.port = int(port) - self.workers = int(kwargs.get('workers', cpu_count())) - self.sig_handler = kwargs.get('sig_handler') - - super().__init__(name, request_class=kwargs.get('request_class', HttpServerRequest)) - - for log in ['sanic.root', 'sanic.access']: - pylog.getLogger(log).setLevel(pylog.ERROR) - - self.template = Template( - kwargs.get('tpl_search', []), - kwargs.get('tpl_globals', {}), - kwargs.get('tpl_context'), - kwargs.get('tpl_autoescape', True) - ) - - self.template.addEnv('app', self) - - self.error_handler.add(TemplateNotFound, NoTemplateError) - self.error_handler.add(Exception, kwargs.get('error_handler', GenericError)) - self.register_middleware(MiddlewareAccessLog, attach_to='response') - - signal.signal(signal.SIGHUP, self.finish) - signal.signal(signal.SIGINT, self.finish) - signal.signal(signal.SIGQUIT, self.finish) - signal.signal(signal.SIGTERM, self.finish) - - - ## Sanic spits out a warning, so this is the workaround to stop it - def __setattr__(self, key, value): - object.__setattr__(self, key, value) - - - def add_method_route(self, method, *routes): - for route in routes: - self.add_route(method.as_view(), route) - - - def add_method_routes(self, routes: list): - for route in routes: - self.add_method_route(*route) - - - def start(self): - options = { - 'host': self.host, - 'port': self.port, - 'workers': self.workers, - 'access_log': False, - 'debug': False - } - - msg = f'Starting {self.name} at {self.host}:{self.port}' - - if self.workers > 1: - msg += f' with {self.workers} workers' - - izzylog.info(msg) - self.run(**options) - - - def finish(self): - if self.sig_handler: - self.sig_handler() - - print('stopping.....') - self.stop() - izzylog.info('Bye! :3') - sys.exit() - - -class HttpServerRequest(sanic.request.Request): - def __init__(self, url_bytes, headers, version, method, transport, app): - super().__init__(url_bytes, headers, version, method, transport, app) - - self.Headers = HttpHeaders(headers) - self.Data = HttpData(self) - self.template = self.app.template - self.__setup_defaults() - self.__parse_path() - - #if self.paths.media: - #return - - self.__parse_signature() - self.Run() - - - def Run(self): - pass - - - def response(self, tpl, *args, **kwargs): - return self.template.response(self, tpl, *args, **kwargs) - - - def alldata(self): - return self.__combine_dicts(self.content.json, self.data.query, self.data.form) - - - def verify(self, actor=None): - self.ap.valid = http.VerifyHeaders(self.headers, self.method, self.path, actor, self.body) - return self.ap.valid - - - def __combine_dicts(self, *dicts): - data = DotDict() - - for item in dicts: - data.update(item) - - return data - - - def __setup_defaults(self): - self.paths = DotDict({'media': False, 'json': False, 'ap': False, 'cookie': False}) - self.ap = DotDict({'valid': False, 'signature': {}, 'actor': None, 'inbox': None, 'domain': None}) - - - def __parse_path(self): - self.paths.media = any(map(self.path.startswith, log_path_ignore)) or any(map(self.path.startswith, log_ext_ignore)) - self.paths.json = self.__json_check() - - - def __parse_signature(self): - sig = self.headers.getone('signature', None) - - if sig: - self.ap.signature = http.ParseSig(sig) - - if self.ap.signature: - self.ap.actor = self.ap.signature.get('keyid', '').split('#', 1)[0] - self.ap.domain = urlparse(self.ap.actor).netloc - - - def __json_check(self): - if self.path.endswith('.json'): - return True - - accept = self.headers.getone('Accept', None) - - if accept: - mimes = [v.strip() for v in accept.split(',')] - - if any(mime in ['application/json', 'application/activity+json'] for mime in mimes): - return True - - return False - - -class HttpServerResponse: - Text = sanic.response.text - Html = sanic.response.html - Json = sanic.response.json - Redir = sanic.response.redirect - - - def Css(*args, headers={}, **kwargs): - ReplaceHeader(headers, 'content-type', 'text/css') - return sanic.response.text(*args, headers=headers, **kwargs) - - - def Js(*args, headers={}, **kwargs): - ReplaceHeader(headers, 'content-type', 'application/javascript') - return sanic.response.text(*args, headers=headers, **kwargs) - - - def Ap(*args, headers={}, **kwargs): - ReplaceHeader(headers, 'content-type', 'application/activity+json') - return sanic.response.json(*args, headers=headers, **kwargs) - - - def Jrd(*args, headers={}, **kwargs): - ReplaceHeader(headers, 'content-type', 'application/jrd+json') - return sanic.response.json(*args, headers=headers, **kwargs) - - -class HttpHeaders(LowerDotDict): - def __init__(self, headers): - super().__init__() - - for k,v in headers.items(): - if not self.get(k): - self[k] = [] - - self[k].append(v) - - - def getone(self, key, default=None): - value = self.get(key) - - if not value: - return default - - return value[0] - - - def getall(self, key, default=[]): - return self.get(key.lower(), default) - - -class HttpData(object): - def __init__(self, request): - self.request = request - - - @property - def combined(self): - return DotDict(**self.form.asDict(), **self.query.asDict(), **self.json.asDict()) - - - @property - def query(self): - data = {k: v for k,v in parse_qsl(self.request.query_string)} - return DotDict(data) - - - @property - def form(self): - data = {k: v[0] for k,v in self.request.form.items()} - return DotDict(data) - - - @property - def files(self): - return DotDict({k:v[0] for k,v in self.request.files.items()}) - - - ### body functions - @property - def raw(self): - try: - return self.request.body - except Exception as e: - izzylog.verbose('IzzyLib.http_server.Data.raw: failed to get body') - izzylog.debug(f'{e.__class__.__name__}: {e}') - return b'' - - - @property - def text(self): - try: - return self.raw.decode() - except Exception as e: - izzylog.verbose('IzzyLib.http_server.Data.text: failed to get body') - izzylog.debug(f'{e.__class__.__name__}: {e}') - return '' - - - @property - def json(self): - try: - return DotDict(self.text) - except Exception as e: - izzylog.verbose('IzzyLib.http_server.Data.json: failed to get body') - izzylog.debug(f'{e.__class__.__name__}: {e}') - data = '{}' - return {} - - -async def MiddlewareAccessLog(request, response): - if request.paths.media: - return - - uagent = request.headers.get('user-agent') - address = request.headers.get('x-real-ip', request.forwarded.get('for', request.remote_addr)) - - izzylog.info(f'({multiprocessing.current_process().name}) {address} {request.method} {request.path} {response.status} "{uagent}"') - - -def GenericError(request, exception): - try: - status = exception.status_code - except: - status = 500 - - if status not in range(200, 499): - traceback.print_exc() - - msg = f'{exception.__class__.__name__}: {str(exception)}' - - if request.paths.json: - return sanic.response.json({'error': {'status': status, 'message': msg}}) - - try: - return request.response('server_error.haml', status=status, context={'status': str(status), 'error': msg}) - - except TemplateNotFound: - return sanic.response.text(f'Error {status}: {msg}') - - -def NoTemplateError(request, exception): - izzylog.error('TEMPLATE_ERROR:', f'{exception.__class__.__name__}: {str(exception)}') - return sanic.response.html('I\'m a dumbass and forgot to create a template for this page', 500) - - -def ReplaceHeader(headers, key, value): - for k,v in headers.items(): - if k.lower() == header.lower(): - del headers[k] diff --git a/requests_client/izzylib/http_requests_client/__init__.py b/requests_client/izzylib/http_requests_client/__init__.py index 902e7b5..e9bd444 100644 --- a/requests_client/izzylib/http_requests_client/__init__.py +++ b/requests_client/izzylib/http_requests_client/__init__.py @@ -7,8 +7,9 @@ from .client import ( verify_headers, parse_signature, fetch_actor, - fetch_webfinger_account, + fetch_instance, fetch_nodeinfo, + fetch_webfinger_account, set_requests_client, generate_rsa_key ) @@ -26,8 +27,9 @@ __all__ = [ 'HttpRequestsResponse', 'SigningError', 'fetch_actor', - 'fetch_webfinger_account', + 'fetch_instance', 'fetch_nodeinfo', + 'fetch_webfinger_account', 'generate_rsa_key', 'parse_signature', 'set_requests_client', diff --git a/requests_client/izzylib/http_requests_client/client.py b/requests_client/izzylib/http_requests_client/client.py index 68ee52a..6316648 100644 --- a/requests_client/izzylib/http_requests_client/client.py +++ b/requests_client/izzylib/http_requests_client/client.py @@ -46,10 +46,6 @@ class HttpRequestsClient(object): def __sign_request(self, request, privkey, keyid): - if not crypto_enabled: - izzylog.error('Crypto functions disabled') - return - request.add_header('(request-target)', f'{request.method.lower()} {request.path}') request.add_header('host', request.host) request.add_header('date', datetime.utcnow().strftime('%a, %d %b %Y %H:%M:%S GMT')) @@ -63,7 +59,7 @@ class HttpRequestsClient(object): 'keyId': keyid, 'algorithm': 'rsa-sha256', 'headers': ' '.join([k.lower() for k in request.headers.keys()]), - 'signature': b64encode(PkcsHeaders(privkey, request.headers)).decode('UTF-8') + 'signature': b64encode(sign_pkcs_headers(privkey, request.headers)).decode('UTF-8') } sig_items = [f'{k}="{v}"' for k,v in sig.items()] @@ -83,6 +79,12 @@ class HttpRequestsClient(object): return HttpRequestsResponse(request.send()) + def signed_request(self, privkey, keyid, *args, **kwargs): + request = HttpRequestsRequest(self, *args, **kwargs) + self.__sign_request(request, privkey, keyid) + return HttpRequestsResponse(request.send()) + + def download(self, url, filepath, *args, filename=None, **kwargs): resp = self.request(url, *args, **kwargs) @@ -130,18 +132,15 @@ class HttpRequestsClient(object): return self.request(*args, headers=headers, **kwargs) - def signed_request(self, privkey, keyid, *args, **kwargs): - request = HttpRequestsRequest(self, *args, **kwargs) - self.__sign_request(request, privkey, keyid) - return HttpRequestsResponse(request.send()) - - class HttpRequestsRequest(object): def __init__(self, client, url, data=None, headers={}, query={}, method='get'): + parsed = urlparse(url) self.args = [url] - self.kwargs = {'params': query} + self.kwargs = DotDict({'params': query}) self.method = method.lower() self.client = client + self.path = parsed.path + self.host = parsed.netloc new_headers = client.headers.copy() new_headers.update(headers) @@ -151,11 +150,34 @@ class HttpRequestsRequest(object): if not parsed_headers.get('user-agent'): parsed_headers['user-agent'] = client.agent - self.kwargs['headers'] = new_headers + self.kwargs['headers'] = DotDict(new_headers) self.kwargs['data'] = data if client.proxy.enabled: - self.kwargs['proxies'] = {self.proxy.ptype: f'{self.proxy.ptype}://{self.proxy.host}:{self.proxy.port}'} + self.kwargs['proxies'] = DotDict({self.proxy.ptype: f'{self.proxy.ptype}://{self.proxy.host}:{self.proxy.port}'}) + + + @property + def body(self): + return self.kwargs.data + + + @body.setter + def body(self, data): + self.kwargs.data = data + + + @property + def headers(self): + return self.kwargs.headers + + + def add_header(self, key, value): + self.kwargs.headers[key] = value + + + def remove_header(self, key): + self.kwargs.headers.pop(key, None) def send(self): @@ -191,7 +213,11 @@ class HttpRequestsResponse(object): @cached_property def json(self): - return DotDict(self.body) + try: + return DotDict(self.text) + + except: + return DotDict(self.body) @cached_property @@ -371,6 +397,25 @@ def fetch_actor(url): return actor +@lru_cache(maxsize=512) +def fetch_instance(domain): + if not Client: + raise ValueError('Please set global client with "SetRequestsClient(client)"') + + headers = {'Accept': 'application/json'} + resp = Client.request(f'https://{domain}/api/v1/instance', headers=headers) + + try: + return resp.json + + except json.decoder.JSONDecodeError: + return + + except Exception as e: + izzylog.debug(f'HTTP {resp.status}: {resp.body}') + raise e from None + + @lru_cache(maxsize=512) def fetch_webfinger_account(handle, domain): if not Client: diff --git a/sql/izzylib/sql/generic.py b/sql/izzylib/sql/generic.py index 7255a50..b794232 100644 --- a/sql/izzylib/sql/generic.py +++ b/sql/izzylib/sql/generic.py @@ -220,7 +220,7 @@ class SqlSession(object): return self.fetch(*args, single=False, **kwargs) - def insert(self, table_name, **kwargs): + def insert(self, table_name, return_row=False, **kwargs): row = self.fetch(table_name, **kwargs) if row: @@ -232,11 +232,13 @@ class SqlSession(object): if getattr(table, 'timestamp', None) and not kwargs.get('timestamp'): kwargs['timestamp'] = datetime.now() - res = self.execute(table.insert().values(**kwargs)) - #return self.fetch(table_name, **kwargs) + self.execute(table.insert().values(**kwargs)) + + if return_row: + return self.fetch(table_name, **kwargs) - def update(self, table=None, rowid=None, row=None, **data): + def update(self, table=None, rowid=None, row=None, return_row=False, **data): if row: rowid = row.id table = row._table_name @@ -247,6 +249,9 @@ class SqlSession(object): tclass = self.table[table] self.execute(tclass.update().where(tclass.c.id == rowid).values(**data)) + if return_row: + return self.fetch(table, id=rowid) + def remove(self, table=None, rowid=None, row=None): if row: