guarantees
parent
51bd6e83bf
commit
30bce74a69
|
@ -1,9 +1,13 @@
|
||||||
import overseer.apis.v1.base as base
|
import overseer.apis.v1.base as base
|
||||||
|
import overseer.apis.v1.whitelist as whitelist
|
||||||
import overseer.apis.v1.endorsements as endorsements
|
import overseer.apis.v1.endorsements as endorsements
|
||||||
|
import overseer.apis.v1.guarantees as guarantees
|
||||||
from overseer.apis.v1.base import api
|
from overseer.apis.v1.base import api
|
||||||
|
|
||||||
api.add_resource(base.Suspicions, "/instances")
|
api.add_resource(base.Suspicions, "/instances")
|
||||||
api.add_resource(base.Whitelist, "/whitelist")
|
api.add_resource(whitelist.Whitelist, "/whitelist")
|
||||||
api.add_resource(base.WhitelistDomain, "/whitelist/<string:domain>")
|
api.add_resource(whitelist.WhitelistDomain, "/whitelist/<string:domain>")
|
||||||
api.add_resource(endorsements.Endorsements, "/endorsements/<string:domain>")
|
api.add_resource(endorsements.Endorsements, "/endorsements/<string:domain>")
|
||||||
api.add_resource(endorsements.Approvals, "/approvals/<string:domain>")
|
api.add_resource(endorsements.Approvals, "/approvals/<string:domain>")
|
||||||
|
api.add_resource(guarantees.Guarantors, "/guarantors/<string:domain>")
|
||||||
|
api.add_resource(guarantees.Guarantees, "/guarantees/<string:domain>")
|
||||||
|
|
|
@ -8,7 +8,7 @@ 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 import exceptions as e
|
||||||
from overseer.utils import hash_api_key
|
from overseer.utils import hash_api_key
|
||||||
from overseer.lemmy import pm_new_api_key
|
from overseer.lemmy import pm_new_api_key, pm_instance
|
||||||
from pythorhead import Lemmy
|
from pythorhead import Lemmy
|
||||||
|
|
||||||
api = Namespace('v1', 'API Version 1' )
|
api = Namespace('v1', 'API Version 1' )
|
||||||
|
@ -50,132 +50,3 @@ class Suspicions(Resource):
|
||||||
return {"domains": [instance["domain"] for instance in sus_instances]},200
|
return {"domains": [instance["domain"] for instance in sus_instances]},200
|
||||||
return {"instances": sus_instances},200
|
return {"instances": sus_instances},200
|
||||||
|
|
||||||
|
|
||||||
class Whitelist(Resource):
|
|
||||||
get_parser = reqparse.RequestParser()
|
|
||||||
get_parser.add_argument("Client-Agent", default="unknown:0:unknown", type=str, required=False, help="The client name and version.", location="headers")
|
|
||||||
get_parser.add_argument("endorsements", required=False, default=0, type=int, help="Limit to this amount of endorsements of more", location="args")
|
|
||||||
get_parser.add_argument("guarantors", required=False, default=1, type=int, help="Limit to this amount of guarantors of more", location="args")
|
|
||||||
get_parser.add_argument("csv", required=False, type=bool, help="Set to true to return just the domains as a csv. Mutually exclusive with domains", 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)
|
|
||||||
@cache.cached(timeout=10, query_string=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(self.args.endorsements,self.args.guarantors):
|
|
||||||
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
|
|
||||||
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')
|
|
||||||
@api.response(400, 'Bad Request', models.response_model_error)
|
|
||||||
def put(self):
|
|
||||||
'''Register a new instance to the overseer
|
|
||||||
An instance account has to exist in the overseer lemmy instance
|
|
||||||
That account will recieve the new API key via PM
|
|
||||||
'''
|
|
||||||
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("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("regenerate_key", required=False, type=bool, help="If True, will PM a new api key to this instance", location="json")
|
|
||||||
|
|
||||||
|
|
||||||
@api.expect(patch_parser)
|
|
||||||
@api.marshal_with(models.response_model_instances, code=200, description='Instances', skip_none=True)
|
|
||||||
@api.response(401, 'Invalid API Key', models.response_model_error)
|
|
||||||
@api.response(403, 'Instance Not Registered', models.response_model_error)
|
|
||||||
def patch(self):
|
|
||||||
'''Regenerate API key for instance
|
|
||||||
'''
|
|
||||||
self.args = self.patch_parser.parse_args()
|
|
||||||
if not self.args.apikey:
|
|
||||||
raise e.Unauthorized("You must provide the API key that was PM'd to your overctrl.dbzer0.com account")
|
|
||||||
instance = database.find_instance_by_api_key(self.args.apikey)
|
|
||||||
if not instance:
|
|
||||||
raise e.Forbidden(f"No Instance found matching provided API key and domain. Have you remembered to register it?")
|
|
||||||
if self.args.regenerate_key:
|
|
||||||
new_key = pm_new_api_key(self.args.domain)
|
|
||||||
instance.api_key = hash_api_key(new_key)
|
|
||||||
db.session.commit()
|
|
||||||
return instance.get_details(),200
|
|
||||||
|
|
||||||
delete_parser = reqparse.RequestParser()
|
|
||||||
delete_parser.add_argument("apikey", type=str, required=True, help="The sending instance's API key.", location='headers')
|
|
||||||
delete_parser.add_argument("Client-Agent", default="unknown:0:unknown", type=str, required=False, help="The client name and version.", location="headers")
|
|
||||||
delete_parser.add_argument("domain", required=False, type=str, help="The instance domain. It MUST be alredy registered in https://overctrl.dbzer0.com", location="json")
|
|
||||||
|
|
||||||
|
|
||||||
@api.expect(delete_parser)
|
|
||||||
@api.marshal_with(models.response_model_simple_response, code=200, description='Instances', skip_none=True)
|
|
||||||
@api.response(400, 'Bad Request', models.response_model_error)
|
|
||||||
@api.response(401, 'Invalid API Key', models.response_model_error)
|
|
||||||
@api.response(403, 'Forbidden', models.response_model_error)
|
|
||||||
def delete(self):
|
|
||||||
'''Delete instance from overseer
|
|
||||||
'''
|
|
||||||
self.args = self.patch_parser.parse_args()
|
|
||||||
if not self.args.apikey:
|
|
||||||
raise e.Unauthorized("You must provide the API key that was PM'd to your overctrl.dbzer0.com account")
|
|
||||||
instance = database.find_authenticated_instance(self.args.domain, self.args.apikey)
|
|
||||||
if not instance:
|
|
||||||
raise e.BadRequest(f"No Instance found matching provided API key and domain. Have you remembered to register it?")
|
|
||||||
if self.args.domain == os.getenv('OVERSEER_LEMMY_DOMAIN'):
|
|
||||||
raise e.Forbidden("Cannot delete overseer control instance")
|
|
||||||
db.session.delete(instance)
|
|
||||||
db.session.commit()
|
|
||||||
logger.warning(f"{self.args.domain} deleted")
|
|
||||||
return {"message":'Changed'}, 200
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class WhitelistDomain(Resource):
|
|
||||||
get_parser = reqparse.RequestParser()
|
|
||||||
get_parser.add_argument("Client-Agent", default="unknown:0:unknown", type=str, required=False, help="The client name and version.", location="headers")
|
|
||||||
|
|
||||||
@api.expect(get_parser)
|
|
||||||
@cache.cached(timeout=10, query_string=True)
|
|
||||||
@api.marshal_with(models.response_model_instances, code=200, description='Instances')
|
|
||||||
def get(self, domain):
|
|
||||||
'''Display info about a specific instance
|
|
||||||
'''
|
|
||||||
self.args = self.get_parser.parse_args()
|
|
||||||
instance = database.find_instance_by_domain(domain)
|
|
||||||
if not instance:
|
|
||||||
raise e.NotFound(f"No Instance found matching provided domain. Have you remembered to register it?")
|
|
||||||
return instance.get_details(),200
|
|
|
@ -77,7 +77,12 @@ class Endorsements(Resource):
|
||||||
raise e.Forbidden("Only guaranteed instances can endorse others.")
|
raise e.Forbidden("Only guaranteed instances can endorse others.")
|
||||||
if instance.domain == domain:
|
if instance.domain == domain:
|
||||||
raise e.BadRequest("Nice try, but you can't endorse yourself.")
|
raise e.BadRequest("Nice try, but you can't endorse yourself.")
|
||||||
|
unbroken_chain, chainbreaker = database.has_unbroken_chain(instance.id)
|
||||||
|
if not unbroken_chain:
|
||||||
|
raise e.Forbidden(f"Guarantee chain for this instance has been broken. Chain ends at {chainbreaker.domain}!")
|
||||||
target_instance = database.find_instance_by_domain(domain=domain)
|
target_instance = database.find_instance_by_domain(domain=domain)
|
||||||
|
if len(target_instance.guarantors) == 0:
|
||||||
|
raise e.Forbidden("Not Guaranteed instances can be endorsed. Please guarantee for them, or find someone who will.")
|
||||||
if not target_instance:
|
if not target_instance:
|
||||||
raise e.BadRequest("Instance to endorse not found")
|
raise e.BadRequest("Instance to endorse not found")
|
||||||
if database.get_endorsement(target_instance.id,instance.id):
|
if database.get_endorsement(target_instance.id,instance.id):
|
||||||
|
@ -88,6 +93,7 @@ class Endorsements(Resource):
|
||||||
)
|
)
|
||||||
db.session.add(new_endorsement)
|
db.session.add(new_endorsement)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
pm_instance(target_instance.domain, f"Your instance has just been endorsed by {instance.domain}")
|
||||||
logger.info(f"{instance.domain} Endorsed {domain}")
|
logger.info(f"{instance.domain} Endorsed {domain}")
|
||||||
return {"message":'Changed'}, 200
|
return {"message":'Changed'}, 200
|
||||||
|
|
||||||
|
@ -118,5 +124,6 @@ class Endorsements(Resource):
|
||||||
return {"message":'OK'}, 200
|
return {"message":'OK'}, 200
|
||||||
db.session.delete(endorsement)
|
db.session.delete(endorsement)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
pm_instance(target_instance.domain, f"Oh now. {instance.domain} has just withdrawn the endorsement of your instance")
|
||||||
logger.info(f"{instance.domain} Withdrew endorsement from {domain}")
|
logger.info(f"{instance.domain} Withdrew endorsement from {domain}")
|
||||||
return {"message":'Changed'}, 200
|
return {"message":'Changed'}, 200
|
|
@ -0,0 +1,143 @@
|
||||||
|
from overseer.apis.v1.base import *
|
||||||
|
from overseer.classes.instance import Guarantee, Endorsement
|
||||||
|
|
||||||
|
class Guarantors(Resource):
|
||||||
|
get_parser = reqparse.RequestParser()
|
||||||
|
get_parser.add_argument("Client-Agent", default="unknown:0:unknown", type=str, required=False, help="The client name and version.", location="headers")
|
||||||
|
get_parser.add_argument("csv", required=False, type=bool, help="Set to true to return just the domains as a csv. Mutually exclusive with domains", 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)
|
||||||
|
@cache.cached(timeout=10, query_string=True)
|
||||||
|
@api.marshal_with(models.response_model_model_Whitelist_get, code=200, description='Instances', skip_none=True)
|
||||||
|
@api.response(404, 'Instance not registered', models.response_model_error)
|
||||||
|
def get(self, domain):
|
||||||
|
'''Display all guarantees given by a specific domain
|
||||||
|
'''
|
||||||
|
self.args = self.get_parser.parse_args()
|
||||||
|
instance = database.find_instance_by_domain(domain)
|
||||||
|
if not instance:
|
||||||
|
raise e.NotFound(f"No Instance found matching provided domain. Have you remembered to register it?")
|
||||||
|
instance_details = []
|
||||||
|
for guaranteed in database.get_all_guaranteed_instances_by_guarantor_id(instance.id):
|
||||||
|
instance_details.append(guaranteed.get_details())
|
||||||
|
if self.args.csv:
|
||||||
|
return {"csv": ",".join([guaranteed["domain"] for guaranteed in instance_details])},200
|
||||||
|
if self.args.domains:
|
||||||
|
return {"domains": [guaranteed["domain"] for guaranteed in instance_details]},200
|
||||||
|
return {"instances": instance_details},200
|
||||||
|
|
||||||
|
class Guarantees(Resource):
|
||||||
|
get_parser = reqparse.RequestParser()
|
||||||
|
get_parser.add_argument("Client-Agent", default="unknown:0:unknown", type=str, required=False, help="The client name and version.", location="headers")
|
||||||
|
get_parser.add_argument("csv", required=False, type=bool, help="Set to true to return just the domains as a csv. Mutually exclusive with domains", 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)
|
||||||
|
@cache.cached(timeout=10, query_string=True)
|
||||||
|
@api.marshal_with(models.response_model_model_Whitelist_get, code=200, description='Instances', skip_none=True)
|
||||||
|
@api.response(404, 'Instance not registered', models.response_model_error)
|
||||||
|
def get(self, domain):
|
||||||
|
'''Display all instances guaranteeing for this domain
|
||||||
|
'''
|
||||||
|
self.args = self.get_parser.parse_args()
|
||||||
|
instance = database.find_instance_by_domain(domain)
|
||||||
|
if not instance:
|
||||||
|
raise e.NotFound(f"No Instance found matching provided domain. Have you remembered to register it?")
|
||||||
|
instance_details = []
|
||||||
|
for guarantor in database.get_all_guarantor_instances_by_guaranteed_id(instance.id):
|
||||||
|
instance_details.append(guarantor.get_details())
|
||||||
|
if self.args.csv:
|
||||||
|
return {"csv": ",".join([guarantor["domain"] for guarantor in instance_details])},200
|
||||||
|
if self.args.domains:
|
||||||
|
return {"domains": [guarantor["domain"] for guarantor in instance_details]},200
|
||||||
|
logger.debug(database.get_guarantor_chain(instance.id))
|
||||||
|
return {"instances": instance_details},200
|
||||||
|
|
||||||
|
put_parser = reqparse.RequestParser()
|
||||||
|
put_parser.add_argument("apikey", type=str, required=True, help="The sending instance's API key.", location='headers')
|
||||||
|
put_parser.add_argument("Client-Agent", default="unknown:0:unknown", type=str, required=False, help="The client name and version.", location="headers")
|
||||||
|
|
||||||
|
|
||||||
|
@api.expect(put_parser)
|
||||||
|
@api.marshal_with(models.response_model_simple_response, code=200, description='Endorse Instance')
|
||||||
|
@api.response(400, 'Bad Request', models.response_model_error)
|
||||||
|
@api.response(401, 'Invalid API Key', models.response_model_error)
|
||||||
|
@api.response(403, 'Instance Not Guaranteed or Tartget instance Guaranteed by others', models.response_model_error)
|
||||||
|
@api.response(404, 'Instance not registered', models.response_model_error)
|
||||||
|
def put(self, domain):
|
||||||
|
'''Endorse an instance
|
||||||
|
'''
|
||||||
|
self.args = self.put_parser.parse_args()
|
||||||
|
if not self.args.apikey:
|
||||||
|
raise e.Unauthorized("You must provide the API key that was PM'd to your overctrl.dbzer0.com account")
|
||||||
|
instance = database.find_instance_by_api_key(self.args.apikey)
|
||||||
|
if not instance:
|
||||||
|
raise e.NotFound(f"No Instance found matching provided API key and domain. Have you remembered to register it?")
|
||||||
|
if len(instance.guarantors) == 0:
|
||||||
|
raise e.Forbidden("Only guaranteed instances can guarantee others.")
|
||||||
|
unbroken_chain, chainbreaker = database.has_unbroken_chain(instance.id)
|
||||||
|
if not unbroken_chain:
|
||||||
|
raise e.Forbidden(f"Guarantee chain for this instance has been broken. Chain ends at {chainbreaker.domain}!")
|
||||||
|
target_instance = database.find_instance_by_domain(domain=domain)
|
||||||
|
if not target_instance:
|
||||||
|
raise e.BadRequest("Instance to endorse not found")
|
||||||
|
if database.get_guarantee(target_instance.id,instance.id):
|
||||||
|
return {"message":'OK'}, 200
|
||||||
|
gdomain = target_instance.get_guarantor_domain()
|
||||||
|
if gdomain:
|
||||||
|
raise e.Forbidden("Target instance already guaranteed by {gdomain}")
|
||||||
|
new_guarantee = Guarantee(
|
||||||
|
guaranteed_id=target_instance.id,
|
||||||
|
guarantor_id=instance.id,
|
||||||
|
)
|
||||||
|
db.session.add(new_guarantee)
|
||||||
|
# Guaranteed instances get their automatic first endorsement
|
||||||
|
new_endorsement = Endorsement(
|
||||||
|
approving_id=instance.id,
|
||||||
|
endorsed_id=target_instance.id,
|
||||||
|
)
|
||||||
|
db.session.add(new_endorsement)
|
||||||
|
db.session.commit()
|
||||||
|
pm_instance(target_instance.domain, f"Congratulations! Your instance has just been guaranteed by {instance.domain}. This also comes with your first endorsement.")
|
||||||
|
logger.info(f"{instance.domain} Guaranteed for {domain}")
|
||||||
|
return {"message":'Changed'}, 200
|
||||||
|
|
||||||
|
|
||||||
|
delete_parser = reqparse.RequestParser()
|
||||||
|
delete_parser.add_argument("apikey", type=str, required=True, help="The sending instance's API key.", location='headers')
|
||||||
|
delete_parser.add_argument("Client-Agent", default="unknown:0:unknown", type=str, required=False, help="The client name and version.", location="headers")
|
||||||
|
|
||||||
|
@api.expect(delete_parser)
|
||||||
|
@api.marshal_with(models.response_model_simple_response, code=200, description='Withdraw Instance Endorsement')
|
||||||
|
@api.response(400, 'Bad Request', models.response_model_error)
|
||||||
|
@api.response(401, 'Invalid API Key', models.response_model_error)
|
||||||
|
@api.response(404, 'Instance not registered', models.response_model_error)
|
||||||
|
def delete(self,domain):
|
||||||
|
'''Withdraw an instance guarantee
|
||||||
|
'''
|
||||||
|
self.args = self.delete_parser.parse_args()
|
||||||
|
if not self.args.apikey:
|
||||||
|
raise e.Unauthorized("You must provide the API key that was PM'd to your overctrl.dbzer0.com account")
|
||||||
|
instance = database.find_instance_by_api_key(self.args.apikey)
|
||||||
|
if not instance:
|
||||||
|
raise e.NotFound(f"No Instance found matching provided API key and domain. Have you remembered to register it?")
|
||||||
|
target_instance = database.find_instance_by_domain(domain=domain)
|
||||||
|
if not target_instance:
|
||||||
|
raise e.BadRequest("Instance from which to withdraw endorsement not found")
|
||||||
|
# If API key matches the target domain, we assume they want to remove the guarantee added to them to allow another domain to guarantee them
|
||||||
|
if instance.id == target_instance.id:
|
||||||
|
guarantee = instance.get_guarantee()
|
||||||
|
else:
|
||||||
|
guarantee = database.get_guarantee(target_instance.id,instance.id)
|
||||||
|
if not guarantee:
|
||||||
|
return {"message":'OK'}, 200
|
||||||
|
# Removing a guarantee removes the endorsement
|
||||||
|
endorsement = database.get_endorsement(target_instance.id,instance.id)
|
||||||
|
if endorsement:
|
||||||
|
db.session.delete(endorsement)
|
||||||
|
db.session.delete(guarantee)
|
||||||
|
db.session.commit()
|
||||||
|
pm_instance(target_instance.domain, f"Attention! You guarantor instance {instance.domain} has withdrawn their backing.\n\nIMPORTANT: All your endorsements and guarantees will be deleted unless you manage to find a new guarantor within 24hours!")
|
||||||
|
logger.info(f"{instance.domain} Withdrew guarantee from {domain}")
|
||||||
|
return {"message":'Changed'}, 200
|
|
@ -0,0 +1,135 @@
|
||||||
|
from overseer.apis.v1.base import *
|
||||||
|
|
||||||
|
class Whitelist(Resource):
|
||||||
|
get_parser = reqparse.RequestParser()
|
||||||
|
get_parser.add_argument("Client-Agent", default="unknown:0:unknown", type=str, required=False, help="The client name and version.", location="headers")
|
||||||
|
get_parser.add_argument("endorsements", required=False, default=0, type=int, help="Limit to this amount of endorsements of more", location="args")
|
||||||
|
get_parser.add_argument("guarantors", required=False, default=1, type=int, help="Limit to this amount of guarantors of more", location="args")
|
||||||
|
get_parser.add_argument("csv", required=False, type=bool, help="Set to true to return just the domains as a csv. Mutually exclusive with domains", 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)
|
||||||
|
@cache.cached(timeout=10, query_string=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(self.args.endorsements,self.args.guarantors):
|
||||||
|
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
|
||||||
|
return {"instances": instance_details},200
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class WhitelistDomain(Resource):
|
||||||
|
get_parser = reqparse.RequestParser()
|
||||||
|
get_parser.add_argument("Client-Agent", default="unknown:0:unknown", type=str, required=False, help="The client name and version.", location="headers")
|
||||||
|
|
||||||
|
@api.expect(get_parser)
|
||||||
|
@cache.cached(timeout=10, query_string=True)
|
||||||
|
@api.marshal_with(models.response_model_instances, code=200, description='Instances')
|
||||||
|
def get(self, domain):
|
||||||
|
'''Display info about a specific instance
|
||||||
|
'''
|
||||||
|
self.args = self.get_parser.parse_args()
|
||||||
|
instance = database.find_instance_by_domain(domain)
|
||||||
|
if not instance:
|
||||||
|
raise e.NotFound(f"No Instance found matching provided domain. Have you remembered to register it?")
|
||||||
|
return instance.get_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("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')
|
||||||
|
@api.response(400, 'Bad Request', models.response_model_error)
|
||||||
|
def put(self, domain):
|
||||||
|
'''Register a new instance to the overseer
|
||||||
|
An instance account has to exist in the overseer lemmylemmy instance
|
||||||
|
That account will recieve the new API key via PM
|
||||||
|
'''
|
||||||
|
self.args = self.put_parser.parse_args()
|
||||||
|
existing_instance = Instance.query.filter_by(domain=domain).first()
|
||||||
|
if existing_instance:
|
||||||
|
return existing_instance.get_details(),200
|
||||||
|
if domain.endswith("test.dbzer0.com"):
|
||||||
|
requested_lemmy = Lemmy(f"https://{domain}")
|
||||||
|
requested_lemmy._requestor.nodeinfo = {"software":{"name":"lemmy"}}
|
||||||
|
site = {"site_view":{"local_site":{"require_email_verification": True,"registration_mode":"open"}}}
|
||||||
|
else:
|
||||||
|
requested_lemmy = Lemmy(f"https://{domain}")
|
||||||
|
site = requested_lemmy.site.get()
|
||||||
|
if not site:
|
||||||
|
raise e.BadRequest(f"Error encountered while polling domain {domain}. Please check it's running correctly")
|
||||||
|
api_key = pm_new_api_key(domain)
|
||||||
|
if not api_key:
|
||||||
|
raise e.BadRequest("Failed to generate API Key")
|
||||||
|
new_instance = Instance(
|
||||||
|
domain=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("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("regenerate_key", required=False, type=bool, help="If True, will PM a new api key to this instance", location="json")
|
||||||
|
|
||||||
|
|
||||||
|
@api.expect(patch_parser)
|
||||||
|
@api.marshal_with(models.response_model_instances, code=200, description='Instances', skip_none=True)
|
||||||
|
@api.response(401, 'Invalid API Key', models.response_model_error)
|
||||||
|
@api.response(403, 'Instance Not Registered', models.response_model_error)
|
||||||
|
def patch(self, domain):
|
||||||
|
'''Regenerate API key for instance
|
||||||
|
'''
|
||||||
|
self.args = self.patch_parser.parse_args()
|
||||||
|
if not self.args.apikey:
|
||||||
|
raise e.Unauthorized("You must provide the API key that was PM'd to your overctrl.dbzer0.com account")
|
||||||
|
instance = database.find_instance_by_api_key(self.args.apikey)
|
||||||
|
if not instance:
|
||||||
|
raise e.Forbidden(f"No Instance found matching provided API key and domain. Have you remembered to register it?")
|
||||||
|
if self.args.regenerate_key:
|
||||||
|
new_key = pm_new_api_key(domain)
|
||||||
|
instance.api_key = hash_api_key(new_key)
|
||||||
|
db.session.commit()
|
||||||
|
return instance.get_details(),200
|
||||||
|
|
||||||
|
delete_parser = reqparse.RequestParser()
|
||||||
|
delete_parser.add_argument("apikey", type=str, required=True, help="The sending instance's API key.", location='headers')
|
||||||
|
delete_parser.add_argument("Client-Agent", default="unknown:0:unknown", type=str, required=False, help="The client name and version.", location="headers")
|
||||||
|
|
||||||
|
|
||||||
|
@api.expect(delete_parser)
|
||||||
|
@api.marshal_with(models.response_model_simple_response, code=200, description='Instances', skip_none=True)
|
||||||
|
@api.response(400, 'Bad Request', models.response_model_error)
|
||||||
|
@api.response(401, 'Invalid API Key', models.response_model_error)
|
||||||
|
@api.response(403, 'Forbidden', models.response_model_error)
|
||||||
|
def delete(self, domain):
|
||||||
|
'''Delete instance from overseer
|
||||||
|
'''
|
||||||
|
self.args = self.patch_parser.parse_args()
|
||||||
|
if not self.args.apikey:
|
||||||
|
raise e.Unauthorized("You must provide the API key that was PM'd to your overctrl.dbzer0.com account")
|
||||||
|
instance = database.find_authenticated_instance(domain, self.args.apikey)
|
||||||
|
if not instance:
|
||||||
|
raise e.BadRequest(f"No Instance found matching provided API key and domain. Have you remembered to register it?")
|
||||||
|
if domain == os.getenv('OVERSEER_LEMMY_DOMAIN'):
|
||||||
|
raise e.Forbidden("Cannot delete overseer control instance")
|
||||||
|
db.session.delete(instance)
|
||||||
|
db.session.commit()
|
||||||
|
logger.warning(f"{domain} deleted")
|
||||||
|
return {"message":'Changed'}, 200
|
||||||
|
|
|
@ -54,7 +54,6 @@ 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 = {
|
||||||
"id": self.id,
|
"id": self.id,
|
||||||
"domain": self.domain,
|
"domain": self.domain,
|
||||||
|
@ -62,12 +61,23 @@ class Instance(db.Model):
|
||||||
"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": guarantor.domain if guarantor else None,
|
"guarantor": self.get_guarantor_domain(),
|
||||||
}
|
}
|
||||||
return ret_dict
|
return ret_dict
|
||||||
|
|
||||||
def get_guarantor(self):
|
|
||||||
|
def get_guarantee(self):
|
||||||
if len(self.guarantors) == 0:
|
if len(self.guarantors) == 0:
|
||||||
return None
|
return None
|
||||||
guarantee = self.guarantors[0]
|
return self.guarantors[0]
|
||||||
|
|
||||||
|
def get_guarantor(self):
|
||||||
|
guarantee = self.get_guarantee()
|
||||||
|
if not guarantee:
|
||||||
|
return None
|
||||||
|
return guarantee.guarantor_instance
|
||||||
return Instance.query.filter_by(id=guarantee.guarantor_id).first()
|
return Instance.query.filter_by(id=guarantee.guarantor_id).first()
|
||||||
|
|
||||||
|
def get_guarantor_domain(self):
|
||||||
|
guarantor = self.get_guarantor()
|
||||||
|
return guarantor.domain if guarantor else None
|
|
@ -8,7 +8,7 @@ from sqlalchemy.orm import noload
|
||||||
from overseer.flask import db, SQLITE_MODE
|
from overseer.flask import db, SQLITE_MODE
|
||||||
from overseer.utils import hash_api_key
|
from overseer.utils import hash_api_key
|
||||||
from sqlalchemy.orm import joinedload
|
from sqlalchemy.orm import joinedload
|
||||||
from overseer.classes.instance import Instance, Endorsement
|
from overseer.classes.instance import Instance, Endorsement, Guarantee
|
||||||
|
|
||||||
def get_all_instances(min_endorsements = 0, min_guarantors = 1):
|
def get_all_instances(min_endorsements = 0, min_guarantors = 1):
|
||||||
query = db.session.query(
|
query = db.session.query(
|
||||||
|
@ -57,6 +57,34 @@ def get_all_approving_instances_by_endorsed_id(endorsed_id):
|
||||||
)
|
)
|
||||||
return query.all()
|
return query.all()
|
||||||
|
|
||||||
|
def get_all_guaranteed_instances_by_guarantor_id(guarantor_id):
|
||||||
|
query = db.session.query(
|
||||||
|
Instance
|
||||||
|
).outerjoin(
|
||||||
|
Instance.guarantors,
|
||||||
|
).options(
|
||||||
|
joinedload(Instance.guarantors),
|
||||||
|
).filter(
|
||||||
|
Guarantee.guarantor_id == guarantor_id
|
||||||
|
).group_by(
|
||||||
|
Instance.id
|
||||||
|
)
|
||||||
|
return query.all()
|
||||||
|
|
||||||
|
def get_all_guarantor_instances_by_guaranteed_id(guaranteed_id):
|
||||||
|
query = db.session.query(
|
||||||
|
Instance
|
||||||
|
).outerjoin(
|
||||||
|
Instance.guarantees,
|
||||||
|
).options(
|
||||||
|
joinedload(Instance.guarantees),
|
||||||
|
).filter(
|
||||||
|
Guarantee.guaranteed_id == guaranteed_id
|
||||||
|
).group_by(
|
||||||
|
Instance.id
|
||||||
|
)
|
||||||
|
return query.all()
|
||||||
|
|
||||||
|
|
||||||
def find_instance_by_api_key(api_key):
|
def find_instance_by_api_key(api_key):
|
||||||
instance = Instance.query.filter_by(api_key=hash_api_key(api_key)).first()
|
instance = Instance.query.filter_by(api_key=hash_api_key(api_key)).first()
|
||||||
|
@ -76,3 +104,43 @@ def get_endorsement(instance_id, endorsing_instance_id):
|
||||||
approving_id=endorsing_instance_id,
|
approving_id=endorsing_instance_id,
|
||||||
)
|
)
|
||||||
return query.first()
|
return query.first()
|
||||||
|
|
||||||
|
def get_guarantee(instance_id, guarantor_id):
|
||||||
|
query = Guarantee.query.filter_by(
|
||||||
|
guaranteed_id=instance_id,
|
||||||
|
guarantor_id=guarantor_id,
|
||||||
|
)
|
||||||
|
return query.first()
|
||||||
|
|
||||||
|
def get_guarantor_chain(instance_id):
|
||||||
|
guarantors = set()
|
||||||
|
chainbreaker = None
|
||||||
|
query = Guarantee.query.filter_by(
|
||||||
|
guaranteed_id=instance_id,
|
||||||
|
)
|
||||||
|
guarantor = query.first()
|
||||||
|
if not guarantor:
|
||||||
|
return set(),instance_id
|
||||||
|
guarantors.add(guarantor.guarantor_id)
|
||||||
|
if guarantor.guarantor_id != 0:
|
||||||
|
higher_guarantors, chainbreaker = get_guarantor_chain(guarantor.guarantor_id)
|
||||||
|
guarantors = higher_guarantors | guarantors
|
||||||
|
return guarantors,chainbreaker
|
||||||
|
|
||||||
|
def has_unbroken_chain(instance_id):
|
||||||
|
guarantors, chainbreaker = get_guarantor_chain(instance_id)
|
||||||
|
if chainbreaker:
|
||||||
|
chainbreaker = Instance.query.filter_by(id=chainbreaker).first()
|
||||||
|
return 0 in guarantors,chainbreaker
|
||||||
|
|
||||||
|
def get_guarantee_chain(instance_id):
|
||||||
|
query = Guarantee.query.filter_by(
|
||||||
|
guarantor_id=instance_id,
|
||||||
|
)
|
||||||
|
guarantees = query.all()
|
||||||
|
if not guarantees:
|
||||||
|
return set()
|
||||||
|
guarantees_ids = set([g.guaranteed_id for g in guarantees])
|
||||||
|
for gid in guarantees_ids:
|
||||||
|
guarantees_ids = guarantees_ids | get_guarantee_chain(gid)
|
||||||
|
return guarantees_ids
|
||||||
|
|
|
@ -10,15 +10,17 @@ if not _login:
|
||||||
raise Exception("Failed to login to overctrl")
|
raise Exception("Failed to login to overctrl")
|
||||||
overseer_lemmy_user = overctrl_lemmy.user.get(username=os.getenv('OVERSEER_LEMMY_USERNAME'))
|
overseer_lemmy_user = overctrl_lemmy.user.get(username=os.getenv('OVERSEER_LEMMY_USERNAME'))
|
||||||
|
|
||||||
def pm_new_api_key(domain: str):
|
def pm_instance(domain: str, message: str):
|
||||||
api_key = secrets.token_urlsafe(16)
|
|
||||||
pm_content = f"The API Key for domain {domain} is\n\n{api_key}\n\nUse this to perform operations on the overseer."
|
|
||||||
domain_username = domain.replace(".", "_")
|
domain_username = domain.replace(".", "_")
|
||||||
domain_user = overctrl_lemmy.user.get(username=domain_username)
|
domain_user = overctrl_lemmy.user.get(username=domain_username)
|
||||||
if not domain_user:
|
if not domain_user:
|
||||||
raise e.BadRequest(f"Could not find domain user '{domain_username}'")
|
raise e.BadRequest(f"Could not find domain user '{domain_username}'")
|
||||||
pm = overctrl_lemmy.private_message(pm_content,domain_user["person_view"]["person"]["id"])
|
pm = overctrl_lemmy.private_message(message,domain_user["person_view"]["person"]["id"])
|
||||||
if not pm:
|
return pm
|
||||||
|
|
||||||
|
def pm_new_api_key(domain: str):
|
||||||
|
api_key = secrets.token_urlsafe(16)
|
||||||
|
pm_content = f"The API Key for domain {domain} is\n\n{api_key}\n\nUse this to perform operations on the overseer."
|
||||||
|
if not pm_instance(domain, pm_content):
|
||||||
raise e.BadRequest("API Key PM failed")
|
raise e.BadRequest("API Key PM failed")
|
||||||
return api_key
|
return api_key
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue