Changeset - 20c97cbcdaa8
[Not reviewed]
Merge default
28 34 504
Dennis Fink - 9 years ago 2015-10-19 21:44:39
dennis.fink@c3l.lu
Merged version_5 into default
43 files changed:
Changeset was too big and was cut off... Show full diff anyway
0 comments (0 inline, 0 general)
.bowerrc
Show inline comments
 
new file 100644
 
{
 
  "directory": "./ennstatus/static/js"
 
}
.hgignore
Show inline comments
 
@@ -2,17 +2,17 @@ syntax: glob
 

	
 
*.pyc
 
__pycache__/*
 
*~
 
*.s[a-w][a-z]
 
*.un~
 
Session.vim
 
.netrwhist
 
log/*
 
vnstats/*
 

	
 
docs/build/*
 
test/*
 

	
 
config.py
 
run.py
 
*.csv
 
*.json
dev-requirements.in
Show inline comments
 
Flask-Script==2.0.5
 
Sphinx==1.2.3
 
pip-tools==0.3.5
 
pip-tools==1.1.3
dev-requirements.txt
Show inline comments
 
#
 
# This file is autogenerated by pip-compile
 
# Make changes in dev-requirements.in, then run this to update:
 
#
 
#    pip-compile dev-requirements.in
 
#
 
click==5.1                # via pip-tools
 
docutils==0.12            # via sphinx
 
first==2.0.1              # via pip-tools
 
Flask-Script==2.0.5
 
pip-tools==0.3.5
 
flask==0.10.1             # via flask-script
 
itsdangerous==0.24        # via flask
 
jinja2==2.8               # via flask, sphinx
 
markupsafe==0.23          # via jinja2
 
pip-tools==1.1.3
 
pygments==2.0.2           # via sphinx
 
six==1.9.0                # via pip-tools
 
Sphinx==1.2.3
 
werkzeug==0.10.4          # via flask
ennstatus/__init__.py
Show inline comments
 
import os.path
 
import json
 

	
 
from flask import Flask, render_template
 
from flask.ext.bootstrap import Bootstrap
 
from flask.ext.compress import Compress
 
from flask.ext.wtf import CsrfProtect
 
from flask.ext.moment import Moment
 

	
 
from werkzeug.contrib.fixers import ProxyFix
 

	
 
import gnupg
 

	
 
bootstrap = Bootstrap()
 
compress = Compress()
 
csrf = CsrfProtect()
 
moment = Moment()
 

	
 
config_file = os.path.abspath('config.json')
 

	
 

	
 
def create_app():
 

	
 
    app = Flask(__name__)
 

	
 
    config_file = os.path.abspath('config.py')
 
    app.config.from_pyfile(config_file)
 
    import ennstatus.config as config
 
    config.init_app(app)
 

	
 
    if not hasattr(app.config, 'from_json'):
 
        def from_json(file, silent=True):
 
            try:
 
                with open(file, encoding='utf-8') as json_file:
 
                    obj = json.load(json_file)
 
            except IOError:
 
                if silent:
 
                    return False
 
                raise
 

	
 
            for key in obj:
 
                if key.isupper():
 
                    app.config[key] = obj[key]
 

	
 
            return True
 

	
 
        app.config.from_json = from_json
 

	
 
    app.config.from_json(config_file)
 

	
 
    app.wsgi_app = ProxyFix(app.wsgi_app)
 

	
 
    bootstrap.init_app(app)
 
    compress.init_app(app)
 
    csrf.init_app(app)
 
    moment.init_app(app)
 

	
 
    from .status.functions import mail
 
    mail.init_app(app)
 

	
 
    from .root.views import root_page
 
    app.register_blueprint(root_page)
 

	
 
    from .api.views import api_page
 
    app.register_blueprint(api_page, url_prefix='/api')
 

	
 
    from .donate.views import donate_page
 
    app.register_blueprint(donate_page, url_prefix='/donate')
 

	
 
    from .status.views import status_page
 
    app.register_blueprint(status_page, url_prefix='/status')
 

	
 
    from .stats.views import stats_page
 
    app.register_blueprint(stats_page, url_prefix='/stats')
 
    from .statistics.views import statistics_page
 
    app.register_blueprint(statistics_page, url_prefix='/stats')
 

	
 
    from .log import init_logging
 
    init_logging(app)
 

	
 
    if 'ENNSTATUS_GPG_HOME' in app.config:
 
        gpg = gnupg.GPG(gnupghome=app.config['ENNSTATUS_GPG_HOME'])
 
        gpg.encoding = 'utf-8'
 
        app.extensions['gnupg'] = gpg
 
    else:
 
        app.extensions['gnupg'] = False
 

	
 
    @app.errorhandler(404)
ennstatus/api/auth.py
Show inline comments
 
new file 100644
 
from flask import current_app
 
from flask.ext.httpauth import HTTPDigestAuth
 

	
 
httpauth = HTTPDigestAuth()
 

	
 

	
 
@httpauth.get_password
 
def get_pw(username):
 

	
 
    if username in current_app.config['ENNSTATUS_SERVERS']:
 
        return current_app.confg['ENNSTATUS_SERVERS'][username]['PASSWORD']
 

	
 
    return None
ennstatus/api/functions.py
Show inline comments
 
deleted file
ennstatus/api/model.py
Show inline comments
 
new file 100644
 
import ipaddress
 
import json
 
import functools
 
import statistics
 

	
 
from pathlib import Path
 
from datetime import datetime
 

	
 
import jsonschema
 
import strict_rfc3339
 
import requests
 

	
 
from flask import current_app
 
from pkg_resources import resource_filename
 

	
 
from ..utils import check_ip
 

	
 

	
 
schema = json.load(
 
    open(
 
        resource_filename('ennstatus.api', 'schema/server.json'),
 
        encoding='utf-8'
 
    )
 
)
 

	
 
validate = functools.partial(
 
    jsonschema.validate,
 
    schema=schema,
 
    format_checker=jsonschema.FormatChecker()
 
)
 

	
 

	
 
def calculate_weight(data):
 

	
 
    obj = {}
 

	
 
    for subkey in ('1_week', '1_month', '3_months', '1_year', '5_years'):
 

	
 
        subdata = data[subkey]
 
        factor = subdata['factor']
 

	
 
        values = [x * factor for x in subdata['values'] if x is not None]
 

	
 
        if values:
 
            obj[subkey] = statistics.mean(values) * 100
 
        else:
 
            obj[subkey] = None
 

	
 
    return obj
 

	
 

	
 
class ServerEncoder(json.JSONEncoder):
 

	
 
    def default(self, obj):
 

	
 
        if isinstance(obj, (ipaddress.IPv4Address, ipaddress.IPv6Address)):
 
            return str(obj)
 

	
 
        if isinstance(obj, datetime):
 
            return strict_rfc3339.timestamp_to_rfc3339_utcoffset(
 
                obj.timestamp()
 
            )
 

	
 
        return json.JSONEncoder.default(self, obj)
 

	
 

	
 
class ServerDecoder(json.JSONDecoder):
 

	
 
    def decode(self, json_string):
 

	
 
        default_obj = super().decode(json_string)
 

	
 
        for key in ('ip', 'ip6'):
 
            if key in default_obj:
 
                current_app.logger.debug('{}: {}'.format(
 
                    key, default_obj[key]
 
                )
 
                )
 
                default_obj[key] = ipaddress.ip_address(default_obj[key])
 

	
 
        current_app.logger.debug('Loading last_updated')
 
        default_obj['last_updated'] = datetime.fromtimestamp(
 
            strict_rfc3339.rfc3339_to_timestamp(default_obj['last_updated'])
 
        )
 

	
 
        return default_obj
 

	
 

	
 
class Server:
 

	
 
    def __init__(self, *args, **kwargs):
 

	
 
        self.name = kwargs['name']
 
        self.type = kwargs['type']
 
        self.status = kwargs.get('status')
 
        self.fingerprint = kwargs['fingerprint']
 
        self.last_updated = kwargs['last_updated']
 
        self.country = kwargs['country']
 
        self.bandwith = kwargs.get('bandwith')
 

	
 
        if self.type == 'bridge':
 
            self.obfs = kwargs.get('obfs')
 
            self.fteproxy = kwargs.get('fteproxy')
 
            self.flashproxy = kwargs.get('flashproxy')
 
            self.meek = kwargs.get('meek')
 
        else:
 
            self.ip = kwargs['ip']
 

	
 
            if 'ip6' in kwargs:
 
                self.ip6 = kwargs['ip6']
 

	
 
            default_weights = {
 
                '1_week': None,
 
                '1_month': None,
 
                '3_months': None,
 
                '1_year': None,
 
                '5_years': None
 
            }
 

	
 
            self.mean_consensus_weight = kwargs.get(
 
                'mean_consensus_weight',
 
                default_weights
 
            )
 
            self.mean_guard_probability = kwargs.get(
 
                'mean_guard_probability',
 
                default_weights
 
            )
 
            self.mean_exit_probability = kwargs.get(
 
                'mean_exit_probability',
 
                default_weights
 
            )
 
            self.mean_consensus_weight_fraction = kwargs.get(
 
                'mean_consensus_weight_fraction',
 
                default_weights
 
            )
 
            self.mean_middle_probability = kwargs.get(
 
                'mean_middle_probability',
 
                default_weights
 
            )
 

	
 
    @classmethod
 
    def from_file_by_name(cls, name):
 

	
 
        filepath = Path('data') / (name.lower() + '.json')
 

	
 
        current_app.logger.info('Loading {}'.format(str(filepath)))
 
        if filepath.exists() and filepath.is_file():
 
            try:
 
                with filepath.open(encoding='utf-8') as f:
 
                    data = json.load(f, cls=ServerDecoder)
 
            except (IOError, ValueError):
 
                current_app.logger.error('IOError or ValueError')
 
                return False
 
            else:
 
                return cls(**data)
 
        else:
 
            current_app.logger.error('File error!')
 
            return False
 

	
 
    @classmethod
 
    def from_json(cls, server):
 

	
 
        try:
 
            if cls.check_json_format(json.loads(server)):
 
                decoded = json.loads(server, cls=ServerDecoder)
 
                return cls(**decoded)
 
        except (jsonschema.ValidationError, ValueError) as e:
 
            raise e
 

	
 
    @classmethod
 
    def from_dict(cls, server):
 
        return cls.from_json(json.dumps(server))
 

	
 
    def json(self):
 
        return json.dumps(self.__dict__, cls=ServerEncoder)
 

	
 
    @staticmethod
 
    def check_json_format(server):
 

	
 
        try:
 
            validate(server)
 
        except jsonschema.ValidationError as e:
 
            raise e
 

	
 
        for key in ('ip', 'ip6'):
 
            if key in server:
 
                address = ipaddress.ip_address(server[key])
 
                if not check_ip(address):
 
                    raise ValueError('{} is not accepted!\n'.format(key))
 
        return True
 

	
 
    def save(self):
 

	
 
        filepath = Path('data') / (self.name.lower() + '.json')
 

	
 
        try:
 
            with filepath.open(mode='w', encoding='utf-8') as f:
 
                json.dump(self.__dict__, f, cls=ServerEncoder)
 
        except Exception as e:
 
            raise e
 

	
 
    def update_weights(self):
 

	
 
        if self.type not in ('exit', 'relay'):
 
            raise NotImplementedError
 

	
 
        url = 'https://onionoo.torproject.org/weights?lookup={}'.format(
 
            self.fingerprint
 
        )
 

	
 
        data = requests.get(url)
 

	
 
        try:
 
            data.raise_for_status()
 
        except requests.HTTPError as e:
 
            raise e
 
        else:
 
            data = data.json()['relays'][0]
 

	
 
        self.mean_consensus_weight = calculate_weight(data['consensus_weight'])
 
        self.mean_exit_probability = calculate_weight(data['exit_probability'])
 
        self.mean_guard_probability = calculate_weight(
 
            data['guard_probability']
 
        )
 
        self.mean_middle_probability = calculate_weight(
 
            data['middle_probability']
 
        )
 
        self.mean_consensus_weight_fraction = calculate_weight(
 
            data['consensus_weight_fraction']
 
        )
 

	
 
    def check_status(self):
 

	
 
        now = datetime.utcnow()
 
        delta = now - self.last_updated
 

	
 
        if delta.seconds >= 3600:
 
            self.status = False
 
        elif delta.seconds >= 600:
 
            self.status = None
ennstatus/api/schema/server.json
Show inline comments
 
new file 100644
 
{
 
    "title": "Relay/Exit schema",
 
    "$schema": "http://json-schema.org/draft-04/schema#",
 
    "type": "object",
 
    "properties": {
 
        "name": {
 
            "type": "string"
 
        },
 
        "status": {
 
            "$ref": "#/definitions/status_data"
 
        },
 
        "country": {
 
            "type": "string",
 
            "enum": [
 
                "Afghanistan", "Aland Islands", "Albania", "Algeria", 
 
                "American Samoa", "Andorra", "Angola", "Anguilla",
 
                "Anonymous Proxy", "Antarctica", "Antigua and Barbuda",
 
                "Argentina", "Armenia", "Aruba", "Asia/Pacific Region",
 
                "Australia", "Austria", "Azerbaijan", "Bahamas", "Bahrain",
 
                "Bangladesh", "Barbados", "Belarus", "Belgium", "Belize",
 
                "Benin", "Bermuda", "Bhutan", "Bolivia",
 
                "Bonaire, Sint Eustatius and Saba", "Bosnia and Herzegovina",
 
                "Botswana", "Bouvet Island", "Brazil",
 
                "British Indian Ocean Territory", "Brunei Darussalam",
 
                "Bulgaria", "Burkina Faso", "Burundi", "Cambodia",
 
                "Cameroon", "Canada", "Cape Verde", "Cayman Islands",
 
                "Central African Republic", "Chad", "Chile", "China",
 
                "Christmas Island", "Cocos (Keeling) Islands", "Colombia",
 
                "Comoros", "Congo", "Congo, The Democratic Republic of the",
 
                "Cook Islands", "Costa Rica", "Cote D'Ivoire", "Croatia",
 
                "Cuba", "Cyprus", "Czech Republic", "Denmark", "Djibouti",
 
                "Dominica", "Dominican Republic", "Ecuador", "Egypt",
 
                "El Salvador", "Equatorial Guinea", "Eritrea", "Estonia",
 
                "Ethiopia", "Europe", "Falkland Islands (Malvinas)",
 
                "Faroe Islands", "Fiji", "Finland", "France",
 
                "France, Metropolitan", "French Guiana", "French Polynesia",
 
                "French Southern Territories", "Gabon", "Gambia", "Georgia",
 
                "Germany", "Ghana", "Gibraltar", "Greece", "Greenland",
 
                "Grenada", "Guadeloupe", "Guam", "Guatemala", "Guernsey",
 
                "Guinea", "Guinea-Bissau", "Guyana", "Haiti",
 
                "Heard Island and McDonald Islands",
 
                "Holy See (Vatican City State)", "Honduras", "Hong Kong",
 
                "Hungary", "Iceland", "India", "Indonesia",
 
                "Iran, Islamic Republic of", "Iraq", "Ireland", "Isle of Man",
 
                "Israel", "Italy", "Jamaica", "Japan", "Jersey", "Jordan",
 
                "Kazakhstan", "Kenya", "Kiribati",
 
                "Korea, Democratic People's Republic of",
 
                "Korea, Republic of", "Kuwait", "Kyrgyzstan",
 
                "Lao People's Democratic Republic", "Latvia", "Lebanon",
 
                "Lesotho", "Liberia", "Libya", "Liechtenstein", "Lithuania",
 
                "Luxembourg", "Macau", "Macedonia", "Madagascar", "Malawi",
 
                "Malaysia", "Maldives", "Mali", "Malta", "Marshall Islands",
 
                "Martinique", "Mauritania", "Mauritius", "Mayotte", "Mexico",
 
                "Micronesia, Federated States of", "Moldova, Republic of",
 
                "Monaco", "Mongolia", "Montenegro", "Montserrat", "Morocco",
 
                "Mozambique", "Myanmar", "Namibia", "Nauru", "Nepal",
 
                "Netherlands", "Netherlands Antilles", "New Caledonia",
 
                "New Zealand", "Nicaragua", "Niger", "Nigeria", "Niue",
 
                "Norfolk Island", "Northern Mariana Islands", "Norway",
 
                "Oman", "Other", "Pakistan", "Palau", "Palestinian Territory",
 
                "Panama", "Papua New Guinea", "Paraguay", "Peru",
 
                "Philippines", "Pitcairn Islands", "Poland", "Portugal",
 
                "Puerto Rico", "Qatar", "Reunion", "Romania",
 
                "Russian Federation", "Rwanda", "Saint Barthelemy",
 
                "Saint Helena", "Saint Kitts and Nevis", "Saint Lucia",
 
                "Saint Martin", "Saint Pierre and Miquelon",
 
                "Saint Vincent and the Grenadines", "Samoa", "San Marino",
 
                "Sao Tome and Principe", "Satellite Provider", "Saudi Arabia",
 
                "Senegal", "Serbia", "Seychelles", "Sierra Leone", "Singapore",
 
                "Slovakia", "Slovenia", "Solomon Islands", "Somalia",
 
                "South Africa", "South Georgia and the South Sandwich Islands",
 
                "South Sudan", "Spain", "Sri Lanka", "Sudan", "Suriname",
 
                "Svalbard and Jan Mayen", "Swaziland", "Sweden", "Switzerland",
 
                "Syrian Arab Republic", "Taiwan", "Tajikistan",
 
                "Tanzania, United Republic of", "Thailand", "Timor-Leste",
 
                "Togo", "Tokelau", "Tonga", "Trinidad and Tobago", "Tunisia",
 
                "Turkey", "Turkmenistan", "Turks and Caicos Islands", "Tuvalu",
 
                "Uganda", "Ukraine", "United Arab Emirates", "United Kingdom",
 
                "United States", "United States Minor Outlying Islands",
 
                "Uruguay", "Uzbekistan", "Vanuatu", "Venezuela", "Vietnam",
 
                "Virgin Islands, British", "Virgin Islands, U.S.",
 
                "Wallis and Futuna", "Western Sahara", "Yemen", "Zambia",
 
                "Zimbabwe"
 
            ]
 
        },
 
        "fingerprint": {
 
            "type": "string",
 
            "pattern": "^[a-zA-Z0-9]{40}$"
 
        },
 
        "last_updated": {
 
            "type": "string",
 
            "format": "date-time"
 
        },
 
        "bandwith": {
 
            "type": [
 
                "string",
 
                "null"
 
            ],
 
            "default": null
 
        },
 
        "type": {
 
            "type": "string"
 
        }
 
    },
 
    "required": [
 
        "name",
 
        "status",
 
        "last_updated",
 
        "country",
 
        "fingerprint",
 
        "type"
 
    ],
 
    "definitions": {
 
        "weights": {
 
            "type": "object",
 
            "properties": {
 
                "1_week": {
 
                    "$ref": "#/definitions/weights_data"
 
                },
 
                "1_month": {
 
                    "$ref": "#/definitions/weights_data"
 
                },
 
                "3_months": {
 
                    "$ref": "#/definitions/weights_data"
 
                },
 
                "1_year": {
 
                    "$ref": "#/definitions/weights_data"
 
                },
 
                "5_years": {
 
                    "$ref": "#/definitions/weights_data"
 
                }
 
            },
 
            "required": [
 
                "1_week",
 
                "1_month",
 
                "3_months",
 
                "1_year",
 
                "5_years"
 
            ],
 
            "additionalProperties": false
 
        },
 
        "weights_data": {
 
            "type": [
 
                "number",
 
                "null"
 
            ],
 
            "default": null
 
        },
 
        "status_data": {
 
            "type": [
 
                "boolean",
 
                "null"
 
            ],
 
            "default": null
 
        }
 
    },
 
    "anyOf": [
 
        {
 
            "properties": {
 
                "type": {
 
                    "enum": [
 
                        "exit",
 
                        "relay"
 
                    ]
 
                },
 
                "ip": {
 
                    "type": "string",
 
                    "format": "ipv4"
 
                },
 
                "ip6": {
 
                    "type": "string",
 
                    "format": "ipv6"
 
                },
 
                "mean_consensus_weight": {
 
                    "$ref": "#/definitions/weights"
 
                },
 
                "mean_guard_probability": {
 
                    "$ref": "#/definitions/weights"
 
                },
 
                "mean_exit_probability": {
 
                    "$ref": "#/definitions/weights"
 
                },
 
                "mean_middle_probability": {
 
                    "$ref": "#/definitions/weights"
 
                },
 
                "mean_consensus_weight_fraction": {
 
                    "$ref": "#/definitions/weights"
 
                }
 
            },
 
            "required": [
 
                "ip"
 
            ],
 
            "not": {
 
                "required": [
 
                    "obfs",
 
                    "fteproxy",
 
                    "flashproxy",
 
                    "meek"
 
                ]
 
            }
 
        },
 
        {
 
            "properties": {
 
                "type": {
 
                    "enum": [
 
                        "bridge"
 
                    ]
 
                },
 
                "obfs": {
 
                    "type": [
 
                        "boolean",
 
                        "null"
 
                    ]
 
                },
 
                "fteproxy": {
 
                    "type": [
 
                        "boolean",
 
                        "null"
 
                    ]
 
                },
 
                "flashproxy": {
 
                    "type": [
 
                        "boolean",
 
                        "null"
 
                    ]
 
                },
 
                "meek": {
 
                    "type": [
 
                        "boolean",
 
                        "null"
 
                    ]
 
                }
 
            },
 
            "required": [
 
                "obfs",
 
                "fteproxy",
 
                "flashproxy",
 
                "meek"
 
            ],
 
            "not": {
 
                "required": [
 
                    "ip",
 
                    "ip6",
 
                    "mean_consensus_weight",
 
                    "mean_guard_probability",
 
                    "mean_exit_probability"
 
                ]
 
            }
 
        }
 
    ]
 
} 
ennstatus/api/views.py
Show inline comments
 
import ipaddress
 
import json
 

	
 
from datetime import datetime
 

	
 
from flask import (Blueprint, request, current_app, jsonify, render_template,
 
                   abort)
 

	
 
from ennstatus.api.functions import check_json_format, update_server
 
from werkzeug.exceptions import BadRequest
 

	
 
import strict_rfc3339
 
import pygeoip
 
import requests
 

	
 
from ennstatus.status.functions import (single_server, all_servers,
 
                                        all_servers_by_type)
 
from .model import Server
 
from .auth import httpauth
 

	
 
api_page = Blueprint('api', __name__)
 
gi4 = pygeoip.GeoIP('/usr/share/GeoIP/GeoIP.dat', pygeoip.MEMORY_CACHE)
 
gi6 = pygeoip.GeoIP('/usr/share/GeoIP/GeoIPv6.dat', pygeoip.MEMORY_CACHE)
 

	
 

	
 
@api_page.route('/update', methods=('POST',))
 
@httpauth.login_required
 
def update():
 

	
 
    current_app.logger.info('Handling update')
 
    if current_app.debug:
 
        accepted_ips = ['127.0.0.1']
 
    else:
 
        accepted_ips = current_app.config.get('ENNSTATUS_ACCEPTED_IPS', [])
 

	
 
    try:
 
        servers = current_app.config['ENNSTATUS_SERVERS']
 
    except KeyError as e:
 
        current_app.logger.error(str(e))
 
        return abort(500)
 

	
 
    username = httpauth.username()
 

	
 
    json = request.get_json()
 
    if json is None:
 
    try:
 
        if request.remote_addr not in servers[username]['IPS']:
 
            current_app.logger.warn(
 
                'Unallowed IP {} tried to update data!'.format(
 
                    request.remote_addr
 
                )
 
            )
 
            return 'IP not allowed!\n', 403, {'Content-Type': 'text/plain'}
 
    except KeyError as e:
 
        current_app.logger.error(str(e))
 
        return abort(500)
 

	
 
    try:
 
        data = request.get_json()
 
    except BadRequest:
 
        current_app.logger.info('No JSON data supplied!')
 
        return 'No JSON data supplied!\n', 400, {'Content-Type': 'text/plain'}
 

	
 
    try:
 
        check_json_format(json)
 
    except ValueError as e:
 
        current_app.logger.warning(' '.join([str(e), str(json)]))
 
        return str(e), 409, {'Content-Type': 'text/plain'}
 
        if username != data['name'].lower():
 
            current_app.logger.warn(
 
                'Unallowed user {} tried to update {}!'.format(
 
                    username, data['name']
 
                )
 
            )
 
            return ('You are not allowed to update this server\n',
 
                    403, {'Content-Type': 'text/plain'})
 
    except KeyError:
 
        return abort(409)
 

	
 
    if 'ip' in json:
 
        ip = json['ip']
 
    elif 'ip6' in json:
 
        ip = json['ip6']
 
    if 'ip' in data:
 
        ip = data['ip']
 
    elif 'ip6' in data:
 
        ip = data['ip6']
 
    else:
 
        ip = request.remote_addr
 

	
 
    try:
 
        ip = ipaddress.ip_address(ip)
 
            temp_ip = ipaddress.ip_address(ip)
 
    except ipaddress.AddressValueError:
 
        return 'IP not allowed!\n', 403, {'Content-Type': 'text/plain'}
 
        else:
 
            if temp_ip.version == 4:
 
                data['ip'] = ip
 
            elif temp_ip.verison == 6:
 
                data['ip6'] = ip
 

	
 
    if request.remote_addr not in accepted_ips:
 
        current_app.logger.warn('Unallowed IP %s tried to update data!'
 
                                % ip)
 
        return 'IP not allowed!\n', 403, {'Content-Type': 'text/plain'}
 
    try:
 
        country = gi4.country_name_by_addr(ip)
 
    except pygeoip.GeoIPError:
 
        country = gi6.country_name_by_addr(ip)
 

	
 
    current_app.logger.info(str(json))
 
    server = update_server(server=json, ip=ip)
 
    data['country'] = country
 
    data['last_updated'] = strict_rfc3339.timestamp_to_rfc3339_utcoffset(
 
        datetime.utcnow().timestamp()
 
    )
 

	
 
    try:
 
        server = Server.from_dict(data)
 
    except Exception as e:
 
        current_app.logger.warning(' '.join([str(e), str(data)]))
 
        return str(e), 409, {'Content-Type': 'text/plain'}
 

	
 
    if server:
 
    try:
 
        server.update_weights()
 
    except NotImplementedError:
 
        pass
 
    except requests.HTTPError as e:
 
        current_app.logger.error(str(e), exc_info=True)
 
        pass
 

	
 
    try:
 
        server.save()
 
    except Exception as e:
 
        current_app.logger.error(str(e))
 
        return str(e), 500, {'Content-Type': 'text/plain'}
 

	
 
        current_app.logger.info('Return result')
 
        current_app.logger.info(str(server))
 
        return (jsonify(server), 201,
 
                {'Location': '/api/export/json/single?server_name=%s'
 
                 % server['server_name']})
 

	
 
    else:
 
        current_app.logger.error('Unexpected error: %s' % server,
 
                                 exc_info=True)
 
        return abort(500)
 
    return (
 
        server.json(), 201,
 
        {
 
            'Location': '/api/export/json/single?server_name={}'.format(
 
                server.name
 
            )
 
        }
 
    )
 

	
 

	
 
@api_page.route('/export', defaults={'server_type': 'all',
 
                                     'export_format': 'json'})
 
@api_page.route('/export/<any("json", "xml"):export_format>',
 
                defaults={'server_type': 'all'})
 
@api_page.route(('/export/<any("json", "xml"):export_format>'
 
                 '/<any("all", "exit", "bridge", "relay", "single")'
 
@api_page.route('/export', defaults={'server_type': 'all'})
 
@api_page.route(('/export/<any("all", "exit", "bridge", "relay", "single")'
 
                 ':server_type>'))
 
def export(export_format, server_type):
 
def export(server_type):
 

	
 
    current_app.logger.info('Handling export')
 
    if server_type == 'single':
 
        server_name = request.args.get('server_name', None)
 
        if server_name is not None:
 
            server = single_server(server_name)
 
            if server:
 
                if export_format == 'json':
 
                    current_app.logger.info('Returning server as json!')
 
                    return jsonify(server)
 
                else:
 
                    current_app.logger.info('Returning server as xml!')
 
                    return (
 
                        render_template(
 
                            'api/export/xml/single_server.xml',
 
                            server=server),
 
                        200, {'Content-Type': 'text/xml'})
 
                return server.json(), 200, {'Content-Type': 'application/json'}
 
            else:
 
                current_app.logger.warning('Server not found!')
 
                return ('Server not found!\n',
 
                        404, {'Content-Type': 'text/plain'})
 
        else:
 
            current_app.logger.warning('No server_name specified!')
 
            return ('No server_name specified!\n',
 
                    400, {'Content-Type': 'text/plain'})
 

	
 
    else:
 
        if server_type == 'all':
 
            current_app.logger.info('Getting all servers!')
 
            servers = list(all_servers())
 
            servers = [
 
                json.loads(
 
                    server.json()
 
                ) for server in all_servers()
 
            ]
 
        else:
 
            current_app.logger.info('Getting all %s!' % server_type)
 
            servers = list(all_servers_by_type(server_type.capitalize()))
 
            current_app.logger.info('Getting all {}!'.format(server_type))
 
            servers = [
 
                json.loads(
 
                    server.json()
 
                ) for server in all_servers_by_type(server_type.lower())
 
            ]
 

	
 
        if export_format == 'json':
 
            response = {'enn_network': servers}
 
            current_app.logger.info('Returning as json!')
 
            return jsonify(response)
 
        return json.dumps(servers), 200, {'Content-Type': 'application/json'}
 

	
 

	
 
@api_page.route('/fingerprints', defaults={'server_type': 'all'})
 
@api_page.route('/fingerprints/<any("all", "exit", "relay"):server_type>')
 
def fingerprint(server_type):
 
    if server_type == 'all':
 
        servers = [server.fingerprint for server in all_servers()
 
                   if server.type != 'bridge']
 
        else:
 
            current_app.logger.info('Returning as xml!')
 
            return (render_template('api/export/xml/network.xml',
 
                                    servers=servers),
 
                    200, {'Content-Type': 'text/xml'})
 
        servers = [server.fingerprint
 
                   for server in all_servers_by_type(server_type.lower())]
 

	
 
    return '\n'.join(servers), 200, {'Content-Type': 'text/plain'}
ennstatus/cli/__init__.py
Show inline comments
 
new file 100644
 
import pathlib
 

	
 
import click
 

	
 

	
 
@click.group()
 
@click.option('-p', '--path', default='/srv/http/enn.lu',
 
              help='Path where the config files are',
 
              type=click.Path(exists=True,
 
                              file_okay=False,
 
                              writable=True,
 
                              readable=True)
 
              )
 
@click.pass_context
 
def cli(ctx, path):
 
    ctx.obj = {}
 
    path = pathlib.Path(path)
 
    ctx.obj['path'] = path
 
    ctx.obj['config_file'] = path / 'config.json'
 
    ctx.obj['data_dir'] = path / 'data'
 

	
 

	
 
from .commands import config
 
cli.add_command(config, 'config')
 

	
 
if __name__ == '__main__':
 
    cli()
ennstatus/cli/commands/__init__.py
Show inline comments
 
new file 100644
 
from .config import config
 

	
 
__all__ = ['config']
ennstatus/cli/commands/config.py
Show inline comments
 
new file 100644
 
import json
 
import ipaddress
 

	
 
from pprint import pprint
 

	
 
import click
 

	
 
from ...utils import check_ip
 

	
 

	
 
@click.group(short_help='Configure ennstatus')
 
def config():
 
    pass
 

	
 

	
 
@config.command('show', short_help='Show current configuration')
 
@click.pass_obj
 
def show(obj):
 

	
 
    with obj['config_file'].open(encoding='utf-8') as f:
 
        config = json.load(f)
 
    pprint(config)
 

	
 

	
 
@config.group(short_help='Configure servers')
 
def server():
 
    pass
 

	
 

	
 
@server.command('add', short_help='Add server')
 
@click.argument('name')
 
@click.option('-i', '--ips', prompt='IPs (comma separated)')
 
@click.password_option()
 
@click.option('--bridgeprogram/--no-bridgeprogram', default=False)
 
@click.pass_obj
 
def add(obj, name, ips, password, bridgeprogram):
 

	
 
    with obj['config_file'].open() as f:
 
        config = json.load(f)
 

	
 
    name = name.lower()
 
    ips = [ip.strip() for ip in ips.split(',')]
 

	
 
    try:
 
        if name in config['ENNSTATUS_SERVERS']:
 
            try:
 
                click.confirm('Server already exits! Overwrite?', abort=True)
 
            except click.Abort as e:
 
                raise SystemExit from e
 
    except KeyError:
 
        config['ENNSTATUS_SERVERS'] = {}
 

	
 
    converted_ips = {ipaddress.ip_address(ip) for ip in ips}
 

	
 
    for ip in converted_ips:
 
        if not check_ip(ip):
 
            raise SystemExit('ip {} is not accepted!'.format(str(ip)))
 

	
 
    config['ENNSTATUS_SERVERS'][name] = {
 
        'IPS': ips,
 
        'PASSWORD': password
 
    }
 

	
 
    if bridgeprogram:
 
        try:
 
            config['ENNSTATUS_BRIDGE_PROGRAM'].append(name)
 
        except KeyError:
 
            config['ENNSTATUS_BRIDGE_PROGRAM'] = [name]
 

	
 
    with obj['config_file'].open(mode='w', encoding='utf-8') as f:
 
        json.dump(config, f, indent=4, sort_keys=True)
 

	
 

	
 
@server.command('delete', short_help='Remove server')
 
@click.argument('name')
 
@click.pass_obj
 
def delete(obj, name):
 

	
 
    with obj['config_file'].open() as f:
 
        config = json.load(f)
 
    name = name.lower()
 

	
 
    try:
 
        del config['ENNSTATUS_SERVERS'][name]
 
    except KeyError:
 
        raise SystemExit('{} does not exists!'.format(name))
 

	
 
    try:
 
        config['ENNSTATUS_BRIDGE_PROGRAM'].remove(name)
 
    except KeyError:
 
        pass
 

	
 
    with obj['config_file'].open(mode='w', encoding='utf-8') as f:
 
        json.dump(config, f, indent=4, sort_keys=True)
 

	
 
    filename = obj['data_dir'] / (name + '.json')
 
    try:
 
        filename.unlink()
 
    except FileNotFoundError:
 
        pass
ennstatus/config.py
Show inline comments
 
new file 100644
 
import os
 
import base64
 

	
 

	
 
def init_app(app):
 

	
 
    config = app.config
 

	
 
    _default_secret_key = base64.b64encode(os.urandom(32)).decode('utf-8')
 

	
 
    config['SECRET_KEY'] = os.environ.get('SECRET_KEY', _default_secret_key)
 

	
 
    # Flask-Bootstrap
 
    config.setdefault('BOOTSTRAP_SERVE_LOCAL', True)
 

	
 
    # ennstatus
 

	
 
    # moment.js string formatting
 
    # http://momentjs.com/docs/#/displaying/format/
 
    config.setdefault('ENNSTATUS_MOMENTJS_FORMAT', 'DD MMMM YYYY HH:mm:ss')
 

	
 
    config.setdefault('ENNSTATUS_STRFTIME_FORMAT', '%d %B %Y %H:%M:%S')
ennstatus/donate/forms.py
Show inline comments
 
from flask_wtf import Form
 
from wtforms import SelectField
 
from wtforms.validators import DataRequired
 

	
 

	
 
class DateForm(Form):
 
    year = SelectField('Year', validators=[DataRequired()])
 
    month = SelectField('Month',
 
                        choices=[('{:02d}'.format(i), str(i)) for i in range(1, 13)],
 
                        choices=[
 
                            ('{:02d}'.format(i), str(i)) for i in range(1, 13)
 
                        ],
 
                        validators=[DataRequired()])
ennstatus/donate/views.py
Show inline comments
 
from flask import (Blueprint, render_template, request,
 
                   redirect, url_for, current_app)
 

	
 
from ennstatus.donate.forms import DateForm
 
from ennstatus.donate.functions import load_csv, get_choices
 

	
 
from ennstatus.root.forms import BPMForm
 
from ennstatus.root.constants import BPM_ADDRESSES
 

	
 
donate_page = Blueprint('donate', __name__)
 

	
 

	
 
@donate_page.route('/')
 
@donate_page.route('/', methods=('GET', 'POST'))
 
def index():
 
    return render_template('donate/index.html')
 

	
 

	
 
@donate_page.route('/wiretransfer')
 
def wiretransfer():
 
    return render_template('donate/wiretransfer.html')
 

	
 

	
 
@donate_page.route('/snailmail', methods=('GET', 'POST'))
 
def snailmail():
 

	
 
    current_app.logger.info('Handling snailmail')
 
    current_app.logger.info('Handling index')
 
    form = BPMForm()
 
    country_choices = [choice[0] for choice in form.country.choices]
 

	
 
    if request.method == 'POST':
 
        current_app.logger.debug('Validating form')
 
        if form.validate_on_submit():
 
            country = form.country.data
 
            return redirect(url_for('donate.snailmail', country=country))
 
            return redirect(url_for('donate.index', country=country))
 
    else:
 
        if 'country' in request.args:
 
            country = request.args['country']
 
            if country in country_choices:
 
                current_app.logger.info('Showing country %s' % country)
 
            else:
 
                current_app.logger.warn('Country %s not found' % country)
 
                country = 'luxembourg'
 
        else:
 
            current_app.logger.info('Using default country')
 
            country = 'luxembourg'
 

	
 
    form.country.data = country
 
    address = BPM_ADDRESSES[country]
 

	
 
    return render_template('donate/snailmail.html', form=form, address=address)
 

	
 

	
 
@donate_page.route('/paypal')
 
def paypal():
 
    return render_template('donate/paypal.html')
 

	
 

	
 
@donate_page.route('/bitcoin')
 
def bitcoin():
 
    return render_template('donate/bitcoin.html')
 

	
 

	
 
@donate_page.route('/flattr')
 
def flattr():
 
    return render_template('donate/flattr.html')
 

	
 

	
 
@donate_page.route('/bpm')
 
def bpm():
 
    return render_template('donate/bpm.html')
 
    return render_template('donate/index.html', form=form, address=address)
 

	
 

	
 
@donate_page.route('/received',
 
                   methods=('GET', 'POST'))
 
def received():
 

	
 
    current_app.logger.info('Handling received')
 
    form = DateForm()
 

	
 
    current_app.logger.debug('Creating choices')
 

	
 
    files = [name for name in get_choices()]
ennstatus/log.py
Show inline comments
 
import logging
 
import logging.handlers
 

	
 
import os.path
 

	
 
logging_debug_string = ('%(levelname)s:%(name)s:%(asctime)s:%(filename)s'
 
                        ':%(lineno)d: %(message)s')
 
logging_string = '%(levelname)s - %(name)s - %(asctime)s - %(message)s'
 
logging_debug_formatter = logging.Formatter(logging_debug_string)
 
logging_formatter = logging.Formatter(logging_string)
 

	
 

	
 
def init_logging(app):
 

	
 
    app.logger.setLevel(logging.DEBUG)
 
    stream_handler = logging.StreamHandler()
 
    stream_handler.setLevel(logging.DEBUG)
 
    stream_handler.setFormatter(logging_debug_formatter)
 

	
 
    if not os.path.exists('log/ennstatus.log'):
 
        open('log/ennstatus.log', mode='a').close()
 

	
 
    rotating_file_handler = logging.handlers.RotatingFileHandler(
 
        'log/ennstatus.log',
 
        maxBytes=1300000,
 
        backupCount=10,
 
        encoding='utf-8')
 
    rotating_file_handler.setLevel(logging.INFO)
 
    rotating_file_handler.setFormatter(logging_formatter)
 

	
 
    if app.debug or ('ENABLE_DEBUG_LOG' in app.config and app.config['ENABLE_DEBUG_LOG']):
 
    if app.debug or ('ENABLE_DEBUG_LOG' in app.config and
 
                     app.config['ENABLE_DEBUG_LOG']):
 

	
 
        if not os.path.exists('log/ennstatus_debug.log'):
 
            open('log/ennstatus_debug.log', mode='a').close()
 

	
 
        second_rotating_file_handler = logging.handlers.RotatingFileHandler(
 
            'log/ennstatus_debug.log',
 
            maxBytes=1300000,
 
            backupCount=20,
 
            encoding='utf-8')
 
        second_rotating_file_handler.setLevel(logging.DEBUG)
 
        second_rotating_file_handler.setFormatter(logging_debug_formatter)
 
        app.logger.addHandler(second_rotating_file_handler)
 

	
 
    smtp_handler = logging.handlers.SMTPHandler(
 
        'localhost',
 
        'ennstatus@enn.lu',
ennstatus/root/constants.py
Show inline comments
 

	
 
BPM_ADDRESSES = {
 
    'united_kingdom': {
 
        'address': '372 Old Street',
 
        'postal_code': 'EC1V 9AU',
 
        'city': 'London',
 
        'country': 'United Kingdom',
 
    },
 
    'united_states': {
 
        'address': '8345 NW 66 Street 2000',
 
        'postal_code': '33166-2626',
 
        'city': 'Miami',
 
        'country': 'United States of America',
 
    },
 
    'germany': {
 
        'address': 'Zum Bürgerwehr 28',
 
        'postal_code': 'D-54516',
 
        'city': 'Wittlich',
 
        'country': 'Germany',
 
    },
 
    'belgium': {
 
        'address': '3, Rue des Deux Luxembourg',
 
        'postal_code': 'B-6791',
 
        'city': 'Athus',
 
        'country': 'Belgium',
 
    },
 
    'france': {
 
        'address': 'RN 18 Les Maragolles',
 
        'address': 'Les Maragolles',
 
        'postal_code': 'F-54720',
 
        'city': 'Lexy',
 
        'country': 'France',
 
    },
 
    'luxembourg': {
 
        'address': '34, Rue Gabriel Lippmann',
 
        'postal_code': 'L-5365',
 
        'city': 'Munsbach',
 
        'country': 'Luxembourg',
 
    },
 
}
ennstatus/root/forms.py
Show inline comments
 
from flask_wtf import Form
 
from wtforms import SelectField, StringField, RadioField, BooleanField, SubmitField
 
from wtforms import (SelectField,
 
                     StringField,
 
                     RadioField,
 
                     BooleanField,
 
                     SubmitField
 
                     )
 
from wtforms.validators import InputRequired, Email, Length, DataRequired
 

	
 

	
 

	
 
COUNTRIES = [
 
    ('luxembourg', 'Luxembourg'),
 
    ('united_kingdom', 'United Kingdom'),
 
    ('united_states', 'United States of America'),
 
    ('belgium', 'Belgium'),
 
    ('france', 'France'),
 
    ('germany', 'Germany'),
 
]
 

	
 

	
 
class BPMForm(Form):
 
    country = SelectField('Country',
 
                          validators=[DataRequired()],
 
                          choices=COUNTRIES)
 

	
 

	
 
@@ -25,45 +27,97 @@ class MembershipForm(Form):
 
    username = StringField('Username*',
 
                           validators=[
 
                               InputRequired('This field is required!'),
 
                               Length(max=255)
 
                           ]
 
                           )
 
    email = StringField('E-Mail*',
 
                        validators=[
 
                            InputRequired('This field is required!'),
 
                            Email()
 
                        ]
 
                        )
 
    firstname = StringField('First Name',
 
                            validators=[Length(max=255)],
 
                            )
 
    surname = StringField('Surname',
 
                          validators=[Length(max=255)],
 
    fullname = StringField('Full name',
 
                           validators=[Length(max=65536)],
 
                          )
 
    street = StringField('Nr., Street',
 
                         validators=[Length(max=4000)],
 
                         )
 
    zip = StringField('ZIP-Code',
 
                      validators=[Length(max=30)],
 
                      )
 
    city = StringField('City/Town',
 
                       validators=[Length(max=500)],
 
                       )
 
    country = StringField('Country',
 
                          validators=[Length(max=500)],
 
                          )
 
    gpg = StringField('GPG-ID',
 
                      validators=[Length(max=18)],
 
                      )
 

	
 
    membership = RadioField('Membership Plan*',
 
                            validators=[InputRequired('Please select one of the options!')],
 
                            validators=[
 
                                InputRequired(
 
                                    'Please select one of the options!'
 
                                )
 
                            ],
 
                            choices=[
 
                                ('regular', 'Regular membership (120€/year)'),
 
                                ('student', 'Student membership (60€/year)'),
 
                                ('starving', 'Starving Hacker - Get in touch with us at info@enn.lu'),
 
                                ('starving',
 
                                 'Starving Hacker - Get in touch with us at info@enn.lu'
 
                                 ),
 
                            ]
 
                            )
 

	
 
    c3l = BooleanField(
 
        'Include "Chaos Computer Club Lëtzebuerg" Membership<sup>1</sup>'
 
    )
 
    submit = SubmitField('Become a member')
 

	
 

	
 
class BridgeprogramForm(Form):
 

	
 
    fullname = StringField('Full name',
 
                           validators=[Length(max=65536)],
 
                           )
 

	
 
    email = StringField('E-Mail*',
 
                        validators=[
 
                            InputRequired('This field is required!'),
 
                            Email()
 
                            ]
 
                            )
 

	
 
    c3l = BooleanField('Include "Chaos Computer Club Lëtzebuerg" Membership<sup>1</sup>')
 
    submit = SubmitField('Become a member')
 
    bridgename = StringField('Bridge name',
 
                             validators=[
 
                                 InputRequired('This field is required!'),
 
                                 Length(max=65536)
 
                             ]
 
                             )
 

	
 
    duration = RadioField('Duration',
 
                          validators=[
 
                              InputRequired(
 
                                  'Please select one of the options!'
 
                              )
 
                          ],
 
                          choices=[
 
                              ('1', '1 year'),
 
                              ('2', '2 years'),
 
                          ]
 
                          )
 

	
 
    payment = RadioField('Payment Method',
 
                         validators=[
 
                             InputRequired('Please select one of the options!')
 
                         ],
 
                         choices=[
 
                             ('wiretransfer', 'Wiretransfer'),
 
                             ('bitcoin', 'Bitcoin'),
 
                             ('paypal', 'PayPal'),
 
                             ('snailmail', 'Snailmail')
 
                         ]
 
                         )
 

	
 
    submit = SubmitField('Apply')
ennstatus/root/functions.py
Show inline comments
 
@@ -3,47 +3,110 @@ from flask import render_template, curre
 
from flask_mail import Mail, Message
 

	
 
from ennstatus.status.functions import mail
 

	
 

	
 
def send_membership_mail(form):
 
    try:
 
        body = render_template('root/membership_mail.txt',
 
                               username=form.username.data,
 
                               email=form.email.data,
 
                               membership=form.membership.data,
 
                               c3l=form.c3l.data,
 
                               firstname=form.firstname.data,
 
                               surname=form.surname.data,
 
                               fullname=form.fullname.data,
 
                               street=form.street.data,
 
                               zip=form.zip.data,
 
                               city=form.city.data,
 
                               country=form.country.data,
 
                               gpg=form.gpg.data
 
                               )
 

	
 
        recipients = current_app.config['ENNSTATUS_MEMBERSHIP_MAIL']
 
        with mail.connect() as conn:
 
            for recipient in recipients:
 
                msg = Message('New membership application', sender='ennstatus@enn.lu')
 
                msg = Message(
 
                    'New membership application',
 
                    sender='ennstatus@enn.lu'
 
                )
 
                msg.add_recipient(recipient)
 
                current_app.logger.debug('Before encryption')
 
                current_app.logger.debug(body)
 
                if current_app.extensions['gnupg']:
 
                    encrypted_body = str(current_app.extensions['gnupg'].encrypt(body, recipient,
 
                                         always_trust=True))
 
                    encrypted_body = str(
 
                        current_app.extensions['gnupg'].encrypt(
 
                            body,
 
                            recipient,
 
                            always_trust=True
 
                        )
 
                    )
 
                else:
 
                    encrypted_body = None
 
                current_app.logger.debug('After encryption')
 
                current_app.logger.debug(body)
 
                msg.body = encrypted_body if encrypted_body else body
 
                conn.send(msg)
 
        flash('Application successfully sended!', 'success')
 
    except KeyError:
 
        flash('Internal server error! Please get in touch with us at info@enn.lu!', 'error')
 
        flash(
 
            'Internal server error! Please get in touch with us at info@enn.lu!',
 
            'error'
 
        )
 
        current_app.logger.error('Membership admin not found!')
 
    except AssertionError:
 
        pass
 
    except Exception as e:
 
        flash('Internal server error! Please get in touch with us at info@enn.lu!', 'error')
 
        flash(
 
            'Internal server error! Please get in touch with us at info@enn.lu!',
 
            'error'
 
        )
 
        current_app.logger.error('Unexpected error: %s' % e,
 
                                 exc_info=True)
 

	
 

	
 
def send_bridgeprogram_mail(form):
 

	
 
    try:
 
        body = render_template('root/bridgeprogram_mail.txt',
 
                               fullname=form.fullname.data,
 
                               email=form.email.data,
 
                               bridgename=form.bridgename.data,
 
                               duration=form.duration.data,
 
                               payment=form.payment.data
 
                               )
 

	
 
        recipients = current_app.config['ENNSTATUS_BRIDGEPROGRAM_MAIL']
 
        with mail.connect() as conn:
 
            for recipient in recipients:
 
                msg = Message('New bridge program application',
 
                              sender='ennstatus@enn.lu')
 
                msg.add_recipient(recipient)
 
                if current_app.extensions['gnupg']:
 
                    encrypted_body = str(
 
                        current_app.extensions['gnupg'].encrypt(
 
                            body,
 
                            recipient,
 
                            always_trust=True
 
                        )
 
                    )
 
                else:
 
                    encrypted_body = None
 
                msg.body = encrypted_body if encrypted_body else body
 
                conn.send(msg)
 
        flash(
 
            'Application successfully sended! We will send you an email with further information!',
 
            'success'
 
        )
 
    except KeyError:
 
        flash(
 
            'Internal server error! Please get in touch with us at info@enn.lu',
 
            'error'
 
        )
 
        current_app.logger.error('Bridgeprogram admin not found!')
 
    except AssertionError:
 
        pass
 
    except Exception as e:
 
        flash(
 
            'Internal server error! Please get in touch with us at info@enn.lu!',
 
            'error'
 
        )
 
        current_app.logger.error('Unexpected error: %s' % e,
 
                                 exc_info=True)
ennstatus/root/views.py
Show inline comments
 
from flask import (Blueprint, render_template, current_app,
 
                   request, redirect, url_for, flash)
 

	
 
from ennstatus.root.forms import BPMForm, MembershipForm
 
from ennstatus.root.forms import BPMForm, MembershipForm, BridgeprogramForm
 
from ennstatus.root.constants import BPM_ADDRESSES
 
from ennstatus.root.functions import send_membership_mail
 
from ennstatus.root.functions import (send_membership_mail,
 
                                      send_bridgeprogram_mail)
 

	
 
root_page = Blueprint('root', __name__)
 

	
 

	
 
@root_page.route('/')
 
def index():
 
    return render_template('root/index.html')
 

	
 

	
 
@root_page.route('/about')
 
def about():
 
    return render_template('root/about.html')
 

	
 

	
 
@root_page.route('/services')
 
def services():
 
    return render_template('root/services.html')
 

	
 

	
 
@root_page.route('/partners')
 
def partners():
 
    return render_template('root/partners.html')
 

	
 

	
 
@root_page.route('/bridgeprogram')
 
@root_page.route('/bridgeprogram', methods=('GET', 'POST'))
 
def bridgeprogram():
 
    return render_template('root/bridgeprogram.html')
 

	
 
    current_app.logger.info('Handling bridgeprogram')
 
    form = BridgeprogramForm()
 

	
 
    if request.method == 'POST':
 
        current_app.logger.debug('Validation form')
 
        if form.validate_on_submit():
 
            send_bridgeprogram_mail(form)
 
            return redirect(url_for('root.bridgeprogram'))
 

	
 
    return render_template('root/bridgeprogram.html', form=form)
 

	
 

	
 
@root_page.route('/member')
 
def member():
 
    return render_template('root/member.html')
 

	
 

	
 
@root_page.route('/membership', methods=('GET', 'POST'))
 
def membership():
 

	
 
    current_app.logger.info('Handling membership')
 
    form = MembershipForm()
 

	
 
    if request.method == 'POST':
 
        current_app.logger.debug('Validating form')
 
        if form.validate_on_submit():
 
            send_membership_mail(form)
 
            return redirect(url_for('root.member'))
 

	
 
    return render_template('root/membership.html', form=form)
 

	
 

	
 
@root_page.route('/mirrors')
 
def mirrors():
 
    return render_template('root/mirrors.html')
 

	
 

	
 
@root_page.route('/contact', methods=('GET', 'POST'))
 
def contact():
 

	
 
    current_app.logger.info('Handling contact')
 
    form = BPMForm()
 
    country_choices = [choice[0] for choice in form.country.choices]
 

	
 
    if request.method == 'POST':
 
        current_app.logger.debug('Validating form')
 
        if form.validate_on_submit():
 
            country = form.country.data
 
            return redirect(url_for('root.contact', country=country))
 
@@ -80,15 +96,15 @@ def contact():
 
    form.country.data = country
 

	
 
    address = BPM_ADDRESSES[country]
 

	
 
    return render_template('root/contact.html', form=form, address=address)
 

	
 

	
 
@root_page.route('/abuse')
 
def abuse():
 
    return render_template('root/abuse.html')
 

	
 

	
 
@root_page.route('/disclaimer')
 
def disclaimer():
 
    return render_template('root/disclaimer.html')
 
@root_page.route('/ennstatus')
 
def ennstatus():
 
    return render_template('root/ennstatus.html')
ennstatus/static/css/ennstatus.css
Show inline comments
 
blockquote {
 
    border: none;
 
}
 

	
 
.thumbnail {
 
    border: none;
 
}
 

	
 
.container {
 
    margin-top:1%;
 
}
 

	
 
.navbar-default {
 
   background-color: #00ae18;
 
   /* border-radius: 50px; */
 
}
 

	
 
h1, h2, h3, h4 {
 
   color: #00ae18;
 
}
 

	
 
.navbar-default .navbar-brand {
 
   color: #fff;
 
}
 

	
 
.navbar-default img.navbar-brand {
 
  padding: 10px 0 10px 10px;
 
}
 

	
 
.navbar-default .navbar-brand:hover, .navbar-default .navbar-brand:focus {
 
   color: #333;
 
}
 

	
 
.navbar-default .navbar-nav > li > a {
 
   color: #fff;
 
}
 

	
 
.dropdown-menu {
 
   background-color: #00ae18;
 
}
 

	
 
.dropdown-menu > li > a {
 
  color: #fff;
 
}
 

	
 
.dropdown-menu > li > a:hover {
 
   background-color: #00bf1a;
 
   color: #333;
 
}
 

	
 
.navbar-default .navbar-nav > .open > a, .navbar-default .navbar-nav > .open > a:hover, .navbar-default .navbar-nav > .open > a:focus {
 
   background-color: #00bf1a;
 
   color: #fff;
 
}
 

	
 
.dropdown-menu .divider {
 
    background-color: #00bf1a;
 
}
 

	
 
a, a:hover, a:active, a:visited {
 
    color: #af24cb;
 
}
 

	
 

	
 

	
 
/* Multi level dropdown menu http://bootsnipp.com/snippets/featured/multi-level-dropdown-menu-bs3 */
 

	
 
.dropdown-submenu {
 
    position: relative;
 
}
 

	
 
.dropdown-submenu > .dropdown-menu {
 
    top: 0;
 
    left: 100%;
 
    margin-top: -6px;
 
    margin-left: -1px;
 
    -webkit-border-radius: 0 6px 6px 6px;
 
    -moz-border-radius: 0 6px 6px 6px;
 
    border-radius: 0 6px 6px 6px;
 
}
 

	
 
.dropdown-submenu:hover > .dropdown-menu {
 
    display: block;
 
}
 

	
 
.dropdown-submenu > a:after {
 
    display: block;
 
    content: " ";
 
    float: right;
 
    width: 0;
 
    height: 0;
 
    border-color: transparent;
 
    border-style: solid;
 
    border-width: 5px 0 5px 5px;
 
    border-left-color: #ccc;
 
    margin-top: 5px;
 
    margin-right: -10px;
 
}
 

	
 
.dropdown-submenu:hover > a:after {
 
    border-left-color: #fff;
 
}
 

	
 
.dropdown-submenu .pull-left {
 
    float: none;
 
}
 

	
 
.dropdown-submenu .pull-left > .dropdown-menu {
 
    left: -100%;
 
    margin-left: 10px;
 
    -webkit-border-radius: 6px 0 6px 6px;
 
    -moz-border-radius: 6px 0 6px 6px;
 
    border-radius: 6px 0 6px 6px;
 
}
 

	
 
/* fancy bootstrap checkboxes */
 

	
 
.form-group input[type="checkbox"] {
 
    display: none;
 
}
 

	
 
.form-group input[type="checkbox"] + .btn-group > label span {
 
    width: 20px;
 
}
 

	
 
.form-group input[type="checkbox"] + .btn-group > label span:first-child {
 
    display: none;
 
}
 

	
 
.form-group input[type="checkbox"] + .btn-group > label span:last-child {
 
    display: inline-block;
 
}
 

	
 
.form-group input[type="checkbox"]:checked + .btn-group > label span:first-child {
 
    display: inline-block;
 
}
 

	
 
.form-group input[type="checkbox"]:checked + .btn-group > label span:last-child {
 
    display: none;
 
}
 

	
 
.form-group input[type="radio"] {
 
    display: none;
 
}
 

	
 
.form-group input[type="radio"] + .btn-group > label span {
 
    width: 20px;
 
}
 

	
 
.form-group input[type="radio"] + .btn-group > label span:first-child {
 
    display: none;
 
}
 

	
 
.form-group input[type="radio"] + .btn-group > label span:last-child {
 
    display: inline-block;
 
}
 

	
 
.form-group input[type="radio"]:checked + .btn-group > label span:first-child {
 
    display: inline-block;
 
}
 

	
 
.form-group input[type="radio"]:checked + .btn-group > label span:last-child {
 
    display: none;
 
}
 

	
 
.checkbox label, .radio label {
 
    padding-left: 12px;
 
}
ennstatus/static/css/map.css
Show inline comments
 
new file 100644
 
.country {
 
	stroke: #000;
 
	stroke-linejoin: round;
 
	stroke-width: 0.1px;
 
}
ennstatus/static/images/BPM.gif
Show inline comments
 
deleted file
 
binary diff not shown
Show images
ennstatus/static/images/BitCoin_Logo.png
Show inline comments
 
deleted file
 
binary diff not shown
Show images
ennstatus/static/images/FVDE_Logo_LQ.png
Show inline comments
 
deleted file
 
binary diff not shown
Show images
ennstatus/static/images/Flattr_Logo.png
Show inline comments
 
deleted file
 
binary diff not shown
Show images
ennstatus/static/images/Objectives.png
Show inline comments
 
deleted file
 
binary diff not shown
Show images
ennstatus/static/images/PayPal_Logo.png
Show inline comments
 
deleted file
 
binary diff not shown
Show images
ennstatus/static/images/QuestionMark.png
Show inline comments
 
deleted file
 
binary diff not shown
Show images
ennstatus/static/images/SnailMail.png
Show inline comments
 
deleted file
 
binary diff not shown
Show images
ennstatus/static/images/Tor_bridge_users.png
Show inline comments
 
new file 100644
 
binary diff not shown
Show images
ennstatus/static/images/Twitter.png
Show inline comments
 
deleted file
 
binary diff not shown
Show images
ennstatus/static/images/WireTransfer.png
Show inline comments
 
deleted file
 
binary diff not shown
Show images
ennstatus/static/images/favicon.png
Show inline comments
 
binary diff not shown
Show images
ennstatus/static/images/logo/FVDE_logo_resize.png
Show inline comments
 
new file 100644
 
binary diff not shown
Show images
ennstatus/static/images/logo/FVDE_logo_thumbnail.png
Show inline comments
 
new file 100644
 
binary diff not shown
Show images
ennstatus/static/js/d3/.bower.json
Show inline comments
 
new file 100644
 
{
 
  "name": "d3",
 
  "main": "d3.js",
 
  "scripts": [
 
    "d3.js"
 
  ],
 
  "ignore": [
 
    ".DS_Store",
 
    ".git",
 
    ".gitignore",
 
    ".npmignore",
 
    ".spmignore",
 
    ".travis.yml",
 
    "Makefile",
 
    "bin",
 
    "component.json",
 
    "composer.json",
 
    "index.js",
 
    "lib",
 
    "node_modules",
 
    "package.json",
 
    "src",
 
    "test"
 
  ],
 
  "homepage": "https://github.com/mbostock/d3",
 
  "version": "3.5.6",
 
  "_release": "3.5.6",
 
  "_resolution": {
 
    "type": "version",
 
    "tag": "v3.5.6",
 
    "commit": "0e88ef5aacbc565b7282790c829ea51dd86c978d"
 
  },
 
  "_source": "git://github.com/mbostock/d3.git",
 
  "_target": "~3.5.6",
 
  "_originalSource": "d3",
 
  "_direct": true
 
}
 
\ No newline at end of file
ennstatus/static/js/d3/.gitattributes
Show inline comments
 
new file 100644
 
bower.json -diff merge=ours
 
component.json -diff merge=ours
 
d3.js -diff merge=ours
 
d3.min.js -diff merge=ours
 
package.js -diff merge=ours
ennstatus/static/js/d3/CONTRIBUTING.md
Show inline comments
 
new file 100644
 
# Contributing
 

	
 
**Important:** these GitHub issues are for *bug reports and feature requests only*. Please use [StackOverflow](http://stackoverflow.com/questions/tagged/d3.js) or the [d3-js Google group](https://groups.google.com/d/forum/d3-js) for general help.
 

	
 
If you’re looking for ways to contribute, please [peruse open issues](https://github.com/mbostock/d3/issues?milestone=&page=1&state=open). The icebox is a good place to find ideas that are not currently in development. If you already have an idea, please check past issues to see whether your idea or a similar one was previously discussed.
 

	
 
Before submitting a pull request, consider implementing a live example first, say using [bl.ocks.org](http://bl.ocks.org). Real-world use cases go a long way to demonstrating the usefulness of a proposed feature. The more complex a feature’s implementation, the more usefulness it should provide. Share your demo using the #d3js tag on Twitter or by sending it to the [d3-js Google group](https://groups.google.com/d/forum/d3-js).
 

	
 
If your proposed feature does not involve changing core functionality, consider submitting it instead as a [D3 plugin](https://github.com/d3/d3-plugins). New core features should be for general use, whereas plugins are suitable for more specialized use cases. When in doubt, it’s easier to start with a plugin before “graduating” to core.
 

	
 
To contribute new documentation or add examples to the gallery, just [edit the Wiki](https://github.com/mbostock/d3/wiki)!
 

	
 
## How to Submit a Pull Request
 

	
 
1. Click the “Fork” button to create your personal fork of the D3 repository.
 

	
 
2. After cloning your fork of the D3 repository in the terminal, run `npm install` to install D3’s dependencies.
 

	
 
3. Create a new branch for your new feature. For example: `git checkout -b my-awesome-feature`. A dedicated branch for your pull request means you can develop multiple features at the same time, and ensures that your pull request is stable even if you later decide to develop an unrelated feature.
 

	
 
4. The `d3.js` and `d3.min.js` files are built from source files in the `src` directory. _Do not edit `d3.js` directly._ Instead, edit the source files, and then run `make` to build the generated files.
 

	
 
5. Use `make test` to run tests and verify your changes. If you are adding a new feature, you should add new tests! If you are changing existing functionality, make sure the existing tests run, or update them as appropriate.
 

	
 
6. Sign D3’s [Individual Contributor License Agreement](https://docs.google.com/forms/d/1CzjdBKtDuA8WeuFJinadx956xLQ4Xriv7-oDvXnZMaI/viewform). Unless you are submitting a trivial patch (such as fixing a typo), this form is needed to verify that you are able to contribute.
 

	
 
7. Submit your pull request, and good luck!
ennstatus/static/js/d3/LICENSE
Show inline comments
 
new file 100644
 
Copyright (c) 2010-2015, Michael Bostock
 
All rights reserved.
 

	
 
Redistribution and use in source and binary forms, with or without
 
modification, are permitted provided that the following conditions are met:
 

	
 
* Redistributions of source code must retain the above copyright notice, this
 
  list of conditions and the following disclaimer.
 

	
 
* Redistributions in binary form must reproduce the above copyright notice,
 
  this list of conditions and the following disclaimer in the documentation
 
  and/or other materials provided with the distribution.
 

	
 
* The name Michael Bostock may not be used to endorse or promote products
 
  derived from this software without specific prior written permission.
 

	
 
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 
DISCLAIMED. IN NO EVENT SHALL MICHAEL BOSTOCK BE LIABLE FOR ANY DIRECT,
 
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 
OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
ennstatus/static/js/d3/README.md
Show inline comments
 
new file 100644
 
# Data-Driven Documents
 

	
 
<a href="http://d3js.org"><img src="http://d3js.org/logo.svg" align="left" hspace="10" vspace="6"></a>
 

	
 
**D3.js** is a JavaScript library for manipulating documents based on data. **D3** helps you bring data to life using HTML, SVG, and CSS. **D3** emphasizes web standards and combines powerful visualization components with a data-driven approach to DOM manipulation, giving you the full capabilities of modern browsers without tying yourself to a proprietary framework.
 

	
 
Want to learn more? [See the wiki.](https://github.com/mbostock/d3/wiki)
 

	
 
For examples, [see the gallery](https://github.com/mbostock/d3/wiki/Gallery) and [mbostock’s bl.ocks](http://bl.ocks.org/mbostock).
ennstatus/static/js/d3/bower.json
Show inline comments
 
new file 100644
 
{
 
  "name": "d3",
 
  "main": "d3.js",
 
  "scripts": [
 
    "d3.js"
 
  ],
 
  "ignore": [
 
    ".DS_Store",
 
    ".git",
 
    ".gitignore",
 
    ".npmignore",
 
    ".spmignore",
 
    ".travis.yml",
 
    "Makefile",
 
    "bin",
 
    "component.json",
 
    "composer.json",
 
    "index.js",
 
    "lib",
 
    "node_modules",
 
    "package.json",
 
    "src",
 
    "test"
 
  ]
 
}

Changeset was too big and was cut off... Show full diff anyway

0 comments (0 inline, 0 general)