Commit 9e159ed5 authored by Ozzieisaacs's avatar Ozzieisaacs

Merge branch 'master' into Develop

# Conflicts:
#	cps/config_sql.py
#	cps/ub.py
#	cps/web.py
parents 3e1c34ef 2c429722
......@@ -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
......
......@@ -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())
......
This diff is collapsed.
......@@ -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,7 +231,10 @@ class _ConfigSQL(object):
return False
if convertor is not None:
new_value = convertor(new_value)
if encode:
new_value = convertor(new_value.encode(encode))
else:
new_value = convertor(new_value)
current_value = self.__dict__.get(field)
if current_value == new_value:
......@@ -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])
......
......@@ -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)
......@@ -61,7 +72,7 @@ try:
use_provider_user_id = True
if self.user_required and not u and not uid and not use_provider_user_id:
#raise ValueError("Cannot get OAuth token without an associated user")
# raise ValueError("Cannot get OAuth token without an associated user")
return None
# check for user ID
if hasattr(self.model, "user_id") and uid:
......@@ -86,7 +97,7 @@ try:
def set(self, blueprint, token, user=None, user_id=None):
uid = first([user_id, self.user_id, blueprint.config.get("user_id")])
u = first(_get_real_user(ref, self.anon_user)
for ref in (user, self.user, blueprint.config.get("user")))
for ref in (user, self.user, blueprint.config.get("user")))
if self.user_required and not u and not uid:
raise ValueError("Cannot set OAuth token without an associated user")
......@@ -152,5 +163,5 @@ try:
blueprint=blueprint, user=user, user_id=user_id,
))
except ImportError:
except Exception:
pass
This diff is collapsed.
......@@ -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:
result = _ldap.bind_user(username, password)
log.debug("LDAP login '%s': %r", username, result)
return result is not None
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, 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
This diff is collapsed.
This diff is collapsed.
......@@ -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();
......
......@@ -35,7 +35,11 @@
{% endif %}
{% endfor %}
</table>
<div class="btn btn-default" id="admin_new_user"><a href="{{url_for('admin.new_user')}}">{{_('Add New User')}}</a></div>
<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>
......@@ -211,7 +222,7 @@
{% endif %}
</select>
</div>
{% if feature_support['ldap'] %}
{% if feature_support['ldap'] %}
<div data-related="login-settings-1">
<div class="form-group">
<label for="config_ldap_provider_url">{{_('LDAP Server Host Name or IP Address')}}</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'] %}
......@@ -282,17 +300,8 @@
{% endfor %}
</div>
{% endif %}
{% 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>
</div>
</div>
</div>
......
......@@ -25,19 +25,21 @@
<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">
<svg height="32" class="octicon octicon-mark-github" viewBox="0 0 16 16" version="1.1" width="32" aria-hidden="true">
{% 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">
<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>
</svg>
</a>
{% endif %}
{% 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>
......
......@@ -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>
......
......@@ -31,7 +31,12 @@ try:
from flask_dance.consumer.backend.sqla import OAuthConsumerMixin
oauth_support = True
except ImportError:
oauth_support = False
# 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
from sqlalchemy import Column, ForeignKey
from sqlalchemy import String, Integer, SmallInteger, Boolean, DateTime, Float
......@@ -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")
......
This diff is collapsed.
# 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
......
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
......
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment