PM API key
parent
39c5e5c423
commit
40aa422656
|
@ -0,0 +1,7 @@
|
|||
POSTGRES_URI="postgresql://postgres:ChangeMe@postgres.example.tld/overseer"
|
||||
USE_SQLITE=0
|
||||
OVERSEER_LEMMY_DOMAIN="overctrl.example.tld"
|
||||
OVERSEER_LEMMY_USERNAME="overseer"
|
||||
OVERSEER_LEMMY_PASSWORD="LemmyPassword"
|
||||
ADMIN_API_KEY="Password"
|
||||
secret_key="VerySecretKey"
|
|
@ -29,7 +29,7 @@ class Models:
|
|||
'endorsements': fields.Integer(description="The amount of endorsements this instance has received"),
|
||||
'guarantor': fields.String(description="The domain of the instance which guaranteed this instance."),
|
||||
})
|
||||
self.response_model_model_Instances_get = api.model('Instances', {
|
||||
self.response_model_model_Whitelist_get = api.model('Instances', {
|
||||
'instances': fields.List(fields.Nested(self.response_model_instances)),
|
||||
'domains': fields.List(fields.String(description="The instance domains as a list.")),
|
||||
'csv': fields.String(description="The instance domains as a csv."),
|
||||
|
|
|
@ -1,10 +1,15 @@
|
|||
import os
|
||||
from flask import request
|
||||
from flask_restx import Namespace, Resource, reqparse
|
||||
from overseer.flask import cache
|
||||
from overseer.flask import cache, db
|
||||
from overseer.observer import retrieve_suspicious_instances
|
||||
from loguru import logger
|
||||
from overseer.classes.instance import Instance
|
||||
from overseer.database import functions as database
|
||||
from overseer import exceptions as e
|
||||
from overseer.utils import hash_api_key
|
||||
from overseer.lemmy import pm_new_api_key
|
||||
from pythorhead import Lemmy
|
||||
|
||||
api = Namespace('v1', 'API Version 1' )
|
||||
|
||||
|
@ -12,6 +17,9 @@ from overseer.apis.models.v1 import Models
|
|||
|
||||
models = Models(api)
|
||||
|
||||
handle_bad_request = api.errorhandler(e.BadRequest)(e.handle_bad_requests)
|
||||
handle_forbidden = api.errorhandler(e.Forbidden)(e.handle_bad_requests)
|
||||
|
||||
# Used to for the flask limiter, to limit requests per url paths
|
||||
def get_request_path():
|
||||
# logger.info(dir(request))
|
||||
|
@ -50,21 +58,69 @@ class Whitelist(Resource):
|
|||
get_parser.add_argument("domains", required=False, type=bool, help="Set to true to return just the domains as a list. Mutually exclusive with csv", location="args")
|
||||
|
||||
@api.expect(get_parser)
|
||||
@logger.catch(reraise=True)
|
||||
@cache.cached(timeout=10, query_string=True)
|
||||
@api.marshal_with(models.response_model_model_Instances_get, code=200, description='Instances', skip_none=True)
|
||||
@api.marshal_with(models.response_model_model_Whitelist_get, code=200, description='Instances', skip_none=True)
|
||||
def get(self):
|
||||
'''A List with the details of all instances and their endorsements
|
||||
'''
|
||||
self.args = self.get_parser.parse_args()
|
||||
instance_details = []
|
||||
for instance in database.get_all_instances():
|
||||
logger.debug(instance)
|
||||
instance_details.append(instance.get_details())
|
||||
if self.args.csv:
|
||||
return {"csv": ",".join([instance["domain"] for instance in instance_details])},200
|
||||
if self.args.domains:
|
||||
return {"domains": [instance["domain"] for instance in instance_details]},200
|
||||
logger.debug(instance_details)
|
||||
return {"instances": instance_details},200
|
||||
|
||||
|
||||
put_parser = reqparse.RequestParser()
|
||||
put_parser.add_argument("Client-Agent", default="unknown:0:unknown", type=str, required=False, help="The client name and version.", location="headers")
|
||||
put_parser.add_argument("domain", required=False, type=str, help="The instance domain. It MUST be alredy registered in https://overctrl.dbzer0.com", location="json")
|
||||
put_parser.add_argument("guarantor", required=False, type=str, help="(Optiona) The domain of the guaranteeing instance. They will receive a PM to validate you", location="json")
|
||||
|
||||
|
||||
@api.expect(put_parser)
|
||||
@api.marshal_with(models.response_model_instances, code=200, description='Instances')
|
||||
def put(self):
|
||||
'''A List with the details of all instances and their endorsements
|
||||
'''
|
||||
self.args = self.put_parser.parse_args()
|
||||
existing_instance = Instance.query.filter_by(domain=self.args.domain).first()
|
||||
if existing_instance:
|
||||
return existing_instance.get_details(),200
|
||||
requested_lemmy = Lemmy(f"https://{self.args.domain}")
|
||||
site = requested_lemmy.site.get()
|
||||
api_key = pm_new_api_key(self.args.domain)
|
||||
if not api_key:
|
||||
raise e.BadRequest("Failed to generate API Key")
|
||||
new_instance = Instance(
|
||||
domain=self.args.domain,
|
||||
api_key=hash_api_key(api_key),
|
||||
open_registrations=site["site_view"]["local_site"]["registration_mode"] == "open",
|
||||
email_verify=site["site_view"]["local_site"]["require_email_verification"],
|
||||
software=requested_lemmy.nodeinfo['software']['name'],
|
||||
)
|
||||
new_instance.create()
|
||||
return new_instance.get_details(),200
|
||||
|
||||
patch_parser = reqparse.RequestParser()
|
||||
patch_parser.add_argument("Authorization: apikey", type=str, required=True, help="The sending instance's API key.", location='headers')
|
||||
patch_parser.add_argument("Client-Agent", default="unknown:0:unknown", type=str, required=False, help="The client name and version.", location="headers")
|
||||
patch_parser.add_argument("domain", required=False, type=str, help="The instance domain. It MUST be alredy registered in https://overctrl.dbzer0.com", location="json")
|
||||
patch_parser.add_argument("guarantor", required=False, type=str, help="(Optiona) The domain of the guaranteeing instance. They will receive a PM to validate you", location="json")
|
||||
|
||||
|
||||
@api.expect(put_parser)
|
||||
@logger.catch(reraise=True)
|
||||
@api.marshal_with(models.response_model_instances, code=200, description='Instances', skip_none=True)
|
||||
def patch(self):
|
||||
'''A List with the details of all instances and their endorsements
|
||||
'''
|
||||
self.args = self.patch_parser.parse_args()
|
||||
self.apikey = self.args["Authorization: apikey"]
|
||||
if not self.apikey:
|
||||
raise e.BadRequest("You must provide the API key that was PM'd to your overctrl.dbzer0.com account")
|
||||
existing_instance = Instance.query.filter_by(domain=self.args.domain).first()
|
||||
if existing_instance:
|
||||
return existing_instance.get_details,200
|
||||
|
|
|
@ -12,15 +12,16 @@ with OVERSEER.app_context():
|
|||
|
||||
db.create_all()
|
||||
|
||||
admin_domain = os.getenv("ADMIN_DOMAIN")
|
||||
admin_domain = os.getenv("OVERSEER_LEMMY_DOMAIN")
|
||||
admin = db.session.query(Instance).filter_by(domain=admin_domain).first()
|
||||
if not admin:
|
||||
admin = Instance(
|
||||
id=0,
|
||||
domain=admin_domain,
|
||||
api_key=hash_api_key(os.getenv("ADMIN_PASSWORD")),
|
||||
api_key=hash_api_key(os.getenv("ADMIN_API_KEY")),
|
||||
open_registrations=False,
|
||||
email_verify=False,
|
||||
software="lemmy",
|
||||
)
|
||||
admin.create()
|
||||
guarantee = Guarantee(
|
||||
|
|
|
@ -35,13 +35,14 @@ class Instance(db.Model):
|
|||
__tablename__ = "instances"
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
domain = db.Column(db.String(255), unique=True, nullable=False)
|
||||
domain = db.Column(db.String(255), unique=True, nullable=False, index=True)
|
||||
api_key = db.Column(db.String(100), unique=True, nullable=False, index=True)
|
||||
created = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
|
||||
updated = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
|
||||
|
||||
open_registrations = db.Column(db.Boolean, unique=False, nullable=False, index=True)
|
||||
email_verify = db.Column(db.Boolean, unique=False, nullable=False, index=True)
|
||||
software = db.Column(db.String(50), unique=False, nullable=False, index=True)
|
||||
|
||||
approvals = db.relationship("Endorsement", back_populates="approving_instance", cascade="all, delete-orphan", foreign_keys=[Endorsement.approving_id])
|
||||
endorsements = db.relationship("Endorsement", back_populates="endorsed_instance", cascade="all, delete-orphan", foreign_keys=[Endorsement.endorsed_id])
|
||||
|
@ -53,16 +54,19 @@ class Instance(db.Model):
|
|||
db.session.commit()
|
||||
|
||||
def get_details(self):
|
||||
guarantor = self.get_guarantor()
|
||||
ret_dict = {
|
||||
"domain": self.domain,
|
||||
"open_registrations": self.open_registrations,
|
||||
"email_verify": self.email_verify,
|
||||
"endorsements": len(self.endorsements),
|
||||
"approvals": len(self.approvals),
|
||||
"guarantor": self.get_guarantor().domain,
|
||||
"guarantor": guarantor.domain if guarantor else None,
|
||||
}
|
||||
return ret_dict
|
||||
|
||||
def get_guarantor(self):
|
||||
if len(self.guarantors) == 0:
|
||||
return None
|
||||
guarantee = self.guarantors[0]
|
||||
return Instance.query.filter_by(id=guarantee.guarantor_id).first()
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
from werkzeug import exceptions as wze
|
||||
from loguru import logger
|
||||
|
||||
class BadRequest(wze.BadRequest):
|
||||
def __init__(self, message, log=None):
|
||||
self.specific = message
|
||||
self.log = log
|
||||
|
||||
class Forbidden(wze.Forbidden):
|
||||
def __init__(self, message, log=None):
|
||||
self.specific = message
|
||||
self.log = log
|
||||
|
||||
class Locked(wze.Locked):
|
||||
def __init__(self, message, log=None):
|
||||
self.specific = message
|
||||
self.log = log
|
||||
|
||||
def handle_bad_requests(error):
|
||||
'''Namespace error handler'''
|
||||
if error.log:
|
||||
logger.warning(error.log)
|
||||
return({'message': error.specific}, error.code)
|
|
@ -0,0 +1,24 @@
|
|||
from pythorhead import Lemmy
|
||||
from loguru import logger
|
||||
import os
|
||||
import secrets
|
||||
import overseer.exceptions as e
|
||||
|
||||
overctrl_lemmy = Lemmy(f"https://{os.getenv('OVERSEER_LEMMY_DOMAIN')}")
|
||||
_login = overctrl_lemmy.log_in(os.getenv('OVERSEER_LEMMY_USERNAME'),os.getenv('OVERSEER_LEMMY_PASSWORD'))
|
||||
if not _login:
|
||||
raise Exception("Failed to login to overctrl")
|
||||
overseer_lemmy_user = overctrl_lemmy.user.get(username=os.getenv('OVERSEER_LEMMY_USERNAME'))
|
||||
|
||||
def pm_new_api_key(domain: str):
|
||||
api_key = secrets.token_urlsafe(16)
|
||||
pm_content = f"The API Key for domain {domain} is {api_key}.\n\nUse this to perform operations on the overseer."
|
||||
domain_username = domain.replace(".", "_")
|
||||
domain_user = overctrl_lemmy.user.get(username=domain_username)
|
||||
if not domain_user:
|
||||
raise e.BadRequest(f"Could not find domain user '{domain_username}'")
|
||||
pm = overctrl_lemmy.private_message(pm_content,domain_user["person_view"]["person"]["id"])
|
||||
if not pm:
|
||||
raise e.BadRequest("API Key PM failed")
|
||||
return api_key
|
||||
|
|
@ -6,13 +6,11 @@ Flask-Caching
|
|||
waitress~=2.1.2
|
||||
requests >= 2.27
|
||||
Markdown~=3.4.1
|
||||
flask-dance[sqla]
|
||||
blinker
|
||||
python-dotenv
|
||||
loguru
|
||||
python-dateutil~=2.8.2
|
||||
redis~=4.3.5
|
||||
flask_sqlalchemy==3.0.2
|
||||
SQLAlchemy~=1.4.44
|
||||
psycopg2-binary
|
||||
regex
|
||||
pythorhead>=0.6.0
|
Loading…
Reference in New Issue