feat(lists): adds redirector data format
This commit is contained in:
parent
c16a80dc16
commit
60255afe3f
8 changed files with 117 additions and 4 deletions
|
@ -3,10 +3,12 @@ from typing import Dict, Callable, Any
|
|||
from app.lists.bc2 import mirror_sites
|
||||
from app.lists.bridgelines import bridgelines
|
||||
from app.lists.mirror_mapping import mirror_mapping
|
||||
from app.lists.redirector import redirector_data
|
||||
from app.models.base import Pool
|
||||
|
||||
lists: Dict[str, Callable[[Pool], Any]] = {
|
||||
"bca": mirror_mapping,
|
||||
"bc2": mirror_sites,
|
||||
"bridgelines": bridgelines,
|
||||
"rdr": redirector_data,
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ class MirrorMapping(BaseModel):
|
|||
title = "Mirror Mapping Version 1.1"
|
||||
|
||||
|
||||
def mirror_mapping(pool: Pool) -> Dict[str, Union[str, Dict[str, str]]]:
|
||||
def mirror_mapping(ignored_pool: Pool) -> Dict[str, Union[str, Dict[str, str]]]:
|
||||
return MirrorMapping(
|
||||
version="1.1",
|
||||
mappings={
|
||||
|
|
51
app/lists/redirector.py
Normal file
51
app/lists/redirector.py
Normal file
|
@ -0,0 +1,51 @@
|
|||
from typing import List, Dict, Union, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from app.models.base import Pool
|
||||
from app.models.mirrors import Proxy
|
||||
|
||||
|
||||
class RedirectorPool(BaseModel):
|
||||
short_name: str
|
||||
description: str
|
||||
api_key: str
|
||||
origins: Dict[str, str]
|
||||
|
||||
|
||||
class RedirectorData(BaseModel):
|
||||
version: str
|
||||
pools: List[RedirectorPool]
|
||||
|
||||
|
||||
def redirector_pool_origins(pool: Pool) -> Dict[str, str]:
|
||||
origins: Dict[str, str] = dict()
|
||||
active_proxies = Proxy.query.filter(
|
||||
Proxy.deprecated.is_(None),
|
||||
Proxy.destroyed.is_(None),
|
||||
Proxy.pool_id == pool.id
|
||||
)
|
||||
for proxy in active_proxies:
|
||||
origins[proxy.origin.domain_name] = proxy.url
|
||||
return origins
|
||||
|
||||
|
||||
def redirector_pool(pool: Pool) -> RedirectorPool:
|
||||
return RedirectorPool(
|
||||
short_name=pool.pool_name,
|
||||
description=pool.description,
|
||||
api_key=pool.api_key,
|
||||
origins=redirector_pool_origins(pool)
|
||||
)
|
||||
|
||||
|
||||
def redirector_data(ignored_pool: Optional[Pool]) -> Dict[str, Union[str, Dict[str, Union[Dict[str, str]]]]]:
|
||||
active_pools = Pool.query.filter(
|
||||
Pool.destroyed.is_(None)
|
||||
).all()
|
||||
return RedirectorData(
|
||||
version="1.0",
|
||||
pools=[
|
||||
redirector_pool(pool) for pool in active_pools
|
||||
]
|
||||
).dict()
|
|
@ -25,6 +25,7 @@ class Group(AbstractConfiguration):
|
|||
|
||||
class Pool(AbstractConfiguration):
|
||||
pool_name = db.Column(db.String(80), unique=True, nullable=False)
|
||||
api_key = db.Column(db.String(80), nullable=False)
|
||||
|
||||
@classmethod
|
||||
def csv_header(cls) -> List[str]:
|
||||
|
@ -65,7 +66,8 @@ class MirrorList(AbstractConfiguration):
|
|||
"bc2": "Bypass Censorship v2",
|
||||
"bc3": "Bypass Censorship v3",
|
||||
"bca": "Bypass Censorship Analytics",
|
||||
"bridgelines": "Tor Bridge Lines"
|
||||
"bridgelines": "Tor Bridge Lines",
|
||||
"rdr": "Redirector Data"
|
||||
}
|
||||
|
||||
encodings_supported = {
|
||||
|
|
|
@ -13,6 +13,7 @@ from app.extensions import db
|
|||
from app.lists.bc2 import mirror_sites
|
||||
from app.lists.bridgelines import bridgelines
|
||||
from app.lists.mirror_mapping import mirror_mapping
|
||||
from app.lists.redirector import redirector_data
|
||||
from app.models.base import MirrorList, Pool
|
||||
from app.portal.util import response_404, view_lifecycle
|
||||
|
||||
|
@ -63,6 +64,8 @@ def list_preview(format_: str, pool_id: int) -> ResponseReturnValue:
|
|||
return Response(json.dumps(mirror_sites(pool)), content_type="application/json")
|
||||
if format_ == "bridgelines":
|
||||
return Response(json.dumps(bridgelines(pool)), content_type="application/json")
|
||||
if format_ == "rdr":
|
||||
return Response(json.dumps(redirector_data(pool)), content_type="application/json")
|
||||
return response_404(message="Format not found")
|
||||
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import secrets
|
||||
from datetime import datetime
|
||||
|
||||
from flask import render_template, url_for, flash, redirect, Response, Blueprint
|
||||
|
@ -22,6 +23,8 @@ class NewPoolForm(FlaskForm): # type: ignore[misc]
|
|||
|
||||
class EditPoolForm(FlaskForm): # type: ignore[misc]
|
||||
description = StringField("Description", validators=[DataRequired()])
|
||||
api_key = StringField("API Key", description=("Any change to this field (e.g. clearing it) will result in the "
|
||||
"API key being regenerated."))
|
||||
submit = SubmitField('Save Changes', render_kw={"class": "btn btn-success"})
|
||||
|
||||
|
||||
|
@ -48,6 +51,7 @@ def pool_new() -> ResponseReturnValue:
|
|||
pool = Pool()
|
||||
pool.pool_name = form.group_name.data
|
||||
pool.description = form.description.data
|
||||
pool.api_key = secrets.token_urlsafe(nbytes=32)
|
||||
pool.created = datetime.utcnow()
|
||||
pool.updated = datetime.utcnow()
|
||||
try:
|
||||
|
@ -70,9 +74,13 @@ def pool_edit(pool_id: int) -> ResponseReturnValue:
|
|||
header="404 Pool Not Found",
|
||||
message="The requested pool could not be found."),
|
||||
status=404)
|
||||
form = EditPoolForm(description=pool.description)
|
||||
form = EditPoolForm(description=pool.description,
|
||||
api_key=pool.api_key)
|
||||
if form.validate_on_submit():
|
||||
pool.description = form.description.data
|
||||
if form.api_key.data != pool.api_key:
|
||||
pool.api_key = secrets.token_urlsafe(nbytes=32)
|
||||
form.api_key.data = pool.api_key
|
||||
pool.updated = datetime.utcnow()
|
||||
try:
|
||||
db.session.commit()
|
||||
|
|
|
@ -367,7 +367,9 @@
|
|||
<tr>
|
||||
<td>{{ pool.pool_name }}</td>
|
||||
<td>{{ pool.description }}</td>
|
||||
<td><a href="{{ url_for("portal.pool.pool_edit", pool_id=pool.id) }}" class="btn btn-primary btn-sm">View/Edit</a></td>
|
||||
<td><a href="{{ url_for("portal.pool.pool_edit", pool_id=pool.id) }}" class="btn btn-primary btn-sm">View/Edit</a>
|
||||
<a href="javascript:navigator.clipboard.writeText('{{ pool.api_key }}');"
|
||||
class="btn btn-sm btn-outline-dark">Copy API key</a></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
"""Adds api key field to pool table
|
||||
|
||||
Revision ID: a08ce5e7246a
|
||||
Revises: 6a59928efeb7
|
||||
Create Date: 2022-12-20 18:10:19.540534
|
||||
|
||||
"""
|
||||
import secrets
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.models.base import Pool
|
||||
|
||||
revision = 'a08ce5e7246a'
|
||||
down_revision = '6a59928efeb7'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
# class Pool(db.Model):
|
||||
# id = db.Column(db.Integer, primary_key=True)
|
||||
# description = db.Column(db.String(255), nullable=False)
|
||||
# added = db.Column(db.DateTime(), default=datetime.utcnow, nullable=False)
|
||||
# updated = db.Column(db.DateTime(), default=datetime.utcnow, nullable=False)
|
||||
# destroyed = db.Column(db.DateTime(), nullable=True)
|
||||
# pool_name = db.Column(db.String(80), unique=True, nullable=False)
|
||||
# api_key = db.Column(db.String(80), nullable=False)
|
||||
|
||||
|
||||
def upgrade():
|
||||
with op.batch_alter_table('pool', schema=None) as batch_op:
|
||||
batch_op.add_column(sa.Column('api_key', sa.String(length=80), nullable=True, unique=False))
|
||||
session = Session(bind=op.get_bind())
|
||||
for pool in session.query(Pool).all():
|
||||
pool.api_key = secrets.token_urlsafe(nbytes=32)
|
||||
session.commit()
|
||||
with op.batch_alter_table('pool', schema=None) as batch_op:
|
||||
batch_op.alter_column('api_key', nullable=False, unique=True)
|
||||
|
||||
|
||||
def downgrade():
|
||||
with op.batch_alter_table('pool', schema=None) as batch_op:
|
||||
batch_op.drop_column('api_key')
|
Loading…
Add table
Add a link
Reference in a new issue