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"),
|
'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."),
|
'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)),
|
'instances': fields.List(fields.Nested(self.response_model_instances)),
|
||||||
'domains': fields.List(fields.String(description="The instance domains as a list.")),
|
'domains': fields.List(fields.String(description="The instance domains as a list.")),
|
||||||
'csv': fields.String(description="The instance domains as a csv."),
|
'csv': fields.String(description="The instance domains as a csv."),
|
||||||
|
|
|
@ -1,10 +1,15 @@
|
||||||
|
import os
|
||||||
from flask import request
|
from flask import request
|
||||||
from flask_restx import Namespace, Resource, reqparse
|
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 overseer.observer import retrieve_suspicious_instances
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from overseer.classes.instance import Instance
|
from overseer.classes.instance import Instance
|
||||||
from overseer.database import functions as database
|
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' )
|
api = Namespace('v1', 'API Version 1' )
|
||||||
|
|
||||||
|
@ -12,6 +17,9 @@ from overseer.apis.models.v1 import Models
|
||||||
|
|
||||||
models = Models(api)
|
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
|
# Used to for the flask limiter, to limit requests per url paths
|
||||||
def get_request_path():
|
def get_request_path():
|
||||||
# logger.info(dir(request))
|
# 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")
|
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)
|
@api.expect(get_parser)
|
||||||
@logger.catch(reraise=True)
|
|
||||||
@cache.cached(timeout=10, query_string=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):
|
def get(self):
|
||||||
'''A List with the details of all instances and their endorsements
|
'''A List with the details of all instances and their endorsements
|
||||||
'''
|
'''
|
||||||
self.args = self.get_parser.parse_args()
|
self.args = self.get_parser.parse_args()
|
||||||
instance_details = []
|
instance_details = []
|
||||||
for instance in database.get_all_instances():
|
for instance in database.get_all_instances():
|
||||||
logger.debug(instance)
|
|
||||||
instance_details.append(instance.get_details())
|
instance_details.append(instance.get_details())
|
||||||
if self.args.csv:
|
if self.args.csv:
|
||||||
return {"csv": ",".join([instance["domain"] for instance in instance_details])},200
|
return {"csv": ",".join([instance["domain"] for instance in instance_details])},200
|
||||||
if self.args.domains:
|
if self.args.domains:
|
||||||
return {"domains": [instance["domain"] for instance in instance_details]},200
|
return {"domains": [instance["domain"] for instance in instance_details]},200
|
||||||
logger.debug(instance_details)
|
|
||||||
return {"instances": instance_details},200
|
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()
|
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()
|
admin = db.session.query(Instance).filter_by(domain=admin_domain).first()
|
||||||
if not admin:
|
if not admin:
|
||||||
admin = Instance(
|
admin = Instance(
|
||||||
id=0,
|
id=0,
|
||||||
domain=admin_domain,
|
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,
|
open_registrations=False,
|
||||||
email_verify=False,
|
email_verify=False,
|
||||||
|
software="lemmy",
|
||||||
)
|
)
|
||||||
admin.create()
|
admin.create()
|
||||||
guarantee = Guarantee(
|
guarantee = Guarantee(
|
||||||
|
|
|
@ -35,13 +35,14 @@ class Instance(db.Model):
|
||||||
__tablename__ = "instances"
|
__tablename__ = "instances"
|
||||||
|
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
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)
|
api_key = db.Column(db.String(100), unique=True, nullable=False, index=True)
|
||||||
created = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
|
created = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
|
||||||
updated = 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)
|
open_registrations = db.Column(db.Boolean, unique=False, nullable=False, index=True)
|
||||||
email_verify = 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])
|
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])
|
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()
|
db.session.commit()
|
||||||
|
|
||||||
def get_details(self):
|
def get_details(self):
|
||||||
|
guarantor = self.get_guarantor()
|
||||||
ret_dict = {
|
ret_dict = {
|
||||||
"domain": self.domain,
|
"domain": self.domain,
|
||||||
"open_registrations": self.open_registrations,
|
"open_registrations": self.open_registrations,
|
||||||
"email_verify": self.email_verify,
|
"email_verify": self.email_verify,
|
||||||
"endorsements": len(self.endorsements),
|
"endorsements": len(self.endorsements),
|
||||||
"approvals": len(self.approvals),
|
"approvals": len(self.approvals),
|
||||||
"guarantor": self.get_guarantor().domain,
|
"guarantor": guarantor.domain if guarantor else None,
|
||||||
}
|
}
|
||||||
return ret_dict
|
return ret_dict
|
||||||
|
|
||||||
def get_guarantor(self):
|
def get_guarantor(self):
|
||||||
|
if len(self.guarantors) == 0:
|
||||||
|
return None
|
||||||
guarantee = self.guarantors[0]
|
guarantee = self.guarantors[0]
|
||||||
return Instance.query.filter_by(id=guarantee.guarantor_id).first()
|
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
|
waitress~=2.1.2
|
||||||
requests >= 2.27
|
requests >= 2.27
|
||||||
Markdown~=3.4.1
|
Markdown~=3.4.1
|
||||||
flask-dance[sqla]
|
|
||||||
blinker
|
|
||||||
python-dotenv
|
python-dotenv
|
||||||
loguru
|
loguru
|
||||||
python-dateutil~=2.8.2
|
python-dateutil~=2.8.2
|
||||||
redis~=4.3.5
|
|
||||||
flask_sqlalchemy==3.0.2
|
flask_sqlalchemy==3.0.2
|
||||||
SQLAlchemy~=1.4.44
|
SQLAlchemy~=1.4.44
|
||||||
psycopg2-binary
|
psycopg2-binary
|
||||||
regex
|
regex
|
||||||
|
pythorhead>=0.6.0
|
Loading…
Reference in New Issue