Implement basic auth #57 and fix for notification provider

This commit is contained in:
morpheus65535 2018-08-02 19:11:34 -04:00
parent 3d98836f3b
commit 55be0aad60
4 changed files with 222 additions and 7 deletions

View File

@ -1,4 +1,4 @@
bazarr_version = '0.5.7'
bazarr_version = '0.5.8'
import gc
gc.enable()
@ -59,7 +59,7 @@ configure_logging()
from update_modules import *
from bottle import route, run, template, static_file, request, redirect, response
from bottle import route, run, template, static_file, request, redirect, response, HTTPError
import bottle
bottle.TEMPLATE_PATH.insert(0,os.path.join(os.path.dirname(__file__), 'views/'))
bottle.debug(True)
@ -77,6 +77,7 @@ from fdsend import send_file
import urllib
import math
import ast
import hashlib
from get_languages import *
from get_providers import *
@ -102,15 +103,47 @@ c.close()
# Load languages in database
load_language_in_db()
from get_auth_settings import get_auth_settings
auth_enabled = get_auth_settings()[0]
def custom_auth_basic(check):
def decorator(func):
def wrapper(*a, **ka):
if auth_enabled == "True":
user, password = request.auth or (None, None)
if user is None or not check(user, password):
err = HTTPError(401, "Access denied")
err.add_header('WWW-Authenticate', 'Basic realm="Bazarr"')
return err
return func(*a, **ka)
else:
return func(*a, **ka)
return wrapper
return decorator
def check_credentials(user, pw):
from get_auth_settings import get_auth_settings
auth_enabled = get_auth_settings()
username = auth_enabled[1]
password = auth_enabled[2]
if hashlib.md5(pw).hexdigest() == password and user == username:
return True
return False
@route('/')
@custom_auth_basic(check_credentials)
def redirect_root():
redirect (base_url)
@route(base_url + 'static/:path#.+#', name='static')
@custom_auth_basic(check_credentials)
def static(path):
return static_file(path, root=os.path.join(os.path.dirname(__file__), 'static'))
@route(base_url + 'emptylog')
@custom_auth_basic(check_credentials)
def emptylog():
ref = request.environ['HTTP_REFERER']
@ -120,10 +153,12 @@ def emptylog():
redirect(ref)
@route(base_url + 'bazarr.log')
@custom_auth_basic(check_credentials)
def download_log():
return static_file('bazarr.log', root=os.path.join(os.path.dirname(__file__), 'data/log/'), download='bazarr.log')
@route(base_url + 'image_proxy/<url:path>', method='GET')
@custom_auth_basic(check_credentials)
def image_proxy(url):
from get_sonarr_settings import get_sonarr_settings
url_sonarr = get_sonarr_settings()[0]
@ -142,6 +177,7 @@ def image_proxy(url):
return send_file(img_buffer, ctype=img_pil.format)
@route(base_url + 'image_proxy_movies/<url:path>', method='GET')
@custom_auth_basic(check_credentials)
def image_proxy_movies(url):
from get_radarr_settings import get_radarr_settings
url_radarr = get_radarr_settings()[0]
@ -162,6 +198,7 @@ def image_proxy_movies(url):
@route(base_url)
@custom_auth_basic(check_credentials)
def redirect_root():
conn = sqlite3.connect(os.path.join(os.path.dirname(__file__), 'data/db/bazarr.db'), timeout=30)
c = conn.cursor()
@ -177,6 +214,7 @@ def redirect_root():
@route(base_url + 'series')
@custom_auth_basic(check_credentials)
def series():
import update_db
single_language = get_general_settings()[7]
@ -208,6 +246,7 @@ def series():
return output
@route(base_url + 'serieseditor')
@custom_auth_basic(check_credentials)
def serieseditor():
single_language = get_general_settings()[7]
@ -228,6 +267,7 @@ def serieseditor():
return output
@route(base_url + 'search_json/<query>', method='GET')
@custom_auth_basic(check_credentials)
def search_json(query):
db = sqlite3.connect(os.path.join(os.path.dirname(__file__), 'data/db/bazarr.db'), timeout=30)
c = db.cursor()
@ -250,6 +290,7 @@ def search_json(query):
@route(base_url + 'edit_series/<no:int>', method='POST')
@custom_auth_basic(check_credentials)
def edit_series(no):
ref = request.environ['HTTP_REFERER']
@ -287,6 +328,7 @@ def edit_series(no):
redirect(ref)
@route(base_url + 'edit_serieseditor', method='POST')
@custom_auth_basic(check_credentials)
def edit_serieseditor():
ref = request.environ['HTTP_REFERER']
@ -317,6 +359,7 @@ def edit_serieseditor():
redirect(ref)
@route(base_url + 'episodes/<no:int>', method='GET')
@custom_auth_basic(check_credentials)
def episodes(no):
single_language = get_general_settings()[7]
url_sonarr_short = get_sonarr_settings()[1]
@ -341,6 +384,7 @@ def episodes(no):
return template('episodes', __file__=__file__, bazarr_version=bazarr_version, no=no, details=series_details, languages=languages, seasons=seasons_list, url_sonarr_short=url_sonarr_short, base_url=base_url, tvdbid=tvdbid, number=number)
@route(base_url + 'movies')
@custom_auth_basic(check_credentials)
def movies():
import update_db
single_language = get_general_settings()[7]
@ -368,6 +412,7 @@ def movies():
return output
@route(base_url + 'movieseditor')
@custom_auth_basic(check_credentials)
def movieseditor():
single_language = get_general_settings()[7]
@ -388,6 +433,7 @@ def movieseditor():
return output
@route(base_url + 'edit_movieseditor', method='POST')
@custom_auth_basic(check_credentials)
def edit_movieseditor():
ref = request.environ['HTTP_REFERER']
@ -418,6 +464,7 @@ def edit_movieseditor():
redirect(ref)
@route(base_url + 'edit_movie/<no:int>', method='POST')
@custom_auth_basic(check_credentials)
def edit_movie(no):
ref = request.environ['HTTP_REFERER']
@ -448,6 +495,7 @@ def edit_movie(no):
redirect(ref)
@route(base_url + 'movie/<no:int>', method='GET')
@custom_auth_basic(check_credentials)
def movie(no):
from get_radarr_settings import get_radarr_settings
single_language = get_general_settings()[7]
@ -467,6 +515,7 @@ def movie(no):
return template('movie', __file__=__file__, bazarr_version=bazarr_version, no=no, details=movies_details, languages=languages, url_radarr_short=url_radarr_short, base_url=base_url, tmdbid=tmdbid)
@route(base_url + 'scan_disk/<no:int>', method='GET')
@custom_auth_basic(check_credentials)
def scan_disk(no):
ref = request.environ['HTTP_REFERER']
@ -475,6 +524,7 @@ def scan_disk(no):
redirect(ref)
@route(base_url + 'scan_disk_movie/<no:int>', method='GET')
@custom_auth_basic(check_credentials)
def scan_disk_movie(no):
ref = request.environ['HTTP_REFERER']
@ -483,6 +533,7 @@ def scan_disk_movie(no):
redirect(ref)
@route(base_url + 'search_missing_subtitles/<no:int>', method='GET')
@custom_auth_basic(check_credentials)
def search_missing_subtitles(no):
ref = request.environ['HTTP_REFERER']
@ -491,6 +542,7 @@ def search_missing_subtitles(no):
redirect(ref)
@route(base_url + 'search_missing_subtitles_movie/<no:int>', method='GET')
@custom_auth_basic(check_credentials)
def search_missing_subtitles_movie(no):
ref = request.environ['HTTP_REFERER']
@ -499,10 +551,12 @@ def search_missing_subtitles_movie(no):
redirect(ref)
@route(base_url + 'history')
@custom_auth_basic(check_credentials)
def history():
return template('history', __file__=__file__, bazarr_version=bazarr_version, base_url=base_url)
@route(base_url + 'historyseries')
@custom_auth_basic(check_credentials)
def historyseries():
db = sqlite3.connect(os.path.join(os.path.dirname(__file__), 'data/db/bazarr.db'), timeout=30)
c = db.cursor()
@ -539,6 +593,7 @@ def historyseries():
return template('historyseries', __file__=__file__, bazarr_version=bazarr_version, rows=data, row_count=row_count, page=page, max_page=max_page, stats=stats, base_url=base_url, page_size=page_size)
@route(base_url + 'historymovies')
@custom_auth_basic(check_credentials)
def historymovies():
db = sqlite3.connect(os.path.join(os.path.dirname(__file__), 'data/db/bazarr.db'), timeout=30)
c = db.cursor()
@ -575,10 +630,12 @@ def historymovies():
return template('historymovies', __file__=__file__, bazarr_version=bazarr_version, rows=data, row_count=row_count, page=page, max_page=max_page, stats=stats, base_url=base_url, page_size=page_size)
@route(base_url + 'wanted')
@custom_auth_basic(check_credentials)
def wanted():
return template('wanted', __file__=__file__, bazarr_version=bazarr_version, base_url=base_url)
@route(base_url + 'wantedseries')
@custom_auth_basic(check_credentials)
def wantedseries():
db = sqlite3.connect(os.path.join(os.path.dirname(__file__), 'data/db/bazarr.db'), timeout=30)
db.create_function("path_substitution", 1, path_replace)
@ -600,6 +657,7 @@ def wantedseries():
return template('wantedseries', __file__=__file__, bazarr_version=bazarr_version, rows=data, missing_count=missing_count, page=page, max_page=max_page, base_url=base_url, page_size=page_size)
@route(base_url + 'wantedmovies')
@custom_auth_basic(check_credentials)
def wantedmovies():
db = sqlite3.connect(os.path.join(os.path.dirname(__file__), 'data/db/bazarr.db'), timeout=30)
db.create_function("path_substitution", 1, path_replace_movie)
@ -621,6 +679,7 @@ def wantedmovies():
return template('wantedmovies', __file__=__file__, bazarr_version=bazarr_version, rows=data, missing_count=missing_count, page=page, max_page=max_page, base_url=base_url, page_size=page_size)
@route(base_url + 'wanted_search_missing_subtitles')
@custom_auth_basic(check_credentials)
def wanted_search_missing_subtitles_list():
ref = request.environ['HTTP_REFERER']
@ -629,11 +688,14 @@ def wanted_search_missing_subtitles_list():
redirect(ref)
@route(base_url + 'settings')
@custom_auth_basic(check_credentials)
def settings():
db = sqlite3.connect(os.path.join(os.path.dirname(__file__), 'data/db/bazarr.db'), timeout=30)
c = db.cursor()
c.execute("SELECT * FROM table_settings_general")
settings_general = c.fetchone()
c.execute("SELECT * FROM table_settings_auth")
settings_auth = c.fetchone()
c.execute("SELECT * FROM table_settings_languages ORDER BY name")
settings_languages = c.fetchall()
c.execute("SELECT * FROM table_settings_providers ORDER BY name")
@ -642,12 +704,13 @@ def settings():
settings_sonarr = c.fetchone()
c.execute("SELECT * FROM table_settings_radarr")
settings_radarr = c.fetchone()
c.execute("SELECT * FROM table_settings_notifier")
c.execute("SELECT * FROM table_settings_notifier ORDER BY name")
settings_notifier = c.fetchall()
c.close()
return template('settings', __file__=__file__, bazarr_version=bazarr_version, settings_general=settings_general, settings_languages=settings_languages, settings_providers=settings_providers, settings_sonarr=settings_sonarr, settings_radarr=settings_radarr, settings_notifier=settings_notifier, base_url=base_url)
return template('settings', __file__=__file__, bazarr_version=bazarr_version, settings_general=settings_general, settings_auth=settings_auth, settings_languages=settings_languages, settings_providers=settings_providers, settings_sonarr=settings_sonarr, settings_radarr=settings_radarr, settings_notifier=settings_notifier, base_url=base_url)
@route(base_url + 'save_settings', method='POST')
@custom_auth_basic(check_credentials)
def save_settings():
ref = request.environ['HTTP_REFERER']
@ -658,6 +721,13 @@ def save_settings():
settings_general_port = request.forms.get('settings_general_port')
settings_general_baseurl = request.forms.get('settings_general_baseurl')
settings_general_loglevel = request.forms.get('settings_general_loglevel')
settings_general_auth_enabled = request.forms.get('settings_general_auth_enabled')
if settings_general_auth_enabled is None:
settings_general_auth_enabled = 'False'
else:
settings_general_auth_enabled = 'True'
settings_general_auth_username = request.forms.get('settings_general_auth_username')
settings_general_auth_password = request.forms.get('settings_general_auth_password')
settings_general_sourcepath = request.forms.getall('settings_general_sourcepath')
settings_general_destpath = request.forms.getall('settings_general_destpath')
settings_general_pathmapping = []
@ -714,6 +784,15 @@ def save_settings():
configured()
get_general_settings()
before_auth_password = c.execute("SELECT enabled, password FROM table_settings_auth").fetchone()
if before_auth_password[0] != settings_general_auth_enabled:
configured()
if before_auth_password[1] == settings_general_auth_password:
c.execute("UPDATE table_settings_auth SET enabled = ?, username = ?", (unicode(settings_general_auth_enabled), unicode(settings_general_auth_username)))
else:
c.execute("UPDATE table_settings_auth SET enabled = ?, username = ?, password = ?", (unicode(settings_general_auth_enabled), unicode(settings_general_auth_username), unicode(hashlib.md5(settings_general_auth_password.encode('utf-8')).hexdigest())))
conn.commit()
settings_sonarr_ip = request.forms.get('settings_sonarr_ip')
settings_sonarr_port = request.forms.get('settings_sonarr_port')
settings_sonarr_baseurl = request.forms.get('settings_sonarr_baseurl')
@ -1002,6 +1081,7 @@ def save_settings():
redirect(ref)
@route(base_url + 'check_update')
@custom_auth_basic(check_credentials)
def check_update():
ref = request.environ['HTTP_REFERER']
@ -1010,6 +1090,7 @@ def check_update():
redirect(ref)
@route(base_url + 'system')
@custom_auth_basic(check_credentials)
def system():
def get_time_from_interval(interval):
interval_clean = interval.split('[')
@ -1113,6 +1194,7 @@ def system():
return template('system', __file__=__file__, bazarr_version=bazarr_version, base_url=base_url, task_list=task_list, row_count=row_count, max_page=max_page, page_size=page_size)
@route(base_url + 'logs/<page:int>')
@custom_auth_basic(check_credentials)
def get_logs(page):
page_size = int(get_general_settings()[21])
begin = (page * page_size) - page_size
@ -1125,6 +1207,7 @@ def get_logs(page):
return template('logs', logs=logs, base_url=base_url)
@route(base_url + 'execute/<taskid>')
@custom_auth_basic(check_credentials)
def execute_task(taskid):
ref = request.environ['HTTP_REFERER']
@ -1134,6 +1217,7 @@ def execute_task(taskid):
@route(base_url + 'remove_subtitles', method='POST')
@custom_auth_basic(check_credentials)
def remove_subtitles():
episodePath = request.forms.get('episodePath')
language = request.forms.get('language')
@ -1152,6 +1236,7 @@ def remove_subtitles():
@route(base_url + 'remove_subtitles_movie', method='POST')
@custom_auth_basic(check_credentials)
def remove_subtitles_movie():
moviePath = request.forms.get('moviePath')
language = request.forms.get('language')
@ -1169,6 +1254,7 @@ def remove_subtitles_movie():
@route(base_url + 'get_subtitle', method='POST')
@custom_auth_basic(check_credentials)
def get_subtitle():
ref = request.environ['HTTP_REFERER']
@ -1215,6 +1301,7 @@ def get_subtitle():
pass
@route(base_url + 'get_subtitle_movie', method='POST')
@custom_auth_basic(check_credentials)
def get_subtitle_movie():
ref = request.environ['HTTP_REFERER']

19
get_auth_settings.py Normal file
View File

@ -0,0 +1,19 @@
import sqlite3
import os
def get_auth_settings():
# Open database connection
db = sqlite3.connect(os.path.join(os.path.dirname(__file__), 'data/db/bazarr.db'), timeout=30)
c = db.cursor()
c.execute('''SELECT * FROM table_settings_auth''')
config_auth = c.fetchone()
# Close database connection
db.close()
auth_enabled = config_auth[0]
auth_username = config_auth[1]
auth_password = config_auth[2]
return [auth_enabled, auth_username, auth_password]

View File

@ -165,11 +165,12 @@ if os.path.exists(os.path.join(os.path.dirname(__file__), 'data/db/bazarr.db'))
c.execute('UPDATE table_settings_general SET page_size="25"')
try:
c.execute('DELETE FROM table_settings_notifier WHERE rowid > 24') #Modify this if we add more notification provider
c.execute('SELECT name FROM table_settings_notifier WHERE name = "Discord"').fetchone()
except:
providers = ['Discord', 'E-Mail', 'Emby', 'IFTTT', 'Stride', 'Windows']
for provider in providers:
c.execute('INSERT INTO `table_settings_notifier` (name, enabled) VALUES (?, ?);', (provider, '0'))
except:
pass
try:
c.execute('alter table table_settings_general add column "use_embedded_subs" "text"')
@ -178,6 +179,13 @@ if os.path.exists(os.path.join(os.path.dirname(__file__), 'data/db/bazarr.db'))
else:
c.execute('UPDATE table_settings_general SET use_embedded_subs="True"')
try:
c.execute('CREATE TABLE `table_settings_auth` ( `enabled` TEXT, `username` TEXT, `password` TEXT);')
except:
pass
else:
c.execute('INSERT INTO `table_settings_auth` (enabled, username, password) VALUES ("False", "", "")')
# Commit change to db
db.commit()

View File

@ -55,7 +55,7 @@
</div>
<div class="ui bottom attached tab segment active" data-tab="general">
<div class="ui container"><button class="submit ui blue right floated button" type="submit" value="Submit" form="settings_form">Save</button></div>
<div class="ui dividing header">Bazarr settings</div>
<div class="ui dividing header">Start-Up</div>
<div class="twelve wide column">
<div class="ui grid">
<div class="middle aligned row">
@ -186,6 +186,71 @@
</div>
</div>
<div class="ui dividing header">Security settings</div>
<div class="twelve wide column">
<div class="ui grid">
<div class="middle aligned row">
<div class="right aligned four wide column">
<label>Use basic authentication</label>
</div>
<div class="one wide column">
<div id="settings_use_auth" class="ui toggle checkbox" data-enabled={{settings_auth[0]}}>
<input name="settings_general_auth_enabled" type="checkbox">
<label></label>
</div>
</div>
<div class="collapsed center aligned column">
<div class="ui basic icon" data-tooltip="Requires restart to take effect" data-inverted="">
<i class="yellow warning sign icon"></i>
</div>
</div>
<div class="collapsed column">
<div class="collapsed center aligned column">
<div class="ui basic icon" data-tooltip="Enable basic authentication to access Bazarr." data-inverted="">
<i class="help circle large icon"></i>
</div>
</div>
</div>
</div>
<div class="middle aligned row">
<div class="right aligned four wide column">
<label>Username</label>
</div>
<div class="five wide column">
<div class='field'>
<div class="ui fluid input">
<input id="settings_general_auth_username" name="settings_general_auth_username" type="text" value="{{settings_auth[1]}}">
</div>
</div>
</div>
</div>
<div class="middle aligned row">
<div class="right aligned four wide column">
<label>Password</label>
</div>
<div class="five wide column">
<div class='field'>
<div class="ui fluid input">
<input id="settings_general_auth_password" name="settings_general_auth_password" type="password" value="{{settings_auth[2]}}">
</div>
</div>
</div>
<div class="collapsed column">
<div class="collapsed center aligned column">
<div class="ui basic icon" data-tooltip="Basic auth transmit username and password in clear over the network. You should add SSL encryption trough a reverse proxy." data-inverted="">
<i class="help circle large icon"></i>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="ui dividing header">Integration settings</div>
<div class="twelve wide column">
<div class="ui grid">
@ -1143,6 +1208,26 @@
$("#settings_embedded").checkbox('uncheck');
}
if ($('#settings_use_auth').data("enabled") == "True") {
$("#settings_use_auth").checkbox('check');
$("#settings_general_auth_username").parent().removeClass('disabled');
$("#settings_general_auth_password").parent().removeClass('disabled');
} else {
$("#settings_use_auth").checkbox('uncheck');
$("#settings_general_auth_username").parent().addClass('disabled');
$("#settings_general_auth_password").parent().addClass('disabled');
}
$("#settings_use_auth").change(function(i, obj) {
if ($("#settings_use_auth").checkbox('is checked')) {
$("#settings_general_auth_username").parent().removeClass('disabled');
$("#settings_general_auth_password").parent().removeClass('disabled');
} else {
$("#settings_general_auth_username").parent().addClass('disabled');
$("#settings_general_auth_password").parent().addClass('disabled');
}
});
if ($('#settings_use_postprocessing').data("postprocessing") == "True") {
$("#settings_use_postprocessing").checkbox('check');
$("#settings_general_postprocessing_cmd_div").removeClass('disabled');
@ -1381,6 +1466,22 @@
}
]
},
settings_general_auth_username : {
depends: 'settings_general_auth_enabled',
rules : [
{
type : 'empty'
}
]
},
settings_general_auth_password : {
depends: 'settings_general_auth_enabled',
rules : [
{
type : 'empty'
}
]
},
settings_sonarr_ip : {
depends: 'settings_general_use_sonarr',
rules : [