Translation of UI (german and english)
Bugfix for feeds
- removed categories related and up
- load new books now working
- category random now working
login page is free of non accessible elements
boolean custom column is vivible in UI
books with only with certain languages can be shown
book shelfs can be deleted from UI
Anonymous user view is more resticted
Added browse of series in sidebar
Dependencys in vendor folder are updated to newer versions (licencs files are now present)
Bugfix editing Authors names
Made upload on windows working
This commit is contained in:
Vendored
+304
-23
@@ -5,24 +5,103 @@
|
||||
|
||||
WSGI application traceback debugger.
|
||||
|
||||
:copyright: (c) 2013 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:copyright: (c) 2014 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import uuid
|
||||
import json
|
||||
import time
|
||||
import getpass
|
||||
import hashlib
|
||||
import mimetypes
|
||||
from itertools import chain
|
||||
from os.path import join, dirname, basename, isfile
|
||||
from werkzeug.wrappers import BaseRequest as Request, BaseResponse as Response
|
||||
from werkzeug.http import parse_cookie
|
||||
from werkzeug.debug.tbtools import get_current_traceback, render_console_html
|
||||
from werkzeug.debug.console import Console
|
||||
from werkzeug.security import gen_salt
|
||||
from werkzeug._internal import _log
|
||||
from werkzeug._compat import text_type
|
||||
|
||||
|
||||
# DEPRECATED
|
||||
#: import this here because it once was documented as being available
|
||||
#: from this module. In case there are users left ...
|
||||
from werkzeug.debug.repr import debug_repr
|
||||
from werkzeug.debug.repr import debug_repr # noqa
|
||||
|
||||
|
||||
# A week
|
||||
PIN_TIME = 60 * 60 * 24 * 7
|
||||
|
||||
|
||||
def hash_pin(pin):
|
||||
if isinstance(pin, text_type):
|
||||
pin = pin.encode('utf-8', 'replace')
|
||||
return hashlib.md5(pin + b'shittysalt').hexdigest()[:12]
|
||||
|
||||
|
||||
_machine_id = None
|
||||
|
||||
|
||||
def get_machine_id():
|
||||
global _machine_id
|
||||
rv = _machine_id
|
||||
if rv is not None:
|
||||
return rv
|
||||
|
||||
def _generate():
|
||||
# Potential sources of secret information on linux. The machine-id
|
||||
# is stable across boots, the boot id is not
|
||||
for filename in '/etc/machine-id', '/proc/sys/kernel/random/boot_id':
|
||||
try:
|
||||
with open(filename, 'rb') as f:
|
||||
return f.readline().strip()
|
||||
except IOError:
|
||||
continue
|
||||
|
||||
# On OS X we can use the computer's serial number assuming that
|
||||
# ioreg exists and can spit out that information.
|
||||
try:
|
||||
# Also catch import errors: subprocess may not be available, e.g.
|
||||
# Google App Engine
|
||||
# See https://github.com/pallets/werkzeug/issues/925
|
||||
from subprocess import Popen, PIPE
|
||||
dump = Popen(['ioreg', '-c', 'IOPlatformExpertDevice', '-d', '2'],
|
||||
stdout=PIPE).communicate()[0]
|
||||
match = re.search(b'"serial-number" = <([^>]+)', dump)
|
||||
if match is not None:
|
||||
return match.group(1)
|
||||
except (OSError, ImportError):
|
||||
pass
|
||||
|
||||
# On Windows we can use winreg to get the machine guid
|
||||
wr = None
|
||||
try:
|
||||
import winreg as wr
|
||||
except ImportError:
|
||||
try:
|
||||
import _winreg as wr
|
||||
except ImportError:
|
||||
pass
|
||||
if wr is not None:
|
||||
try:
|
||||
with wr.OpenKey(wr.HKEY_LOCAL_MACHINE,
|
||||
'SOFTWARE\\Microsoft\\Cryptography', 0,
|
||||
wr.KEY_READ | wr.KEY_WOW64_64KEY) as rk:
|
||||
return wr.QueryValueEx(rk, 'MachineGuid')[0]
|
||||
except WindowsError:
|
||||
pass
|
||||
|
||||
_machine_id = rv = _generate()
|
||||
return rv
|
||||
|
||||
|
||||
class _ConsoleFrame(object):
|
||||
|
||||
"""Helper class so that we can reuse the frame console code for the
|
||||
standalone console.
|
||||
"""
|
||||
@@ -32,6 +111,90 @@ class _ConsoleFrame(object):
|
||||
self.id = 0
|
||||
|
||||
|
||||
def get_pin_and_cookie_name(app):
|
||||
"""Given an application object this returns a semi-stable 9 digit pin
|
||||
code and a random key. The hope is that this is stable between
|
||||
restarts to not make debugging particularly frustrating. If the pin
|
||||
was forcefully disabled this returns `None`.
|
||||
|
||||
Second item in the resulting tuple is the cookie name for remembering.
|
||||
"""
|
||||
pin = os.environ.get('WERKZEUG_DEBUG_PIN')
|
||||
rv = None
|
||||
num = None
|
||||
|
||||
# Pin was explicitly disabled
|
||||
if pin == 'off':
|
||||
return None, None
|
||||
|
||||
# Pin was provided explicitly
|
||||
if pin is not None and pin.replace('-', '').isdigit():
|
||||
# If there are separators in the pin, return it directly
|
||||
if '-' in pin:
|
||||
rv = pin
|
||||
else:
|
||||
num = pin
|
||||
|
||||
modname = getattr(app, '__module__',
|
||||
getattr(app.__class__, '__module__'))
|
||||
|
||||
try:
|
||||
# `getpass.getuser()` imports the `pwd` module,
|
||||
# which does not exist in the Google App Engine sandbox.
|
||||
username = getpass.getuser()
|
||||
except ImportError:
|
||||
username = None
|
||||
|
||||
mod = sys.modules.get(modname)
|
||||
|
||||
# This information only exists to make the cookie unique on the
|
||||
# computer, not as a security feature.
|
||||
probably_public_bits = [
|
||||
username,
|
||||
modname,
|
||||
getattr(app, '__name__', getattr(app.__class__, '__name__')),
|
||||
getattr(mod, '__file__', None),
|
||||
]
|
||||
|
||||
# This information is here to make it harder for an attacker to
|
||||
# guess the cookie name. They are unlikely to be contained anywhere
|
||||
# within the unauthenticated debug page.
|
||||
private_bits = [
|
||||
str(uuid.getnode()),
|
||||
get_machine_id(),
|
||||
]
|
||||
|
||||
h = hashlib.md5()
|
||||
for bit in chain(probably_public_bits, private_bits):
|
||||
if not bit:
|
||||
continue
|
||||
if isinstance(bit, text_type):
|
||||
bit = bit.encode('utf-8')
|
||||
h.update(bit)
|
||||
h.update(b'cookiesalt')
|
||||
|
||||
cookie_name = '__wzd' + h.hexdigest()[:20]
|
||||
|
||||
# If we need to generate a pin we salt it a bit more so that we don't
|
||||
# end up with the same value and generate out 9 digits
|
||||
if num is None:
|
||||
h.update(b'pinsalt')
|
||||
num = ('%09d' % int(h.hexdigest(), 16))[:9]
|
||||
|
||||
# Format the pincode in groups of digits for easier remembering if
|
||||
# we don't have a result yet.
|
||||
if rv is None:
|
||||
for group_size in 5, 4, 3:
|
||||
if len(num) % group_size == 0:
|
||||
rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
|
||||
for x in range(0, len(num), group_size))
|
||||
break
|
||||
else:
|
||||
rv = num
|
||||
|
||||
return rv, cookie_name
|
||||
|
||||
|
||||
class DebuggedApplication(object):
|
||||
"""Enables debugging support for a given application::
|
||||
|
||||
@@ -58,19 +221,19 @@ class DebuggedApplication(object):
|
||||
:param show_hidden_frames: by default hidden traceback frames are skipped.
|
||||
You can show them by setting this parameter
|
||||
to `True`.
|
||||
:param pin_security: can be used to disable the pin based security system.
|
||||
:param pin_logging: enables the logging of the pin system.
|
||||
"""
|
||||
|
||||
# this class is public
|
||||
__module__ = 'werkzeug'
|
||||
|
||||
def __init__(self, app, evalex=False, request_key='werkzeug.request',
|
||||
console_path='/console', console_init_func=None,
|
||||
show_hidden_frames=False, lodgeit_url=None):
|
||||
show_hidden_frames=False, lodgeit_url=None,
|
||||
pin_security=True, pin_logging=True):
|
||||
if lodgeit_url is not None:
|
||||
from warnings import warn
|
||||
warn(DeprecationWarning('Werkzeug now pastes into gists.'))
|
||||
if not console_init_func:
|
||||
console_init_func = dict
|
||||
console_init_func = None
|
||||
self.app = app
|
||||
self.evalex = evalex
|
||||
self.frames = {}
|
||||
@@ -80,6 +243,39 @@ class DebuggedApplication(object):
|
||||
self.console_init_func = console_init_func
|
||||
self.show_hidden_frames = show_hidden_frames
|
||||
self.secret = gen_salt(20)
|
||||
self._failed_pin_auth = 0
|
||||
|
||||
self.pin_logging = pin_logging
|
||||
if pin_security:
|
||||
# Print out the pin for the debugger on standard out.
|
||||
if os.environ.get('WERKZEUG_RUN_MAIN') == 'true' and \
|
||||
pin_logging:
|
||||
_log('warning', ' * Debugger is active!')
|
||||
if self.pin is None:
|
||||
_log('warning', ' * Debugger pin disabled. '
|
||||
'DEBUGGER UNSECURED!')
|
||||
else:
|
||||
_log('info', ' * Debugger pin code: %s' % self.pin)
|
||||
else:
|
||||
self.pin = None
|
||||
|
||||
def _get_pin(self):
|
||||
if not hasattr(self, '_pin'):
|
||||
self._pin, self._pin_cookie = get_pin_and_cookie_name(self.app)
|
||||
return self._pin
|
||||
|
||||
def _set_pin(self, value):
|
||||
self._pin = value
|
||||
|
||||
pin = property(_get_pin, _set_pin)
|
||||
del _get_pin, _set_pin
|
||||
|
||||
@property
|
||||
def pin_cookie_name(self):
|
||||
"""The name of the pin cookie."""
|
||||
if not hasattr(self, '_pin_cookie'):
|
||||
self._pin, self._pin_cookie = get_pin_and_cookie_name(self.app)
|
||||
return self._pin_cookie
|
||||
|
||||
def debug_application(self, environ, start_response):
|
||||
"""Run the application and conserve the traceback frames."""
|
||||
@@ -93,9 +289,9 @@ class DebuggedApplication(object):
|
||||
except Exception:
|
||||
if hasattr(app_iter, 'close'):
|
||||
app_iter.close()
|
||||
traceback = get_current_traceback(skip=1, show_hidden_frames=
|
||||
self.show_hidden_frames,
|
||||
ignore_system_exceptions=True)
|
||||
traceback = get_current_traceback(
|
||||
skip=1, show_hidden_frames=self.show_hidden_frames,
|
||||
ignore_system_exceptions=True)
|
||||
for frame in traceback.frames:
|
||||
self.frames[frame.id] = frame
|
||||
self.tracebacks[traceback.id] = traceback
|
||||
@@ -117,9 +313,11 @@ class DebuggedApplication(object):
|
||||
'response at a point where response headers were already '
|
||||
'sent.\n')
|
||||
else:
|
||||
is_trusted = bool(self.check_pin_trust(environ))
|
||||
yield traceback.render_full(evalex=self.evalex,
|
||||
evalex_trusted=is_trusted,
|
||||
secret=self.secret) \
|
||||
.encode('utf-8', 'replace')
|
||||
.encode('utf-8', 'replace')
|
||||
|
||||
traceback.log(environ['wsgi.errors'])
|
||||
|
||||
@@ -130,8 +328,15 @@ class DebuggedApplication(object):
|
||||
def display_console(self, request):
|
||||
"""Display a standalone shell."""
|
||||
if 0 not in self.frames:
|
||||
self.frames[0] = _ConsoleFrame(self.console_init_func())
|
||||
return Response(render_console_html(secret=self.secret),
|
||||
if self.console_init_func is None:
|
||||
ns = {}
|
||||
else:
|
||||
ns = dict(self.console_init_func())
|
||||
ns.setdefault('app', self.app)
|
||||
self.frames[0] = _ConsoleFrame(ns)
|
||||
is_trusted = bool(self.check_pin_trust(request.environ))
|
||||
return Response(render_console_html(secret=self.secret,
|
||||
evalex_trusted=is_trusted),
|
||||
mimetype='text/html')
|
||||
|
||||
def paste_traceback(self, request, traceback):
|
||||
@@ -139,10 +344,6 @@ class DebuggedApplication(object):
|
||||
rv = traceback.paste()
|
||||
return Response(json.dumps(rv), mimetype='application/json')
|
||||
|
||||
def get_source(self, request, frame):
|
||||
"""Render the source viewer."""
|
||||
return Response(frame.render_source(), mimetype='text/html')
|
||||
|
||||
def get_resource(self, request, filename):
|
||||
"""Return a static resource from the shared folder."""
|
||||
filename = join(dirname(__file__), 'shared', basename(filename))
|
||||
@@ -156,6 +357,83 @@ class DebuggedApplication(object):
|
||||
f.close()
|
||||
return Response('Not Found', status=404)
|
||||
|
||||
def check_pin_trust(self, environ):
|
||||
"""Checks if the request passed the pin test. This returns `True` if the
|
||||
request is trusted on a pin/cookie basis and returns `False` if not.
|
||||
Additionally if the cookie's stored pin hash is wrong it will return
|
||||
`None` so that appropriate action can be taken.
|
||||
"""
|
||||
if self.pin is None:
|
||||
return True
|
||||
val = parse_cookie(environ).get(self.pin_cookie_name)
|
||||
if not val or '|' not in val:
|
||||
return False
|
||||
ts, pin_hash = val.split('|', 1)
|
||||
if not ts.isdigit():
|
||||
return False
|
||||
if pin_hash != hash_pin(self.pin):
|
||||
return None
|
||||
return (time.time() - PIN_TIME) < int(ts)
|
||||
|
||||
def _fail_pin_auth(self):
|
||||
time.sleep(self._failed_pin_auth > 5 and 5.0 or 0.5)
|
||||
self._failed_pin_auth += 1
|
||||
|
||||
def pin_auth(self, request):
|
||||
"""Authenticates with the pin."""
|
||||
exhausted = False
|
||||
auth = False
|
||||
trust = self.check_pin_trust(request.environ)
|
||||
|
||||
# If the trust return value is `None` it means that the cookie is
|
||||
# set but the stored pin hash value is bad. This means that the
|
||||
# pin was changed. In this case we count a bad auth and unset the
|
||||
# cookie. This way it becomes harder to guess the cookie name
|
||||
# instead of the pin as we still count up failures.
|
||||
bad_cookie = False
|
||||
if trust is None:
|
||||
self._fail_pin_auth()
|
||||
bad_cookie = True
|
||||
|
||||
# If we're trusted, we're authenticated.
|
||||
elif trust:
|
||||
auth = True
|
||||
|
||||
# If we failed too many times, then we're locked out.
|
||||
elif self._failed_pin_auth > 10:
|
||||
exhausted = True
|
||||
|
||||
# Otherwise go through pin based authentication
|
||||
else:
|
||||
entered_pin = request.args.get('pin')
|
||||
if entered_pin.strip().replace('-', '') == \
|
||||
self.pin.replace('-', ''):
|
||||
self._failed_pin_auth = 0
|
||||
auth = True
|
||||
else:
|
||||
self._fail_pin_auth()
|
||||
|
||||
rv = Response(json.dumps({
|
||||
'auth': auth,
|
||||
'exhausted': exhausted,
|
||||
}), mimetype='application/json')
|
||||
if auth:
|
||||
rv.set_cookie(self.pin_cookie_name, '%s|%s' % (
|
||||
int(time.time()),
|
||||
hash_pin(self.pin)
|
||||
), httponly=True)
|
||||
elif bad_cookie:
|
||||
rv.delete_cookie(self.pin_cookie_name)
|
||||
return rv
|
||||
|
||||
def log_pin_request(self):
|
||||
"""Log the pin if needed."""
|
||||
if self.pin_logging and self.pin is not None:
|
||||
_log('info', ' * To enable the debugger you need to '
|
||||
'enter the security pin:')
|
||||
_log('info', ' * Debugger pin code: %s' % self.pin)
|
||||
return Response('')
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
"""Dispatch the requests."""
|
||||
# important: don't ever access a function here that reads the incoming
|
||||
@@ -172,14 +450,17 @@ class DebuggedApplication(object):
|
||||
if cmd == 'resource' and arg:
|
||||
response = self.get_resource(request, arg)
|
||||
elif cmd == 'paste' and traceback is not None and \
|
||||
secret == self.secret:
|
||||
secret == self.secret:
|
||||
response = self.paste_traceback(request, traceback)
|
||||
elif cmd == 'source' and frame and self.secret == secret:
|
||||
response = self.get_source(request, frame)
|
||||
elif self.evalex and cmd is not None and frame is not None and \
|
||||
self.secret == secret:
|
||||
elif cmd == 'pinauth' and secret == self.secret:
|
||||
response = self.pin_auth(request)
|
||||
elif cmd == 'printpin' and secret == self.secret:
|
||||
response = self.log_pin_request()
|
||||
elif self.evalex and cmd is not None and frame is not None \
|
||||
and self.secret == secret and \
|
||||
self.check_pin_trust(environ):
|
||||
response = self.execute_command(request, cmd, frame)
|
||||
elif self.evalex and self.console_path is not None and \
|
||||
request.path == self.console_path:
|
||||
request.path == self.console_path:
|
||||
response = self.display_console(request)
|
||||
return response(environ, start_response)
|
||||
|
||||
Reference in New Issue
Block a user