diff options
Diffstat (limited to 'app/func')
-rw-r--r-- | app/func/delete_link.py | 20 | ||||
-rw-r--r-- | app/func/link_records.py | 24 | ||||
-rw-r--r-- | app/func/log.py | 74 | ||||
-rw-r--r-- | app/func/newlink.py | 38 | ||||
-rw-r--r-- | app/func/renew_link.py | 22 | ||||
-rw-r--r-- | app/func/signup.py | 24 |
6 files changed, 202 insertions, 0 deletions
diff --git a/app/func/delete_link.py b/app/func/delete_link.py new file mode 100644 index 0000000..c036af3 --- /dev/null +++ b/app/func/delete_link.py @@ -0,0 +1,20 @@ +import sqlalchemy + +from db import engine + +""" +Delete the specified link from the users associated links +""" +def delete_link(link, owner): + with engine.begin() as conn: + try: + link_owner = conn.execute(sqlalchemy.text('SELECT owner FROM links WHERE link = :link'), [{'link': link}]).fetchone()[0] + except TypeError: + return 'Link does not exist', 200 + + if owner == link_owner: + with engine.begin() as conn: + conn.execute(sqlalchemy.text('DELETE FROM links WHERE link = :link'), [{'link': link}]) + return 'Link has been deleted', 200 + else: + return 'You are not the owner of this link', 401
\ No newline at end of file diff --git a/app/func/link_records.py b/app/func/link_records.py new file mode 100644 index 0000000..a29f8dd --- /dev/null +++ b/app/func/link_records.py @@ -0,0 +1,24 @@ +import sqlalchemy +import tabulate + +from db import engine + +""" +Retrieve all records associated with a specific link +""" +def link_records(link, owner): + with engine.begin() as conn: + try: + link_owner = conn.execute(sqlalchemy.text('SELECT owner FROM links WHERE link = :link'), [{'link': link}]).fetchone()[0] + except TypeError: + return 'Link does not exist', 200 + + if owner == link_owner: + with engine.begin() as conn: + records = conn.execute(sqlalchemy.text('SELECT timestamp, ip, location, browser, os, user_agent, isp FROM records WHERE owner = :owner and link = :link'), [{'owner': owner, 'link': link}]).fetchall() + if not records: + return 'No records are associated with this link', 200 + else: + return 'You are not the owner of this link', 401 + + return tabulate.tabulate(records, headers=['Timestamp', 'IP', 'Location', 'Browser', 'OS', 'User Agent', 'ISP']), 200
\ No newline at end of file diff --git a/app/func/log.py b/app/func/log.py new file mode 100644 index 0000000..0a7dddc --- /dev/null +++ b/app/func/log.py @@ -0,0 +1,74 @@ +import ip2locationio +import sqlalchemy +import datetime +import validators +from ua_parser import user_agent_parser +from dotenv import load_dotenv +import os +from ip2locationio.ipgeolocation import IP2LocationIOAPIError + +from db import engine + +load_dotenv() +try: + ip_to_location = os.getenv('IP_TO_LOCATION').upper().replace('"', '') + if ip_to_location == 'TRUE': + api_key = os.getenv('API_KEY').replace('"', '') + else: + api_key = "NO_API_KEY" + + base_url = os.getenv('BASE_URL').replace('"', '') +# .env File does not exist - likely a docker run +except AttributeError: + ip_to_location = str(os.environ['IP_TO_LOCATION']).upper().replace('"', '') + if ip_to_location == 'TRUE': + api_key = str(os.environ('API_KEY')).replace('"', '') + else: + api_key = "NO_API_KEY" + + base_url = str(os.environ('BASE_URL')).replace('"', '') + +if not validators.url(base_url): + print(base_url) + print('BASE_URL varaible is malformed.') + exit() + +configuration = ip2locationio.Configuration(api_key) +ipgeolocation = ip2locationio.IPGeolocation(configuration) + +""" +Create a new log record whenever a link is visited +""" +def log(link, request): + with engine.begin() as conn: + try: + redirect_link, owner = conn.execute(sqlalchemy.text('SELECT redirect_link, owner FROM links WHERE link = :link'), [{'link': link}]).fetchone() + except TypeError: + return base_url + + with engine.begin() as conn: + if ip_to_location == 'TRUE': + # Get IP to GEO via IP2Location.io + try: + data = ipgeolocation.lookup(request.remote_addr) + location = f'{data["country_name"]}, {data["city_name"]}' + isp = data['as'] + # Fatal error, API key is invalid or out of requests, quit + except IP2LocationIOAPIError: + print('Invalid API key or insifficient credit. Change .env file if you do not need IP to location feature.') + location = '-, -' + isp = '-' + else: + location = '-, -' + isp = '-' + + timestamp = datetime.datetime.now() + ip = request.remote_addr + user_agent = request.user_agent.string + ua_string = user_agent_parser.Parse(user_agent) + browser = ua_string['user_agent']['family'] + os = f'{ua_string["os"]["family"]} {ua_string["os"]["major"]}' + + conn.execute(sqlalchemy.text('INSERT INTO records (owner, link, timestamp, ip, location, browser, os, user_agent, isp) VALUES (:owner, :link, :timestamp, :ip, :location, :browser, :os, :user_agent, :isp)'), [{'owner': owner, 'link': link, 'timestamp': timestamp, 'ip': ip, 'location': location, 'browser': browser, 'os': os, 'user_agent': user_agent, 'isp': isp}]) + + return redirect_link
\ No newline at end of file diff --git a/app/func/newlink.py b/app/func/newlink.py new file mode 100644 index 0000000..6fa1340 --- /dev/null +++ b/app/func/newlink.py @@ -0,0 +1,38 @@ +import validators +import random +import string +import datetime +import sqlalchemy +from sqlalchemy import exc + +from db import engine + +""" +Generate and return a new randomized link that is connected to the user +Links are composed of 5 uppercase ASCII characters + numbers +""" +def generate_link(request, owner): + content_type = request.headers.get('Content-Type') + if content_type == 'application/json': + try: + redirect_link = request.json['redirect_link'] + except KeyError: + return 'Redirect link not provided', 400 + + if not validators.url(redirect_link): + return 'Redirect link is malformed. Please try again', 400 + else: + return 'Content-Type not supported', 400 + + with engine.begin() as conn: + choices = string.ascii_uppercase + '1234567890' + while True: + try: + link = ''.join(random.choices(choices, k=5)) + conn.execute(sqlalchemy.text('INSERT INTO links(owner, link, redirect_link, expire_date) VALUES (:owner, :link, :redirect_link, :expire_date)'), [{'owner': owner, 'link': link, 'redirect_link': redirect_link, 'expire_date': (datetime.datetime.now() + datetime.timedelta(days=7)).strftime('%d/%m/%Y')}]) + conn.commit() + break + except exc.IntegrityError: + continue + + return link, 200 diff --git a/app/func/renew_link.py b/app/func/renew_link.py new file mode 100644 index 0000000..f0fc166 --- /dev/null +++ b/app/func/renew_link.py @@ -0,0 +1,22 @@ +import sqlalchemy +import datetime + +from db import engine + +""" +Renew a specified link so that the user can continue logging through that URL +Adds 7 days from the current date +""" +def renew_link(link, owner): + with engine.begin() as conn: + try: + link_owner = conn.execute(sqlalchemy.text('SELECT owner FROM links WHERE link = :link'), [{'link': link}]).fetchone()[0] + except TypeError: + return 'Link does not exist', 200 + + if owner == link_owner: + with engine.begin() as conn: + conn.execute(sqlalchemy.text('UPDATE links SET expire_date = :expire_date WHERE link = :link'), [{'expire_date': (datetime.datetime.now() + datetime.timedelta(days=7)).strftime('%d/%m/%Y'), 'link': link}]) + return f'Link renewed, now expires on {(datetime.datetime.now() + datetime.timedelta(days=7)).strftime("%d/%m/%Y")}', 200 + else: + return 'You are not the owner of this link', 401
\ No newline at end of file diff --git a/app/func/signup.py b/app/func/signup.py new file mode 100644 index 0000000..f7c19a2 --- /dev/null +++ b/app/func/signup.py @@ -0,0 +1,24 @@ +import sqlalchemy +from sqlalchemy import exc +import random +import string + +from db import engine + +""" +Generate and return a randomized account string for the user +Account strings function as API authenticaton keys and are composed +of 20 uppercase ASCII characters +""" +def generate_account(): + with engine.begin() as conn: + while True: + try: + account_string = ''.join(random.choices(string.ascii_uppercase, k=20)) + conn.execute(sqlalchemy.text('INSERT INTO accounts(account_name) VALUES(:account_name)'), [{'account_name': account_string}]) + conn.commit() + break + except exc.IntegrityError: + continue + + return account_string
\ No newline at end of file |