Commit 5e91170e authored by echel0n's avatar echel0n
Browse files

Release v9.3.36

parent e6f02d43
# Changelog
- * b211d04 - 2018-06-19: Pre-Release v9.3.36.dev1
- * 2e7b758 - 2018-06-20: Release v9.3.36
- * e6f02d4 - 2018-06-19: Pre-Release v9.3.36.dev1
- * 977b971 - 2018-06-17: Switched WebUI to use API for login credentials
- * c800afc - 2018-06-16: Release v9.3.35
- * 2e05d78 - 2018-06-16: Added function to randomize external upnp port number Added ability to manually set external upnp port number
......
......@@ -36,6 +36,7 @@ from apscheduler.schedulers.tornado import TornadoScheduler
from apscheduler.triggers.interval import IntervalTrigger
from dateutil import tz
from fake_useragent import UserAgent
from keycloak.realm import KeycloakRealm
from tornado.ioloop import IOLoop
import adba
......@@ -141,6 +142,7 @@ class Core(object):
self.subtitle_searcher = None
self.auto_postprocessor = None
self.upnp_client = None
self.oidc_client = None
def start(self):
self.started = True
......@@ -177,6 +179,11 @@ class Core(object):
self.auto_postprocessor = AutoPostProcessor()
self.upnp_client = UPNPClient()
# setup oidc client
realm = KeycloakRealm(server_url='https://auth.sickrage.ca', realm_name='sickrage')
self.oidc_client = realm.open_id_connect(client_id='sickrage-app',
client_secret='5d4710b2-ca70-4d39-b5a3-0705e2c5e703')
# Check if we need to perform a restore first
if os.path.exists(os.path.abspath(os.path.join(self.data_dir, 'restore'))):
success = restoreSR(os.path.abspath(os.path.join(self.data_dir, 'restore')), self.data_dir)
......
......@@ -5,7 +5,7 @@ import os
import time
from urlparse import urljoin
from oauthlib.oauth2 import LegacyApplicationClient, MissingTokenError, InvalidClientIdError
from oauthlib.oauth2 import MissingTokenError, InvalidClientIdError, TokenExpiredError
from requests_oauthlib import OAuth2Session
import sickrage
......@@ -13,59 +13,29 @@ from sickrage.core.api.exceptions import unauthorized, error
class API(object):
def __init__(self, username=None, password=None):
self.api_url = 'https://api.sickrage.ca/'
self.token_url = urljoin(self.api_url, 'oauth/token')
def __init__(self):
self.api_url = 'https://api.sickrage.ca/api/v1/'
self.token_file = os.path.join(sickrage.app.data_dir, 'sr_token.json')
self._username = username
self._password = password
self._session = None
self._token = None
@property
def credentials(self):
return {
'client_id': 2,
'client_secret': '7kEyr9jKuqOV4FFy2bOxOwA2RiB4WSHsEUU2P3BJ',
'username': self._username or sickrage.app.config.app_username,
'password': self._password or sickrage.app.config.app_password,
}
@property
def session(self):
if self._session is None:
self._session = OAuth2Session(self.credentials['client_id'],
token=self.token,
auto_refresh_url=self.token_url,
auto_refresh_kwargs={'client_id': self.credentials['client_id'],
'client_secret': self.credentials['client_secret']},
token_updater=self._token_updater)
self._session = OAuth2Session(token=self.token)
return self._session
@property
def token(self):
if self._token is None:
if any([self._username, self._password]) and os.path.isfile(self.token_file):
os.remove(self.token_file)
if not os.path.exists(self.token_file):
oauth = OAuth2Session(client=LegacyApplicationClient(client_id=self.credentials['client_id']))
try:
self._token = oauth.fetch_token(token_url=self.token_url, timeout=30, **self.credentials)
except MissingTokenError:
self._token = None
self._token_updater(self._token)
else:
with open(self.token_file) as infile:
self._token = json.load(infile)
if os.path.exists(self.token_file):
with open(self.token_file) as infile:
self._token = json.load(infile)
return self._token
def _token_updater(self, token):
@token.setter
def token(self, value):
with open(self.token_file, 'w') as outfile:
json.dump(token, outfile)
json.dump(value, outfile)
def _request(self, method, url, **kwargs):
try:
......@@ -78,6 +48,9 @@ class API(object):
raise error(resp.json()['message'])
return resp.json()
except TokenExpiredError as e:
self.token = sickrage.app.oidc_client.refresh_token(self.token['refresh_token'])
return self._request(method, url, **kwargs)
except (InvalidClientIdError, MissingTokenError) as e:
sickrage.app.log.warning("SiCKRAGE username or password is incorrect, please try again")
......
......@@ -3,18 +3,18 @@ from sickrage.core import API
class ProviderCacheAPI(API):
def get(self, provider, indexerid, season, episode):
query = 'api/v1/cache/providers/{}/indexerids/{}/seasons/()/episodes/()'.format(provider, indexerid, season,
query = 'cache/providers/{}/indexerids/{}/seasons/()/episodes/()'.format(provider, indexerid, season,
episode)
return self._request('GET', query)
def add(self, data):
self._request('POST', 'api/v1/cache/providers', json=data)
self._request('POST', 'cache/providers', json=data)
class TorrentCacheAPI(API):
def get(self, hash):
query = 'api/v1/cache/torrents/{}'.format(hash)
query = 'cache/torrents/{}'.format(hash)
return self._request('GET', query)
def add(self, url):
self._request('POST', 'api/v1/cache/torrents', data=dict({'url': url}))
self._request('POST', 'cache/torrents', data=dict({'url': url}))
......@@ -3,29 +3,29 @@ from sickrage.core import API
class GoogleDriveAPI(API):
def is_connected(self):
query = 'api/v1/google-drive/is-connected'
query = 'google-drive/is-connected'
return self._request('GET', query)
def upload(self, file, folder):
query = 'api/v1/google-drive/upload'
query = 'google-drive/upload'
return self._request('POST', query, files={'file': open(file, 'rb')}, params={'folder': folder})
def download(self, id):
query = 'api/v1/google-drive/download/{id}'.format(id=id)
query = 'google-drive/download/{id}'.format(id=id)
return self._request('GET', query)
def delete(self, id):
query = 'api/v1/google-drive/delete/{id}'.format(id=id)
query = 'google-drive/delete/{id}'.format(id=id)
return self._request('GET', query)
def search_files(self, id, term):
query = 'api/v1/google-drive/search-files/{id}/{term}'.format(id=id, term=term)
query = 'google-drive/search-files/{id}/{term}'.format(id=id, term=term)
return self._request('GET', query)
def list_files(self, id):
query = 'api/v1/google-drive/list-files/{id}'.format(id=id)
query = 'google-drive/list-files/{id}'.format(id=id)
return self._request('GET', query)
def clear_folder(self, id):
query = 'api/v1/google-drive/clear-folder/{id}'.format(id=id)
query = 'google-drive/clear-folder/{id}'.format(id=id)
return self._request('GET', query)
......@@ -3,9 +3,9 @@ from sickrage.core import API
class IMDbAPI(API):
def search_by_imdb_title(self, title):
query = 'api/v1/imdb/search-by-title/{}'.format(title)
query = 'imdb/search-by-title/{}'.format(title)
return self._request('GET', query)
def search_by_imdb_id(self, id):
query = 'api/v1/imdb/search-by-id/{}'.format(id)
query = 'imdb/search-by-id/{}'.format(id)
return self._request('GET', query)
\ No newline at end of file
......@@ -30,13 +30,15 @@ from collections import OrderedDict
import dateutil.tz
import markdown2
import requests
import tornado.locale
from CodernityDB.database import RecordNotFound
from concurrent.futures import ThreadPoolExecutor
from keycloak.realm import KeycloakRealm
from mako.exceptions import RichTraceback
from mako.lookup import TemplateLookup
from tornado.concurrent import run_on_executor
from tornado.escape import json_encode, recursive_unicode
from tornado.escape import json_encode, recursive_unicode, json_decode
from tornado.gen import coroutine
from tornado.process import cpu_count
from tornado.web import RequestHandler, authenticated
......@@ -160,13 +162,13 @@ class BaseHandler(RequestHandler):
super(BaseHandler, self).redirect(url, permanent, status)
def set_current_user(self, user, remember_me=False):
self.set_secure_cookie('sickrage_user', user, expires_days=30 if remember_me else None)
self.set_secure_cookie('sickrage_token', user, expires_days=30 if remember_me else None)
def get_current_user(self):
return self.get_secure_cookie('sickrage_user')
return self.get_secure_cookie('sickrage_token')
def clear_current_user(self):
self.clear_cookie('sickrage_user')
self.clear_cookie('sickrage_token')
def render_string(self, template_name, **kwargs):
template_kwargs = {
......@@ -260,44 +262,18 @@ class LoginHandler(BaseHandler):
def __init__(self, *args, **kwargs):
super(LoginHandler, self).__init__(*args, **kwargs)
def prepare(self, *args, **kwargs):
self.finish(self.auth())
def auth(self):
if self.get_current_user():
return self.redirect("/{}/".format(sickrage.app.config.default_page))
username = self.get_argument('username', '')
password = self.get_argument('password', '')
api_token = API(username, password).token
if all([username, password]) and api_token:
sickrage.app.config.app_username = username
sickrage.app.config.app_password = password
sickrage.app.config.save()
remember_me = bool(self.get_argument('remember_me', default=0))
self.set_current_user(json_encode(api_token), remember_me)
Notifiers.mass_notify_login(self.request.remote_ip)
sickrage.app.log.info('User logged into the SiCKRAGE web interface')
def get(self, *args, **kwargs):
redirect_uri = "{}://{}/login".format(self.request.protocol, self.request.host)
code = self.get_argument('code', False)
if code:
API().token = access_token = sickrage.app.oidc_client.authorization_code(code, redirect_uri)
self.set_current_user(json_encode(access_token), True)
redirect_page = self.get_argument('next', "/{}/".format(sickrage.app.config.default_page))
return self.redirect("{}".format(redirect_page))
elif all([username, password]):
sickrage.app.log.warning(
'User attempted a failed login to the SiCKRAGE web interface from IP: {}'.format(
self.request.remote_ip)
)
return self.render(
"/login.mako",
title="Login",
header="Login",
topmenu="login",
controller='root',
action='login'
)
else:
authorization_url = sickrage.app.oidc_client.authorization_url(redirect_uri=redirect_uri)
self.redirect(authorization_url)
class LogoutHandler(BaseHandler):
......@@ -305,6 +281,9 @@ class LogoutHandler(BaseHandler):
super(LogoutHandler, self).__init__(*args, **kwargs)
def prepare(self, *args, **kwargs):
access_token = json_decode(self.get_current_user())
sickrage.app.oidc_client.logout(access_token['refresh_token'])
self.clear_current_user()
return self.redirect('/login/')
......
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
Supports Markdown
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