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 @@ ...@@ -5,7 +5,7 @@
* Install python3 and virtualenv * Install python3 and virtualenv
* Create a virtualenv and install the requirements * Create a virtualenv and install the requirements
``` ```sh
virtualenv -p python3 venv virtualenv -p python3 venv
source venv/bin/activate source venv/bin/activate
pip install -r requirements.txt pip install -r requirements.txt
...@@ -14,7 +14,7 @@ pip install -r requirements.txt ...@@ -14,7 +14,7 @@ pip install -r requirements.txt
* Create a database (with sqlite, postgres, …) * Create a database (with sqlite, postgres, …)
* Create a config file * Create a config file
``` ```sh
cp config.py.example config.py cp config.py.example config.py
``` ```
...@@ -25,17 +25,49 @@ cp config.py.example config.py ...@@ -25,17 +25,49 @@ cp config.py.example config.py
* Fill your database * Fill your database
``` ```sh
./server.py db upgrade ./server.py db upgrade
``` ```
## Running the program ## Running the program
``` Run (in two terminals, one for the server and one for celery):
```sh
source venv/bin/activate source venv/bin/activate
./server.py runserver ./server.py runserver
``` ```
```sh
source venv/bin/activate
./start_celery.sh
```
The website will run on `localhost:5000`. 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 ...@@ -3,6 +3,7 @@ import ssl
import ldap3 import ldap3
from ldap3.utils.dn import parse_dn from ldap3.utils.dn import parse_dn
from datetime import datetime from datetime import datetime
import grp, pwd, pam
class User: class User:
def __init__(self, username, groups, timestamp=None, obsolete=False, permanent=False): def __init__(self, username, groups, timestamp=None, obsolete=False, permanent=False):
...@@ -42,7 +43,7 @@ class UserManager: ...@@ -42,7 +43,7 @@ class UserManager:
def login(self, username, password, permanent=False): def login(self, username, password, permanent=False):
for backend in self.backends: for backend in self.backends:
if backend.authenticate(username, password): 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 User(username, groups, obsolete=backend.obsolete, permanent=permanent)
return None return None
...@@ -135,6 +136,50 @@ class ADManager: ...@@ -135,6 +136,50 @@ class ADManager:
for result in reader.search(): for result in reader.search():
yield result.name.value 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: class SecurityManager:
def __init__(self, key, max_duration=300): def __init__(self, key, max_duration=300):
self.maccer = hmac.new(key.encode("utf-8"), digestmod=hashlib.sha512) self.maccer = hmac.new(key.encode("utf-8"), digestmod=hashlib.sha512)
......
...@@ -26,10 +26,10 @@ CELERY_ACCEPT_CONTENT = ["pickle"] # do not change ...@@ -26,10 +26,10 @@ CELERY_ACCEPT_CONTENT = ["pickle"] # do not change
PRINTING_ACTIVE = True PRINTING_ACTIVE = True
PRINTING_SERVER = "printsrv.example.com:631" PRINTING_SERVER = "printsrv.example.com:631"
PRINTING_USER = "protocols" PRINTING_USER = "protocols"
PRINTING_PRINTERS = [ PRINTING_PRINTERS = {
"example_printer": ["Duplex=DuplexNoTumble", "option2=value"], "example_printer": ["Duplex=DuplexNoTumble", "option2=value"],
"other_printer": ["list", "of", "options"] "other_printer": ["list", "of", "options"]
] }
# etherpad (optional) # etherpad (optional)
ETHERPAD_ACTIVE = True ETHERPAD_ACTIVE = True
...@@ -62,6 +62,7 @@ SESSION_PROTECTION = "strong" # do not change ...@@ -62,6 +62,7 @@ SESSION_PROTECTION = "strong" # do not change
# authentication # authentication
SECURITY_KEY = "some other random string" # change this SECURITY_KEY = "some other random string" # change this
AUTH_MAX_DURATION = 300 AUTH_MAX_DURATION = 300
from auth import LdapManager, ADManager, StaticUserManager
AUTH_BACKENDS = [ AUTH_BACKENDS = [
LdapManager( LdapManager(
host="ldap.example.com", host="ldap.example.com",
...@@ -72,7 +73,13 @@ AUTH_BACKENDS = [ ...@@ -72,7 +73,13 @@ AUTH_BACKENDS = [
domain="EXAMPLE", domain="EXAMPLE",
user_dn="cn=users,dc=example,dc=com", user_dn="cn=users,dc=example,dc=com",
group_dn="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 OBSOLETION_WARNING = """Please migrate your account!""" # not important
......
...@@ -701,6 +701,8 @@ class Error(DatabaseModel): ...@@ -701,6 +701,8 @@ class Error(DatabaseModel):
return self.protocol return self.protocol
def get_short_description(self): def get_short_description(self):
if not self.description:
return ""
lines = self.description.splitlines() lines = self.description.splitlines()
if len(lines) <= 4: if len(lines) <= 4:
return "\n".join(lines) return "\n".join(lines)
......
...@@ -32,7 +32,7 @@ MarkupSafe==0.23 ...@@ -32,7 +32,7 @@ MarkupSafe==0.23
nose==1.3.7 nose==1.3.7
packaging==16.8 packaging==16.8
pathtools==0.1.2 pathtools==0.1.2
psycopg2==2.6.2 psycopg2==2.7.4
pyasn1==0.2.3 pyasn1==0.2.3
Pygments==2.2.0 Pygments==2.2.0
pyldap==2.4.28 pyldap==2.4.28
...@@ -41,6 +41,7 @@ python-dateutil==2.6.0 ...@@ -41,6 +41,7 @@ python-dateutil==2.6.0
python-editor==1.0.3 python-editor==1.0.3
python-engineio==1.2.2 python-engineio==1.2.2
python-Levenshtein==0.12.0 python-Levenshtein==0.12.0
python-pam==1.8.2
python-socketio==1.7.1 python-socketio==1.7.1
pytz==2016.10 pytz==2016.10
PyYAML==3.12 PyYAML==3.12
......
...@@ -637,9 +637,9 @@ def print_file_async(filename, protocol_id): ...@@ -637,9 +637,9 @@ def print_file_async(filename, protocol_id):
for option in config.PRINTING_PRINTERS[protocol.protocoltype.printer]: for option in config.PRINTING_PRINTERS[protocol.protocoltype.printer]:
command.extend(["-o", '"{}"'.format(option) if " " in option else option]) command.extend(["-o", '"{}"'.format(option) if " " in option else option])
command.append(filename) command.append(filename)
subprocess.check_call(command, universal_newlines=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) subprocess.check_output(command, universal_newlines=True, stderr=subprocess.STDOUT)
except subprocess.SubprocessError: except subprocess.SubprocessError as exception:
error = protocol.create_error("Printing", "Printing {} failed.".format(protocol.get_identifier()), "") error = protocol.create_error("Printing", "Printing {} failed.".format(protocol.get_identifier()), exception.stdout)
db.session.add(error) db.session.add(error)
db.session.commit() db.session.commit()
......
#Datum;{{protocol.date|datify_short}} #Datum;{{protocol.date|datify_short}}
#Beginn;{{protocol.protocoltype.usual_time|timify_short}} #Beginn;{{protocol.protocoltype.usual_time|timify_short}}
#Ende; #Ende;
{% for meta in protocol.metas %}
#{{meta.name}};{{meta.value}}
{% endfor %}
{% for defaultmeta in protocol.protocoltype.metas %} {% for defaultmeta in protocol.protocoltype.metas %}
{% if not defaultmeta.prior %}
#{{defaultmeta.key}};{{defaultmeta.value}} #{{defaultmeta.key}};{{defaultmeta.value}}
{% endif %}
{% endfor %} {% endfor %}
{% macro render_top(top, use_description=False) %} {% macro render_top(top, use_description=False) %}
......
Supports Markdown
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