mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Content server: Add a new setting to allow un-authenticated users from specific IP addresses to make changes to the calibre library
This commit is contained in:
parent
7158d21c93
commit
714bc11820
2420
src/backports/ipaddress.py
Normal file
2420
src/backports/ipaddress.py
Normal file
File diff suppressed because it is too large
Load Diff
@ -29,6 +29,7 @@ from calibre.gui2.preferences import AbortCommit, ConfigWidgetBase, test_widget
|
||||
from calibre.gui2.widgets import HistoryLineEdit
|
||||
from calibre.srv.code import custom_list_template as default_custom_list_template
|
||||
from calibre.srv.embedded import custom_list_template, search_the_net_urls
|
||||
from calibre.srv.loop import parse_trusted_ips
|
||||
from calibre.srv.library_broker import load_gui_libraries
|
||||
from calibre.srv.opts import change_settings, options, server_config
|
||||
from calibre.srv.users import (
|
||||
@ -1380,6 +1381,14 @@ class ConfigWidget(ConfigWidgetBase):
|
||||
)
|
||||
self.tabs_widget.setCurrentWidget(self.users_tab)
|
||||
return False
|
||||
if settings['trusted_ips']:
|
||||
try:
|
||||
tuple(parse_trusted_ips(settings['trusted_ips']))
|
||||
except Exception as e:
|
||||
error_dialog(
|
||||
self, _('Invalid trusted IPs'), str(e), show=True)
|
||||
return False
|
||||
|
||||
if not self.custom_list_tab.commit():
|
||||
return False
|
||||
if not self.search_net_tab.commit():
|
||||
|
@ -108,7 +108,7 @@ class Context(object):
|
||||
|
||||
def check_for_write_access(self, request_data):
|
||||
if not request_data.username:
|
||||
if request_data.is_local_connection and self.opts.local_write:
|
||||
if request_data.is_trusted_ip:
|
||||
return
|
||||
raise HTTPForbidden('Anonymous users are not allowed to make changes')
|
||||
if self.user_manager.is_readonly(request_data.username):
|
||||
|
@ -219,7 +219,7 @@ class RequestData(object): # {{{
|
||||
username = None
|
||||
|
||||
def __init__(self, method, path, query, inheaders, request_body_file, outheaders, response_protocol,
|
||||
static_cache, opts, remote_addr, remote_port, is_local_connection, translator_cache,
|
||||
static_cache, opts, remote_addr, remote_port, is_trusted_ip, translator_cache,
|
||||
tdir, forwarded_for, request_original_uri=None):
|
||||
|
||||
(self.method, self.path, self.query, self.inheaders, self.request_body_file, self.outheaders,
|
||||
@ -228,7 +228,7 @@ class RequestData(object): # {{{
|
||||
response_protocol, static_cache, translator_cache
|
||||
)
|
||||
|
||||
self.remote_addr, self.remote_port, self.is_local_connection = remote_addr, remote_port, is_local_connection
|
||||
self.remote_addr, self.remote_port, self.is_trusted_ip = remote_addr, remote_port, is_trusted_ip
|
||||
self.forwarded_for = forwarded_for
|
||||
self.request_original_uri = request_original_uri
|
||||
self.opts = opts
|
||||
@ -446,7 +446,7 @@ class HTTPConnection(HTTPRequest):
|
||||
data = RequestData(
|
||||
self.method, self.path, self.query, inheaders, request_body_file,
|
||||
outheaders, self.response_protocol, self.static_cache, self.opts,
|
||||
self.remote_addr, self.remote_port, self.is_local_connection,
|
||||
self.remote_addr, self.remote_port, self.is_trusted_ip,
|
||||
self.translator_cache, self.tdir, self.forwarded_for, self.request_original_uri
|
||||
)
|
||||
self.queue_job(self.run_request_handler, data)
|
||||
|
@ -25,6 +25,11 @@ from calibre.utils.monotonic import monotonic
|
||||
from calibre.utils.mdns import get_external_ip
|
||||
from polyglot.builtins import iteritems, unicode_type
|
||||
from polyglot.queue import Empty, Full
|
||||
try:
|
||||
import ipaddress
|
||||
except ImportError:
|
||||
from backports import ipaddress
|
||||
|
||||
|
||||
READ, WRITE, RDWR, WAIT = 'READ', 'WRITE', 'RDWR', 'WAIT'
|
||||
WAKEUP, JOB_DONE = b'\0', b'\x01'
|
||||
@ -119,6 +124,33 @@ class ReadBuffer(object): # {{{
|
||||
# }}}
|
||||
|
||||
|
||||
class BadIPSpec(ValueError):
|
||||
pass
|
||||
|
||||
|
||||
def parse_trusted_ips(spec):
|
||||
for part in as_unicode(spec).split(','):
|
||||
part = part.strip()
|
||||
try:
|
||||
if '/' in part:
|
||||
yield ipaddress.ip_network(part)
|
||||
else:
|
||||
yield ipaddress.ip_address(part)
|
||||
except Exception as e:
|
||||
raise BadIPSpec(_('{0} is not a valid IP address/network, with error: {1}').format(part, e))
|
||||
|
||||
|
||||
def is_ip_trusted(remote_addr, trusted_ips):
|
||||
for tip in trusted_ips:
|
||||
if hasattr(tip, 'hosts'):
|
||||
if remote_addr in tip:
|
||||
return True
|
||||
else:
|
||||
if tip == remote_addr:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class Connection(object): # {{{
|
||||
|
||||
def __init__(self, socket, opts, ssl_context, tdir, addr, pool, log, access_log, wakeup):
|
||||
@ -126,10 +158,13 @@ class Connection(object): # {{{
|
||||
try:
|
||||
self.remote_addr = addr[0]
|
||||
self.remote_port = addr[1]
|
||||
self.parsed_remote_addr = ipaddress.ip_address(as_unicode(self.remote_addr))
|
||||
except Exception:
|
||||
# In case addr is None, which can occassionally happen
|
||||
self.remote_addr = self.remote_port = None
|
||||
self.is_local_connection = self.remote_addr in ('127.0.0.1', '::1')
|
||||
# In case addr is None, which can occasionally happen
|
||||
self.remote_addr = self.remote_port = self.parsed_remote_addr = None
|
||||
self.is_trusted_ip = bool(self.opts.local_write and getattr(self.parsed_remote_addr, 'is_loopback', False))
|
||||
if not self.is_trusted_ip and self.opts.trusted_ips and self.parsed_remote_addr is not None:
|
||||
self.is_trusted_ip = is_ip_trusted(self.parsed_remote_addr, self.opts.trusted_ips)
|
||||
self.orig_send_bufsize = self.send_bufsize = 4096
|
||||
self.tdir = tdir
|
||||
self.wait_for = READ
|
||||
@ -347,6 +382,8 @@ class ServerLoop(object):
|
||||
self.ready = False
|
||||
self.handler = handler
|
||||
self.opts = opts or Options()
|
||||
if self.opts.trusted_ips:
|
||||
self.opts.trusted_ips = tuple(parse_trusted_ips(self.opts.trusted_ips))
|
||||
self.log = log or ThreadSafeLog(level=ThreadSafeLog.DEBUG)
|
||||
self.jobs_manager = JobsManager(self.opts, self.log)
|
||||
self.access_log = access_log
|
||||
|
@ -155,6 +155,17 @@ raw_options = (
|
||||
' turning on this option means any program running on the computer'
|
||||
' can make changes to your calibre libraries.'),
|
||||
|
||||
_('Allow un-authenticated connections from specific IP addresses to make changes'),
|
||||
'trusted_ips', None,
|
||||
_('Normally, if you do not turn on authentication, the server operates in'
|
||||
' read-only mode, so as to not allow anonymous users to make changes to your'
|
||||
' calibre libraries. This option allows anybody connecting from the specified'
|
||||
' IP addresses to make changes. Must be a comma separated list of address or network specifications.'
|
||||
' This is useful if you want to run the server without authentication but still'
|
||||
' use calibredb to make changes to your calibre libraries. Note that'
|
||||
' turning on this option means anyone connecting from the specified IP addresses'
|
||||
' can make changes to your calibre libraries.'),
|
||||
|
||||
_('Path to user database'),
|
||||
'userdb', None,
|
||||
_('Path to a file in which to store the user and password information. Normally a'
|
||||
|
@ -16,7 +16,7 @@ from calibre.srv.bonjour import BonJour
|
||||
from calibre.srv.handler import Handler
|
||||
from calibre.srv.http_response import create_http_handler
|
||||
from calibre.srv.library_broker import load_gui_libraries
|
||||
from calibre.srv.loop import ServerLoop
|
||||
from calibre.srv.loop import BadIPSpec, ServerLoop
|
||||
from calibre.srv.manage_users_cli import manage_users_cli
|
||||
from calibre.srv.opts import opts_to_parser
|
||||
from calibre.srv.users import connect
|
||||
@ -222,7 +222,10 @@ def main(args=sys.argv):
|
||||
raise SystemExit('The --log option must point to a file, not a directory')
|
||||
if opts.access_log and os.path.isdir(opts.access_log):
|
||||
raise SystemExit('The --access-log option must point to a file, not a directory')
|
||||
server = Server(libraries, opts)
|
||||
try:
|
||||
server = Server(libraries, opts)
|
||||
except BadIPSpec as e:
|
||||
raise SystemExit('{}'.format(e))
|
||||
if getattr(opts, 'daemonize', False):
|
||||
if not opts.log and not iswindows:
|
||||
raise SystemExit(
|
||||
|
Loading…
x
Reference in New Issue
Block a user