guarantees

pull/3/head
db0 2023-06-22 15:40:28 +02:00
parent 51bd6e83bf
commit 30bce74a69
8 changed files with 384 additions and 144 deletions

View File

@ -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>")

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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()
@ -75,4 +103,44 @@ def get_endorsement(instance_id, endorsing_instance_id):
endorsed_id=instance_id, endorsed_id=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

View File

@ -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