From 107be1af8c4582be12d62f1b99d8bb45996f6324 Mon Sep 17 00:00:00 2001 From: Amritanshu Date: Tue, 3 Dec 2013 00:11:03 +0530 Subject: [PATCH] Initial commit but hopefully working application. --- .gitignore | 7 +++++ CHANGES.txt | 4 +++ MANIFEST.in | 2 ++ README.txt | 10 +++++++ bifrost/__init__.py | 39 ++++++++++++++++++++++++++++ bifrost/views.py | 58 +++++++++++++++++++++++++++++++++++++++++ development.ini | 63 +++++++++++++++++++++++++++++++++++++++++++++ production.ini | 63 +++++++++++++++++++++++++++++++++++++++++++++ setup.cfg | 27 +++++++++++++++++++ setup.py | 38 +++++++++++++++++++++++++++ 10 files changed, 311 insertions(+) create mode 100644 .gitignore create mode 100644 CHANGES.txt create mode 100644 MANIFEST.in create mode 100644 README.txt create mode 100644 bifrost/__init__.py create mode 100644 bifrost/views.py create mode 100644 development.ini create mode 100644 production.ini create mode 100644 setup.cfg create mode 100644 setup.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bc9f961 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +*.pyc +env +.project +.pydevproject +*/__pycache__/ +.idea/ +*.egg-info/ \ No newline at end of file diff --git a/CHANGES.txt b/CHANGES.txt new file mode 100644 index 0000000..35a34f3 --- /dev/null +++ b/CHANGES.txt @@ -0,0 +1,4 @@ +0.0 +--- + +- Initial version diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..c4329a1 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,2 @@ +include *.txt *.ini *.cfg *.rst +recursive-include bifrost *.txt diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..ca7440d --- /dev/null +++ b/README.txt @@ -0,0 +1,10 @@ +This application provides a dyndns server style updater for using webfaction domain as dyndns. +the url to be used in updating is as follows +http://[USERNAME]:[PASSWORD]@tanshu.com/update?hostname=[DOMAIN] +or with custom ip address +http://[USERNAME]:[PASSWORD]@tanshu.com/update?hostname=[DOMAIN]&ip=[IP] +The users are maintained in the .htpasswd file in the production.ini directory. +The way to add/update users is "htpasswd .htpasswd [USERNAME] +the domains should be in the "dns_info" file in the format "domain:ipaddress" +The location of both the files can be changed in the ini file. +Also, the webfaction username and password needs to be configured in the ini file. \ No newline at end of file diff --git a/bifrost/__init__.py b/bifrost/__init__.py new file mode 100644 index 0000000..1ae9386 --- /dev/null +++ b/bifrost/__init__.py @@ -0,0 +1,39 @@ +from crypt import crypt +import os +from pyramid.authentication import BasicAuthAuthenticationPolicy +from pyramid.authorization import ACLAuthorizationPolicy +from pyramid.config import Configurator +from pyramid.security import Authenticated, Allow, Everyone + + +def main(global_config, **settings): + config = Configurator(settings=settings, authentication_policy=BasicAuthAuthenticationPolicy(htpasswd), + authorization_policy=ACLAuthorizationPolicy(), root_factory='bifrost.RootFactory') + config.add_route('update', '/update') + config.scan() + return config.make_wsgi_app() + + +def htpasswd(username, password, request): + settings = request.registry.settings + file = settings['biforst.auth'] + if not os.path.isfile(file): + return None + users = {} + with open(file) as f: + for line in f: + login, pwd = line.split(':') + users[login] = pwd.rstrip('\n') + if username in users and crypt(password, users[username]) == users[username]: + return [Authenticated] + return None + + +class RootFactory(object): + @property + def __acl__(self): + acl = [(Allow, Authenticated, Authenticated)] + return acl + + def __init__(self, request): + pass diff --git a/bifrost/views.py b/bifrost/views.py new file mode 100644 index 0000000..0b7115f --- /dev/null +++ b/bifrost/views.py @@ -0,0 +1,58 @@ +import logging +import os +import xmlrpc.client + +from pyramid.httpexceptions import HTTPUnauthorized +from pyramid.security import forget, Authenticated +from pyramid.view import view_config, forbidden_view_config + +log = logging.getLogger('bifrost.updater') + + +@view_config(route_name='update', request_param='domain', renderer='json', permission=Authenticated) +def update_view(request): + file = request.registry.settings['biforst.file'] + username = request.registry.settings['webfaction.username'] + password = request.registry.settings['webfaction.password'] + current_ip = request.GET.get('ip',None) + if current_ip is None: + current_ip = request.remote_addr + domain = request.GET['domain'] + db = load(file) + + if domain in db and db[domain] != current_ip: + server = xmlrpc.client.ServerProxy('https://api.webfaction.com/') + session_id, account = server.login(username, password) + server.delete_dns_override(session_id, domain) + server.create_dns_override(session_id, domain, current_ip, '', '', '', '') + db[domain] = current_ip + update(file, db) + + log.info('{0} updated to {1} at {3}'.format(domain, current_ip)) + else: + log.info('{0} not updated'.format(domain)) + return {} + + +def load(file): + if not os.path.isfile(file): + return {} + + current = {} + with open(file) as f: + for line in f: + domain, ip = line.split(':') + current[domain] = ip.rstrip('\n') + return current + + +def update(file, db): + with open(file, 'w') as f: + f.writelines(['{0}:{1}'.format(key, value) for key, value in db.items()]) + + +@forbidden_view_config() +def basic_challenge(request): + response = HTTPUnauthorized() + response.headers.update(forget(request)) + return response \ No newline at end of file diff --git a/development.ini b/development.ini new file mode 100644 index 0000000..4b800a7 --- /dev/null +++ b/development.ini @@ -0,0 +1,63 @@ +### +# app configuration +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html +### + +[app:main] +use = egg:bifrost + +pyramid.reload_templates = false +pyramid.debug_authorization = false +pyramid.debug_notfound = false +pyramid.debug_routematch = false +pyramid.default_locale_name = en +biforst.file = %(here)s/dns_info +biforst.auth = %(here)s/.htpasswd +webfaction.username = +webfaction.password = + +### +# wsgi server configuration +### + +[server:main] +use = egg:waitress#main +host = 0.0.0.0 +port = 6543 + +### +# logging configuration +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html +### + +[loggers] +keys = root, bifrost, bifrost.updater + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = INFO +handlers = console + +[logger_bifrost] +level = DEBUG +handlers = +qualname = bifrost + +[logger_bifrost.updater] +level = INFO +handlers = +qualname = bifrost.updater + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s diff --git a/production.ini b/production.ini new file mode 100644 index 0000000..b229ecf --- /dev/null +++ b/production.ini @@ -0,0 +1,63 @@ +### +# app configuration +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html +### + +[app:main] +use = egg:bifrost + +pyramid.reload_templates = false +pyramid.debug_authorization = false +pyramid.debug_notfound = false +pyramid.debug_routematch = false +pyramid.default_locale_name = en +biforst.file = %(here)s/dns_info +biforst.auth = %(here)s/.htpasswd +webfaction.username = +webfaction.password = + +### +# wsgi server configuration +### + +[server:main] +use = egg:waitress#main +host = 0.0.0.0 +port = 6543 + +### +# logging configuration +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html +### + +[loggers] +keys = root, bifrost, bifrost.updater + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console + +[logger_bifrost] +level = WARN +handlers = +qualname = bifrost + +[logger_bifrost.updater] +level = INFO +handlers = +qualname = bifrost.updater + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..d6c4386 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,27 @@ +[nosetests] +match = ^test +nocapture = 1 +cover-package = bifrost +with-coverage = 1 +cover-erase = 1 + +[compile_catalog] +directory = bifrost/locale +domain = bifrost +statistics = true + +[extract_messages] +add_comments = TRANSLATORS: +output_file = bifrost/locale/bifrost.pot +width = 80 + +[init_catalog] +domain = bifrost +input_file = bifrost/locale/bifrost.pot +output_dir = bifrost/locale + +[update_catalog] +domain = bifrost +input_file = bifrost/locale/bifrost.pot +output_dir = bifrost/locale +previous = true diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..19792de --- /dev/null +++ b/setup.py @@ -0,0 +1,38 @@ +import os + +from setuptools import setup, find_packages + +here = os.path.abspath(os.path.dirname(__file__)) +README = open(os.path.join(here, 'README.txt')).read() +CHANGES = open(os.path.join(here, 'CHANGES.txt')).read() + +requires = [ + 'pyramid', + 'waitress', + ] + +setup(name='bifrost', + version='0.0', + description='bifrost', + long_description=README + '\n\n' + CHANGES, + classifiers=[ + "Programming Language :: Python", + "Framework :: Pyramid", + "Topic :: Internet :: WWW/HTTP", + "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", + ], + author='', + author_email='', + url='', + keywords='web pyramid pylons', + packages=find_packages(), + include_package_data=True, + zip_safe=False, + install_requires=requires, + tests_require=requires, + test_suite="bifrost", + entry_points="""\ + [paste.app_factory] + main = bifrost:main + """, + )