diff --git a/.gitignore b/.gitignore index 48c3721..9c1ba27 100644 --- a/.gitignore +++ b/.gitignore @@ -1,19 +1,10 @@ # to avoid leaking credentials to github: -sqldatabasename -sqlpassword -sqlserver -sqlusername -secrets/ -docker-compose.yml hash_password.py # to avoid leaking references to github: references references/ -# because this relates to my personal config: -config/www.webref.conf - # general things to ignore: build/ dist/ diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 5b32b4d..0000000 --- a/Dockerfile +++ /dev/null @@ -1,19 +0,0 @@ -FROM httpd:2.4 - -COPY static/ /webapp/static -COPY templates/ /webapp/templates -COPY webref.py /webapp -COPY config/webref.wsgi /webapp -COPY config/httpd.conf /usr/local/apache2/conf -COPY requirements.txt /webapp -RUN mkdir /var/run/apache2 # needed to store files for WSGI -RUN mkdir /var/www # home of www-data user -WORKDIR /webapp - -RUN apt update -RUN apt install -y python3 -RUN apt install -y python3-pip -RUN apt install -y libapache2-mod-wsgi-py3 -RUN pip3 install -r requirements.txt --break-system-packages - -CMD ["/usr/local/apache2/bin/httpd", "-D", "FOREGROUND"] diff --git a/LICENSE b/LICENSE index 6e966b8..30bcce9 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2021 Adrian Schlatter +Copyright (c) 2021-2023 Adrian Schlatter Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..7e3eb77 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,6 @@ +exclude .gitignore +exclude MANIFEST.in +exclude tox.ini + +recursive-exclude .github * +recursive-exclude tests * diff --git a/config/httpd.conf b/config/httpd.conf deleted file mode 100644 index e095a03..0000000 --- a/config/httpd.conf +++ /dev/null @@ -1,50 +0,0 @@ -ServerName localhost -ServerAdmin root@localhost -ServerRoot /usr/local/apache2 -User www-data -Group www-data -PidFile logs/httpd.pid - -ServerTokens Prod -UseCanonicalName On -TraceEnable Off - -Timeout 10 -MaxRequestWorkers 100 - -Listen 0.0.0.0:80 - -LoadModule mpm_event_module modules/mod_mpm_event.so -LoadModule unixd_module modules/mod_unixd.so - -LoadModule log_config_module modules/mod_log_config.so - -LoadModule authn_core_module modules/mod_authn_core.so -LoadModule authz_core_module modules/mod_authz_core.so - -# apt installs mod_wsgi.so in a different folder: -LoadModule wsgi_module /usr/lib/apache2/modules/mod_wsgi.so - -ErrorLogFormat "[%{cu}t] [%-m:%-l] %-a %-L %M" -LogFormat "%h %l %u [%{%Y-%m-%d %H:%M:%S}t.%{usec_frac}t] \"%r\" %>s %b \ -\"%{Referer}i\" \"%{User-Agent}i\"" combined - -LogLevel debug -ErrorLog logs/error.log -CustomLog logs/access.log combined - - - Require all denied - Options SymLinksIfOwnerMatch - - - - WSGIDaemonProcess webref user=www-data group=www-data threads=5 - WSGIScriptAlias / /webapp/webref.wsgi - - - WSGIProcessGroup webref - WSGIApplicationGroup %{GLOBAL} - Require all granted - - diff --git a/config/webref.wsgi b/config/webref.wsgi deleted file mode 100644 index 32ffd43..0000000 --- a/config/webref.wsgi +++ /dev/null @@ -1,4 +0,0 @@ -import sys -sys.path.insert(0, '/webapp') - -from webref import app as application diff --git a/docker-compose_templ.yml b/docker-compose_templ.yml deleted file mode 100644 index 664a695..0000000 --- a/docker-compose_templ.yml +++ /dev/null @@ -1,22 +0,0 @@ -services: - webref: - image: webref - build: . - ports: - - "7000:80" - volumes: - - :/webapp/references - secrets: - - sqlusername - - sqlpassword - - sqlserver - - sqldatabasename -secrets: - sqlusername: - file: ./secrets/sqlusername - sqlpassword: - file: ./secrets/sqlpassword - sqlserver: - file: ./secrets/sqlserver - sqldatabasename: - file: ./secrets/sqldatabasename diff --git a/docs/README.md b/docs/README.md index af2755c..1d70bf9 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,16 +1,17 @@ -# WebRef +# ppf.webref -WebRef is a web interface to a [JabRef SQL database](https://docs.jabref.org/collaborative-work/sqldatabase). +ppf.webref is a web app providing an interface to a [JabRef SQL +database](https://docs.jabref.org/collaborative-work/sqldatabase). It allows you to access your references from anywhere in the world and from any device with a web browser. You do not need to install Java, you do not need to install an app. Any non-archaic phone, tablet, PC, Mac, or Raspberry Pi will do. -Create a JabRef database (using your normal JabRef) and configure WebRef +Create a JabRef database (using your normal JabRef) and configure ppf.webref to point to this database. Voila: Your references just became accessible worldwide. -Note: WebRef provides *read-only* access to your library. To add, edit, or +Note: ppf.webref provides *read-only* access to your library. To add, edit, or delete entries from your library, you still need a standard JabRef installation somewhere. @@ -21,33 +22,39 @@ somewhere. ## Installation -You need: +Prerequisite: You need JabRef to create, edit, and extend your library. -* JabRef: To create, edit, and extend your library -* Docker: To create and run docker images +Install ppf.webref: +```shell +pip install ppf.webref +``` + +Then, tell ppf.webref about your database by adding a section as follows to +`~/.config/ppf.webref/ppf.webref.conf` (create it if it does not exist): -Steps: +``` +[database] +server = : +databasename = +username = +password = +``` + +Finally, run + +```shell +flask --app ppf.webref run +``` -* Clone this repo -* Create a suitable docker-compose.yml (use - [docker-compose_templ.yml](../docker-compose_templ.yml) as a starting point) -* Create the following text files (assuming you did not change the paths - from the template docker-compose.yml): - - ./secrets/sqlusername: Username used to access your JabRef database - - ./secrets/sqlpassword: Password for that username - - ./secrets/sqlserver: The sql server holding your JabRef database - - ./secrets/sqldatabasename: The name of your JabRef database -* Run ```docker-compose up``` -* Point your webbrowser to localhost:7000 (or where you configured your - WebRef to be) +and point your webbrowser to http://localhost:5000. -This will start WebRef on your local machine which is nice for testing. -To get the most out of WebRef, you will probably want to -run this docker image on a web server. +This will start ppf.webref on your local machine which is nice for testing. +To get the most out of ppf.webref, you will probably want to run ppf.webref on +a web server. As we have not created any users yet, we can't login. To create -users, open your JabRef database (the one named in ./secrets/sqldatabasename) +users, open your JabRef database (the one named in the config file above) and run this sql-code (make sure you don't have a table with this name already): @@ -61,9 +68,9 @@ create table user ( ) ``` -Now we have a user table but no users in it, yet. Let's find a password and hash -it with the following python code (of course, we replace the dummy password -with your own password beforehand): +Now we have a user table but no users in it, yet. Let's find a password and +hash it with the following python code (of course, we replace the dummy +password with your own password beforehand): ``` import bcrypt diff --git a/docs/README_pypi.md b/docs/README_pypi.md new file mode 100644 index 0000000..be8e2cc --- /dev/null +++ b/docs/README_pypi.md @@ -0,0 +1,99 @@ +# ppf.webref + +ppf.webref is a web app providing an interface to a [JabRef SQL +database](https://docs.jabref.org/collaborative-work/sqldatabase). +It allows you to access your references from anywhere in the world and from +any device with a web browser. You do not need to install Java, you +do not need to install an app. Any non-archaic phone, tablet, PC, Mac, or +Raspberry Pi will do. + +Create a JabRef database (using your normal JabRef) and configure ppf.webref +to point to this database. Voila: Your references just became accessible +worldwide. + +Note: ppf.webref provides *read-only* access to your library. To add, edit, or +delete entries from your library, you still need a standard JabRef installation +somewhere. + + +## Installation + +Prerequisite: You need JabRef to create, edit, and extend your library. + +Install ppf.webref: + +```shell +pip install ppf.webref +``` + +Then, tell ppf.webref about your database by adding a section as follows to +`~/.config/ppf.webref/ppf.webref.conf` (create it if it does not exist): + +``` +[database] +server = : +databasename = +username = +password = +``` + +Finally, run + +```shell +flask --app ppf.webref run +``` + +and point your webbrowser to http://localhost:5000. + +This will start ppf.webref on your local machine which is nice for testing. +To get the most out of ppf.webref, you will probably want to run ppf.webref on +a web server. + +As we have not created any users yet, we can't login. To create +users, open your JabRef database (the one named in the config file above) +and run this sql-code (make sure you don't have a table with this name +already): + +``` +create table user ( + id INT auto_increment, + username varchar(20) character set utf8 not null, + password char(80) character set ascii not null, + primary key (id), + unique(username) +) +``` + +Now we have a user table but no users in it, yet. Let's find a password and +hash it with the following python code (of course, we replace the dummy +password with your own password beforehand): + +``` +import bcrypt + +password = 'This is my password' + +bytes = password.encode('utf-8') +salt = bcrypt.gensalt() +print(bcrypt.hashpw(bytes, salt)) +``` + +The output looks something like this: + +``` +b'$2b$12$1royHRBq6o/mbDdO7LjR8eaThWYErI6HLLdn7MBfajtpRLlwWSJ8m' +``` + +Now add your user to the user table in you JabRef database using this sql-code +(again, replace "webref" with your username and the password hash with the +hash you generated above): + +``` +insert into user (username, password) +values ( + "webref", + "$2b$12$1royHRBq6o/mbDdO7LjR8eaThWYErI6HLLdn7MBfajtpRLlwWSJ8m" +); +``` + +Now we are ready to go. diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index a51cd93..0000000 --- a/requirements.txt +++ /dev/null @@ -1,21 +0,0 @@ -bcrypt==4.0.1 -blinker==1.7.0 -click==8.1.3 -Flask==2.3.3 -Flask-Bcrypt==1.0.1 -Flask-Login==0.6.2 -Flask-SQLAlchemy==3.0.3 -flask-talisman==1.0.0 -Flask-WTF==1.1.1 -greenlet==2.0.2 -importlib-metadata==6.1.0 -itsdangerous==2.1.2 -Jinja2==3.1.2 -MarkupSafe==2.1.2 -ppf-jabref==0.1.0 -PyMySQL==1.0.3 -SQLAlchemy==2.0.7 -typing-extensions==4.5.0 -Werkzeug==2.3.8 -WTForms==3.0.1 -zipp==3.15.0 diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..53de360 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,86 @@ +[metadata] +name = ppf.webref +description = Flask app providing a web-interface to a JabRef database +long_description = file: docs/README_pypi.md +long_description_content_type = text/markdown +url = https://github.com/adrianschlatter/ppf.webref/tree/master +project_urls = + Bug Reports = https://github.com/adrianschlatter/ppf.webref/issues + Source = https://github.com/adrianschlatter/ppf.webref +author = Adrian Schlatter + +license = MIT +license_files = LICENSE + +# See https://pypi.python.org/pypi?%3Aaction=list_classifiers +classifiers = + Development Status :: 3 - Alpha + Intended Audience :: Developers + Intended Audience :: Information Technology + License :: OSI Approved :: MIT License + Programming Language :: Python :: 3 + Operating System :: OS Independent +keywords = jabref, references, web, server, app + +[options] +package_dir = + = src +packages = find_namespace: +python_requires = >=3.5, <4 +install_requires = + flask + flask-sqlalchemy + flask_talisman + flask_login + flask_wtf + flask_bcrypt + pymysql + ppf.jabref + plumbum + importlib_metadata;python_version<'3.8' + +[options.packages.find] +where = src + +[options.extras_require] +# List additional groups of dependencies here. You can install these using: +# pip install -e .[dev,test] +test = + check-manifest + setuptools>=40.5.0 + flake8 + pytest + coverage +dev = + build + tox + twine + +[tool:pytest] +testpaths = + tests + +[flake8] +per-file-ignores = + # imported but unused, import *, undefined name: + __init__.py: F401, F403, F821 +filename = + */src/*.py + */docs/*.py + */tests/*.py + setup.py + +[check-manifest] +ignore = + tox.ini + tests + tests/** + docs/** + +[coverage:run] +command_line = -m pytest +branch = True + +[coverage:report] +omit = tests/* + diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..7c7f94d --- /dev/null +++ b/setup.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +# required for backwards compatibility + +from setuptools import setup + +setup() diff --git a/webref.py b/src/ppf/webref/__init__.py similarity index 79% rename from webref.py rename to src/ppf/webref/__init__.py index 632f51e..b285b80 100644 --- a/webref.py +++ b/src/ppf/webref/__init__.py @@ -21,6 +21,8 @@ from flask_talisman import Talisman from ppf.jabref import Entry, Field, split_by_unescaped_sep from pathlib import Path +from plumbum import cli +from urllib.parse import quote_plus # Credential management # @@ -31,16 +33,36 @@ # Inside the container, the named secret is available in the text file # /run/secrets/. -if __name__ == '__main__': - secrets_path = Path('./secrets') -else: - secrets_path = Path('/run/secrets') -sqlusername = open(secrets_path / 'sqlusername').readline().strip() -sqlpassword = open(secrets_path / 'sqlpassword').readline().strip() -sqlserver = open(secrets_path / 'sqlserver').readline().strip() -sqldatabasename = open(secrets_path / 'sqldatabasename').readline().strip() +def get_secrets(): + # read config: + config_path = Path('~/.config/ppf.webref/ppf.webref.conf').expanduser() + if __name__ == '__main__': + secrets_path = Path('./secrets') + else: + secrets_path = Path('/run/secrets') + + if config_path.exists(): + with cli.Config(config_path) as config: + sqlusername = config.get('database.username', None) + sqlpassword = config.get('database.password', None) + sqlserver = config.get('database.server', None) + sqldatabasename = config.get('database.databasename', None) + elif secrets_path.exists(): + sqlusername = open(secrets_path / 'sqlusername').readline().strip() + sqlpassword = open(secrets_path / 'sqlpassword').readline().strip() + sqlserver = open(secrets_path / 'sqlserver').readline().strip() + sqldatabasename = (open(secrets_path / 'sqldatabasename') + .readline().strip()) + else: + raise RuntimeError('No config file found') + + sqlpassword = quote_plus(sqlpassword) + + return sqlusername, sqlpassword, sqlserver, sqldatabasename + +sqlusername, sqlpassword, sqlserver, sqldatabasename = get_secrets() app = Flask(__name__, static_url_path='', diff --git a/static/script.js b/src/ppf/webref/static/script.js similarity index 100% rename from static/script.js rename to src/ppf/webref/static/script.js diff --git a/static/style.css b/src/ppf/webref/static/style.css similarity index 100% rename from static/style.css rename to src/ppf/webref/static/style.css diff --git a/templates/entry_table.tmpl b/src/ppf/webref/templates/entry_table.tmpl similarity index 100% rename from templates/entry_table.tmpl rename to src/ppf/webref/templates/entry_table.tmpl diff --git a/templates/index.php b/src/ppf/webref/templates/index.php similarity index 89% rename from templates/index.php rename to src/ppf/webref/templates/index.php index 15d6aba..dc1bf96 100644 --- a/templates/index.php +++ b/src/ppf/webref/templates/index.php @@ -3,7 +3,7 @@ - WebRef: A Web Interface to JabRef + ppf.webref: A Web Interface to JabRef