mirror of
https://github.com/fergalmoran/bitchmin.git
synced 2025-12-22 09:27:53 +00:00
Initial unit tests done
This commit is contained in:
@@ -52,7 +52,7 @@ def login():
|
||||
'accessToken': access_token,
|
||||
'refreshToken': refresh_token,
|
||||
'user': {
|
||||
'fullName': user.fullName
|
||||
'fullName': user.full_name
|
||||
}
|
||||
}), 200
|
||||
|
||||
|
||||
@@ -8,8 +8,7 @@ from IPy import IP
|
||||
|
||||
from app import db
|
||||
from app.api import api
|
||||
from app.models import DnsUpdate
|
||||
from app.models import User
|
||||
from app.models import User, DnsHost, DnsZone
|
||||
from app.utils import dnsupdate
|
||||
from app.utils.dnsupdate import delete_record
|
||||
from app.utils.dnsutils import get_dns_records
|
||||
@@ -18,6 +17,11 @@ from app.utils.iputils import is_valid_ip, is_valid_hostname
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@api.route('/dns/hosts')
|
||||
def get_hosts():
|
||||
return DnsZone.get_delete_put_post()
|
||||
|
||||
|
||||
@api.route('/dns/refresh', methods=['POST'])
|
||||
@jwt_required
|
||||
def refresh_dns():
|
||||
@@ -94,7 +98,7 @@ def delete_dns_record():
|
||||
os.getenv('DNS_KEY'),
|
||||
host)
|
||||
|
||||
records = DnsUpdate.query.filter_by(host=host).all()
|
||||
records = db.session(DnsHost).query.filter_by(host=host).all()
|
||||
for record in records:
|
||||
db.session.delete(record)
|
||||
db.session.commit()
|
||||
@@ -135,7 +139,7 @@ def update_dns():
|
||||
'payload': '{} is not a valid IP address'.format(hostname)
|
||||
}), 400
|
||||
|
||||
count = DnsUpdate.query.filter_by(host=hostname).count()
|
||||
count = db.session(DnsHost).query.filter_by(host=hostname).count()
|
||||
if count != 0:
|
||||
logger.warning('HOST {} is already in the system'.format(hostname))
|
||||
return jsonify({
|
||||
@@ -151,7 +155,7 @@ def update_dns():
|
||||
args['ip'])
|
||||
|
||||
if update_result:
|
||||
update = DnsUpdate(args['host'], ip, user)
|
||||
update = db.session(DnsHost)(args['host'], ip, user)
|
||||
db.session.add(update)
|
||||
db.session.commit()
|
||||
|
||||
@@ -169,7 +173,7 @@ def update_dns():
|
||||
@jwt_required
|
||||
def get_dns_list():
|
||||
user = get_current_user()
|
||||
updates = DnsUpdate \
|
||||
updates = db.session(DnsHost) \
|
||||
.query \
|
||||
.filter(User.id == user.id) \
|
||||
.all()
|
||||
|
||||
@@ -14,7 +14,7 @@ def get_user():
|
||||
user = get_current_user()
|
||||
if user:
|
||||
user = {
|
||||
'fullName': user.fullName,
|
||||
'fullName': user.full_name,
|
||||
}
|
||||
return jsonify({
|
||||
'status': 'success',
|
||||
|
||||
@@ -7,14 +7,15 @@ basedir = os.path.abspath(os.path.dirname(__file__))
|
||||
load_dotenv(os.path.join(basedir, '../.env'))
|
||||
|
||||
ISDEV = os.getenv('FLASK_ENV') == 'development'
|
||||
DEBUG_CONNECTION = 'postgresql+psycopg2://bitchmin:bitchmin@localhost/bitchmin'
|
||||
# DEBUG_CONNECTION = 'sqlite:///' + os.path.join(basedir, '../app.db')
|
||||
|
||||
|
||||
class Config(object):
|
||||
ISDEV = ISDEV
|
||||
SECRET_KEY = os.getenv('SECRET_KEY') or 'you-will-never-guess'
|
||||
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \
|
||||
'postgresql+psycopg2://bitchmin:bitchmin@localhost/bitchmin'
|
||||
# 'sqlite:///' + os.path.join(basedir, '../app.db')
|
||||
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or DEBUG_CONNECTION
|
||||
|
||||
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
||||
LOG_TO_STDOUT = os.getenv('LOG_TO_STDOUT')
|
||||
ADMINS = ['Ferg@lMoran.me']
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
from .dnsupdate import DnsUpdate
|
||||
from .dns import DnsZone, DnsNameServer, DnsHost
|
||||
from .user import User
|
||||
from .bindstate import BindState
|
||||
89
bitchmin-api/app/models/dns.py
Normal file
89
bitchmin-api/app/models/dns.py
Normal file
@@ -0,0 +1,89 @@
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
|
||||
from flask_serialize import FlaskSerializeMixin
|
||||
from sqlalchemy import event
|
||||
from sqlalchemy.orm import relationship, validates, MapperExtension
|
||||
from sqlalchemy_utils import IPAddressType
|
||||
|
||||
from app import db
|
||||
from app.models._basemodel import _BaseModelMixin
|
||||
|
||||
|
||||
@dataclass
|
||||
class DnsNameServer(db.Model, _BaseModelMixin, FlaskSerializeMixin):
|
||||
__tablename__ = 'dns_nameservers'
|
||||
|
||||
host = db.Column(db.String(255), unique=True, nullable=False)
|
||||
ip = db.Column(IPAddressType(255), nullable=False)
|
||||
zone_id = db.Column(db.Integer, db.ForeignKey('dns_zones.id'))
|
||||
zone = relationship("DnsZone", back_populates="nameservers")
|
||||
|
||||
def __init__(self, zone, host, ip):
|
||||
self.zone = zone
|
||||
self.host = host
|
||||
self.ip = ip
|
||||
|
||||
|
||||
@dataclass
|
||||
class DnsZone(db.Model, _BaseModelMixin, FlaskSerializeMixin):
|
||||
__tablename__ = 'dns_zones'
|
||||
relationship_fields = ['nameservers', 'hosts']
|
||||
|
||||
def __init__(self, zone_name):
|
||||
self.zone_name = zone_name
|
||||
self.serial = self._create_serial()
|
||||
|
||||
def get_serial_increment(self):
|
||||
return int(str(self.serial)[8:])
|
||||
|
||||
def _create_serial(self):
|
||||
return datetime.today().strftime('%Y%m%d{:02d}'.format(0))
|
||||
|
||||
def increment_serial(self):
|
||||
current = str(self.serial)
|
||||
if current and len(current) == 10:
|
||||
inc = self.get_serial_increment()
|
||||
return datetime.today().strftime('%Y%m%d{:02d}'.format(inc + 1))
|
||||
|
||||
return self._create_serial()
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
zone_name = db.Column(db.String(253), unique=True, nullable=False)
|
||||
serial = db.Column(db.Integer, default=_create_serial)
|
||||
|
||||
hosts = relationship("DnsHost", back_populates="zone")
|
||||
nameservers = relationship("DnsNameServer", back_populates="zone")
|
||||
|
||||
|
||||
@dataclass
|
||||
class DnsHost(db.Model, _BaseModelMixin, FlaskSerializeMixin):
|
||||
__tablename__ = 'dns_hosts'
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
zone_id = db.Column(db.Integer, db.ForeignKey('dns_zones.id'))
|
||||
zone = relationship("DnsZone", back_populates="hosts")
|
||||
|
||||
host = db.Column(db.String(255), unique=True, nullable=False)
|
||||
ip = db.Column(IPAddressType(255), nullable=False)
|
||||
|
||||
def __init__(self, zone, host, ip):
|
||||
self.zone = zone
|
||||
self.host = host
|
||||
self.ip = ip
|
||||
pass
|
||||
|
||||
def to_dict(self):
|
||||
return dict(id=self.id, host=self.host, ip=self.ip)
|
||||
|
||||
|
||||
# @event.listens_for(DnsZone.hosts, 'append')
|
||||
# @event.listens_for(DnsZone.hosts, 'remove')
|
||||
# def increment_zone_serial_handler(target, value, initiator):
|
||||
# target.serial = target.increment_serial()
|
||||
|
||||
|
||||
@event.listens_for(DnsHost.ip, 'set', active_history=True)
|
||||
def increment_zone_serial_handler(target, value, old, initiator):
|
||||
if target.zone:
|
||||
target.zone.serial = target.zone.increment_serial()
|
||||
@@ -12,17 +12,15 @@ from app.models._basemodel import _BaseModelMixin
|
||||
class User(db.Model, _BaseModelMixin):
|
||||
__tablename__ = 'users'
|
||||
id: str
|
||||
fullName: str
|
||||
full_name: str
|
||||
|
||||
email = db.Column(db.String(120), unique=True, nullable=False)
|
||||
fullName = db.Column(db.String(120), unique=False, nullable=True)
|
||||
full_name = db.Column(db.String(120), unique=False, nullable=True)
|
||||
password = db.Column(db.String(255), nullable=False)
|
||||
|
||||
dns_updates = relationship("DnsUpdate")
|
||||
|
||||
def __init__(self, email, fullName, password):
|
||||
def __init__(self, email, full_name, password):
|
||||
self.email = email
|
||||
self.fullName = fullName
|
||||
self.full_name = full_name
|
||||
self.password = generate_password_hash(password, method='sha256')
|
||||
|
||||
@property
|
||||
|
||||
0
bitchmin-api/app/serialisers/__init__.py
Normal file
0
bitchmin-api/app/serialisers/__init__.py
Normal file
4
bitchmin-api/app/serialisers/zone.py
Normal file
4
bitchmin-api/app/serialisers/zone.py
Normal file
@@ -0,0 +1,4 @@
|
||||
# class ZoneSerializerMixin(SerializerMixin):
|
||||
# serialize_types = (
|
||||
# (UUID, lambda x: str(x)),
|
||||
# )
|
||||
@@ -1,13 +1,11 @@
|
||||
from ipaddress import ip_address
|
||||
|
||||
from flask_mail import Message
|
||||
import logging
|
||||
import os
|
||||
from ipaddress import ip_address
|
||||
|
||||
from sqlalchemy.orm.exc import NoResultFound
|
||||
|
||||
from app import create_celery_app, mail, db
|
||||
from app.models import DnsUpdate, BindState, dnsupdate
|
||||
import logging
|
||||
from app import create_celery_app, db
|
||||
from app.models import DnsHost, DnsNameServer
|
||||
from app.utils import dnsupdate, iputils
|
||||
from app.utils.twilio import send_sms
|
||||
|
||||
@@ -22,15 +20,24 @@ def check_host_records():
|
||||
|
||||
try:
|
||||
logger.info('Checking for existing state')
|
||||
bind_state = BindState.query.one()
|
||||
bind_states = db.session.query(DnsNameServer)
|
||||
except NoResultFound:
|
||||
bind_state = BindState(
|
||||
nameserver_1_ip=platform_ip,
|
||||
nameserver_1_host=os.getenv('DNSro_SERVER')
|
||||
send_sms(
|
||||
os.getenv('SMS_NOTIFY_TO'),
|
||||
os.getenv('SMS_NOTIFY_FROM'),
|
||||
'BITCHMIN: No hosts found\nPlatform IP {}'.format(
|
||||
platform_ip
|
||||
)
|
||||
db.session.add(bind_state)
|
||||
db.session.commit()
|
||||
)
|
||||
# bind_state = DnsNameServer(
|
||||
# ip=platform_ip,
|
||||
# host=os.getenv('DNS_SERVER')
|
||||
# )
|
||||
# db.session.add(bind_state)
|
||||
# db.session.commit()
|
||||
return
|
||||
|
||||
for bind_state in bind_states:
|
||||
previous_ip = bind_state.nameserver_1_ip
|
||||
if ip_address(platform_ip) != previous_ip:
|
||||
logger.info('External IP has changed')
|
||||
@@ -46,7 +53,7 @@ def check_host_records():
|
||||
)
|
||||
|
||||
logger.info('Checking host records')
|
||||
hosts = DnsUpdate.query.filter_by(ip=previous_ip)
|
||||
hosts = db.session(DnsHost).query.filter_by(ip=previous_ip)
|
||||
result = 'Here are the hosts we have\n'
|
||||
for host in hosts:
|
||||
result += '\tHost: {} IP: {} Date Updated: {}'.format(
|
||||
|
||||
0
bitchmin-api/app/tests/__init__.py
Normal file
0
bitchmin-api/app/tests/__init__.py
Normal file
68
bitchmin-api/app/tests/scaffolder.py
Normal file
68
bitchmin-api/app/tests/scaffolder.py
Normal file
@@ -0,0 +1,68 @@
|
||||
import unittest
|
||||
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from sqlalchemy.pool import StaticPool
|
||||
|
||||
from app import db
|
||||
from app.models import DnsZone, User, DnsHost, DnsNameServer
|
||||
|
||||
|
||||
class Scaffolder(object):
|
||||
engine = create_engine(
|
||||
'postgresql+psycopg2://bitchmin:bitchmin@localhost/bitchmin',
|
||||
poolclass=StaticPool)
|
||||
Session = sessionmaker(bind=engine)
|
||||
session = Session()
|
||||
|
||||
def _create_user(self):
|
||||
user = User(
|
||||
'fergal.moran@gmail.com',
|
||||
'Fergal Moran',
|
||||
'topsecret'
|
||||
)
|
||||
self.session.add(user)
|
||||
|
||||
def _create_zone(self):
|
||||
zone = DnsZone('bitchmints.com')
|
||||
self.session.add(zone)
|
||||
return zone
|
||||
|
||||
def _create_nameservers(self, zone):
|
||||
for i in range(1, 3):
|
||||
ns = DnsNameServer(
|
||||
zone,
|
||||
'host-{}'.format(i),
|
||||
'10.1.1.10{}'.format(i)
|
||||
|
||||
)
|
||||
self.session.add(ns)
|
||||
|
||||
def _create_hosts(self, zone):
|
||||
for i in range(1, 11):
|
||||
host = DnsHost(
|
||||
zone,
|
||||
'host-{}'.format(i),
|
||||
'10.1.1.{}'.format(i)
|
||||
)
|
||||
self.session.add(host)
|
||||
|
||||
def scaffold(self):
|
||||
db.metadata.drop_all(self.engine)
|
||||
db.metadata.create_all(self.engine)
|
||||
|
||||
self._create_user()
|
||||
z = self._create_zone()
|
||||
self._create_nameservers(z)
|
||||
self._create_hosts(z)
|
||||
|
||||
self.session.commit()
|
||||
self.session.flush()
|
||||
|
||||
def teardown(self):
|
||||
db.metadata.drop_all(self.engine)
|
||||
self.engine.dispose()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
Scaffolder().scaffold()
|
||||
57
bitchmin-api/app/tests/test_db_updates.py
Normal file
57
bitchmin-api/app/tests/test_db_updates.py
Normal file
@@ -0,0 +1,57 @@
|
||||
import uuid
|
||||
|
||||
import pytest
|
||||
from sqlalchemy import func
|
||||
|
||||
from app.models import User, DnsZone, DnsNameServer, DnsHost
|
||||
from scaffolder import Scaffolder
|
||||
|
||||
|
||||
@pytest.fixture(scope="function") # or "module" (to teardown at a module level)
|
||||
def db():
|
||||
scaffolder = Scaffolder()
|
||||
scaffolder.teardown()
|
||||
scaffolder.scaffold()
|
||||
|
||||
yield scaffolder.session
|
||||
|
||||
scaffolder.teardown()
|
||||
|
||||
|
||||
class TestDatabaseUpdate:
|
||||
def test_load(self, db) -> None:
|
||||
assert db.query(User).count() == 1
|
||||
assert db.query(DnsZone).count() == 1
|
||||
assert db.query(DnsNameServer).count() == 2
|
||||
assert db.query(DnsHost).count() == 10
|
||||
|
||||
def test_serial_on_create(self, db) -> None:
|
||||
zone = db.query(DnsZone).first()
|
||||
assert len(str(zone.serial)) == 10
|
||||
assert zone.get_serial_increment() == 10
|
||||
|
||||
def test_serial_on_add_host(self, db) -> None:
|
||||
zone = db.query(DnsZone).first()
|
||||
serial = zone.get_serial_increment()
|
||||
|
||||
DnsHost(
|
||||
zone=zone,
|
||||
host=str(uuid.uuid4()),
|
||||
ip='99.99.99.99'
|
||||
)
|
||||
db.commit()
|
||||
"""
|
||||
Probably better to test that the new serial is > old serial...
|
||||
Not hugely important that it's only ONE more than it
|
||||
"""
|
||||
assert zone.get_serial_increment() > serial
|
||||
|
||||
def test_serial_on_update_host(self, db) -> None:
|
||||
host = db.query(DnsHost).get(4)
|
||||
serial = host.zone.get_serial_increment()
|
||||
|
||||
host.ip = '99.99.99.99'
|
||||
db.commit()
|
||||
|
||||
assert host.zone.get_serial_increment() > serial
|
||||
|
||||
@@ -7,6 +7,8 @@ Create Date: ${create_date}
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
import sqlalchemy_utils
|
||||
|
||||
${imports if imports else ""}
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
"""Added Bind State
|
||||
|
||||
Revision ID: 7de781bdfffe
|
||||
Revises: e4b3d04da7fc
|
||||
Create Date: 2020-09-04 21:47:57.400947
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
import sqlalchemy_utils
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '7de781bdfffe'
|
||||
down_revision = 'e4b3d04da7fc'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('bind_state',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('created_on', sa.DateTime(), server_default=sa.text('(CURRENT_TIMESTAMP)'), nullable=True),
|
||||
sa.Column('updated_on', sa.DateTime(), server_default=sa.text('(CURRENT_TIMESTAMP)'), nullable=True),
|
||||
sa.Column('nameserver_1_ip', sqlalchemy_utils.types.ip_address.IPAddressType(length=255), nullable=False),
|
||||
sa.Column('nameserver_1_host', sa.String(length=255), nullable=False),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('nameserver_1_host')
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_table('bind_state')
|
||||
# ### end Alembic commands ###
|
||||
@@ -0,0 +1,73 @@
|
||||
"""Initial migration.
|
||||
|
||||
Revision ID: ca46ac8d1ba5
|
||||
Revises:
|
||||
Create Date: 2020-11-09 18:33:08.250178
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
import sqlalchemy_utils
|
||||
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'ca46ac8d1ba5'
|
||||
down_revision = None
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('dns_zones',
|
||||
sa.Column('created_on', sa.DateTime(), server_default=sa.text('now()'), nullable=True),
|
||||
sa.Column('updated_on', sa.DateTime(), server_default=sa.text('now()'), nullable=True),
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('zone_name', sa.String(length=253), nullable=False),
|
||||
sa.Column('serial', sa.Integer(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('zone_name')
|
||||
)
|
||||
op.create_table('users',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('created_on', sa.DateTime(), server_default=sa.text('now()'), nullable=True),
|
||||
sa.Column('updated_on', sa.DateTime(), server_default=sa.text('now()'), nullable=True),
|
||||
sa.Column('email', sa.String(length=120), nullable=False),
|
||||
sa.Column('full_name', sa.String(length=120), nullable=True),
|
||||
sa.Column('password', sa.String(length=255), nullable=False),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('email')
|
||||
)
|
||||
op.create_table('dns_hosts',
|
||||
sa.Column('created_on', sa.DateTime(), server_default=sa.text('now()'), nullable=True),
|
||||
sa.Column('updated_on', sa.DateTime(), server_default=sa.text('now()'), nullable=True),
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('zone_id', sa.Integer(), nullable=True),
|
||||
sa.Column('host', sa.String(length=255), nullable=False),
|
||||
sa.Column('ip', sqlalchemy_utils.types.ip_address.IPAddressType(length=255), nullable=False),
|
||||
sa.ForeignKeyConstraint(['zone_id'], ['dns_zones.id'], ),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('host')
|
||||
)
|
||||
op.create_table('dns_nameservers',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('created_on', sa.DateTime(), server_default=sa.text('now()'), nullable=True),
|
||||
sa.Column('updated_on', sa.DateTime(), server_default=sa.text('now()'), nullable=True),
|
||||
sa.Column('ip', sqlalchemy_utils.types.ip_address.IPAddressType(length=255), nullable=False),
|
||||
sa.Column('host', sa.String(length=255), nullable=False),
|
||||
sa.Column('zone_id', sa.Integer(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['zone_id'], ['dns_zones.id'], ),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('host')
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_table('dns_nameservers')
|
||||
op.drop_table('dns_hosts')
|
||||
op.drop_table('users')
|
||||
op.drop_table('dns_zones')
|
||||
# ### end Alembic commands ###
|
||||
@@ -1,49 +0,0 @@
|
||||
"""Initial
|
||||
|
||||
Revision ID: e4b3d04da7fc
|
||||
Revises:
|
||||
Create Date: 2020-08-30 22:21:56.289213
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
import sqlalchemy_utils
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'e4b3d04da7fc'
|
||||
down_revision = None
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('users',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('created_on', sa.DateTime(), server_default=sa.text('(CURRENT_TIMESTAMP)'), nullable=True),
|
||||
sa.Column('updated_on', sa.DateTime(), server_default=sa.text('(CURRENT_TIMESTAMP)'), nullable=True),
|
||||
sa.Column('email', sa.String(length=120), nullable=False),
|
||||
sa.Column('fullName', sa.String(length=120), nullable=True),
|
||||
sa.Column('password', sa.String(length=255), nullable=False),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('email')
|
||||
)
|
||||
op.create_table('dns_updates',
|
||||
sa.Column('created_on', sa.DateTime(), server_default=sa.text('(CURRENT_TIMESTAMP)'), nullable=True),
|
||||
sa.Column('updated_on', sa.DateTime(), server_default=sa.text('(CURRENT_TIMESTAMP)'), nullable=True),
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('host', sa.String(length=120), nullable=False),
|
||||
sa.Column('ip', sqlalchemy_utils.types.ip_address.IPAddressType(length=255), nullable=False),
|
||||
sa.Column('user_id', sa.Integer(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('host')
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_table('dns_updates')
|
||||
op.drop_table('users')
|
||||
# ### end Alembic commands ###
|
||||
@@ -4,6 +4,7 @@ Flask-SQLAlchemy
|
||||
Flask_Migrate
|
||||
flask_jwt_extended
|
||||
Flask-Mail
|
||||
Flask-Serialize
|
||||
sqlalchemy_utils
|
||||
phue
|
||||
python-dotenv
|
||||
@@ -15,9 +16,11 @@ werkzeug
|
||||
celery
|
||||
redis
|
||||
flower
|
||||
sqlalchemy
|
||||
requests
|
||||
IPy
|
||||
pydig
|
||||
tinder
|
||||
twilio
|
||||
flask_monitoringdashboard
|
||||
pytest
|
||||
@@ -1,13 +1,10 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
docker run -e POSTGRES_PASSWORD=docker library/postgres
|
||||
|
||||
|
||||
docker run -d \
|
||||
--name bitchmin-postgres \
|
||||
-e POSTGRES_PASSWORD=postgres \
|
||||
-p 5432:5432 \
|
||||
-e /opt/postgres/bitchmin=/var/lib/postgresql/data \
|
||||
-e /home/fergalm/dev/BitchMin/bitchmin-api/.working/pgdata=/var/lib/postgresql \
|
||||
-d library/postgres
|
||||
|
||||
docker exec -it bitchmin-postgres psql \
|
||||
@@ -22,19 +19,4 @@ docker exec -it bitchmin-postgres psql \
|
||||
-U postgres \
|
||||
-c "grant all privileges on database bitchmin to bitchmin;"
|
||||
|
||||
kubectl exec -it postgres-5f4895b95d-4xz92 \
|
||||
'psql -U postgres -c "CREATE DATABASE bitchmin;"'
|
||||
|
||||
kubectl exec -it postgres-5f4895b95d-4xz92 \
|
||||
"psql -U postgres -c $"CREATE USER bitchmin WITH PASSWORD \'bitchmin\';'"
|
||||
|
||||
psql -U postgres -c 'psql -c "CREATE USER bitchmin WITH PASSWORD '\''bitchmin'\'';"'
|
||||
|
||||
kubectl exec -it postgres-5f4895b95d-4xz92 \
|
||||
'psql -U postgres -c "grant all privileges on database bitchmin to bitchmin;"'
|
||||
su - postgres -c 'psql -c "grant all privileges on database bitchmin to bitchmin;"'
|
||||
|
||||
|
||||
|
||||
# DATABASE_URL='postgresql+psycopg2://bitchmin:bitchmin@10.1.1.1/bitchmin'
|
||||
# psql postgres -c "CREATE DATABASE BitchMin WITH ENCODING 'UTF8'
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Api } from '@/api/apiBase';
|
||||
import { AxiosRequestConfig, AxiosResponse } from 'axios';
|
||||
import { apiConfig } from '@/api/config';
|
||||
import { ApiResult, DataApiResult } from '@/api/apiResult';
|
||||
import { DnsRecord } from '@/models/dnsRecord';
|
||||
import { DnsHost } from '@/models/dnsHost';
|
||||
|
||||
export class DnsApi extends Api {
|
||||
constructor(config: AxiosRequestConfig) {
|
||||
@@ -13,11 +13,11 @@ export class DnsApi extends Api {
|
||||
public async updateDnsRecord(
|
||||
hostName: string,
|
||||
ipAddress: string,
|
||||
): Promise<DataApiResult<DnsRecord>> {
|
||||
): Promise<DataApiResult<DnsHost>> {
|
||||
const result = await this.post<
|
||||
ApiResult,
|
||||
any,
|
||||
AxiosResponse<DataApiResult<DnsRecord>>
|
||||
AxiosResponse<DataApiResult<DnsHost>>
|
||||
>('/dns/', {
|
||||
host: hostName,
|
||||
ip: ipAddress,
|
||||
@@ -35,11 +35,11 @@ export class DnsApi extends Api {
|
||||
public async refreshDnsRecord(
|
||||
hostName: string,
|
||||
ip: string,
|
||||
): Promise<DataApiResult<DnsRecord>> {
|
||||
): Promise<DataApiResult<DnsHost>> {
|
||||
const result = await this.post<
|
||||
ApiResult,
|
||||
any,
|
||||
AxiosResponse<DataApiResult<DnsRecord>>
|
||||
AxiosResponse<DataApiResult<DnsHost>>
|
||||
>('/dns/refresh', {
|
||||
host: hostName,
|
||||
ip,
|
||||
@@ -62,8 +62,8 @@ export class DnsApi extends Api {
|
||||
return result.data;
|
||||
}
|
||||
|
||||
public async getDnsRecords(): Promise<DnsRecord[]> {
|
||||
const result = await this.get<DnsRecord[]>('/dns/list');
|
||||
public async getDnsRecords(): Promise<DnsHost[]> {
|
||||
const result = await this.get<DnsHost[]>('/dns/list');
|
||||
return result.data;
|
||||
}
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
<script lang="ts">
|
||||
import { Component, PropSync, Vue } from 'vue-property-decorator';
|
||||
import { dnsApi } from '@/api';
|
||||
import { DnsRecord } from '@/models/dnsRecord';
|
||||
import { DnsHost } from '@/models/dnsHost';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
@@ -78,13 +78,13 @@ import localizedFormat from 'dayjs/plugin/localizedFormat';
|
||||
})
|
||||
export default class DnsRecordsList extends Vue {
|
||||
@PropSync('inrecords')
|
||||
public records!: DnsRecord[];
|
||||
public records!: DnsHost[];
|
||||
|
||||
private callInProgress = false;
|
||||
|
||||
errorMessage = '';
|
||||
|
||||
async refreshRecord(host: DnsRecord) {
|
||||
async refreshRecord(host: DnsHost) {
|
||||
this.callInProgress = true;
|
||||
console.log('DnsRecordsList', 'refreshRecord', host);
|
||||
const result = await dnsApi.refreshDnsRecord(host.host, host.ip);
|
||||
@@ -94,7 +94,7 @@ export default class DnsRecordsList extends Vue {
|
||||
this.callInProgress = false;
|
||||
}
|
||||
|
||||
async verifyRecord(record: DnsRecord) {
|
||||
async verifyRecord(record: DnsHost) {
|
||||
this.callInProgress = true;
|
||||
const result = await dnsApi.verifyDnsRecord(record.host, record.ip);
|
||||
if (result.status === 'success') {
|
||||
@@ -106,7 +106,7 @@ export default class DnsRecordsList extends Vue {
|
||||
this.callInProgress = false;
|
||||
}
|
||||
|
||||
async deleteRecord(record: DnsRecord) {
|
||||
async deleteRecord(record: DnsHost) {
|
||||
this.callInProgress = true;
|
||||
const result = await dnsApi.deleteDnsRecord(record.host);
|
||||
if (result === 200) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<v-card class="mx-auto" outlined>
|
||||
<v-card class="mx-auto" >
|
||||
<template slot="progress">
|
||||
<v-progress-linear color="deep-purple" height="10" indeterminate></v-progress-linear>
|
||||
</template>
|
||||
@@ -30,7 +30,7 @@
|
||||
<script lang="ts">
|
||||
import { Component, PropSync, Vue } from 'vue-property-decorator';
|
||||
import { dnsApi } from '@/api';
|
||||
import { DnsRecord } from '@/models/dnsRecord';
|
||||
import { DnsHost } from '@/models/dnsHost';
|
||||
import { DataApiResult } from '@/api/apiResult';
|
||||
|
||||
@Component({
|
||||
@@ -67,7 +67,7 @@ export default class DnsUpdateForm extends Vue {
|
||||
];
|
||||
|
||||
@PropSync('inrecords')
|
||||
public records!: DnsRecord[];
|
||||
public records!: DnsHost[];
|
||||
|
||||
validate() {
|
||||
// this.$refs.form.validate();
|
||||
@@ -76,7 +76,7 @@ export default class DnsUpdateForm extends Vue {
|
||||
processUpdate() {
|
||||
dnsApi
|
||||
.updateDnsRecord(this.hostName, this.ipAddress)
|
||||
.then((r: DataApiResult<DnsRecord>) => {
|
||||
.then((r: DataApiResult<DnsHost>) => {
|
||||
if (r.status === 'success') {
|
||||
this.error = '';
|
||||
Vue.toasted.success('Update successful');
|
||||
|
||||
28
bitchmin-client/src/components/Dns/DnsZonesList.vue
Normal file
28
bitchmin-client/src/components/Dns/DnsZonesList.vue
Normal file
@@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<v-card class="mx-auto">
|
||||
<v-card-title>Choose Zone</v-card-title>
|
||||
<v-card-text>
|
||||
<v-select :options="zones"
|
||||
item-text="zone"
|
||||
item-value="id"
|
||||
></v-select>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { Component, PropSync, Vue } from 'vue-property-decorator';
|
||||
import { DnsZone } from '@/models/dnsZone';
|
||||
|
||||
@Component({
|
||||
name: 'DnsHostsList'
|
||||
})
|
||||
export default class DnsZonesList extends Vue {
|
||||
|
||||
@PropSync('inhosts')
|
||||
public zones!: DnsZone[];
|
||||
|
||||
mounted() {
|
||||
console.log('DnsZonesList', 'mounted', this.zones);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
6
bitchmin-client/src/models/dnsHost.ts
Normal file
6
bitchmin-client/src/models/dnsHost.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export interface DnsHost {
|
||||
id: number;
|
||||
host: string;
|
||||
ip: string;
|
||||
created_on: Date;
|
||||
}
|
||||
4
bitchmin-client/src/models/dnsZone.ts
Normal file
4
bitchmin-client/src/models/dnsZone.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export interface DnsZone {
|
||||
id: number;
|
||||
zone: string;
|
||||
}
|
||||
@@ -4,4 +4,4 @@ export * from './userLoginModel';
|
||||
export * from './user';
|
||||
export * from './user';
|
||||
export * from './light';
|
||||
export * from './dnsRecord';
|
||||
export * from './dnsHost';
|
||||
|
||||
@@ -2,35 +2,50 @@
|
||||
<v-container id="dashboard" fluid tag="section">
|
||||
<v-row>
|
||||
<v-col cols="12" sm="4" lg="4">
|
||||
<DnsUpdateForm :inrecords="dnsRecords" />
|
||||
<DnsZonesList :inhosts="dnsZones" />
|
||||
</v-col>
|
||||
<v-col cols="12" sm="8" lg="8">
|
||||
<DnsUpdateForm :inrecords="dnsRecords" />
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="12" lg="12">
|
||||
<DnsRecordsList :inrecords="dnsRecords" />
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from 'vue-property-decorator';
|
||||
import HelloWorld from '@/components/HelloWorld.vue'; // @ is an alias to /src
|
||||
import DnsZonesList from '@/components/Dns/DnsZonesList.vue';
|
||||
import DnsRecordsList from '@/components/Dns/DnsRecordsList.vue';
|
||||
import DnsUpdateForm from '@/components/Dns/DnsUpdateForm.vue';
|
||||
import { DnsRecord } from '@/models';
|
||||
import { DnsHost } from '@/models';
|
||||
import { dnsApi } from '@/api';
|
||||
import { DnsZone } from '@/models/dnsZone';
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
HelloWorld,
|
||||
DnsZonesList,
|
||||
DnsRecordsList,
|
||||
DnsUpdateForm,
|
||||
},
|
||||
DnsUpdateForm
|
||||
}
|
||||
})
|
||||
export default class BitchNS extends Vue {
|
||||
dnsRecords: DnsRecord[] = [];
|
||||
dnsZones: DnsZone[] = [];
|
||||
dnsRecords: DnsHost[] = [];
|
||||
|
||||
async mounted() {
|
||||
this.dnsRecords = await dnsApi.getDnsRecords();
|
||||
this.dnsZones = [
|
||||
{ id: 1, zone: 'bitchmints.com' },
|
||||
{ id: 2, zone: 'fergl.ie' }
|
||||
];
|
||||
// this.dnsHosts = await dnsApi.getHosts();
|
||||
// this.dnsRecords = await dnsApi.getDnsRecords();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
1
bitchmin-server/.venv
Normal file
1
bitchmin-server/.venv
Normal file
@@ -0,0 +1 @@
|
||||
BitchMin
|
||||
1
bitchmin-server/commands.txt
Normal file
1
bitchmin-server/commands.txt
Normal file
@@ -0,0 +1 @@
|
||||
ASDAKSJH
|
||||
1
bitchmin-server/requirements.txt
Normal file
1
bitchmin-server/requirements.txt
Normal file
@@ -0,0 +1 @@
|
||||
twisted
|
||||
0
bitchmin-server/resolvers/__init__.py
Normal file
0
bitchmin-server/resolvers/__init__.py
Normal file
BIN
bitchmin-server/resolvers/__pycache__/__init__.cpython-38.pyc
Normal file
BIN
bitchmin-server/resolvers/__pycache__/__init__.cpython-38.pyc
Normal file
Binary file not shown.
BIN
bitchmin-server/resolvers/__pycache__/commands.cpython-38.pyc
Normal file
BIN
bitchmin-server/resolvers/__pycache__/commands.cpython-38.pyc
Normal file
Binary file not shown.
Binary file not shown.
19
bitchmin-server/resolvers/commands.py
Normal file
19
bitchmin-server/resolvers/commands.py
Normal file
@@ -0,0 +1,19 @@
|
||||
class Command:
|
||||
pass
|
||||
|
||||
|
||||
class AddRecordCommand(Command):
|
||||
def __init__(self, record_type, zone, host, ip, ttl):
|
||||
self.type = record_type
|
||||
self.zone = zone
|
||||
self.host = host
|
||||
self.ip = ip
|
||||
self.ttl = ttl
|
||||
|
||||
def __str__(self):
|
||||
return 'AddRecordCommand({}) - Host: {} IP: {}'.format(
|
||||
self.type,
|
||||
self.zone,
|
||||
self.host,
|
||||
self.ip,
|
||||
)
|
||||
115
bitchmin-server/resolvers/memory_resolver.py
Normal file
115
bitchmin-server/resolvers/memory_resolver.py
Normal file
@@ -0,0 +1,115 @@
|
||||
from twisted.internet import defer
|
||||
from twisted.names import dns, error
|
||||
import logging
|
||||
|
||||
|
||||
class InvalidZoneException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Resolver:
|
||||
def _build_host(self, record_type, host, ip, ttl):
|
||||
return {
|
||||
host: {
|
||||
'type': record_type,
|
||||
'ttl': ttl,
|
||||
'ip': ip
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class MemoryResolver(Resolver):
|
||||
"""
|
||||
A resolver which calculates the answers to certain queries based on the
|
||||
query type and name.
|
||||
"""
|
||||
|
||||
def __init__(self, zones):
|
||||
self._zones = zones
|
||||
|
||||
def add_host(self, record_type, zone, host, ip, ttl):
|
||||
if self._zones[zone]:
|
||||
# 'ns1': {'type': 'A', 'ttl': 30, 'ip': '10.1.33.7'},
|
||||
new = {
|
||||
host: {
|
||||
'type': record_type,
|
||||
'ttl': ttl,
|
||||
'ip': ip
|
||||
},
|
||||
}
|
||||
try:
|
||||
self._zones[zone]['hosts'].update(new)
|
||||
except Exception as e:
|
||||
logging.error(e)
|
||||
else:
|
||||
raise InvalidZoneException('Zone {} does not exist'.format(zone))
|
||||
|
||||
@staticmethod
|
||||
def _parse_host(zone, host):
|
||||
parsed = [x for x in host.split('.') if x not in zone.split('.')]
|
||||
return '.'.join(parsed) if len(parsed) != 0 else ''
|
||||
|
||||
def _get_authoritative_zone(self, query):
|
||||
"""
|
||||
split on "." and keep removing from left until we find a zone or we run out of string
|
||||
"""
|
||||
parts = str(query.name).split('.')
|
||||
while len(parts) != 0:
|
||||
t = '.'.join(parts)
|
||||
if t in self._zones:
|
||||
return t, self._zones[t]
|
||||
parts.pop(0)
|
||||
|
||||
return None
|
||||
|
||||
def _get_response(self, zone, query):
|
||||
"""
|
||||
Calculate the response to a query.
|
||||
"""
|
||||
try:
|
||||
host = self._parse_host(zone, str(query.name))
|
||||
record = self._zones[zone]['hosts'][host]
|
||||
|
||||
if query.type == dns.NS:
|
||||
payload = dns.Record_NS(
|
||||
name=zone['nameservers'][0],
|
||||
ttl=record['ttl']
|
||||
)
|
||||
else:
|
||||
payload = dns.Record_A(
|
||||
address=record['ip'],
|
||||
ttl=record['ttl']
|
||||
)
|
||||
|
||||
answer = dns.RRHeader(
|
||||
name=zone,
|
||||
payload=payload,
|
||||
ttl=record['ttl']
|
||||
)
|
||||
|
||||
answers = [answer]
|
||||
authority = []
|
||||
additional = []
|
||||
|
||||
return answers, authority, additional
|
||||
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
def query(self, query, timeout=None):
|
||||
"""
|
||||
Check if the query should be answered dynamically, otherwise dispatch to
|
||||
the fallback resolver.
|
||||
"""
|
||||
|
||||
logging.info(query)
|
||||
name, zone = self._get_authoritative_zone(query)
|
||||
if zone and name:
|
||||
logging.debug('BitchNS: Authoritative for {}'.format(name))
|
||||
result = self._get_response(name, query)
|
||||
if result:
|
||||
logging.debug('BitchNS: Resolving {} to {}'.format(name, result))
|
||||
return defer.succeed(result)
|
||||
logging.debug('BitchNS: Host {} not found in zone {}'.format(name, zone))
|
||||
|
||||
return defer.fail(error.DomainError())
|
||||
73
bitchmin-server/server.py
Normal file
73
bitchmin-server/server.py
Normal file
@@ -0,0 +1,73 @@
|
||||
import logging
|
||||
|
||||
from twisted.internet import reactor
|
||||
from twisted.internet.endpoints import TCP4ServerEndpoint
|
||||
from twisted.names import client, dns, server
|
||||
|
||||
from resolvers.memory_resolver import MemoryResolver
|
||||
from servers.worker_server import WorkerServerFactory
|
||||
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
|
||||
PORT = 10053
|
||||
WORKER_PORT = 10054
|
||||
|
||||
|
||||
def main():
|
||||
zones = {
|
||||
'bitchmints.com': {
|
||||
'serial': 'BOOO',
|
||||
'admin': 'Ferg@lMoran.me',
|
||||
'nameservers': [
|
||||
'ns1.bitchmints.com',
|
||||
'ns2.bitchmints.com'
|
||||
],
|
||||
'hosts': {
|
||||
'ns1': {'type': 'A', 'ttl': 30, 'ip': '10.1.33.7'},
|
||||
'ns2': {'type': 'A', 'ttl': 30, 'ip': '10.1.33.8'},
|
||||
'host-1': {'type': 'A', 'ttl': 30, 'ip': '10.1.33.1'},
|
||||
'host-2': {'type': 'A', 'ttl': 30, 'ip': '10.1.33.1'},
|
||||
'host-3': {'type': 'A', 'ttl': 30, 'ip': '10.1.33.1'},
|
||||
'host-4': {'type': 'A', 'ttl': 30, 'ip': '10.1.33.1'},
|
||||
}
|
||||
},
|
||||
'fergl.ie': {
|
||||
'serial': 'BOOO',
|
||||
'admin': 'Ferg@lMoran.me',
|
||||
'nameservers': [
|
||||
'ns1.bitchmints.com',
|
||||
'ns2.bitchmints.com'
|
||||
],
|
||||
'hosts': {
|
||||
'farts': {'type': 'A', 'ttl': 30, 'ip': '10.1.33.8'},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
memory_resolver = MemoryResolver(zones)
|
||||
|
||||
dns_factory = server.DNSServerFactory(
|
||||
clients=[
|
||||
memory_resolver,
|
||||
client.Resolver(resolv='/etc/resolv.conf')]
|
||||
|
||||
)
|
||||
|
||||
protocol = dns.DNSDatagramProtocol(controller=dns_factory)
|
||||
|
||||
logging.info('BitchNS: Starting UDP/ns listener on {}'.format(PORT))
|
||||
reactor.listenUDP(PORT, protocol)
|
||||
|
||||
logging.info('BitchNS: Starting TCP/ns listener on {}'.format(PORT))
|
||||
reactor.listenTCP(PORT, dns_factory)
|
||||
|
||||
logging.info('BitchNS: Starting TCP/worker listener on {}'.format(WORKER_PORT))
|
||||
worker_factory = TCP4ServerEndpoint(reactor, WORKER_PORT)
|
||||
worker_factory.listen(WorkerServerFactory(memory_resolver))
|
||||
|
||||
reactor.run()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
logging.debug('BitchNS: Server starting')
|
||||
raise SystemExit(main())
|
||||
0
bitchmin-server/servers/__init__.py
Normal file
0
bitchmin-server/servers/__init__.py
Normal file
BIN
bitchmin-server/servers/__pycache__/__init__.cpython-38.pyc
Normal file
BIN
bitchmin-server/servers/__pycache__/__init__.cpython-38.pyc
Normal file
Binary file not shown.
BIN
bitchmin-server/servers/__pycache__/worker_server.cpython-38.pyc
Normal file
BIN
bitchmin-server/servers/__pycache__/worker_server.cpython-38.pyc
Normal file
Binary file not shown.
59
bitchmin-server/servers/worker_server.py
Normal file
59
bitchmin-server/servers/worker_server.py
Normal file
@@ -0,0 +1,59 @@
|
||||
from twisted.internet.protocol import Protocol, Factory
|
||||
|
||||
from resolvers.commands import AddRecordCommand
|
||||
import logging
|
||||
|
||||
|
||||
class WorkerServerFactory(Factory):
|
||||
def __init__(self, resolver):
|
||||
self._resolver = resolver
|
||||
|
||||
def buildProtocol(self, addr):
|
||||
return WorkerServer(self._resolver)
|
||||
|
||||
|
||||
class UnknownCommandException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class WorkerServer(Protocol):
|
||||
def __init__(self, resolver):
|
||||
self._resolver = resolver
|
||||
|
||||
def dataReceived(self, data):
|
||||
logging.debug('BitchNS: Command received: {}'.format(data))
|
||||
try:
|
||||
command = self.__parse_data(data)
|
||||
logging.debug('BitchNS: {}'.format(command))
|
||||
self._resolver.add_host(
|
||||
record_type=command.type,
|
||||
zone=command.zone,
|
||||
host=command.host,
|
||||
ip=command.ip,
|
||||
ttl=command.ttl
|
||||
)
|
||||
self.__send_response(str(command))
|
||||
|
||||
except UnknownCommandException as e:
|
||||
self.__send_response('Unable to parse command: {0}'.format(e))
|
||||
|
||||
def __send_response(self, response):
|
||||
self.transport.write(bytes(
|
||||
'{}\n'.format(
|
||||
response
|
||||
).encode('utf-8')
|
||||
))
|
||||
|
||||
@staticmethod
|
||||
def __parse_data(data):
|
||||
parts = data.decode('utf-8').split('|')
|
||||
if parts[0] == 'A':
|
||||
return AddRecordCommand(
|
||||
record_type=parts[1],
|
||||
zone=parts[2],
|
||||
host=parts[3],
|
||||
ip=parts[4],
|
||||
ttl=parts[5]
|
||||
)
|
||||
|
||||
raise UnknownCommandException(data)
|
||||
Reference in New Issue
Block a user