diff --git a/.gitignore b/.gitignore index 501bc14..453e636 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ olddata webapp/data.db -config.py *pyc *sublime* kasownik.ini diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..028d671 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,13 @@ +version: "3.8" + +services: + kasownik-web: + build: web + ports: + - 5000:5000 + volumes: + - ./web:/usr/src + environment: + - SPACEAUTH_CONSUMER_KEY + - SPACEAUTH_CONSUMER_SECRET + - DISABLE_LDAP=true diff --git a/web/Dockerfile b/web/Dockerfile new file mode 100644 index 0000000..6eb963d --- /dev/null +++ b/web/Dockerfile @@ -0,0 +1,14 @@ +FROM python:3.9-slim + +RUN apt-get update && apt-get install --no-install-recommends -y build-essential libsasl2-dev libldap2-dev + +ADD requirements.txt . +# Downgrade setuptools to fix anyjson dependency +RUN pip install setuptools~=57.5.0 && \ + pip install -r requirements.txt + +WORKDIR /usr/src +ADD . . + +STOPSIGNAL SIGINT +CMD ["uwsgi", "--http-socket", "0.0.0.0:5000", "--plugins", "python3", "--wsgi", "webapp.wsgi:app", "--threads", "10", "--master"] diff --git a/web/config.py b/web/config.py new file mode 100644 index 0000000..91b18b8 --- /dev/null +++ b/web/config.py @@ -0,0 +1,34 @@ +import environs + +env = environs.Env() +env.read_env() + +DEBUG = env.bool("DEBUG", False) +SQLALCHEMY_DATABASE_URI = env.str("SQLALCHEMY_DATABASE_URI", "sqlite:///data.db") +DUMMY_TRANSFER_UID = "NOTAMEMBER" + +SPACEAUTH_CONSUMER_KEY = env.str("SPACEAUTH_CONSUMER_KEY", "kasownik") +SPACEAUTH_CONSUMER_SECRET = env.str("SPACEAUTH_CONSUMER_SECRET", "changeme") + +SECRET_KEY = env.str("SECRET_KEY", "changeme") + +DISABLE_LDAP = env.bool("DISABLE_LDAP", False) + +LDAP_URI = env.str("LDAP_URI", "ldaps://ldap.hackerspace.pl") +LDAP_BIND_DN = env.str("LDAP_BIND_DN", "cn=kasownik,ou=Services,dc=hackerspace,dc=pl") +LDAP_BIND_PASSWORD = env.str("LDAP_BIND_PASSWORD", "changeme") + +LDAP_USER_FILTER = env.str("LDAP_USER_FILTER", "(objectClass=hsMember)") +LDAP_USER_BASE = env.str("LDAP_USER_BASE", "ou=People,dc=hackerspace,dc=pl") +LDAP_CA_PATH = env.str("LDAP_CA_PATH", "/etc/ssl/certs/ca-certificates.crt") + +CACHE_TYPE = env.str("CACHE_TYPE", "null") +CACHE_NO_NULL_WARNING = True + +MEMBERSHIP_FEES = env.json( + "MEMBERSHIP_FEES", + { + "starving": 75, + "fatty": 150, + }, +) diff --git a/web/config.py.dist b/web/config.py.dist deleted file mode 100644 index 1aad7e7..0000000 --- a/web/config.py.dist +++ /dev/null @@ -1,33 +0,0 @@ -class Config(object): - DEBUG = False - TESTING = False - SQLALCHEMY_DATABASE_URI = "sqlite:///data.db" - DUMMY_TRANSFER_UID = 'NOTAMEMBER' - - SPACEAUTH_CONSUMER_KEY = 'kasownik' - SPACEAUTH_CONSUMER_SECRET = 'changeme' - - SECRET_KEY = 'changeme' - - LDAP_URI = 'ldap://ldap.hackerspace.pl' - LDAP_BIND_DN = 'cn=fascist,ou=Services,dc=hackerspace,dc=pl' - LDAP_BIND_PASSWORD = 'changeme' - - LDAP_USER_FILTER = '(objectClass=hsMember)' - LDAP_USER_BASE = 'ou=People,dc=hackerspace,dc=pl' - LDAP_CA_PATH = '/etc/ssl/certs/ca-certificates.crt' - - CACHE_TYPE = 'null' - CACHE_NO_NULL_WARNING = True - - MEMBERSHIP_FEES = { - 'starving': 75, - 'fatty': 150, - } - -class DevelopmentConfig(Config): - DEBUG = True - DISABLE_LDAP = True - -class CurrentConfig(DevelopmentConfig): - pass diff --git a/web/requirements.txt b/web/requirements.txt index b975e34..ea3d966 100644 --- a/web/requirements.txt +++ b/web/requirements.txt @@ -11,13 +11,14 @@ chardet==3.0.4 click==6.7 configparser==3.5.0 enum34==1.1.6 +environs==11.0.0 Flask==0.12.2 Flask-Caching==1.3.3 Flask-Gravatar==0.4.2 Flask-Login==0.4.1 Flask-OAuthlib==0.9.4 Flask-Prometheus==0.0.1 -Flask-SpaceAuth @ git+https://code.hackerspace.pl/informatic/flask-spaceauth@0f997ab8fa307f7eabacf27e5f19fc1c715a69f3 +Flask-SpaceAuth @ https://code.hackerspace.pl/informatic/flask-spaceauth/archive/v0.2.0.tar.gz Flask-SQLAlchemy==2.1 Flask-WTF==0.10.3 idna==2.10 @@ -27,14 +28,17 @@ Jinja2==2.11.3 lazy-object-proxy==1.3.1 Mako==1.0.0 MarkupSafe==1.1.0 +marshmallow==3.21.3 mccabe==0.6.1 oauthlib==2.0.7 +packaging==24.1 prometheus-client==0.1.1 prometheus-flask-exporter==0.8.0 -psycopg2==2.9.4 +psycopg2-binary==2.9.4 pyasn1==0.4.8 pyasn1-modules==0.2.8 pylint==1.8.2 +python-dotenv==1.0.1 python-ldap==3.3.1 python-memcached==1.59 pytz==2014.10 @@ -46,6 +50,7 @@ SQLAlchemy==1.3.24 sqlparse==0.4.1 sqltap==0.3.5 urllib3==1.25.10 +uWSGI==2.0.26 Werkzeug==0.16.1 wrapt==1.10.11 WTForms==2.0.1 diff --git a/web/webapp/__init__.py b/web/webapp/__init__.py index 9152df1..c5b107f 100644 --- a/web/webapp/__init__.py +++ b/web/webapp/__init__.py @@ -25,7 +25,6 @@ import os from functools import wraps -import memcache import requests import sqltap.wsgi import click @@ -40,13 +39,19 @@ from flask_gravatar import Gravatar from spaceauth import SpaceAuth app = Flask(__name__) -app.config.from_object("config.CurrentConfig") +app.config.from_object("config") auth = SpaceAuth() db = SQLAlchemy() cache = Cache() -gravatar = Gravatar(size=256, rating='g', default='retro', - force_default=False, use_ssl=True, base_url=None) +gravatar = Gravatar( + size=256, + rating="g", + default="retro", + force_default=False, + use_ssl=True, + base_url=None, +) # TODO unsubscribe me from life cache_enabled = False @@ -57,16 +62,20 @@ import webapp.models # noqa from webapp.authutils import AnonymousUser, User auth.anonymous_user = AnonymousUser + + @auth.user_loader def load_user(username, profile=None): return User(username) + def admin_required(func): @wraps(func) def wrapper(*args, **kwargs): if not current_user.is_admin: return auth.unauthorized() return func(*args, **kwargs) + return wrapper @@ -77,10 +86,10 @@ class DecimalEncoder(json.JSONEncoder): # but that would mean a yield on the line with super(...), # which wouldn't work (see my comment below), so... return str(o) - #return (str(o) for o in [o]) + # return (str(o) for o in [o]) elif isinstance(o, datetime.datetime): return str(o) - #return (str(o) for o in [o]) + # return (str(o) for o in [o]) return super(DecimalEncoder, self).default(o) @@ -95,40 +104,43 @@ def create_app(): app.wsgi_app = sqltap.wsgi.SQLTapMiddleware(app.wsgi_app) # Setup prometheus metrics - if app.config.get('PROMETHEUS_DIR'): + if app.config.get("PROMETHEUS_DIR"): # This needs to be set before importing prometheus_client - os.environ['prometheus_multiproc_dir'] = app.config['PROMETHEUS_DIR'] + os.environ["prometheus_multiproc_dir"] = app.config["PROMETHEUS_DIR"] # FIXME: we could expose this somehow from prometheus_flask_exporter.multiprocess import UWsgiPrometheusMetrics - metrics = UWsgiPrometheusMetrics(group_by='url_rule') + + metrics = UWsgiPrometheusMetrics(group_by="url_rule") metrics.init_app(app) - #metrics.register_endpoint('/varz', app) + # metrics.register_endpoint('/varz', app) # Register blueprints import webapp.views import webapp.admin import webapp.api + app.register_blueprint(webapp.admin.bp) app.register_blueprint(webapp.api.bp) # Custom filters - @app.template_filter('inflect') + @app.template_filter("inflect") def inflect(v, one, two, five): num = abs(v) if num == 0: - return '%d %s' % (v, five) + return "%d %s" % (v, five) elif num == 1: - return '%d %s' % (v, one) + return "%d %s" % (v, one) elif num <= 4: - return '%d %s' % (v, two) + return "%d %s" % (v, two) - return '%d %s' % (v, five) + return "%d %s" % (v, five) # Custom CLI commands import webapp.commands + app.cli.commands.update(webapp.commands.group.commands) app.json_encoder = DecimalEncoder