Adds censures

Closes #15
pull/17/head
kthouky 2023-08-08 12:36:13 +02:00
parent de35d211e0
commit 2b9e1207f6
3 changed files with 184 additions and 5 deletions

View File

@ -0,0 +1,131 @@
from fediseer.apis.v1.base import *
from fediseer.classes.instance import Censure
class Censures(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, domains_csv):
'''Display all censures given out by one or more domains
You can pass a comma-separated list of domain names
and the results will be a set of all their censures together.
'''
domains_list = domains_csv.split(',')
self.args = self.get_parser.parse_args()
instances = database.find_multiple_instance_by_domains(domains_list)
if not instances:
raise e.NotFound(f"No Instances found matching any of the provided domains. Have you remembered to register them?")
instance_details = []
for instance in database.get_all_censured_instances_by_censuring_id([instance.id for instance in instances]):
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 CensuredReceived(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 censures received 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 instance in database.get_all_censuring_instances_by_censured_id(instance.id):
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("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, 'Not Guaranteed', models.response_model_error)
@api.response(404, 'Instance not registered', models.response_model_error)
def put(self, domain):
'''Censure an instance
A censure signifies a strong disapproval from your instance to how that instance is being run.
'''
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 admin 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 censure others.")
if instance.domain == domain:
raise e.BadRequest("You're a mad lad, but you can't censure 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, nodeinfo, admin_usernames = ensure_instance_registered(domain)
if not target_instance:
raise e.NotFound(f"Something went wrong trying to register this instance.")
if not target_instance:
raise e.BadRequest("Instance to censure not found")
if database.get_censure(target_instance.id,instance.id):
return {"message":'OK'}, 200
new_censure = Censure(
censuring_id=instance.id,
censured_id=target_instance.id,
)
db.session.add(new_censure)
db.session.commit()
logger.info(f"{instance.domain} Censured {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 censure
'''
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 admin 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 censure not found")
censure = database.get_censure(target_instance.id,instance.id)
if not censure:
return {"message":'OK'}, 200
db.session.delete(censure)
db.session.commit()
logger.info(f"{instance.domain} Withdrew censure from {domain}")
return {"message":'Changed'}, 200

View File

@ -13,7 +13,7 @@ uuid_column_type = lambda: UUID(as_uuid=True) if not SQLITE_MODE else db.String(
# This is used to know when last time an instance removed their guarantee from another to prevent trolling/spamming # This is used to know when last time an instance removed their guarantee from another to prevent trolling/spamming
# By someone adding/removing guarantees # By someone adding/removing guarantees
class RejectionRecord(db.Model): class RejectionRecord(db.Model):
__tablename__ = "rejection_records" __tablename__ = "rejection_records"
__table_args__ = (UniqueConstraint('rejector_id', 'rejected_id', name='endoresements_rejector_id_rejected_id'),) __table_args__ = (UniqueConstraint('rejector_id', 'rejected_id', name='endoresements_rejector_id_rejected_id'),)
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
@ -48,11 +48,21 @@ class Endorsement(db.Model):
endorsed_instance = db.relationship("Instance", back_populates="endorsements", foreign_keys=[endorsed_id]) endorsed_instance = db.relationship("Instance", back_populates="endorsements", foreign_keys=[endorsed_id])
created = db.Column(db.DateTime, default=datetime.utcnow, nullable=False) created = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
class Censure(db.Model):
__tablename__ = "censures"
__table_args__ = (UniqueConstraint('censuring_id', 'censured_id', name='censures_censuring_id_censured_id'),)
id = db.Column(db.Integer, primary_key=True)
censuring_id = db.Column(db.Integer, db.ForeignKey("instances.id", ondelete="CASCADE"), nullable=False)
censuring_instance = db.relationship("Instance", back_populates="censures_given", foreign_keys=[censuring_id])
censured_id = db.Column(db.Integer, db.ForeignKey("instances.id", ondelete="CASCADE"), nullable=False)
censured_instance = db.relationship("Instance", back_populates="censures_received", foreign_keys=[censured_id])
created = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
class Instance(db.Model): 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, index=True) domain = db.Column(db.String(255), 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)
@ -64,6 +74,8 @@ class Instance(db.Model):
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])
censures_given = db.relationship("Censure", back_populates="censuring_instance", cascade="all, delete-orphan", foreign_keys=[Censure.censuring_id])
censures_received = db.relationship("Censure", back_populates="censured_instance", cascade="all, delete-orphan", foreign_keys=[Censure.censured_id])
guarantees = db.relationship("Guarantee", back_populates="guarantor_instance", cascade="all, delete-orphan", foreign_keys=[Guarantee.guarantor_id]) guarantees = db.relationship("Guarantee", back_populates="guarantor_instance", cascade="all, delete-orphan", foreign_keys=[Guarantee.guarantor_id])
guarantors = db.relationship("Guarantee", back_populates="guaranteed_instance", cascade="all, delete-orphan", foreign_keys=[Guarantee.guaranteed_id]) guarantors = db.relationship("Guarantee", back_populates="guaranteed_instance", cascade="all, delete-orphan", foreign_keys=[Guarantee.guaranteed_id])
rejections = db.relationship("RejectionRecord", back_populates="rejector_instance", cascade="all, delete-orphan", foreign_keys=[RejectionRecord.rejector_id]) rejections = db.relationship("RejectionRecord", back_populates="rejector_instance", cascade="all, delete-orphan", foreign_keys=[RejectionRecord.rejector_id])
@ -108,8 +120,7 @@ class Instance(db.Model):
def set_as_oprhan(self): def set_as_oprhan(self):
self.oprhan_since = datetime.utcnow() self.oprhan_since = datetime.utcnow()
db.session.commit() db.session.commit()
def unset_as_orphan(self): def unset_as_orphan(self):
self.oprhan_since = None self.oprhan_since = None
db.session.commit() db.session.commit()

View File

@ -8,7 +8,7 @@ from sqlalchemy.orm import noload
from fediseer.flask import db, SQLITE_MODE from fediseer.flask import db, SQLITE_MODE
from fediseer.utils import hash_api_key from fediseer.utils import hash_api_key
from sqlalchemy.orm import joinedload from sqlalchemy.orm import joinedload
from fediseer.classes.instance import Instance, Endorsement, Guarantee, RejectionRecord from fediseer.classes.instance import Instance, Endorsement, Guarantee, RejectionRecord, Censure
from fediseer.classes.user import Claim, User from fediseer.classes.user import Claim, User
def get_all_instances(min_endorsements = 0, min_guarantors = 1): def get_all_instances(min_endorsements = 0, min_guarantors = 1):
@ -63,6 +63,36 @@ def get_all_approving_instances_by_endorsed_id(endorsed_ids):
) )
return query.all() return query.all()
def get_all_censured_instances_by_censuring_id(censuring_ids):
query = db.session.query(
Instance
).outerjoin(
Instance.endorsements,
).options(
joinedload(Instance.endorsements),
).filter(
Censure.approving_id.in_(censuring_ids)
).group_by(
Instance.id
)
return query.all()
def get_all_censuring_instances_by_censured_id(censured_ids):
query = db.session.query(
Instance
).outerjoin(
Instance.approvals,
).options(
joinedload(Instance.approvals),
).filter(
Censure.endorsed_id.in_(censured_ids)
).group_by(
Instance.id
)
return query.all()
def get_all_guaranteed_instances_by_guarantor_id(guarantor_id): def get_all_guaranteed_instances_by_guarantor_id(guarantor_id):
query = db.session.query( query = db.session.query(
Instance Instance
@ -180,6 +210,13 @@ def get_endorsement(instance_id, endorsing_instance_id):
) )
return query.first() return query.first()
def get_censure(instance_id, censuring_instance_id):
query = Censure.query.filter_by(
censured_id=instance_id,
censuring_id=censuring_instance_id,
)
return query.first()
def has_recent_endorsement(instance_id): def has_recent_endorsement(instance_id):
query = Endorsement.query.filter( query = Endorsement.query.filter(
Endorsement.endorsed_id == instance_id, Endorsement.endorsed_id == instance_id,