Commit 9ae236dc authored by Julian Rother's avatar Julian Rother
Browse files

Merge branch 'master' of git.fsmpi.rwth-aachen.de:protokollsystem/proto3

parents b1c97df9 fbbbd0d8
......@@ -5,7 +5,7 @@
* Install python3 and virtualenv
* Create a virtualenv and install the requirements
```
```sh
virtualenv -p python3 venv
source venv/bin/activate
pip install -r requirements.txt
......@@ -14,7 +14,7 @@ pip install -r requirements.txt
* Create a database (with sqlite, postgres, …)
* Create a config file
```
```sh
cp config.py.example config.py
```
......@@ -25,17 +25,49 @@ cp config.py.example config.py
* Fill your database
```
```sh
./server.py db upgrade
```
## Running the program
```
Run (in two terminals, one for the server and one for celery):
```sh
source venv/bin/activate
./server.py runserver
```
```sh
source venv/bin/activate
./start_celery.sh
```
The website will run on `localhost:5000`.
## Data model
The data model is defined in `models/database.py`.
Each type should inherit from `DatabaseModel` and have a method `get_parent()`, which is responsible for the right management and some magic.
## Server endpoints
The actual websites are defined in `server.py`. They begin with the decorator `@app.route((route)` and return a string, usually through `render_template(template, **parameters)`.
There can be more decorators inbetween `app.route` and the function.
A simple website might look like this:
```python
@app.route("/documentation")
@login_required
def documentation():
todostates = list(TodoState)
name_to_state = TodoState.get_name_to_state()
return render_template("documentation.html", todostates=todostates, name_to_state=name_to_state)
```
### Decorators
* `app.route(route)`: Defines for which URLs this function will be called.
- The route may contain variables: `"/type/edit/<int:protocoltype:id>"`. These will be passed to the function.
- Additionally, allowed HTTP methods may be defined: `@app.route("/type/new", methods=["GET", "POST"])`. The default is `GET`, but endpoints for forms require `POST` as well.
* `login_required`: Anonymous users will be redirected to the login page.
* `group_required(group)`: Users without this group will see an error message.
* `db_lookup(DataModel)`: Looks up an element of this type. The route needs to have an argument "{model_name}_id".
......@@ -3,6 +3,7 @@ import ssl
import ldap3
from ldap3.utils.dn import parse_dn
from datetime import datetime
import grp, pwd, pam
class User:
def __init__(self, username, groups, timestamp=None, obsolete=False, permanent=False):
......@@ -42,7 +43,7 @@ class UserManager:
def login(self, username, password, permanent=False):
for backend in self.backends:
if backend.authenticate(username, password):
groups = backend.groups(username, password)
groups = sorted(list(set(backend.groups(username, password))))
return User(username, groups, obsolete=backend.obsolete, permanent=permanent)
return None
......@@ -135,6 +136,50 @@ class ADManager:
for result in reader.search():
yield result.name.value
class StaticUserManager:
def __init__(self, users, obsolete=False):
self.passwords = {
username: password
for (username, password, groups) in users
}
self.group_map = {
username: groups
for (username, password, groups) in users
}
self.obsolete = obsolete
def authenticate(self, username, password):
return (username in self.passwords
and self.passwords[username] == password)
def groups(self, username, password=None):
if username in self.group_map:
yield from self.group_map[username]
def all_groups(self):
yield from list(set(group for group in groups.values()))
class PAMManager:
def __init__(self, obsolete=False):
self.pam = pam.pam()
self.obsolete = obsolete
def authenticate(self, username, password):
return self.pam.authenticate(username, password)
def groups(self, username, password=None):
print(username)
yield grp.getgrgid(pwd.getpwnam(username).pw_gid).gr_name
for group in grp.getgrall():
if username in group.gr_mem:
yield group.gr_name
def all_groups(self):
for group in grp.getgrall():
yield group.gr_name
class SecurityManager:
def __init__(self, key, max_duration=300):
self.maccer = hmac.new(key.encode("utf-8"), digestmod=hashlib.sha512)
......
......@@ -26,10 +26,10 @@ CELERY_ACCEPT_CONTENT = ["pickle"] # do not change
PRINTING_ACTIVE = True
PRINTING_SERVER = "printsrv.example.com:631"
PRINTING_USER = "protocols"
PRINTING_PRINTERS = [
PRINTING_PRINTERS = {
"example_printer": ["Duplex=DuplexNoTumble", "option2=value"],
"other_printer": ["list", "of", "options"]
]
}
# etherpad (optional)
ETHERPAD_ACTIVE = True
......@@ -62,6 +62,7 @@ SESSION_PROTECTION = "strong" # do not change
# authentication
SECURITY_KEY = "some other random string" # change this
AUTH_MAX_DURATION = 300
from auth import LdapManager, ADManager, StaticUserManager
AUTH_BACKENDS = [
LdapManager(
host="ldap.example.com",
......@@ -72,7 +73,13 @@ AUTH_BACKENDS = [
domain="EXAMPLE",
user_dn="cn=users,dc=example,dc=com",
group_dn="dc=example,dc=com",
ca_cert="/etc/ssl/certs/example-ca.pem")
ca_cert="/etc/ssl/certs/example-ca.pem"),
StaticUserManager(
users=(
("username", "password", ("group1", "group2")),
("testuser", "abc123", ("group1")),
)
)
]
OBSOLETION_WARNING = """Please migrate your account!""" # not important
......
......@@ -701,6 +701,8 @@ class Error(DatabaseModel):
return self.protocol
def get_short_description(self):
if not self.description:
return ""
lines = self.description.splitlines()
if len(lines) <= 4:
return "\n".join(lines)
......
......@@ -32,7 +32,7 @@ MarkupSafe==0.23
nose==1.3.7
packaging==16.8
pathtools==0.1.2
psycopg2==2.6.2
psycopg2==2.7.4
pyasn1==0.2.3
Pygments==2.2.0
pyldap==2.4.28
......@@ -41,6 +41,7 @@ python-dateutil==2.6.0
python-editor==1.0.3
python-engineio==1.2.2
python-Levenshtein==0.12.0
python-pam==1.8.2
python-socketio==1.7.1
pytz==2016.10
PyYAML==3.12
......
......@@ -637,9 +637,9 @@ def print_file_async(filename, protocol_id):
for option in config.PRINTING_PRINTERS[protocol.protocoltype.printer]:
command.extend(["-o", '"{}"'.format(option) if " " in option else option])
command.append(filename)
subprocess.check_call(command, universal_newlines=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
except subprocess.SubprocessError:
error = protocol.create_error("Printing", "Printing {} failed.".format(protocol.get_identifier()), "")
subprocess.check_output(command, universal_newlines=True, stderr=subprocess.STDOUT)
except subprocess.SubprocessError as exception:
error = protocol.create_error("Printing", "Printing {} failed.".format(protocol.get_identifier()), exception.stdout)
db.session.add(error)
db.session.commit()
......
#Datum;{{protocol.date|datify_short}}
#Beginn;{{protocol.protocoltype.usual_time|timify_short}}
#Ende;
{% for meta in protocol.metas %}
#{{meta.name}};{{meta.value}}
{% endfor %}
{% for defaultmeta in protocol.protocoltype.metas %}
{% if not defaultmeta.prior %}
#{{defaultmeta.key}};{{defaultmeta.value}}
{% endif %}
{% endfor %}
{% macro render_top(top, use_description=False) %}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment