Merge branch 'master' into Develop
# Conflicts: # cps/config_sql.py # cps/ub.py # cps/web.py
This commit is contained in:
commit
9e159ed5ab
3
.github/ISSUE_TEMPLATE/bug_report.md
vendored
3
.github/ISSUE_TEMPLATE/bug_report.md
vendored
|
@ -17,6 +17,9 @@ Steps to reproduce the behavior:
|
|||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Logfile**
|
||||
Add content of calibre-web.log file or the relevant error, try to reproduce your problem with "debug" log-level to get more output.
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
|
|
|
@ -23,12 +23,12 @@ Calibre-Web is a web app providing a clean interface for browsing, reading and d
|
|||
- Send eBooks to Kindle devices with the click of a button
|
||||
- Sync your Kobo devices through Calibre-Web with your Calibre library
|
||||
- Support for reading eBooks directly in the browser (.txt, .epub, .pdf, .cbr, .cbt, .cbz)
|
||||
- Upload new books in many formats
|
||||
- Support for Calibre custom columns
|
||||
- Upload new books in many formats, including audio formats (.mp3, .m4a, .m4b)
|
||||
- Support for Calibre Custom Columns
|
||||
- Ability to hide content based on categories and Custom Column content per user
|
||||
- Self-update capability
|
||||
- "Magic Link" login to make it easy to log on eReaders
|
||||
- Login via google/github oauth and via proxy authentication
|
||||
- Login via LDAP, google/github oauth and via proxy authentication
|
||||
|
||||
## Quick start
|
||||
|
||||
|
|
13
cps/about.py
13
cps/about.py
|
@ -43,6 +43,11 @@ try:
|
|||
except ImportError:
|
||||
unidecode_version = _(u'not installed')
|
||||
|
||||
try:
|
||||
from flask_dance import __version__ as flask_danceVersion
|
||||
except ImportError:
|
||||
flask_danceVersion = None
|
||||
|
||||
from . import services
|
||||
|
||||
about = flask.Blueprint('about', __name__)
|
||||
|
@ -68,9 +73,11 @@ _VERSIONS = OrderedDict(
|
|||
iso639=isoLanguages.__version__,
|
||||
pytz=pytz.__version__,
|
||||
Unidecode = unidecode_version,
|
||||
Flask_SimpleLDAP = u'installed' if bool(services.ldap) else u'not installed',
|
||||
Goodreads = u'installed' if bool(services.goodreads_support) else u'not installed',
|
||||
jsonschema = services.SyncToken.__version__ if bool(services.SyncToken) else u'not installed',
|
||||
Flask_SimpleLDAP = u'installed' if bool(services.ldap) else None,
|
||||
python_LDAP = services.ldapVersion if bool(services.ldapVersion) else None,
|
||||
Goodreads = u'installed' if bool(services.goodreads_support) else None,
|
||||
jsonschema = services.SyncToken.__version__ if bool(services.SyncToken) else None,
|
||||
flask_dance = flask_danceVersion
|
||||
)
|
||||
_VERSIONS.update(uploader.get_versions())
|
||||
|
||||
|
|
117
cps/admin.py
117
cps/admin.py
|
@ -42,8 +42,10 @@ from .helper import speaking_language, check_valid_domain, send_test_mail, reset
|
|||
from .gdriveutils import is_gdrive_ready, gdrive_support
|
||||
from .web import admin_required, render_title_template, before_request, unconfigured, login_required_if_no_ano
|
||||
|
||||
log = logger.create()
|
||||
|
||||
feature_support = {
|
||||
'ldap': False, # bool(services.ldap),
|
||||
'ldap': bool(services.ldap),
|
||||
'goodreads': bool(services.goodreads_support),
|
||||
'kobo': bool(services.kobo)
|
||||
}
|
||||
|
@ -57,7 +59,8 @@ feature_support = {
|
|||
try:
|
||||
from .oauth_bb import oauth_check, oauthblueprints
|
||||
feature_support['oauth'] = True
|
||||
except ImportError:
|
||||
except ImportError as err:
|
||||
log.debug('Cannot import Flask-Dance, login with Oauth will not work: %s', err)
|
||||
feature_support['oauth'] = False
|
||||
oauthblueprints = []
|
||||
oauth_check = {}
|
||||
|
@ -65,7 +68,7 @@ except ImportError:
|
|||
|
||||
feature_support['gdrive'] = gdrive_support
|
||||
admi = Blueprint('admin', __name__)
|
||||
log = logger.create()
|
||||
|
||||
|
||||
|
||||
@admi.route("/admin")
|
||||
|
@ -79,12 +82,12 @@ def admin_forbidden():
|
|||
@admin_required
|
||||
def shutdown():
|
||||
task = int(request.args.get("parameter").strip())
|
||||
showtext = {}
|
||||
if task in (0, 1): # valid commandos received
|
||||
# close all database connections
|
||||
db.dispose()
|
||||
ub.dispose()
|
||||
|
||||
showtext = {}
|
||||
if task == 0:
|
||||
showtext['text'] = _(u'Server restarted, please reload page')
|
||||
else:
|
||||
|
@ -96,9 +99,11 @@ def shutdown():
|
|||
if task == 2:
|
||||
log.warning("reconnecting to calibre database")
|
||||
db.setup_db(config)
|
||||
return '{}'
|
||||
showtext['text'] = _(u'Reconnect successful')
|
||||
return json.dumps(showtext)
|
||||
|
||||
abort(404)
|
||||
showtext['text'] = _(u'Unknown command')
|
||||
return json.dumps(showtext), 400
|
||||
|
||||
|
||||
@admi.route("/admin/view")
|
||||
|
@ -502,7 +507,7 @@ def _configuration_update_helper():
|
|||
with open(gdriveutils.CLIENT_SECRETS, 'r') as settings:
|
||||
gdrive_secrets = json.load(settings)['web']
|
||||
if not gdrive_secrets:
|
||||
return _configuration_result('client_secrets.json is not configured for web application')
|
||||
return _configuration_result(_('client_secrets.json Is Not Configured For Web Application'))
|
||||
gdriveutils.update_settings(
|
||||
gdrive_secrets['client_id'],
|
||||
gdrive_secrets['client_secret'],
|
||||
|
@ -518,11 +523,11 @@ def _configuration_update_helper():
|
|||
|
||||
reboot_required |= _config_string("config_keyfile")
|
||||
if config.config_keyfile and not os.path.isfile(config.config_keyfile):
|
||||
return _configuration_result('Keyfile location is not valid, please enter correct path', gdriveError)
|
||||
return _configuration_result(_('Keyfile Location is not Valid, Please Enter Correct Path'), gdriveError)
|
||||
|
||||
reboot_required |= _config_string("config_certfile")
|
||||
if config.config_certfile and not os.path.isfile(config.config_certfile):
|
||||
return _configuration_result('Certfile location is not valid, please enter correct path', gdriveError)
|
||||
return _configuration_result(_('Certfile Location is not Valid, Please Enter Correct Path'), gdriveError)
|
||||
|
||||
_config_checkbox_int("config_uploading")
|
||||
_config_checkbox_int("config_anonbrowse")
|
||||
|
@ -540,26 +545,57 @@ def _configuration_update_helper():
|
|||
|
||||
#LDAP configurator,
|
||||
if config.config_login_type == constants.LOGIN_LDAP:
|
||||
_config_string("config_ldap_provider_url")
|
||||
_config_int("config_ldap_port")
|
||||
_config_string("config_ldap_schema")
|
||||
_config_string("config_ldap_dn")
|
||||
_config_string("config_ldap_user_object")
|
||||
if not config.config_ldap_provider_url or not config.config_ldap_port or not config.config_ldap_dn or not config.config_ldap_user_object:
|
||||
return _configuration_result('Please enter a LDAP provider, port, DN and user object identifier', gdriveError)
|
||||
reboot_required |= _config_string("config_ldap_provider_url")
|
||||
reboot_required |= _config_int("config_ldap_port")
|
||||
# _config_string("config_ldap_schema")
|
||||
reboot_required |= _config_string("config_ldap_dn")
|
||||
reboot_required |= _config_string("config_ldap_serv_username")
|
||||
reboot_required |= _config_string("config_ldap_user_object")
|
||||
reboot_required |= _config_string("config_ldap_group_object_filter")
|
||||
reboot_required |= _config_string("config_ldap_group_members_field")
|
||||
reboot_required |= _config_checkbox("config_ldap_openldap")
|
||||
reboot_required |= _config_int("config_ldap_encryption")
|
||||
reboot_required |= _config_string("config_ldap_cert_path")
|
||||
_config_string("config_ldap_group_name")
|
||||
if "config_ldap_serv_password" in to_save and to_save["config_ldap_serv_password"] != "":
|
||||
reboot_required |= 1
|
||||
config.set_from_dictionary(to_save, "config_ldap_serv_password", base64.b64encode, encode='UTF-8')
|
||||
config.save()
|
||||
|
||||
_config_string("config_ldap_serv_username")
|
||||
if not config.config_ldap_serv_username or "config_ldap_serv_password" not in to_save:
|
||||
return _configuration_result('Please enter a LDAP service account and password', gdriveError)
|
||||
config.set_from_dictionary(to_save, "config_ldap_serv_password", base64.b64encode)
|
||||
if not config.config_ldap_provider_url \
|
||||
or not config.config_ldap_port \
|
||||
or not config.config_ldap_dn \
|
||||
or not config.config_ldap_user_object:
|
||||
return _configuration_result(_('Please Enter a LDAP Provider, '
|
||||
'Port, DN and User Object Identifier'), gdriveError)
|
||||
|
||||
_config_checkbox("config_ldap_use_ssl")
|
||||
_config_checkbox("config_ldap_use_tls")
|
||||
_config_checkbox("config_ldap_openldap")
|
||||
_config_checkbox("config_ldap_require_cert")
|
||||
_config_string("config_ldap_cert_path")
|
||||
if config.config_ldap_cert_path and not os.path.isfile(config.config_ldap_cert_path):
|
||||
return _configuration_result('LDAP Certfile location is not valid, please enter correct path', gdriveError)
|
||||
|
||||
if not config.config_ldap_serv_username or not bool(config.config_ldap_serv_password):
|
||||
return _configuration_result('Please Enter a LDAP Service Account and Password', gdriveError)
|
||||
|
||||
#_config_checkbox("config_ldap_use_ssl")
|
||||
#_config_checkbox("config_ldap_use_tls")
|
||||
# reboot_required |= _config_checkbox("config_ldap_openldap")
|
||||
# _config_checkbox("config_ldap_require_cert")
|
||||
|
||||
if config.config_ldap_group_object_filter:
|
||||
if config.config_ldap_group_object_filter.count("%s") != 1:
|
||||
return _configuration_result(_('LDAP Group Object Filter Needs to Have One "%s" Format Identifier'),
|
||||
gdriveError)
|
||||
if config.config_ldap_group_object_filter.count("(") != config.config_ldap_group_object_filter.count(")"):
|
||||
return _configuration_result(_('LDAP Group Object Filter Has Unmatched Parenthesis'),
|
||||
gdriveError)
|
||||
|
||||
if config.config_ldap_user_object.count("%s") != 1:
|
||||
return _configuration_result(_('LDAP User Object Filter needs to Have One "%s" Format Identifier'),
|
||||
gdriveError)
|
||||
if config.config_ldap_user_object.count("(") != config.config_ldap_user_object.count(")"):
|
||||
return _configuration_result(_('LDAP User Object Filter Has Unmatched Parenthesis'),
|
||||
gdriveError)
|
||||
|
||||
if config.config_ldap_cert_path and not os.path.isdir(config.config_ldap_cert_path):
|
||||
return _configuration_result(_('LDAP Certificate Location is not Valid, Please Enter Correct Path'),
|
||||
gdriveError)
|
||||
|
||||
# Remote login configuration
|
||||
_config_checkbox("config_remote_login")
|
||||
|
@ -586,33 +622,32 @@ def _configuration_update_helper():
|
|||
active_oauths = 0
|
||||
|
||||
for element in oauthblueprints:
|
||||
if to_save["config_"+str(element['id'])+"_oauth_client_id"] \
|
||||
and to_save["config_"+str(element['id'])+"_oauth_client_secret"]:
|
||||
active_oauths += 1
|
||||
element["active"] = 1
|
||||
ub.session.query(ub.OAuthProvider).filter(ub.OAuthProvider.id == element['id']).update(
|
||||
{"oauth_client_id":to_save["config_"+str(element['id'])+"_oauth_client_id"],
|
||||
"oauth_client_secret":to_save["config_"+str(element['id'])+"_oauth_client_secret"],
|
||||
"active":1})
|
||||
if to_save["config_" + str(element['id']) + "_oauth_client_id"] != element['oauth_client_id'] \
|
||||
or to_save["config_" + str(element['id']) + "_oauth_client_secret"] != element['oauth_client_secret']:
|
||||
reboot_required = True
|
||||
element['oauth_client_id'] = to_save["config_" + str(element['id']) + "_oauth_client_id"]
|
||||
element['oauth_client_secret'] = to_save["config_" + str(element['id']) + "_oauth_client_secret"]
|
||||
if to_save["config_"+str(element['id'])+"_oauth_client_id"] \
|
||||
and to_save["config_"+str(element['id'])+"_oauth_client_secret"]:
|
||||
active_oauths += 1
|
||||
element["active"] = 1
|
||||
else:
|
||||
ub.session.query(ub.OAuthProvider).filter(ub.OAuthProvider.id == element['id']).update(
|
||||
{"active":0})
|
||||
element["active"] = 0
|
||||
ub.session.query(ub.OAuthProvider).filter(ub.OAuthProvider.id == element['id']).update(
|
||||
{"oauth_client_id":to_save["config_"+str(element['id'])+"_oauth_client_id"],
|
||||
"oauth_client_secret":to_save["config_"+str(element['id'])+"_oauth_client_secret"],
|
||||
"active":element["active"]})
|
||||
|
||||
|
||||
reboot_required |= _config_int("config_log_level")
|
||||
reboot_required |= _config_string("config_logfile")
|
||||
if not logger.is_valid_logfile(config.config_logfile):
|
||||
return _configuration_result('Logfile location is not valid, please enter correct path', gdriveError)
|
||||
return _configuration_result(_('Logfile Location is not Valid, Please Enter Correct Path'), gdriveError)
|
||||
|
||||
reboot_required |= _config_checkbox_int("config_access_log")
|
||||
reboot_required |= _config_string("config_access_logfile")
|
||||
if not logger.is_valid_logfile(config.config_access_logfile):
|
||||
return _configuration_result('Access Logfile location is not valid, please enter correct path', gdriveError)
|
||||
return _configuration_result(_('Access Logfile Location is not Valid, Please Enter Correct Path'), gdriveError)
|
||||
|
||||
# Rarfile Content configuration
|
||||
_config_string("config_rarfile_location")
|
||||
|
@ -631,7 +666,7 @@ def _configuration_update_helper():
|
|||
if db_change:
|
||||
# reload(db)
|
||||
if not db.setup_db(config):
|
||||
return _configuration_result('DB location is not valid, please enter correct path', gdriveError)
|
||||
return _configuration_result(_('DB Location is not Valid, Please Enter Correct Path'), gdriveError)
|
||||
|
||||
config.save()
|
||||
flash(_(u"Calibre-Web configuration updated"), category="success")
|
||||
|
@ -657,7 +692,7 @@ def _configuration_result(error_flash=None, gdriveError=None):
|
|||
show_login_button = config.db_configured and not current_user.is_authenticated
|
||||
if error_flash:
|
||||
config.load()
|
||||
flash(_(error_flash), category="error")
|
||||
flash(error_flash, category="error")
|
||||
show_login_button = False
|
||||
|
||||
return render_title_template("config_edit.html", config=config, provider=oauthblueprints,
|
||||
|
|
|
@ -71,7 +71,7 @@ class _Settings(_Base):
|
|||
config_kobo_sync = Column(Boolean, default=False)
|
||||
|
||||
config_default_role = Column(SmallInteger, default=0)
|
||||
config_default_show = Column(SmallInteger, default=38911)
|
||||
config_default_show = Column(SmallInteger, default=constants.ADMIN_USER_SIDEBAR)
|
||||
config_columns_to_ignore = Column(String)
|
||||
|
||||
config_denied_tags = Column(String, default="")
|
||||
|
@ -93,18 +93,21 @@ class _Settings(_Base):
|
|||
config_kobo_proxy = Column(Boolean, default=False)
|
||||
|
||||
|
||||
config_ldap_provider_url = Column(String, default='localhost')
|
||||
config_ldap_provider_url = Column(String, default='example.org')
|
||||
config_ldap_port = Column(SmallInteger, default=389)
|
||||
config_ldap_schema = Column(String, default='ldap')
|
||||
config_ldap_serv_username = Column(String)
|
||||
config_ldap_serv_password = Column(String)
|
||||
config_ldap_use_ssl = Column(Boolean, default=False)
|
||||
config_ldap_use_tls = Column(Boolean, default=False)
|
||||
config_ldap_require_cert = Column(Boolean, default=False)
|
||||
config_ldap_cert_path = Column(String)
|
||||
config_ldap_dn = Column(String)
|
||||
config_ldap_user_object = Column(String)
|
||||
config_ldap_openldap = Column(Boolean, default=False)
|
||||
# config_ldap_schema = Column(String, default='ldap')
|
||||
config_ldap_serv_username = Column(String, default='cn=admin,dc=example,dc=org')
|
||||
config_ldap_serv_password = Column(String, default="")
|
||||
config_ldap_encryption = Column(SmallInteger, default=0)
|
||||
# config_ldap_use_tls = Column(Boolean, default=False)
|
||||
# config_ldap_require_cert = Column(Boolean, default=False)
|
||||
config_ldap_cert_path = Column(String, default="")
|
||||
config_ldap_dn = Column(String, default='dc=example,dc=org')
|
||||
config_ldap_user_object = Column(String, default='uid=%s')
|
||||
config_ldap_openldap = Column(Boolean, default=True)
|
||||
config_ldap_group_object_filter = Column(String, default='(&(objectclass=posixGroup)(cn=%s))')
|
||||
config_ldap_group_members_field = Column(String, default='memberUid')
|
||||
config_ldap_group_name = Column(String, default='calibreweb')
|
||||
|
||||
config_ebookconverter = Column(Integer, default=0)
|
||||
config_converterpath = Column(String)
|
||||
|
@ -212,7 +215,7 @@ class _ConfigSQL(object):
|
|||
return not bool(self.mail_server == constants.DEFAULT_MAIL_SERVER)
|
||||
|
||||
|
||||
def set_from_dictionary(self, dictionary, field, convertor=None, default=None):
|
||||
def set_from_dictionary(self, dictionary, field, convertor=None, default=None, encode=None):
|
||||
'''Possibly updates a field of this object.
|
||||
The new value, if present, is grabbed from the given dictionary, and optionally passed through a convertor.
|
||||
|
||||
|
@ -228,6 +231,9 @@ class _ConfigSQL(object):
|
|||
return False
|
||||
|
||||
if convertor is not None:
|
||||
if encode:
|
||||
new_value = convertor(new_value.encode(encode))
|
||||
else:
|
||||
new_value = convertor(new_value)
|
||||
|
||||
current_value = self.__dict__.get(field)
|
||||
|
@ -277,7 +283,9 @@ class _ConfigSQL(object):
|
|||
self._session.commit()
|
||||
self.load()
|
||||
|
||||
def invalidate(self):
|
||||
def invalidate(self, error=None):
|
||||
if error:
|
||||
log.error(error)
|
||||
log.warning("invalidating configuration")
|
||||
self.db_configured = False
|
||||
self.config_calibre_dir = None
|
||||
|
|
|
@ -344,14 +344,13 @@ def setup_db(config):
|
|||
isolation_level="SERIALIZABLE",
|
||||
connect_args={'check_same_thread': False})
|
||||
conn = engine.connect()
|
||||
except:
|
||||
config.invalidate()
|
||||
# conn.text_factory = lambda b: b.decode(errors = 'ignore') possible fix for #1302
|
||||
except Exception as e:
|
||||
config.invalidate(e)
|
||||
return False
|
||||
|
||||
config.db_configured = True
|
||||
update_title_sort(config, conn.connection)
|
||||
# conn.connection.create_function('lower', 1, lcase)
|
||||
# conn.connection.create_function('upper', 1, ucase)
|
||||
|
||||
if not cc_classes:
|
||||
cc = conn.execute("SELECT id, datatype FROM custom_columns")
|
||||
|
|
|
@ -796,6 +796,7 @@ def fill_indexpage_with_archived_books(page, database, db_filter, order, allow_s
|
|||
|
||||
|
||||
def get_typeahead(database, query, replace=('',''), tag_filter=true()):
|
||||
query = query or ''
|
||||
db.session.connection().connection.connection.create_function("lower", 1, lcase)
|
||||
entries = db.session.query(database).filter(tag_filter).filter(func.lower(database.name).ilike("%" + query + "%")).all()
|
||||
json_dumps = json.dumps([dict(name=r.name.replace(*replace)) for r in entries])
|
||||
|
|
15
cps/oauth.py
15
cps/oauth.py
|
@ -23,7 +23,18 @@ from flask import session
|
|||
try:
|
||||
from flask_dance.consumer.backend.sqla import SQLAlchemyBackend, first, _get_real_user
|
||||
from sqlalchemy.orm.exc import NoResultFound
|
||||
backend_resultcode = False # prevent storing values with this resultcode
|
||||
except ImportError:
|
||||
# fails on flask-dance >1.3, due to renaming
|
||||
try:
|
||||
from flask_dance.consumer.storage.sqla import SQLAlchemyStorage as SQLAlchemyBackend
|
||||
from flask_dance.consumer.storage.sqla import first, _get_real_user
|
||||
from sqlalchemy.orm.exc import NoResultFound
|
||||
backend_resultcode = True # prevent storing values with this resultcode
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
try:
|
||||
class OAuthBackend(SQLAlchemyBackend):
|
||||
"""
|
||||
Stores and retrieves OAuth tokens using a relational database through
|
||||
|
@ -39,7 +50,7 @@ try:
|
|||
|
||||
def get(self, blueprint, user=None, user_id=None):
|
||||
if self.provider_id + '_oauth_token' in session and session[self.provider_id + '_oauth_token'] != '':
|
||||
return session[blueprint.name + '_oauth_token']
|
||||
return session[self.provider_id + '_oauth_token']
|
||||
# check cache
|
||||
cache_key = self.make_cache_key(blueprint=blueprint, user=user, user_id=user_id)
|
||||
token = self.cache.get(cache_key)
|
||||
|
@ -152,5 +163,5 @@ try:
|
|||
blueprint=blueprint, user=user, user_id=user_id,
|
||||
))
|
||||
|
||||
except ImportError:
|
||||
except Exception:
|
||||
pass
|
||||
|
|
108
cps/oauth_bb.py
108
cps/oauth_bb.py
|
@ -35,8 +35,7 @@ from sqlalchemy.orm.exc import NoResultFound
|
|||
|
||||
from . import constants, logger, config, app, ub
|
||||
from .web import login_required
|
||||
from .oauth import OAuthBackend
|
||||
# from .web import github_oauth_required
|
||||
from .oauth import OAuthBackend, backend_resultcode
|
||||
|
||||
|
||||
oauth_check = {}
|
||||
|
@ -59,29 +58,29 @@ def oauth_required(f):
|
|||
return inner
|
||||
|
||||
|
||||
def register_oauth_blueprint(id, show_name):
|
||||
oauth_check[id] = show_name
|
||||
def register_oauth_blueprint(cid, show_name):
|
||||
oauth_check[cid] = show_name
|
||||
|
||||
|
||||
def register_user_with_oauth(user=None):
|
||||
all_oauth = {}
|
||||
for oauth in oauth_check.keys():
|
||||
if str(oauth) + '_oauth_user_id' in session and session[str(oauth) + '_oauth_user_id'] != '':
|
||||
all_oauth[oauth] = oauth_check[oauth]
|
||||
for oauth_key in oauth_check.keys():
|
||||
if str(oauth_key) + '_oauth_user_id' in session and session[str(oauth_key) + '_oauth_user_id'] != '':
|
||||
all_oauth[oauth_key] = oauth_check[oauth_key]
|
||||
if len(all_oauth.keys()) == 0:
|
||||
return
|
||||
if user is None:
|
||||
flash(_(u"Register with %(provider)s", provider=", ".join(list(all_oauth.values()))), category="success")
|
||||
else:
|
||||
for oauth in all_oauth.keys():
|
||||
for oauth_key in all_oauth.keys():
|
||||
# Find this OAuth token in the database, or create it
|
||||
query = ub.session.query(ub.OAuth).filter_by(
|
||||
provider=oauth,
|
||||
provider_user_id=session[str(oauth) + "_oauth_user_id"],
|
||||
provider=oauth_key,
|
||||
provider_user_id=session[str(oauth_key) + "_oauth_user_id"],
|
||||
)
|
||||
try:
|
||||
oauth = query.one()
|
||||
oauth.user_id = user.id
|
||||
oauth_key = query.one()
|
||||
oauth_key.user_id = user.id
|
||||
except NoResultFound:
|
||||
# no found, return error
|
||||
return
|
||||
|
@ -93,22 +92,23 @@ def register_user_with_oauth(user=None):
|
|||
|
||||
|
||||
def logout_oauth_user():
|
||||
for oauth in oauth_check.keys():
|
||||
if str(oauth) + '_oauth_user_id' in session:
|
||||
session.pop(str(oauth) + '_oauth_user_id')
|
||||
for oauth_key in oauth_check.keys():
|
||||
if str(oauth_key) + '_oauth_user_id' in session:
|
||||
session.pop(str(oauth_key) + '_oauth_user_id')
|
||||
|
||||
|
||||
if ub.oauth_support:
|
||||
oauthblueprints = []
|
||||
if not ub.session.query(ub.OAuthProvider).count():
|
||||
oauth = ub.OAuthProvider()
|
||||
oauth.provider_name = "github"
|
||||
oauth.active = False
|
||||
ub.session.add(oauth)
|
||||
oauthProvider = ub.OAuthProvider()
|
||||
oauthProvider.provider_name = "github"
|
||||
oauthProvider.active = False
|
||||
ub.session.add(oauthProvider)
|
||||
ub.session.commit()
|
||||
oauth = ub.OAuthProvider()
|
||||
oauth.provider_name = "google"
|
||||
oauth.active = False
|
||||
ub.session.add(oauth)
|
||||
oauthProvider = ub.OAuthProvider()
|
||||
oauthProvider.provider_name = "google"
|
||||
oauthProvider.active = False
|
||||
ub.session.add(oauthProvider)
|
||||
ub.session.commit()
|
||||
|
||||
oauth_ids = ub.session.query(ub.OAuthProvider).all()
|
||||
|
@ -141,9 +141,9 @@ if ub.oauth_support:
|
|||
scope=element['scope']
|
||||
)
|
||||
element['blueprint'] = blueprint
|
||||
app.register_blueprint(blueprint, url_prefix="/login")
|
||||
element['blueprint'].backend = OAuthBackend(ub.OAuth, ub.session, str(element['id']),
|
||||
user=current_user, user_required=True)
|
||||
app.register_blueprint(blueprint, url_prefix="/login")
|
||||
if element['active']:
|
||||
register_oauth_blueprint(element['id'], element['provider_name'])
|
||||
|
||||
|
@ -190,54 +190,64 @@ if ub.oauth_support:
|
|||
provider_user_id=provider_user_id,
|
||||
)
|
||||
try:
|
||||
oauth = query.one()
|
||||
oauth_entry = query.one()
|
||||
# update token
|
||||
oauth.token = token
|
||||
oauth_entry.token = token
|
||||
except NoResultFound:
|
||||
oauth = ub.OAuth(
|
||||
oauth_entry = ub.OAuth(
|
||||
provider=provider_id,
|
||||
provider_user_id=provider_user_id,
|
||||
token=token,
|
||||
)
|
||||
try:
|
||||
ub.session.add(oauth)
|
||||
ub.session.add(oauth_entry)
|
||||
ub.session.commit()
|
||||
except Exception as e:
|
||||
log.exception(e)
|
||||
ub.session.rollback()
|
||||
|
||||
# Disable Flask-Dance's default behavior for saving the OAuth token
|
||||
return False
|
||||
# Value differrs depending on flask-dance version
|
||||
return backend_resultcode
|
||||
|
||||
|
||||
def bind_oauth_or_register(provider_id, provider_user_id, redirect_url):
|
||||
def bind_oauth_or_register(provider_id, provider_user_id, redirect_url, provider_name):
|
||||
query = ub.session.query(ub.OAuth).filter_by(
|
||||
provider=provider_id,
|
||||
provider_user_id=provider_user_id,
|
||||
)
|
||||
try:
|
||||
oauth = query.one()
|
||||
oauth_entry = query.first()
|
||||
# already bind with user, just login
|
||||
if oauth.user:
|
||||
login_user(oauth.user)
|
||||
if oauth_entry.user:
|
||||
login_user(oauth_entry.user)
|
||||
log.debug(u"You are now logged in as: '%s'", oauth_entry.user.nickname)
|
||||
flash(_(u"you are now logged in as: '%(nickname)s'", nickname= oauth_entry.user.nickname),
|
||||
category="success")
|
||||
return redirect(url_for('web.index'))
|
||||
else:
|
||||
# bind to current user
|
||||
if current_user and current_user.is_authenticated:
|
||||
oauth.user = current_user
|
||||
oauth_entry.user = current_user
|
||||
try:
|
||||
ub.session.add(oauth)
|
||||
ub.session.add(oauth_entry)
|
||||
ub.session.commit()
|
||||
flash(_(u"Link to %(oauth)s Succeeded", oauth=provider_name), category="success")
|
||||
return redirect(url_for('web.profile'))
|
||||
except Exception as e:
|
||||
log.exception(e)
|
||||
ub.session.rollback()
|
||||
else:
|
||||
flash(_(u"Login failed, No User Linked With OAuth Account"), category="error")
|
||||
log.info('Login failed, No User Linked With OAuth Account')
|
||||
return redirect(url_for('web.login'))
|
||||
# return redirect(url_for('web.login'))
|
||||
# if config.config_public_reg:
|
||||
# return redirect(url_for('web.register'))
|
||||
# else:
|
||||
# flash(_(u"Public registration is not enabled"), category="error")
|
||||
# return redirect(url_for(redirect_url))
|
||||
except NoResultFound:
|
||||
except (NoResultFound, AttributeError):
|
||||
return redirect(url_for(redirect_url))
|
||||
|
||||
|
||||
|
@ -248,8 +258,8 @@ if ub.oauth_support:
|
|||
)
|
||||
try:
|
||||
oauths = query.all()
|
||||
for oauth in oauths:
|
||||
status.append(int(oauth.provider))
|
||||
for oauth_entry in oauths:
|
||||
status.append(int(oauth_entry.provider))
|
||||
return status
|
||||
except NoResultFound:
|
||||
return None
|
||||
|
@ -263,21 +273,21 @@ if ub.oauth_support:
|
|||
user_id=current_user.id,
|
||||
)
|
||||
try:
|
||||
oauth = query.one()
|
||||
oauth_entry = query.one()
|
||||
if current_user and current_user.is_authenticated:
|
||||
oauth.user = current_user
|
||||
oauth_entry.user = current_user
|
||||
try:
|
||||
ub.session.delete(oauth)
|
||||
ub.session.delete(oauth_entry)
|
||||
ub.session.commit()
|
||||
logout_oauth_user()
|
||||
flash(_(u"Unlink to %(oauth)s success.", oauth=oauth_check[provider]), category="success")
|
||||
flash(_(u"Unlink to %(oauth)s Succeeded", oauth=oauth_check[provider]), category="success")
|
||||
except Exception as e:
|
||||
log.exception(e)
|
||||
ub.session.rollback()
|
||||
flash(_(u"Unlink to %(oauth)s failed.", oauth=oauth_check[provider]), category="error")
|
||||
flash(_(u"Unlink to %(oauth)s Failed", oauth=oauth_check[provider]), category="error")
|
||||
except NoResultFound:
|
||||
log.warning("oauth %s for user %d not fount", provider, current_user.id)
|
||||
flash(_(u"Not linked to %(oauth)s.", oauth=oauth_check[provider]), category="error")
|
||||
log.warning("oauth %s for user %d not found", provider, current_user.id)
|
||||
flash(_(u"Not Linked to %(oauth)s.", oauth=oauth_check[provider]), category="error")
|
||||
return redirect(url_for('web.profile'))
|
||||
|
||||
|
||||
|
@ -296,7 +306,7 @@ if ub.oauth_support:
|
|||
flash(msg, category="error")
|
||||
|
||||
|
||||
@oauth.route('/github')
|
||||
@oauth.route('/link/github')
|
||||
@oauth_required
|
||||
def github_login():
|
||||
if not github.authorized:
|
||||
|
@ -304,7 +314,7 @@ if ub.oauth_support:
|
|||
account_info = github.get('/user')
|
||||
if account_info.ok:
|
||||
account_info_json = account_info.json()
|
||||
return bind_oauth_or_register(oauthblueprints[0]['id'], account_info_json['id'], 'github.login')
|
||||
return bind_oauth_or_register(oauthblueprints[0]['id'], account_info_json['id'], 'github.login', 'github')
|
||||
flash(_(u"GitHub Oauth error, please retry later."), category="error")
|
||||
return redirect(url_for('web.login'))
|
||||
|
||||
|
@ -315,7 +325,7 @@ if ub.oauth_support:
|
|||
return unlink_oauth(oauthblueprints[0]['id'])
|
||||
|
||||
|
||||
@oauth.route('/login/google')
|
||||
@oauth.route('/link/google')
|
||||
@oauth_required
|
||||
def google_login():
|
||||
if not google.authorized:
|
||||
|
@ -323,7 +333,7 @@ if ub.oauth_support:
|
|||
resp = google.get("/oauth2/v2/userinfo")
|
||||
if resp.ok:
|
||||
account_info_json = resp.json()
|
||||
return bind_oauth_or_register(oauthblueprints[1]['id'], account_info_json['id'], 'google.login')
|
||||
return bind_oauth_or_register(oauthblueprints[1]['id'], account_info_json['id'], 'google.login', 'google')
|
||||
flash(_(u"Google Oauth error, please retry later."), category="error")
|
||||
return redirect(url_for('web.login'))
|
||||
|
||||
|
|
|
@ -26,19 +26,22 @@ log = logger.create()
|
|||
|
||||
try: from . import goodreads_support
|
||||
except ImportError as err:
|
||||
log.debug("cannot import goodreads, showing authors-metadata will not work: %s", err)
|
||||
log.debug("Cannot import goodreads, showing authors-metadata will not work: %s", err)
|
||||
goodreads_support = None
|
||||
|
||||
|
||||
try: from . import simpleldap as ldap
|
||||
try:
|
||||
from . import simpleldap as ldap
|
||||
from .simpleldap import ldapVersion
|
||||
except ImportError as err:
|
||||
log.debug("cannot import simpleldap, logging in with ldap will not work: %s", err)
|
||||
log.debug("Cannot import simpleldap, logging in with ldap will not work: %s", err)
|
||||
ldap = None
|
||||
ldapVersion = None
|
||||
|
||||
try:
|
||||
from . import SyncToken as SyncToken
|
||||
kobo = True
|
||||
except ImportError as err:
|
||||
log.debug("cannot import SyncToken, syncing books with Kobo Devices will not work: %s", err)
|
||||
log.debug("Cannot import SyncToken, syncing books with Kobo Devices will not work: %s", err)
|
||||
kobo = None
|
||||
SyncToken = None
|
||||
|
|
|
@ -23,6 +23,10 @@ from flask_simpleldap import LDAP, LDAPException
|
|||
|
||||
from .. import constants, logger
|
||||
|
||||
try:
|
||||
from ldap.pkginfo import __version__ as ldapVersion
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
log = logger.create()
|
||||
_ldap = LDAP()
|
||||
|
@ -34,44 +38,69 @@ def init_app(app, config):
|
|||
|
||||
app.config['LDAP_HOST'] = config.config_ldap_provider_url
|
||||
app.config['LDAP_PORT'] = config.config_ldap_port
|
||||
app.config['LDAP_SCHEMA'] = config.config_ldap_schema
|
||||
app.config['LDAP_USERNAME'] = config.config_ldap_user_object.replace('%s', config.config_ldap_serv_username)\
|
||||
+ ',' + config.config_ldap_dn
|
||||
if config.config_ldap_encryption == 2:
|
||||
app.config['LDAP_SCHEMA'] = 'ldaps'
|
||||
else:
|
||||
app.config['LDAP_SCHEMA'] = 'ldap'
|
||||
# app.config['LDAP_SCHEMA'] = config.config_ldap_schema
|
||||
app.config['LDAP_USERNAME'] = config.config_ldap_serv_username
|
||||
if config.config_ldap_serv_password is None:
|
||||
config.config_ldap_serv_password = ''
|
||||
app.config['LDAP_PASSWORD'] = base64.b64decode(config.config_ldap_serv_password)
|
||||
app.config['LDAP_REQUIRE_CERT'] = bool(config.config_ldap_require_cert)
|
||||
if config.config_ldap_require_cert:
|
||||
if bool(config.config_ldap_cert_path):
|
||||
app.config['LDAP_REQUIRE_CERT'] = True
|
||||
app.config['LDAP_CERT_PATH'] = config.config_ldap_cert_path
|
||||
app.config['LDAP_BASE_DN'] = config.config_ldap_dn
|
||||
app.config['LDAP_USER_OBJECT_FILTER'] = config.config_ldap_user_object
|
||||
app.config['LDAP_USE_SSL'] = bool(config.config_ldap_use_ssl)
|
||||
app.config['LDAP_USE_TLS'] = bool(config.config_ldap_use_tls)
|
||||
|
||||
app.config['LDAP_USE_TLS'] = bool(config.config_ldap_encryption == 1)
|
||||
app.config['LDAP_USE_SSL'] = bool(config.config_ldap_encryption == 2)
|
||||
app.config['LDAP_OPENLDAP'] = bool(config.config_ldap_openldap)
|
||||
app.config['LDAP_GROUP_OBJECT_FILTER'] = config.config_ldap_group_object_filter
|
||||
app.config['LDAP_GROUP_MEMBERS_FIELD'] = config.config_ldap_group_members_field
|
||||
# app.config['LDAP_CUSTOM_OPTIONS'] = {'OPT_NETWORK_TIMEOUT': 10}
|
||||
|
||||
_ldap.init_app(app)
|
||||
|
||||
|
||||
def get_object_details(user=None, group=None, query_filter=None, dn_only=False):
|
||||
return _ldap.get_object_details(user, group, query_filter, dn_only)
|
||||
|
||||
|
||||
def bind():
|
||||
return _ldap.bind()
|
||||
|
||||
|
||||
def get_group_members(group):
|
||||
return _ldap.get_group_members(group)
|
||||
|
||||
|
||||
def basic_auth_required(func):
|
||||
return _ldap.basic_auth_required(func)
|
||||
|
||||
|
||||
def bind_user(username, password):
|
||||
# ulf= _ldap.get_object_details('admin')
|
||||
'''Attempts a LDAP login.
|
||||
|
||||
:returns: True if login succeeded, False if login failed, None if server unavailable.
|
||||
'''
|
||||
try:
|
||||
if _ldap.get_object_details(username):
|
||||
result = _ldap.bind_user(username, password)
|
||||
log.debug("LDAP login '%s': %r", username, result)
|
||||
return result is not None
|
||||
return result is not None, None
|
||||
return None, None # User not found
|
||||
except (TypeError, AttributeError) as ex:
|
||||
error = ("LDAP bind_user: %s" % ex)
|
||||
return None, error
|
||||
except LDAPException as ex:
|
||||
if ex.message == 'Invalid credentials':
|
||||
log.info("LDAP login '%s' failed: %s", username, ex)
|
||||
return False
|
||||
error = ("LDAP admin login failed")
|
||||
return None, error
|
||||
if ex.message == "Can't contact LDAP server":
|
||||
log.warning('LDAP Server down: %s', ex)
|
||||
return None
|
||||
# log.warning('LDAP Server down: %s', ex)
|
||||
error = ('LDAP Server down: %s' % ex)
|
||||
return None, error
|
||||
else:
|
||||
log.warning('LDAP Server error: %s', ex.message)
|
||||
return None
|
||||
error = ('LDAP Server error: %s' % ex.message)
|
||||
return None, error
|
||||
|
|
2
cps/static/css/caliBlur.min.css
vendored
2
cps/static/css/caliBlur.min.css
vendored
File diff suppressed because one or more lines are too long
|
@ -29,15 +29,15 @@ $(function () {
|
|||
var msg = i18nMsg;
|
||||
var douban = "https://api.douban.com";
|
||||
var dbSearch = "/v2/book/search";
|
||||
var dbDone = true;
|
||||
var dbDone = 0;
|
||||
|
||||
var google = "https://www.googleapis.com";
|
||||
var ggSearch = "/books/v1/volumes";
|
||||
var ggDone = false;
|
||||
var ggDone = 0;
|
||||
|
||||
var comicvine = "https://comicvine.gamespot.com";
|
||||
var cvSearch = "/api/search/";
|
||||
var cvDone = false;
|
||||
var cvDone = 0;
|
||||
|
||||
var showFlag = 0;
|
||||
|
||||
|
@ -73,7 +73,9 @@ $(function () {
|
|||
if (showFlag === 1) {
|
||||
$("#meta-info").html("<ul id=\"book-list\" class=\"media-list\"></ul>");
|
||||
}
|
||||
if (!ggDone && !dbDone) {
|
||||
if ((ggDone == 3 || (ggDone == 1 && ggResults.length === 0)) &&
|
||||
(dbDone == 3 || (ggDone == 1 && dbResults.length === 0)) &&
|
||||
(cvDone == 3 || (ggDone == 1 && cvResults.length === 0))) {
|
||||
$("#meta-info").html("<p class=\"text-danger\">" + msg.no_result + "</p>");
|
||||
return;
|
||||
}
|
||||
|
@ -93,7 +95,8 @@ $(function () {
|
|||
return [year, month, day].join("-");
|
||||
}
|
||||
|
||||
if (ggDone && ggResults.length > 0) {
|
||||
if (ggResults.length > 0) {
|
||||
if (ggDone < 2) {
|
||||
ggResults.forEach(function(result) {
|
||||
var book = {
|
||||
id: result.id,
|
||||
|
@ -121,9 +124,14 @@ $(function () {
|
|||
|
||||
$("#book-list").append($book);
|
||||
});
|
||||
ggDone = false;
|
||||
ggDone = 2;
|
||||
} else {
|
||||
ggDone = 3;
|
||||
}
|
||||
if (dbDone && dbResults.length > 0) {
|
||||
}
|
||||
|
||||
if (dbResults.length > 0) {
|
||||
if (dbDone < 2) {
|
||||
dbResults.forEach(function(result) {
|
||||
var seriesTitle = "";
|
||||
if (result.series) {
|
||||
|
@ -168,9 +176,13 @@ $(function () {
|
|||
|
||||
$("#book-list").append($book);
|
||||
});
|
||||
dbDone = false;
|
||||
dbDone = 2;
|
||||
} else {
|
||||
dbDone = 3;
|
||||
}
|
||||
if (cvDone && cvResults.length > 0) {
|
||||
}
|
||||
if (cvResults.length > 0) {
|
||||
if (cvDone < 2) {
|
||||
cvResults.forEach(function(result) {
|
||||
var seriesTitle = "";
|
||||
if (result.volume.name) {
|
||||
|
@ -214,7 +226,10 @@ $(function () {
|
|||
|
||||
$("#book-list").append($book);
|
||||
});
|
||||
cvDone = false;
|
||||
cvDone = 2;
|
||||
} else {
|
||||
cvDone = 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -227,11 +242,10 @@ $(function () {
|
|||
success: function success(data) {
|
||||
if ("items" in data) {
|
||||
ggResults = data.items;
|
||||
ggDone = true;
|
||||
}
|
||||
},
|
||||
complete: function complete() {
|
||||
ggDone = true;
|
||||
ggDone = 1;
|
||||
showResult();
|
||||
$("#show-google").trigger("change");
|
||||
}
|
||||
|
@ -252,7 +266,7 @@ $(function () {
|
|||
$("#meta-info").html("<p class=\"text-danger\">" + msg.search_error + "!</p>" + $("#meta-info")[0].innerHTML);
|
||||
},
|
||||
complete: function complete() {
|
||||
dbDone = true;
|
||||
dbDone = 1;
|
||||
showResult();
|
||||
$("#show-douban").trigger("change");
|
||||
}
|
||||
|
@ -274,7 +288,7 @@ $(function () {
|
|||
$("#meta-info").html("<p class=\"text-danger\">" + msg.search_error + "!</p>" + $("#meta-info")[0].innerHTML);
|
||||
},
|
||||
complete: function complete() {
|
||||
cvDone = true;
|
||||
cvDone = 1;
|
||||
showResult();
|
||||
$("#show-comics").trigger("change");
|
||||
}
|
||||
|
@ -283,6 +297,10 @@ $(function () {
|
|||
|
||||
function doSearch (keyword) {
|
||||
showFlag = 0;
|
||||
dbDone = ggDone = cvDone = 0;
|
||||
dbResults = [];
|
||||
ggResults = [];
|
||||
cvResults = [];
|
||||
$("#meta-info").text(msg.loading);
|
||||
if (keyword) {
|
||||
dbSearchBook(keyword);
|
||||
|
|
|
@ -29,7 +29,6 @@ $(document).on("change", "input[type=\"checkbox\"][data-control]", function () {
|
|||
});
|
||||
});
|
||||
|
||||
|
||||
// Generic control/related handler to show/hide fields based on a select' value
|
||||
$(document).on("change", "select[data-control]", function() {
|
||||
var $this = $(this);
|
||||
|
@ -39,13 +38,26 @@ $(document).on("change", "select[data-control]", function() {
|
|||
for (var i = 0; i < $(this)[0].length; i++) {
|
||||
var element = parseInt($(this)[0][i].value);
|
||||
if (element === showOrHide) {
|
||||
$("[data-related=" + name + "-" + element + "]").show();
|
||||
$("[data-related^=" + name + "][data-related*=-" + element + "]").show();
|
||||
} else {
|
||||
$("[data-related=" + name + "-" + element + "]").hide();
|
||||
$("[data-related^=" + name + "][data-related*=-" + element + "]").hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Generic control/related handler to show/hide fields based on a select' value
|
||||
// this one is made to show all values if select value is not 0
|
||||
$(document).on("change", "select[data-controlall]", function() {
|
||||
var $this = $(this);
|
||||
var name = $this.data("controlall");
|
||||
var showOrHide = parseInt($this.val());
|
||||
if (showOrHide) {
|
||||
$("[data-related=" + name + "]").show();
|
||||
} else {
|
||||
$("[data-related=" + name + "]").hide();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
$(function() {
|
||||
var updateTimerID;
|
||||
|
@ -64,7 +76,7 @@ $(function() {
|
|||
function cleanUp() {
|
||||
clearInterval(updateTimerID);
|
||||
$("#spinner2").hide();
|
||||
$("#updateFinished").removeClass("hidden");
|
||||
$("#DialogFinished").removeClass("hidden");
|
||||
$("#check_for_update").removeClass("hidden");
|
||||
$("#perform_update").addClass("hidden");
|
||||
$("#message").alert("close");
|
||||
|
@ -81,13 +93,13 @@ $(function() {
|
|||
url: window.location.pathname + "/../../get_updater_status",
|
||||
success: function success(data) {
|
||||
// console.log(data.status);
|
||||
$("#Updatecontent").html(updateText[data.status]);
|
||||
$("#DialogContent").html(updateText[data.status]);
|
||||
if (data.status > 6) {
|
||||
cleanUp();
|
||||
}
|
||||
},
|
||||
error: function error() {
|
||||
$("#Updatecontent").html(updateText[7]);
|
||||
$("#DialogContent").html(updateText[7]);
|
||||
cleanUp();
|
||||
},
|
||||
timeout: 2000
|
||||
|
@ -146,8 +158,8 @@ $(function() {
|
|||
var $this = $(this);
|
||||
var buttonText = $this.html();
|
||||
$this.html("...");
|
||||
$("#Updatecontent").html("");
|
||||
$("#updateFinished").addClass("hidden");
|
||||
$("#DialogContent").html("");
|
||||
$("#DialogFinished").addClass("hidden");
|
||||
$("#update_error").addClass("hidden");
|
||||
if ($("#message").length) {
|
||||
$("#message").alert("close");
|
||||
|
@ -189,13 +201,24 @@ $(function() {
|
|||
});
|
||||
});
|
||||
$("#restart_database").click(function() {
|
||||
$("#DialogHeader").addClass("hidden");
|
||||
$("#DialogFinished").addClass("hidden");
|
||||
$("#DialogContent").html("");
|
||||
$("#spinner2").show();
|
||||
$.ajax({
|
||||
dataType: "json",
|
||||
url: window.location.pathname + "/../../shutdown",
|
||||
data: {"parameter":2}
|
||||
data: {"parameter":2},
|
||||
success: function success(data) {
|
||||
$("#spinner2").hide();
|
||||
ResultText = data.text;
|
||||
$("#DialogContent").html(ResultText);
|
||||
$("#DialogFinished").removeClass("hidden");
|
||||
}
|
||||
});
|
||||
});
|
||||
$("#perform_update").click(function() {
|
||||
$("#DialogHeader").removeClass("hidden");
|
||||
$("#spinner2").show();
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
|
@ -204,7 +227,7 @@ $(function() {
|
|||
url: window.location.pathname + "/../../get_updater_status",
|
||||
success: function success(data) {
|
||||
updateText = data.text;
|
||||
$("#Updatecontent").html(updateText[data.status]);
|
||||
$("#DialogContent").html(updateText[data.status]);
|
||||
// console.log(data.status);
|
||||
updateTimerID = setInterval(updateTimer, 2000);
|
||||
}
|
||||
|
@ -214,6 +237,7 @@ $(function() {
|
|||
// Init all data control handlers to default
|
||||
$("input[data-control]").trigger("change");
|
||||
$("select[data-control]").trigger("change");
|
||||
$("select[data-controlall]").trigger("change");
|
||||
|
||||
$("#bookDetailsModal")
|
||||
.on("show.bs.modal", function(e) {
|
||||
|
@ -274,6 +298,26 @@ $(function() {
|
|||
$(".discover .row").isotope("layout");
|
||||
});
|
||||
|
||||
$('#import_ldap_users').click(function() {
|
||||
$("#DialogHeader").addClass("hidden");
|
||||
$("#DialogFinished").addClass("hidden");
|
||||
$("#DialogContent").html("");
|
||||
$("#spinner2").show();
|
||||
var pathname = document.getElementsByTagName("script"), src = pathname[pathname.length - 1].src;
|
||||
var path = src.substring(0, src.lastIndexOf("/"));
|
||||
$.ajax({
|
||||
method:"get",
|
||||
dataType: "json",
|
||||
url: path + "/../../import_ldap_users",
|
||||
success: function success(data) {
|
||||
$("#spinner2").hide();
|
||||
ResultText = data.text;
|
||||
$("#DialogContent").html(ResultText);
|
||||
$("#DialogFinished").removeClass("hidden");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(".author-expand").click(function() {
|
||||
$(this).parent().find("a.author-name").slice($(this).data("authors-max")).toggle();
|
||||
$(this).parent().find("span.author-hidden-divider").toggle();
|
||||
|
|
|
@ -36,6 +36,10 @@
|
|||
{% endfor %}
|
||||
</table>
|
||||
<div class="btn btn-default" id="admin_new_user"><a href="{{url_for('admin.new_user')}}">{{_('Add New User')}}</a></div>
|
||||
{% if (config.config_login_type == 1) %}
|
||||
<div class="btn btn-default" id="import_ldap_users" data-toggle="modal" data-target="#StatusDialog">{{_('Import LDAP Users')}}</div>
|
||||
<!--a href="#" id="import_ldap_users" name="import_ldap_users"><button type="submit" class="btn btn-default">{{_('Import LDAP Users')}}</button></a-->
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -120,7 +124,7 @@
|
|||
<div class="col">
|
||||
<h2>{{_('Administration')}}</h2>
|
||||
<div class="btn btn-default"><a id="logfile" href="{{url_for('admin.view_logfile')}}">{{_('View Logs')}}</a></div>
|
||||
<div class="btn btn-default" id="restart_database">{{_('Reconnect Calibre Database')}}</div>
|
||||
<div class="btn btn-default" id="restart_database" data-toggle="modal" data-target="#StatusDialog">{{_('Reconnect Calibre Database')}}</div>
|
||||
<div class="btn btn-default" id="admin_restart" data-toggle="modal" data-target="#RestartDialog">{{_('Restart')}}</div>
|
||||
<div class="btn btn-default" id="admin_stop" data-toggle="modal" data-target="#ShutdownDialog">{{_('Shutdown')}}</div>
|
||||
</div>
|
||||
|
@ -146,7 +150,7 @@
|
|||
|
||||
<div class="hidden" id="update_error"> <span>{{update_error}}</span></div>
|
||||
<div class="btn btn-default" id="check_for_update">{{_('Check for Update')}}</div>
|
||||
<div class="btn btn-default hidden" id="perform_update" data-toggle="modal" data-target="#UpdateprogressDialog">{{_('Perform Update')}}</div>
|
||||
<div class="btn btn-default hidden" id="perform_update" data-toggle="modal" data-target="#StatusDialog">{{_('Perform Update')}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -183,21 +187,21 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="UpdateprogressDialog" class="modal fade" role="dialog">
|
||||
<div id="StatusDialog" class="modal fade" role="dialog">
|
||||
<div class="modal-dialog modal-sm">
|
||||
<!-- Modal content-->
|
||||
<div class="modal-content">
|
||||
<div class="modal-header bg-info text-center">
|
||||
<span>{{_('Updating, please do not reload this page')}}</span>
|
||||
<span id="DialogHeader">{{_('Updating, please do not reload this page')}}</span>
|
||||
</div>
|
||||
<div class="modal-body text-center">
|
||||
<div id="spinner2" class="spinner2" style="display:none;">
|
||||
<img id="img-spinner2" src="{{ url_for('static', filename='css/libs/images/loading-icon.gif') }}"/>
|
||||
</div>
|
||||
<p></p>
|
||||
<div id="Updatecontent"></div>
|
||||
<div id="DialogContent"></div>
|
||||
<p></p>
|
||||
<button type="button" class="btn btn-default hidden" id="updateFinished" data-dismiss="modal">{{_('OK')}}</button>
|
||||
<button type="button" class="btn btn-default hidden" id="DialogFinished" data-dismiss="modal">{{_('OK')}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -198,6 +198,17 @@
|
|||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="form-group">
|
||||
<input type="checkbox" id="config_allow_reverse_proxy_header_login" name="config_allow_reverse_proxy_header_login" data-control="reverse-proxy-login-settings" {% if config.config_allow_reverse_proxy_header_login %}checked{% endif %}>
|
||||
<label for="config_allow_reverse_proxy_header_login">{{_('Allow Reverse Proxy Authentication')}}</label>
|
||||
</div>
|
||||
<div data-related="reverse-proxy-login-settings">
|
||||
<div class="form-group">
|
||||
<label for="config_reverse_proxy_login_header_name">{{_('Reverse Proxy Header Name')}}</label>
|
||||
<input type="text" class="form-control" id="config_reverse_proxy_login_header_name" name="config_reverse_proxy_login_header_name" value="{% if config.config_reverse_proxy_login_header_name != None %}{{ config.config_reverse_proxy_login_header_name }}{% endif %}" autocomplete="off">
|
||||
</div>
|
||||
</div>
|
||||
{% if not config.config_is_initial %}
|
||||
{% if feature_support['ldap'] or feature_support['oauth'] %}
|
||||
<div class="form-group">
|
||||
<label for="config_login_type">{{_('Login type')}}</label>
|
||||
|
@ -219,37 +230,31 @@
|
|||
</div>
|
||||
<div class="form-group">
|
||||
<label for="config_ldap_port">{{_('LDAP Server Port')}}</label>
|
||||
<input type="text" class="form-control" id="config_ldap_port" name="config_ldap_port" value="{% if config.config_ldap_port != None %}{{ config.config_ldap_port }}{% endif %}" autocomplete="off">
|
||||
<input type="number" min="1" max="65535" class="form-control" id="config_ldap_port" name="config_ldap_port" value="{% if config.config_ldap_port != None %}{{ config.config_ldap_port }}{% endif %}" autocomplete="off" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="config_ldap_schema">{{_('LDAP Schema (LDAP or LPAPS)')}}</label>
|
||||
<input type="text" class="form-control" id="config_ldap_schema" name="config_ldap_schema" value="{% if config.config_ldap_schema != None %}{{ config.config_ldap_schema }}{% endif %}" autocomplete="off">
|
||||
<label for="config_ldap_encryption">{{_('LDAP Encryption')}}</label>
|
||||
<label for="config_ldap_encryption">{{_('LDAP Encryption')}}</label>
|
||||
<select name="config_ldap_encryption" id="config_ldap_encryption" class="form-control" data-controlall="ldap-cert-settings">
|
||||
<option value="0" {% if config.config_ldap_encryption == 0 %}selected{% endif %}>{{ _('None') }}</option>
|
||||
<option value="1" {% if config.config_ldap_encryption == 1 %}selected{% endif %}>{{ _('TLS') }}</option>
|
||||
<option value="2" {% if config.config_ldap_encryption == 2 %}selected{% endif %}>{{ _('SSL') }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div data-related="ldap-cert-settings">
|
||||
<div class="form-group">
|
||||
<label for="config_ldap_cert_path">{{_('LDAP Certificate Path')}}</label>
|
||||
<input type="text" class="form-control" id="config_ldap_cert_path" name="config_ldap_cert_path" value="{% if config.config_ldap_cert_path != None %}{{ config.config_ldap_cert_path }}{% endif %}" autocomplete="off">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="config_ldap_serv_username">{{_('LDAP Administrator Username')}}</label>
|
||||
<input type="text" class="form-control" id="config_ldap_serv_username" name="config_ldap_serv_username" value="{% if config.config_ldap_serv_username != None %}{{ config.config_ldap_serv_username }}{% endif %}" autocomplete="off">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="config_ldap_serv_password">{{_('LDAP Administrator Password')}}</label>
|
||||
<input type="password" class="form-control" id="config_ldap_serv_password" name="config_ldap_serv_password" value="{% if config.config_ldap_serv_password != None %}{{ config.config_ldap_serv_password }}{% endif %}" autocomplete="off">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="checkbox" id="config_ldap_use_ssl" name="config_ldap_use_ssl" {% if config.config_ldap_use_ssl %}checked{% endif %}>
|
||||
<label for="config_ldap_use_ssl">{{_('LDAP Server Enable SSL')}}</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="checkbox" id="config_ldap_use_tls" name="config_ldap_use_tls" {% if config.config_ldap_use_tls %}checked{% endif %}>
|
||||
<label for="config_ldap_use_tls">{{_('LDAP Server Enable TLS')}}</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="checkbox" id="config_ldap_require_cert" name="config_ldap_require_cert" data-control="ldap-cert-settings" {% if config.config_ldap_require_cert %}checked{% endif %}>
|
||||
<label for="config_ldap_require_cert">{{_('LDAP Server Certificate')}}</label>
|
||||
</div>
|
||||
<div data-related="ldap-cert-settings">
|
||||
<div class="form-group">
|
||||
<label for="config_ldap_cert_path">{{_('LDAP SSL Certificate Path')}}</label>
|
||||
<input type="text" class="form-control" id="config_ldap_cert_path" name="config_ldap_cert_path" value="{% if config.config_ldap_cert_path != None and config.config_ldap_require_cert !=None %}{{ config.config_ldap_cert_path }}{% endif %}" autocomplete="off">
|
||||
</div>
|
||||
<input type="password" class="form-control" id="config_ldap_serv_password" name="config_ldap_serv_password" value="" autocomplete="off">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="config_ldap_dn">{{_('LDAP Distinguished Name (DN)')}}</label>
|
||||
|
@ -263,6 +268,19 @@
|
|||
<input type="checkbox" id="config_ldap_openldap" name="config_ldap_openldap" {% if config.config_ldap_openldap %}checked{% endif %}>
|
||||
<label for="config_ldap_openldap">{{_('LDAP Server is OpenLDAP?')}}</label>
|
||||
</div>
|
||||
<h4 class="text-center">{{_('Following Settings are Needed For User Import')}}</h4>
|
||||
<div class="form-group">
|
||||
<label for="config_ldap_group_object_filter">{{_('LDAP Group Object Filter')}}</label>
|
||||
<input type="text" class="form-control" id="config_ldap_group_object_filter" name="config_ldap_group_object_filter" value="{% if config.config_ldap_group_object_filter != None %}{{ config.config_ldap_group_object_filter }}{% endif %}" autocomplete="off">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="config_ldap_group_name">{{_('LDAP Group Name')}}</label>
|
||||
<input type="text" class="form-control" id="config_ldap_group_name" name="config_ldap_group_name" value="{% if config.config_ldap_group_name != None %}{{ config.config_ldap_group_name }}{% endif %}" autocomplete="off">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="config_ldap_group_members_field">{{_('LDAP Group Members Field')}}</label>
|
||||
<input type="text" class="form-control" id="config_ldap_group_members_field" name="config_ldap_group_members_field" value="{% if config.config_ldap_group_members_field != None %}{{ config.config_ldap_group_members_field }}{% endif %}" autocomplete="off">
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if feature_support['oauth'] %}
|
||||
|
@ -283,16 +301,7 @@
|
|||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<div class="form-group">
|
||||
<input type="checkbox" id="config_allow_reverse_proxy_header_login" name="config_allow_reverse_proxy_header_login" data-control="reverse-proxy-login-settings" {% if config.config_allow_reverse_proxy_header_login %}checked{% endif %}>
|
||||
<label for="config_allow_reverse_proxy_header_login">{{_('Allow Reverse Proxy Authentication')}}</label>
|
||||
</div>
|
||||
<div data-related="reverse-proxy-login-settings">
|
||||
<div class="form-group">
|
||||
<label for="config_reverse_proxy_login_header_name">{{_('Reverse Proxy Header Name')}}</label>
|
||||
<input type="text" class="form-control" id="config_reverse_proxy_login_header_name" name="config_reverse_proxy_login_header_name" value="{% if config.config_reverse_proxy_login_header_name != None %}{{ config.config_reverse_proxy_login_header_name }}{% endif %}" autocomplete="off">
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -25,20 +25,22 @@
|
|||
<a href="{{url_for('web.remote_login')}}" class="pull-right">{{_('Log in with Magic Link')}}</a>
|
||||
{% endif %}
|
||||
{% if config.config_login_type == 2 %}
|
||||
<a href="{{url_for('oauth.github_login')}}" class="pull-right">
|
||||
{% if 1 in oauth_check %}
|
||||
<a href="{{url_for('oauth.github_login')}}" class="pull-right github">
|
||||
<svg height="32" class="octicon octicon-mark-github" viewBox="0 0 16 16" version="1.1" width="32" aria-hidden="true">
|
||||
<path fill-rule="evenodd" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z"></path>
|
||||
</svg>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if config.config_login_type == 3 %}
|
||||
<a href="{{url_for('oauth.google_login')}}" class="pull-right">
|
||||
{% if 2 in oauth_check %}
|
||||
<a href="{{url_for('oauth.google_login')}}" class="pull-right google">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
|
||||
width="40" height="40"
|
||||
viewBox="0 3 48 49"
|
||||
style="fill:#000000;"><g id="surface1"><path style=" fill:#FFC107;" d="M 43.609375 20.082031 L 42 20.082031 L 42 20 L 24 20 L 24 28 L 35.304688 28 C 33.652344 32.65625 29.222656 36 24 36 C 17.371094 36 12 30.628906 12 24 C 12 17.371094 17.371094 12 24 12 C 27.058594 12 29.84375 13.152344 31.960938 15.039063 L 37.617188 9.382813 C 34.046875 6.054688 29.269531 4 24 4 C 12.953125 4 4 12.953125 4 24 C 4 35.046875 12.953125 44 24 44 C 35.046875 44 44 35.046875 44 24 C 44 22.660156 43.863281 21.351563 43.609375 20.082031 Z "></path><path style=" fill:#FF3D00;" d="M 6.304688 14.691406 L 12.878906 19.511719 C 14.65625 15.109375 18.960938 12 24 12 C 27.058594 12 29.84375 13.152344 31.960938 15.039063 L 37.617188 9.382813 C 34.046875 6.054688 29.269531 4 24 4 C 16.316406 4 9.65625 8.335938 6.304688 14.691406 Z "></path><path style=" fill:#4CAF50;" d="M 24 44 C 29.164063 44 33.859375 42.023438 37.410156 38.808594 L 31.21875 33.570313 C 29.210938 35.089844 26.714844 36 24 36 C 18.796875 36 14.382813 32.683594 12.71875 28.054688 L 6.195313 33.078125 C 9.503906 39.554688 16.226563 44 24 44 Z "></path><path style=" fill:#1976D2;" d="M 43.609375 20.082031 L 42 20.082031 L 42 20 L 24 20 L 24 28 L 35.304688 28 C 34.511719 30.238281 33.070313 32.164063 31.214844 33.570313 C 31.21875 33.570313 31.21875 33.570313 31.21875 33.570313 L 37.410156 38.808594 C 36.972656 39.203125 44 34 44 24 C 44 22.660156 43.863281 21.351563 43.609375 20.082031 Z "></path></g></svg>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</form>
|
||||
</div>
|
||||
{% if error %}
|
||||
|
|
|
@ -35,10 +35,12 @@
|
|||
</thead>
|
||||
<tbody>
|
||||
{% for library,version in versions.items() %}
|
||||
{% if version %}
|
||||
<tr>
|
||||
<th>{{library}}</th>
|
||||
<td>{{_(version)}}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
|
@ -46,7 +46,7 @@
|
|||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
{% if registered_oauth.keys()| length > 0 %}
|
||||
{% if registered_oauth.keys()| length > 0 and not new_user %}
|
||||
{% for id, name in registered_oauth.items() %}
|
||||
<div class="form-group">
|
||||
<label>{{ name }} {{_('OAuth Settings')}}</label>
|
||||
|
@ -161,7 +161,7 @@
|
|||
</div>
|
||||
<div class="modal-body">...</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">{{_('Close')}}</button>
|
||||
<button type="button" id="kobo_close" class="btn btn-default" data-dismiss="modal">{{_('Close')}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
15
cps/ub.py
15
cps/ub.py
|
@ -30,6 +30,11 @@ from werkzeug.local import LocalProxy
|
|||
try:
|
||||
from flask_dance.consumer.backend.sqla import OAuthConsumerMixin
|
||||
oauth_support = True
|
||||
except ImportError:
|
||||
# fails on flask-dance >1.3, due to renaming
|
||||
try:
|
||||
from flask_dance.consumer.storage.sqla import OAuthConsumerMixin
|
||||
oauth_support = True
|
||||
except ImportError:
|
||||
oauth_support = False
|
||||
from sqlalchemy import create_engine, exc, exists, event
|
||||
|
@ -237,7 +242,7 @@ class Anonymous(AnonymousUserMixin, UserBase):
|
|||
self.sidebar_view = data.sidebar_view
|
||||
self.default_language = data.default_language
|
||||
self.locale = data.locale
|
||||
self.mature_content = data.mature_content
|
||||
# self.mature_content = data.mature_content
|
||||
self.kindle_mail = data.kindle_mail
|
||||
self.denied_tags = data.denied_tags
|
||||
self.allowed_tags = data.allowed_tags
|
||||
|
@ -531,14 +536,12 @@ def migrate_Database(session):
|
|||
"locale VARCHAR(2),"
|
||||
"sidebar_view INTEGER,"
|
||||
"default_language VARCHAR(3),"
|
||||
"mature_content BOOLEAN,"
|
||||
"UNIQUE (nickname),"
|
||||
"UNIQUE (email),"
|
||||
"CHECK (mature_content IN (0, 1)))")
|
||||
"UNIQUE (email))")
|
||||
conn.execute("INSERT INTO user_id(id, nickname, email, role, password, kindle_mail,locale,"
|
||||
"sidebar_view, default_language, mature_content) "
|
||||
"sidebar_view, default_language) "
|
||||
"SELECT id, nickname, email, role, password, kindle_mail, locale,"
|
||||
"sidebar_view, default_language, mature_content FROM user")
|
||||
"sidebar_view, default_language FROM user")
|
||||
# delete old user table and rename new user_id table to user:
|
||||
conn.execute("DROP TABLE user")
|
||||
conn.execute("ALTER TABLE user_id RENAME TO user")
|
||||
|
|
161
cps/web.py
161
cps/web.py
|
@ -28,6 +28,7 @@ import json
|
|||
import mimetypes
|
||||
import traceback
|
||||
import binascii
|
||||
import re
|
||||
|
||||
from babel import Locale as LC
|
||||
from babel.dates import format_date
|
||||
|
@ -53,13 +54,14 @@ from .pagination import Pagination
|
|||
from .redirect import redirect_back
|
||||
|
||||
feature_support = {
|
||||
'ldap': False, # bool(services.ldap),
|
||||
'ldap': bool(services.ldap),
|
||||
'goodreads': bool(services.goodreads_support),
|
||||
'kobo': bool(services.kobo)
|
||||
}
|
||||
|
||||
try:
|
||||
from .oauth_bb import oauth_check, register_user_with_oauth, logout_oauth_user, get_oauth_status
|
||||
|
||||
feature_support['oauth'] = True
|
||||
except ImportError:
|
||||
feature_support['oauth'] = False
|
||||
|
@ -109,14 +111,15 @@ for ex in default_exceptions:
|
|||
elif ex == 500:
|
||||
app.register_error_handler(ex, internal_error)
|
||||
|
||||
|
||||
web = Blueprint('web', __name__)
|
||||
log = logger.create()
|
||||
|
||||
|
||||
# ################################### Login logic and rights management ###############################################
|
||||
def _fetch_user_by_name(username):
|
||||
return ub.session.query(ub.User).filter(func.lower(ub.User.nickname) == username.lower()).first()
|
||||
|
||||
|
||||
@lm.user_loader
|
||||
def load_user(user_id):
|
||||
return ub.session.query(ub.User).filter(ub.User.id == int(user_id)).first()
|
||||
|
@ -164,6 +167,7 @@ def login_required_if_no_ano(func):
|
|||
if config.config_anonbrowse == 1:
|
||||
return func(*args, **kwargs)
|
||||
return login_required(func)(*args, **kwargs)
|
||||
|
||||
return decorated_view
|
||||
|
||||
|
||||
|
@ -256,7 +260,8 @@ def edit_required(f):
|
|||
# Returns the template for rendering and includes the instance name
|
||||
def render_title_template(*args, **kwargs):
|
||||
sidebar = ub.get_sidebar_config(kwargs)
|
||||
return render_template(instance=config.config_calibre_web_title, sidebar=sidebar, accept=constants.EXTENSIONS_UPLOAD,
|
||||
return render_template(instance=config.config_calibre_web_title, sidebar=sidebar,
|
||||
accept=constants.EXTENSIONS_UPLOAD,
|
||||
*args, **kwargs)
|
||||
|
||||
|
||||
|
@ -268,11 +273,81 @@ def before_request():
|
|||
g.allow_upload = config.config_uploading
|
||||
g.current_theme = config.config_theme
|
||||
g.config_authors_max = config.config_authors_max
|
||||
g.shelves_access = ub.session.query(ub.Shelf).filter(or_(ub.Shelf.is_public == 1, ub.Shelf.user_id == current_user.id)).order_by(ub.Shelf.name).all()
|
||||
if not config.db_configured and request.endpoint not in ('admin.basic_configuration', 'login') and '/static/' not in request.path:
|
||||
g.shelves_access = ub.session.query(ub.Shelf).filter(
|
||||
or_(ub.Shelf.is_public == 1, ub.Shelf.user_id == current_user.id)).order_by(ub.Shelf.name).all()
|
||||
if not config.db_configured and request.endpoint not in (
|
||||
'admin.basic_configuration', 'login') and '/static/' not in request.path:
|
||||
return redirect(url_for('admin.basic_configuration'))
|
||||
|
||||
|
||||
@app.route('/import_ldap_users')
|
||||
def import_ldap_users():
|
||||
showtext = {}
|
||||
try:
|
||||
new_users = services.ldap.get_group_members(config.config_ldap_group_name)
|
||||
except (services.ldap.LDAPException, TypeError, AttributeError) as e:
|
||||
log.debug(e)
|
||||
showtext['text'] = _(u'Error: %(ldaperror)s', ldaperror=e)
|
||||
return json.dumps(showtext)
|
||||
if not new_users:
|
||||
log.debug('LDAP empty response')
|
||||
showtext['text'] = _(u'Error: No user returned in response of LDAP server')
|
||||
return json.dumps(showtext)
|
||||
|
||||
for username in new_users:
|
||||
user = username.decode('utf-8')
|
||||
if '=' in user:
|
||||
match = re.search("([a-zA-Z0-9-]+)=%s", config.config_ldap_user_object, re.IGNORECASE | re.UNICODE)
|
||||
if match:
|
||||
match_filter = match.group(1)
|
||||
match = re.search(match_filter + "=([[\d\w-]+)", user, re.IGNORECASE | re.UNICODE)
|
||||
if match:
|
||||
user = match.group(1)
|
||||
else:
|
||||
log.warning("Could Not Parse LDAP User: %s", user)
|
||||
continue
|
||||
else:
|
||||
log.warning("Could Not Parse LDAP User: %s", user)
|
||||
continue
|
||||
if ub.session.query(ub.User).filter(ub.User.nickname == user.lower()).first():
|
||||
log.warning("LDAP User: %s Already in Database", user)
|
||||
continue
|
||||
user_data = services.ldap.get_object_details(user=user,
|
||||
group=None,
|
||||
query_filter=None,
|
||||
dn_only=False)
|
||||
if user_data:
|
||||
content = ub.User()
|
||||
content.nickname = user
|
||||
content.password = '' # dummy password which will be replaced by ldap one
|
||||
if 'mail' in user_data:
|
||||
content.email = user_data['mail'][0].decode('utf-8')
|
||||
if (len(user_data['mail']) > 1):
|
||||
content.kindle_mail = user_data['mail'][1].decode('utf-8')
|
||||
else:
|
||||
log.debug('No Mail Field Found in LDAP Response')
|
||||
content.email = user + '@email.com'
|
||||
content.role = config.config_default_role
|
||||
content.sidebar_view = config.config_default_show
|
||||
content.allowed_tags = config.config_allowed_tags
|
||||
content.denied_tags = config.config_denied_tags
|
||||
content.allowed_column_value = config.config_allowed_column_value
|
||||
content.denied_column_value = config.config_denied_column_value
|
||||
ub.session.add(content)
|
||||
try:
|
||||
ub.session.commit()
|
||||
except Exception as e:
|
||||
log.warning("Failed to create LDAP user: %s - %s", user, e)
|
||||
ub.session.rollback()
|
||||
showtext['text'] = _(u'Failed to Create at Least One LDAP User')
|
||||
else:
|
||||
log.warning("LDAP User: %s Not Found", user)
|
||||
showtext['text'] = _(u'At Least One LDAP User Not Found in Database')
|
||||
if not showtext:
|
||||
showtext['text'] = _(u'User Successfully Imported')
|
||||
return json.dumps(showtext)
|
||||
|
||||
|
||||
# ################################### data provider functions #########################################################
|
||||
|
||||
|
||||
|
@ -418,39 +493,34 @@ def get_comic_book(book_id, book_format, page):
|
|||
# ################################### Typeahead ##################################################################
|
||||
|
||||
|
||||
@web.route("/get_authors_json")
|
||||
@web.route("/get_authors_json", methods=['GET'])
|
||||
@login_required_if_no_ano
|
||||
def get_authors_json():
|
||||
if request.method == "GET":
|
||||
return get_typeahead(db.Authors, request.args.get('q'), ('|', ','))
|
||||
|
||||
|
||||
@web.route("/get_publishers_json")
|
||||
@web.route("/get_publishers_json", methods=['GET'])
|
||||
@login_required_if_no_ano
|
||||
def get_publishers_json():
|
||||
if request.method == "GET":
|
||||
return get_typeahead(db.Publishers, request.args.get('q'), ('|', ','))
|
||||
|
||||
|
||||
@web.route("/get_tags_json")
|
||||
@web.route("/get_tags_json", methods=['GET'])
|
||||
@login_required_if_no_ano
|
||||
def get_tags_json():
|
||||
if request.method == "GET":
|
||||
return get_typeahead(db.Tags, request.args.get('q'), tag_filter=tags_filters())
|
||||
|
||||
|
||||
@web.route("/get_series_json")
|
||||
@web.route("/get_series_json", methods=['GET'])
|
||||
@login_required_if_no_ano
|
||||
def get_series_json():
|
||||
if request.method == "GET":
|
||||
return get_typeahead(db.Series, request.args.get('q'))
|
||||
|
||||
|
||||
@web.route("/get_languages_json", methods=['GET', 'POST'])
|
||||
@web.route("/get_languages_json", methods=['GET'])
|
||||
@login_required_if_no_ano
|
||||
def get_languages_json():
|
||||
if request.method == "GET":
|
||||
query = request.args.get('q').lower()
|
||||
query = (request.args.get('q') or '').lower()
|
||||
language_names = isoLanguages.get_language_names(get_locale())
|
||||
entries_start = [s for key, s in language_names.items() if s.lower().startswith(query.lower())]
|
||||
if len(entries_start) < 5:
|
||||
|
@ -461,19 +531,18 @@ def get_languages_json():
|
|||
return json_dumps
|
||||
|
||||
|
||||
@web.route("/get_matching_tags", methods=['GET', 'POST'])
|
||||
@web.route("/get_matching_tags", methods=['GET'])
|
||||
@login_required_if_no_ano
|
||||
def get_matching_tags():
|
||||
tag_dict = {'tags': []}
|
||||
if request.method == "GET":
|
||||
q = db.session.query(db.Books)
|
||||
db.session.connection().connection.connection.create_function("lower", 1, lcase)
|
||||
author_input = request.args.get('author_name')
|
||||
title_input = request.args.get('book_title')
|
||||
include_tag_inputs = request.args.getlist('include_tag')
|
||||
exclude_tag_inputs = request.args.getlist('exclude_tag')
|
||||
include_extension_inputs = request.args.getlist('include_extension')
|
||||
exclude_extension_inputs = request.args.getlist('exclude_extension')
|
||||
author_input = request.args.get('author_name') or ''
|
||||
title_input = request.args.get('book_title') or ''
|
||||
include_tag_inputs = request.args.getlist('include_tag') or ''
|
||||
exclude_tag_inputs = request.args.getlist('exclude_tag') or ''
|
||||
# include_extension_inputs = request.args.getlist('include_extension') or ''
|
||||
# exclude_extension_inputs = request.args.getlist('exclude_extension') or ''
|
||||
q = q.filter(db.Books.authors.any(func.lower(db.Authors.name).ilike("%" + author_input + "%")),
|
||||
func.lower(db.Books.title).ilike("%" + title_input + "%"))
|
||||
if len(include_tag_inputs) > 0:
|
||||
|
@ -595,13 +664,13 @@ def render_hot_books(page):
|
|||
abort(404)
|
||||
|
||||
|
||||
|
||||
def render_author_books(page, author_id, order):
|
||||
entries, __, pagination = fill_indexpage(page, db.Books, db.Books.authors.any(db.Authors.id == author_id),
|
||||
[order[0], db.Series.name, db.Books.series_index],
|
||||
db.books_series_link, db.Series)
|
||||
if entries is None or not len(entries):
|
||||
flash(_(u"Oops! Selected book title is unavailable. File does not exist or is not accessible"), category="error")
|
||||
flash(_(u"Oops! Selected book title is unavailable. File does not exist or is not accessible"),
|
||||
category="error")
|
||||
return redirect(url_for("web.index"))
|
||||
|
||||
author = db.session.query(db.Authors).get(author_id)
|
||||
|
@ -656,7 +725,8 @@ def render_ratings_books(page, book_id, order):
|
|||
def render_formats_books(page, book_id, order):
|
||||
name = db.session.query(db.Data).filter(db.Data.format == book_id.upper()).first()
|
||||
if name:
|
||||
entries, random, pagination = fill_indexpage(page, db.Books, db.Books.data.any(db.Data.format == book_id.upper()),
|
||||
entries, random, pagination = fill_indexpage(page, db.Books,
|
||||
db.Books.data.any(db.Data.format == book_id.upper()),
|
||||
[db.Books.timestamp.desc(), order[0]])
|
||||
return render_title_template('index.html', random=random, pagination=pagination, entries=entries, id=book_id,
|
||||
title=_(u"File format: %(format)s", format=name.format), page="formats")
|
||||
|
@ -832,11 +902,13 @@ def reconnect():
|
|||
db.reconnect_db(config)
|
||||
return json.dumps({})
|
||||
|
||||
|
||||
@web.route("/search", methods=["GET"])
|
||||
@login_required_if_no_ano
|
||||
def search():
|
||||
term = request.args.get("query").strip().lower()
|
||||
term = request.args.get("query")
|
||||
if term:
|
||||
term.strip().lower()
|
||||
entries = get_search_results(term)
|
||||
ids = list()
|
||||
for element in entries:
|
||||
|
@ -1083,6 +1155,7 @@ def serve_book(book_id, book_format, anyname):
|
|||
else:
|
||||
return send_from_directory(os.path.join(config.config_calibre_dir, book.path), data.name + "." + book_format)
|
||||
|
||||
|
||||
@web.route("/download/<int:book_id>/<book_format>", defaults={'anyname': 'None'})
|
||||
@web.route("/download/<int:book_id>/<book_format>/<anyname>")
|
||||
@login_required_if_no_ano
|
||||
|
@ -1186,17 +1259,25 @@ def login():
|
|||
form = request.form.to_dict()
|
||||
user = ub.session.query(ub.User).filter(func.lower(ub.User.nickname) == form['username'].strip().lower()) \
|
||||
.first()
|
||||
if config.config_login_type == constants.LOGIN_LDAP and services.ldap and user:
|
||||
login_result = services.ldap.bind_user(form['username'], form['password'])
|
||||
if config.config_login_type == constants.LOGIN_LDAP and services.ldap and user and form['password'] != "":
|
||||
login_result, error = services.ldap.bind_user(form['username'], form['password'])
|
||||
if login_result:
|
||||
login_user(user, remember=True)
|
||||
log.debug(u"You are now logged in as: '%s'", user.nickname)
|
||||
flash(_(u"you are now logged in as: '%(nickname)s'", nickname=user.nickname),
|
||||
category="success")
|
||||
return redirect_back(url_for("web.index"))
|
||||
if login_result is None:
|
||||
log.error('Could not login. LDAP server down, please contact your administrator')
|
||||
flash(_(u"Could not login. LDAP server down, please contact your administrator"), category="error")
|
||||
elif login_result is None and user and check_password_hash(str(user.password), form['password']) \
|
||||
and user.nickname != "Guest":
|
||||
login_user(user, remember=True)
|
||||
log.info("Local Fallback Login as: '%s'", user.nickname)
|
||||
flash(_(u"Fallback Login as: '%(nickname)s', LDAP Server not reachable, or user not known",
|
||||
nickname=user.nickname),
|
||||
category="warning")
|
||||
return redirect_back(url_for("web.index"))
|
||||
elif login_result is None:
|
||||
log.info(error)
|
||||
flash(_(u"Could not login: %(message)s", message=error), category="error")
|
||||
else:
|
||||
ipAdress = request.headers.get('X-Forwarded-For', request.remote_addr)
|
||||
log.info('LDAP Login failed for user "%s" IP-adress: %s', form['username'], ipAdress)
|
||||
|
@ -1210,7 +1291,7 @@ def login():
|
|||
flash(_(u"New Password was send to your email address"), category="info")
|
||||
log.info('Password reset for user "%s" IP-adress: %s', form['username'], ipAdress)
|
||||
else:
|
||||
log.info(u"An unknown error occurred. Please try again later.")
|
||||
log.info(u"An unknown error occurred. Please try again later")
|
||||
flash(_(u"An unknown error occurred. Please try again later."), category="error")
|
||||
else:
|
||||
flash(_(u"Please enter valid username to reset password"), category="error")
|
||||
|
@ -1220,13 +1301,23 @@ def login():
|
|||
login_user(user, remember=True)
|
||||
log.debug(u"You are now logged in as: '%s'", user.nickname)
|
||||
flash(_(u"You are now logged in as: '%(nickname)s'", nickname=user.nickname), category="success")
|
||||
config.config_is_initial = False
|
||||
return redirect_back(url_for("web.index"))
|
||||
else:
|
||||
log.info('Login failed for user "%s" IP-adress: %s', form['username'], ipAdress)
|
||||
flash(_(u"Wrong Username or Password"), category="error")
|
||||
|
||||
if feature_support['oauth']:
|
||||
oauth_status = get_oauth_status()
|
||||
else:
|
||||
oauth_status = None
|
||||
next_url = url_for('web.index')
|
||||
return render_title_template('login.html', title=_(u"login"), next_url=next_url, config=config,
|
||||
return render_title_template('login.html',
|
||||
title=_(u"login"),
|
||||
next_url=next_url,
|
||||
config=config,
|
||||
# oauth_status=oauth_status,
|
||||
oauth_check=oauth_check,
|
||||
mail=config.get_mail_server_configured(), page="login")
|
||||
|
||||
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
# GDrive Integration
|
||||
google-api-python-client==1.7.11,<1.8.0
|
||||
gevent>=1.2.1,<1.5.0
|
||||
gevent>=1.2.1,<1.6.0
|
||||
greenlet>=0.4.12,<0.5.0
|
||||
httplib2>=0.9.2,<0.18.0
|
||||
oauth2client>=4.0.0,<4.14.0
|
||||
uritemplate>=3.0.0,<3.1.0
|
||||
pyasn1-modules>=0.0.8,<0.3.0
|
||||
pyasn1>=0.1.9,<0.5.0
|
||||
PyDrive>=1.3.1,<1.14.0
|
||||
PyDrive>=1.3.1,<1.4.0
|
||||
PyYAML>=3.12
|
||||
rsa==3.4.2,<4.1.0
|
||||
six>=1.10.0,<1.14.0
|
||||
six>=1.10.0,<1.15.0
|
||||
|
||||
# goodreads
|
||||
goodreads>=0.3.2,<0.4.0
|
||||
|
@ -18,15 +18,15 @@ python-Levenshtein>=0.12.0,<0.13.0
|
|||
|
||||
# ldap login
|
||||
python_ldap>=3.0.0,<3.3.0
|
||||
flask-simpleldap>1.3.0,<1.5.0
|
||||
flask-simpleldap>=1.4.0,<1.5.0
|
||||
|
||||
#oauth
|
||||
flask-dance>=0.13.0
|
||||
flask-dance>=1.4.0,<3.1.0
|
||||
sqlalchemy_utils>=0.33.5,<0.37.0
|
||||
|
||||
# extracting metadata
|
||||
lxml>=3.8.0,<4.6.0
|
||||
Pillow>=4.0.0,<7.1.0
|
||||
Pillow>=4.0.0,<7.2.0
|
||||
rarfile>=2.7
|
||||
|
||||
# other
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
Babel>=1.3, <2.9
|
||||
Flask-Babel>=0.11.1,<1.1.0
|
||||
Flask-Login>=0.3.2,<0.5.1
|
||||
Flask-Principal>=0.3.2,<0.5.0
|
||||
Flask-Principal>=0.3.2,<0.5.1
|
||||
singledispatch>=3.4.0.0,<3.5.0.0
|
||||
backports_abc>=0.4
|
||||
Flask>=1.0.2,<1.2.0
|
||||
iso-639>=0.4.5,<0.5.0
|
||||
PyPDF2==1.26.0,<1.27.0
|
||||
pytz>=2016.10
|
||||
requests>=2.11.1,<2.23.0
|
||||
requests>=2.11.1,<2.24.0
|
||||
SQLAlchemy>=1.1.0,<1.4.0
|
||||
tornado>=4.1,<6.1
|
||||
Wand>=0.4.4,<0.6.0
|
||||
|
|
709
test/Calibre-Web TestSummary.html
Normal file → Executable file
709
test/Calibre-Web TestSummary.html
Normal file → Executable file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user