+
+{% endblock %}
+
+
\ No newline at end of file
diff --git a/cps/web.py b/cps/web.py
index 5d6e766c..b18a2ecc 100644
--- a/cps/web.py
+++ b/cps/web.py
@@ -2676,6 +2676,35 @@ def show_shelf(shelf_id):
return redirect(url_for("index"))
+@app.route("/shelfdown/")
+@login_required_if_no_ano
+def show_shelf_down(shelf_id):
+ if current_user.is_anonymous:
+ shelf = ub.session.query(ub.Shelf).filter(ub.Shelf.is_public == 1, ub.Shelf.id == shelf_id).first()
+ else:
+ shelf = ub.session.query(ub.Shelf).filter(ub.or_(ub.and_(ub.Shelf.user_id == int(current_user.id),
+ ub.Shelf.id == shelf_id),
+ ub.and_(ub.Shelf.is_public == 1,
+ ub.Shelf.id == shelf_id))).first()
+ result = list()
+ # user is allowed to access shelf
+ if shelf:
+ books_in_shelf = ub.session.query(ub.BookShelf).filter(ub.BookShelf.shelf == shelf_id).order_by(
+ ub.BookShelf.order.asc()).all()
+ for book in books_in_shelf:
+ cur_book = db.session.query(db.Books).filter(db.Books.id == book.book_id).first()
+ if cur_book:
+ result.append(cur_book)
+ else:
+ app.logger.info('Not existing book %s in shelf %s deleted' % (book.book_id, shelf.id))
+ ub.session.query(ub.BookShelf).filter(ub.BookShelf.book_id == book.book_id).delete()
+ ub.session.commit()
+ return render_title_template('shelfdown.html', entries=result, title=_(u"Shelf: '%(name)s'", name=shelf.name),
+ shelf=shelf, page="shelf")
+ else:
+ flash(_(u"Error opening shelf. Shelf does not exist or is not accessible"), category="error")
+ return redirect(url_for("index"))
+
@app.route("/shelf/order/", methods=["GET", "POST"])
@login_required
def order_shelf(shelf_id):
From c6d3613e576aa8d0cfa943df5ad51f53c749219d Mon Sep 17 00:00:00 2001
From: otapi <31888571+otapi@users.noreply.github.com>
Date: Thu, 11 Oct 2018 18:20:38 +0200
Subject: [PATCH 002/349] Add UI link button to shelves
---
cps/templates/shelf.html | 6 +++++-
cps/web.py | 1 -
2 files changed, 5 insertions(+), 2 deletions(-)
diff --git a/cps/templates/shelf.html b/cps/templates/shelf.html
index 28cd20bb..645dad74 100644
--- a/cps/templates/shelf.html
+++ b/cps/templates/shelf.html
@@ -2,9 +2,13 @@
{% block body %}
{{title}}
+ {% if g.user.role_download() %}
+ {{ _('Download') }}
+ {% endif %}
{% if g.user.is_authenticated %}
{% if (g.user.role_edit_shelfs() and shelf.is_public ) or not shelf.is_public %}
-
diff --git a/cps/templates/login.html b/cps/templates/login.html
index 3e8ebe1e..8e622079 100644
--- a/cps/templates/login.html
+++ b/cps/templates/login.html
@@ -18,9 +18,24 @@
- {% if remote_login %}
+ {% if config.config_remote_login %}
{{_('Log in with magic link')}}
{% endif %}
+ {% if config.config_use_github_oauth %}
+
+
+
+ {% endif %}
+ {% if config.config_use_google_oauth %}
+
+
+
+ {% endif %}
{% if error %}
diff --git a/cps/templates/register.html b/cps/templates/register.html
index 70bd10c7..24edc3a2 100644
--- a/cps/templates/register.html
+++ b/cps/templates/register.html
@@ -12,6 +12,21 @@
+ {% if config.config_use_github_oauth %}
+
+
+
+ {% endif %}
+ {% if config.config_use_google_oauth %}
+
+
+
+ {% endif %}
{% if error %}
diff --git a/cps/ub.py b/cps/ub.py
index f1b19d02..8811b972 100644
--- a/cps/ub.py
+++ b/cps/ub.py
@@ -6,6 +6,7 @@ from sqlalchemy import exc
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import *
from flask_login import AnonymousUserMixin
+from flask_dance.consumer.backend.sqla import OAuthConsumerMixin
import sys
import os
import logging
@@ -169,6 +170,12 @@ class User(UserBase, Base):
theme = Column(Integer, default=0)
+class OAuth(OAuthConsumerMixin, Base):
+ provider_user_id = Column(String(256))
+ user_id = Column(Integer, ForeignKey(User.id))
+ user = relationship(User)
+
+
# Class for anonymous user is derived from User base and completly overrides methods and properties for the
# anonymous user
class Anonymous(AnonymousUserMixin, UserBase):
@@ -306,6 +313,12 @@ class Settings(Base):
config_use_goodreads = Column(Boolean)
config_goodreads_api_key = Column(String)
config_goodreads_api_secret = Column(String)
+ config_use_github_oauth = Column(Boolean)
+ config_github_oauth_client_id = Column(String)
+ config_github_oauth_client_secret = Column(String)
+ config_use_google_oauth = Column(Boolean)
+ config_google_oauth_client_id = Column(String)
+ config_google_oauth_client_secret = Column(String)
config_mature_content_tags = Column(String)
config_logfile = Column(String)
config_ebookconverter = Column(Integer, default=0)
@@ -378,6 +391,12 @@ class Config:
self.config_use_goodreads = data.config_use_goodreads
self.config_goodreads_api_key = data.config_goodreads_api_key
self.config_goodreads_api_secret = data.config_goodreads_api_secret
+ self.config_use_github_oauth = data.config_use_github_oauth
+ self.config_github_oauth_client_id = data.config_github_oauth_client_id
+ self.config_github_oauth_client_secret = data.config_github_oauth_client_secret
+ self.config_use_google_oauth = data.config_use_google_oauth
+ self.config_google_oauth_client_id = data.config_google_oauth_client_id
+ self.config_google_oauth_client_secret = data.config_google_oauth_client_secret
if data.config_mature_content_tags:
self.config_mature_content_tags = data.config_mature_content_tags
else:
@@ -661,6 +680,22 @@ def migrate_Database():
conn.execute("ALTER TABLE Settings ADD column `config_calibre` String DEFAULT ''")
session.commit()
+ try:
+ session.query(exists().where(Settings.config_use_github_oauth)).scalar()
+ except exc.OperationalError:
+ conn = engine.connect()
+ conn.execute("ALTER TABLE Settings ADD column `config_use_github_oauth` INTEGER DEFAULT 0")
+ conn.execute("ALTER TABLE Settings ADD column `config_github_oauth_client_id` String DEFAULT ''")
+ conn.execute("ALTER TABLE Settings ADD column `config_github_oauth_client_secret` String DEFAULT ''")
+ session.commit()
+ try:
+ session.query(exists().where(Settings.config_use_google_oauth)).scalar()
+ except exc.OperationalError:
+ conn = engine.connect()
+ conn.execute("ALTER TABLE Settings ADD column `config_use_google_oauth` INTEGER DEFAULT 0")
+ conn.execute("ALTER TABLE Settings ADD column `config_google_oauth_client_id` String DEFAULT ''")
+ conn.execute("ALTER TABLE Settings ADD column `config_google_oauth_client_secret` String DEFAULT ''")
+ session.commit()
# Remove login capability of user Guest
conn = engine.connect()
conn.execute("UPDATE user SET password='' where nickname = 'Guest' and password !=''")
diff --git a/cps/web.py b/cps/web.py
index 5d6e766c..0937fdb7 100644
--- a/cps/web.py
+++ b/cps/web.py
@@ -4,7 +4,7 @@
import mimetypes
import logging
from logging.handlers import RotatingFileHandler
-from flask import (Flask, render_template, request, Response, redirect,
+from flask import (Flask, session, render_template, request, Response, redirect,
url_for, send_from_directory, make_response, g, flash,
abort, Markup)
from flask import __version__ as flaskVersion
@@ -55,6 +55,11 @@ from redirect import redirect_back
import time
import server
from reverseproxy import ReverseProxied
+from flask_dance.contrib.github import make_github_blueprint, github
+from flask_dance.contrib.google import make_google_blueprint, google
+from flask_dance.consumer import oauth_authorized, oauth_error
+from sqlalchemy.orm.exc import NoResultFound
+from oauth import OAuthBackend
try:
from googleapiclient.errors import HttpError
except ImportError:
@@ -114,6 +119,7 @@ EXTENSIONS_CONVERT = {'pdf', 'epub', 'mobi', 'azw3', 'docx', 'rtf', 'fb2', 'lit'
# EXTENSIONS_READER = set(['txt', 'pdf', 'epub', 'zip', 'cbz', 'tar', 'cbt'] + (['rar','cbr'] if rar_support else []))
+oauth_check = []
'''class ReverseProxied(object):
"""Wrap the application in this middleware and configure the
@@ -348,6 +354,35 @@ def remote_login_required(f):
return inner
+def github_oauth_required(f):
+ @wraps(f)
+ def inner(*args, **kwargs):
+ if config.config_use_github_oauth:
+ return f(*args, **kwargs)
+ if request.is_xhr:
+ data = {'status': 'error', 'message': 'Not Found'}
+ response = make_response(json.dumps(data, ensure_ascii=False))
+ response.headers["Content-Type"] = "application/json; charset=utf-8"
+ return response, 404
+ abort(404)
+
+ return inner
+
+
+def google_oauth_required(f):
+ @wraps(f)
+ def inner(*args, **kwargs):
+ if config.config_use_google_oauth:
+ return f(*args, **kwargs)
+ if request.is_xhr:
+ data = {'status': 'error', 'message': 'Not Found'}
+ response = make_response(json.dumps(data, ensure_ascii=False))
+ response.headers["Content-Type"] = "application/json; charset=utf-8"
+ return response, 404
+ abort(404)
+
+ return inner
+
# custom jinja filters
# pagination links in jinja
@@ -2264,6 +2299,7 @@ def register():
try:
ub.session.add(content)
ub.session.commit()
+ register_user_with_oauth(content)
helper.send_registration_mail(to_save["email"],to_save["nickname"], password)
except Exception:
ub.session.rollback()
@@ -2279,7 +2315,8 @@ def register():
flash(_(u"This username or e-mail address is already in use."), category="error")
return render_title_template('register.html', title=_(u"register"), page="register")
- return render_title_template('register.html', title=_(u"register"), page="register")
+ register_user_with_oauth()
+ return render_title_template('register.html', config=config, title=_(u"register"), page="register")
@app.route('/login', methods=['GET', 'POST'])
@@ -2304,8 +2341,7 @@ def login():
# if next_url is None or not is_safe_url(next_url):
next_url = url_for('index')
- return render_title_template('login.html', title=_(u"login"), next_url=next_url,
- remote_login=config.config_remote_login, page="login")
+ return render_title_template('login.html', title=_(u"login"), next_url=next_url, config=config, page="login")
@app.route('/logout')
@@ -2313,6 +2349,7 @@ def login():
def logout():
if current_user is not None and current_user.is_authenticated:
logout_user()
+ logout_oauth_user()
return redirect(url_for('login'))
@@ -3019,6 +3056,29 @@ def configuration_helper(origin):
content.config_goodreads_api_key = to_save["config_goodreads_api_key"]
if "config_goodreads_api_secret" in to_save:
content.config_goodreads_api_secret = to_save["config_goodreads_api_secret"]
+
+ # GitHub OAuth configuration
+ content.config_use_github_oauth = ("config_use_github_oauth" in to_save and to_save["config_use_github_oauth"] == "on")
+ if "config_github_oauth_client_id" in to_save:
+ content.config_github_oauth_client_id = to_save["config_github_oauth_client_id"]
+ if "config_github_oauth_client_secret" in to_save:
+ content.config_github_oauth_client_secret = to_save["config_github_oauth_client_secret"]
+
+ if content.config_github_oauth_client_id != config.config_github_oauth_client_id or \
+ content.config_github_oauth_client_secret != config.config_github_oauth_client_secret:
+ reboot_required = True
+
+ # Google OAuth configuration
+ content.config_use_google_oauth = ("config_use_google_oauth" in to_save and to_save["config_use_google_oauth"] == "on")
+ if "config_google_oauth_client_id" in to_save:
+ content.config_google_oauth_client_id = to_save["config_google_oauth_client_id"]
+ if "config_google_oauth_client_secret" in to_save:
+ content.config_google_oauth_client_secret = to_save["config_google_oauth_client_secret"]
+
+ if content.config_google_oauth_client_id != config.config_google_oauth_client_id or \
+ content.config_google_oauth_client_secret != config.config_google_oauth_client_secret:
+ reboot_required = True
+
if "config_log_level" in to_save:
content.config_log_level = int(to_save["config_log_level"])
if content.config_logfile != to_save["config_logfile"]:
@@ -3883,3 +3943,214 @@ def convert_bookformat(book_id):
else:
flash(_(u"There was an error converting this book: %(res)s", res=rtn), category="error")
return redirect(request.environ["HTTP_REFERER"])
+
+
+def register_oauth_blueprint(blueprint):
+ if blueprint.name != "":
+ oauth_check.append(blueprint.name)
+
+
+def register_user_with_oauth(user=None):
+ all_oauth = []
+ for oauth in oauth_check:
+ if oauth + '_oauth_user_id' in session and session[oauth + '_oauth_user_id'] != '':
+ all_oauth.append(oauth)
+ if len(all_oauth) == 0:
+ return
+ if user is None:
+ flash(_(u"Register with %s" % ", ".join(all_oauth)), category="success")
+ else:
+ for oauth in all_oauth:
+ # Find this OAuth token in the database, or create it
+ query = ub.session.query(ub.OAuth).filter_by(
+ provider=oauth,
+ provider_user_id=session[oauth + "_oauth_user_id"],
+ )
+ try:
+ oauth = query.one()
+ oauth.user_id = user.id
+ except NoResultFound:
+ # no found, return error
+ return
+ try:
+ ub.session.commit()
+ except Exception as e:
+ app.logger.exception(e)
+ ub.session.rollback()
+
+
+def logout_oauth_user():
+ for oauth in oauth_check:
+ if oauth + '_oauth_user_id' in session:
+ session.pop(oauth + '_oauth_user_id')
+
+
+github_blueprint = make_github_blueprint(
+ client_id=config.config_github_oauth_client_id,
+ client_secret=config.config_github_oauth_client_secret,
+ redirect_to="github_login",)
+
+google_blueprint = make_google_blueprint(
+ client_id=config.config_google_oauth_client_id,
+ client_secret=config.config_google_oauth_client_secret,
+ redirect_to="google_login",
+ scope=[
+ "https://www.googleapis.com/auth/plus.me",
+ "https://www.googleapis.com/auth/userinfo.email",
+ ]
+)
+
+app.register_blueprint(google_blueprint, url_prefix="/login")
+app.register_blueprint(github_blueprint, url_prefix='/login')
+
+github_blueprint.backend = OAuthBackend(ub.OAuth, ub.session, user=current_user, user_required=True)
+google_blueprint.backend = OAuthBackend(ub.OAuth, ub.session, user=current_user, user_required=True)
+
+register_oauth_blueprint(github_blueprint)
+register_oauth_blueprint(google_blueprint)
+
+
+@oauth_authorized.connect_via(github_blueprint)
+def github_logged_in(blueprint, token):
+ if not token:
+ flash("Failed to log in with GitHub.", category="error")
+ return False
+
+ resp = blueprint.session.get("/user")
+ if not resp.ok:
+ msg = "Failed to fetch user info from GitHub."
+ flash(msg, category="error")
+ return False
+
+ github_info = resp.json()
+ github_user_id = str(github_info["id"])
+ return oauth_update_token(blueprint, token, github_user_id)
+
+
+@oauth_authorized.connect_via(google_blueprint)
+def google_logged_in(blueprint, token):
+ if not token:
+ flash("Failed to log in with Google.", category="error")
+ return False
+
+ resp = blueprint.session.get("/oauth2/v2/userinfo")
+ if not resp.ok:
+ msg = "Failed to fetch user info from Google."
+ flash(msg, category="error")
+ return False
+
+ google_info = resp.json()
+ google_user_id = str(google_info["id"])
+
+ return oauth_update_token(blueprint, token, google_user_id)
+
+
+def oauth_update_token(blueprint, token, provider_user_id):
+ session[blueprint.name + "_oauth_user_id"] = provider_user_id
+ session[blueprint.name + "_oauth_token"] = token
+
+ # Find this OAuth token in the database, or create it
+ query = ub.session.query(ub.OAuth).filter_by(
+ provider=blueprint.name,
+ provider_user_id=provider_user_id,
+ )
+ try:
+ oauth = query.one()
+ # update token
+ oauth.token = token
+ except NoResultFound:
+ oauth = ub.OAuth(
+ provider=blueprint.name,
+ provider_user_id=provider_user_id,
+ token=token,
+ )
+ try:
+ ub.session.add(oauth)
+ ub.session.commit()
+ except Exception as e:
+ app.logger.exception(e)
+ ub.session.rollback()
+
+ # Disable Flask-Dance's default behavior for saving the OAuth token
+ return False
+
+
+def bind_oauth_or_register(provider, provider_user_id, redirect_url):
+ query = ub.session.query(ub.OAuth).filter_by(
+ provider=provider,
+ provider_user_id=provider_user_id,
+ )
+ try:
+ oauth = query.one()
+ # already bind with user, just login
+ if oauth.user:
+ login_user(oauth.user)
+ return redirect(url_for('index'))
+ else:
+ # bind to current user
+ if current_user and not current_user.is_anonymous:
+ oauth.user = current_user
+ try:
+ ub.session.add(oauth)
+ ub.session.commit()
+ except Exception as e:
+ app.logger.exception(e)
+ ub.session.rollback()
+ return redirect(url_for('register'))
+ except NoResultFound:
+ return redirect(url_for(redirect_url))
+
+
+# notify on OAuth provider error
+@oauth_error.connect_via(github_blueprint)
+def github_error(blueprint, error, error_description=None, error_uri=None):
+ msg = (
+ "OAuth error from {name}! "
+ "error={error} description={description} uri={uri}"
+ ).format(
+ name=blueprint.name,
+ error=error,
+ description=error_description,
+ uri=error_uri,
+ )
+ flash(msg, category="error")
+
+
+@app.route('/github')
+@github_oauth_required
+def github_login():
+ if not github.authorized:
+ return redirect(url_for('github.login'))
+ account_info = github.get('/user')
+ if account_info.ok:
+ account_info_json = account_info.json()
+ return bind_oauth_or_register(github_blueprint.name, account_info_json['id'], 'github.login')
+ flash(_(u"GitHub Oauth error, please retry later."), category="error")
+ return redirect(url_for('login'))
+
+
+@app.route('/google')
+@google_oauth_required
+def google_login():
+ if not google.authorized:
+ return redirect(url_for("google.login"))
+ resp = google.get("/oauth2/v2/userinfo")
+ if resp.ok:
+ account_info_json = resp.json()
+ return bind_oauth_or_register(google_blueprint.name, account_info_json['id'], 'google.login')
+ flash(_(u"Google Oauth error, please retry later."), category="error")
+ return redirect(url_for('login'))
+
+
+@oauth_error.connect_via(google_blueprint)
+def google_error(blueprint, error, error_description=None, error_uri=None):
+ msg = (
+ "OAuth error from {name}! "
+ "error={error} description={description} uri={uri}"
+ ).format(
+ name=blueprint.name,
+ error=error,
+ description=error_description,
+ uri=error_uri,
+ )
+ flash(msg, category="error")
diff --git a/requirements.txt b/requirements.txt
index 3fb23ea3..2b13eb54 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -13,3 +13,5 @@ SQLAlchemy>=1.1.0
tornado>=4.1
Wand>=0.4.4
unidecode>=0.04.19
+flask-dance>=0.13.0
+sqlalchemy_utils>=0.33.5
From 4b76b8400da49f1d04e3f1eb67d0553346e7834f Mon Sep 17 00:00:00 2001
From: Jim Ma
Date: Sat, 13 Oct 2018 14:40:08 +0800
Subject: [PATCH 005/349] Add OAuth link&unlink in user profile
---
cps/templates/user_edit.html | 15 ++++++
cps/web.py | 96 ++++++++++++++++++++++++++++--------
2 files changed, 90 insertions(+), 21 deletions(-)
diff --git a/cps/templates/user_edit.html b/cps/templates/user_edit.html
index ecf8042e..e5dcb59d 100644
--- a/cps/templates/user_edit.html
+++ b/cps/templates/user_edit.html
@@ -52,6 +52,21 @@
{% endfor %}
+ {% if registered_oauth.keys()| length > 0 %}
+
+
+
+ {% for oauth, name in registered_oauth.iteritems() %}
+
+ {% if oauth not in oauth_status %}
+ Link
+ {% else %}
+ Unlink
+ {% endif %}
+
+ {% endfor %}
+
+ {% endif %}
diff --git a/cps/web.py b/cps/web.py
index 0937fdb7..a85bcf31 100644
--- a/cps/web.py
+++ b/cps/web.py
@@ -119,7 +119,7 @@ EXTENSIONS_CONVERT = {'pdf', 'epub', 'mobi', 'azw3', 'docx', 'rtf', 'fb2', 'lit'
# EXTENSIONS_READER = set(['txt', 'pdf', 'epub', 'zip', 'cbz', 'tar', 'cbt'] + (['rar','cbr'] if rar_support else []))
-oauth_check = []
+oauth_check = {}
'''class ReverseProxied(object):
"""Wrap the application in this middleware and configure the
@@ -2751,6 +2751,7 @@ def profile():
downloads = list()
languages = speaking_language()
translations = babel.list_translations() + [LC('en')]
+ oauth_status = get_oauth_status()
for book in content.downloads:
downloadBook = db.session.query(db.Books).filter(db.Books.id == book.book_id).first()
if downloadBook:
@@ -2812,11 +2813,11 @@ def profile():
ub.session.rollback()
flash(_(u"Found an existing account for this e-mail address."), category="error")
return render_title_template("user_edit.html", content=content, downloads=downloads,
- title=_(u"%(name)s's profile", name=current_user.nickname))
+ title=_(u"%(name)s's profile", name=current_user.nickname, registered_oauth=oauth_check, oauth_status=oauth_status))
flash(_(u"Profile updated"), category="success")
return render_title_template("user_edit.html", translations=translations, profile=1, languages=languages,
content=content, downloads=downloads, title=_(u"%(name)s's profile",
- name=current_user.nickname), page="me")
+ name=current_user.nickname), page="me", registered_oauth=oauth_check, oauth_status=oauth_status)
@app.route("/admin/view")
@@ -3945,22 +3946,22 @@ def convert_bookformat(book_id):
return redirect(request.environ["HTTP_REFERER"])
-def register_oauth_blueprint(blueprint):
+def register_oauth_blueprint(blueprint, show_name):
if blueprint.name != "":
- oauth_check.append(blueprint.name)
+ oauth_check[blueprint.name] = show_name
def register_user_with_oauth(user=None):
- all_oauth = []
- for oauth in oauth_check:
+ all_oauth = {}
+ for oauth in oauth_check.keys():
if oauth + '_oauth_user_id' in session and session[oauth + '_oauth_user_id'] != '':
- all_oauth.append(oauth)
- if len(all_oauth) == 0:
+ all_oauth[oauth] = oauth_check[oauth]
+ if len(all_oauth.keys()) == 0:
return
if user is None:
- flash(_(u"Register with %s" % ", ".join(all_oauth)), category="success")
+ flash(_(u"Register with %s" % ", ".join(list(all_oauth.values()))), category="success")
else:
- for oauth in all_oauth:
+ for oauth in all_oauth.keys():
# Find this OAuth token in the database, or create it
query = ub.session.query(ub.OAuth).filter_by(
provider=oauth,
@@ -3980,7 +3981,7 @@ def register_user_with_oauth(user=None):
def logout_oauth_user():
- for oauth in oauth_check:
+ for oauth in oauth_check.keys():
if oauth + '_oauth_user_id' in session:
session.pop(oauth + '_oauth_user_id')
@@ -4006,20 +4007,22 @@ app.register_blueprint(github_blueprint, url_prefix='/login')
github_blueprint.backend = OAuthBackend(ub.OAuth, ub.session, user=current_user, user_required=True)
google_blueprint.backend = OAuthBackend(ub.OAuth, ub.session, user=current_user, user_required=True)
-register_oauth_blueprint(github_blueprint)
-register_oauth_blueprint(google_blueprint)
+
+if config.config_use_github_oauth:
+ register_oauth_blueprint(github_blueprint, 'GitHub')
+if config.config_use_google_oauth:
+ register_oauth_blueprint(google_blueprint, 'Google')
@oauth_authorized.connect_via(github_blueprint)
def github_logged_in(blueprint, token):
if not token:
- flash("Failed to log in with GitHub.", category="error")
+ flash(_("Failed to log in with GitHub."), category="error")
return False
resp = blueprint.session.get("/user")
if not resp.ok:
- msg = "Failed to fetch user info from GitHub."
- flash(msg, category="error")
+ flash(_("Failed to fetch user info from GitHub."), category="error")
return False
github_info = resp.json()
@@ -4030,13 +4033,12 @@ def github_logged_in(blueprint, token):
@oauth_authorized.connect_via(google_blueprint)
def google_logged_in(blueprint, token):
if not token:
- flash("Failed to log in with Google.", category="error")
+ flash(_("Failed to log in with Google."), category="error")
return False
resp = blueprint.session.get("/oauth2/v2/userinfo")
if not resp.ok:
- msg = "Failed to fetch user info from Google."
- flash(msg, category="error")
+ flash(_("Failed to fetch user info from Google."), category="error")
return False
google_info = resp.json()
@@ -4088,7 +4090,7 @@ def bind_oauth_or_register(provider, provider_user_id, redirect_url):
return redirect(url_for('index'))
else:
# bind to current user
- if current_user and not current_user.is_anonymous:
+ if current_user and current_user.is_authenticated:
oauth.user = current_user
try:
ub.session.add(oauth)
@@ -4101,6 +4103,46 @@ def bind_oauth_or_register(provider, provider_user_id, redirect_url):
return redirect(url_for(redirect_url))
+def get_oauth_status():
+ status = []
+ query = ub.session.query(ub.OAuth).filter_by(
+ user_id=current_user.id,
+ )
+ try:
+ oauths = query.all()
+ for oauth in oauths:
+ status.append(oauth.provider)
+ return status
+ except NoResultFound:
+ return None
+
+
+def unlink_oauth(provider):
+ if request.host_url + 'me' != request.referrer:
+ pass
+ query = ub.session.query(ub.OAuth).filter_by(
+ provider=provider,
+ user_id=current_user.id,
+ )
+ try:
+ oauth = query.one()
+ if current_user and current_user.is_authenticated:
+ oauth.user = current_user
+ try:
+ ub.session.delete(oauth)
+ ub.session.commit()
+ logout_oauth_user()
+ flash(_("Unlink to %(oauth)s success.", oauth=oauth_check[provider]), category="success")
+ except Exception as e:
+ app.logger.exception(e)
+ ub.session.rollback()
+ flash(_("Unlink to %(oauth)s failed.", oauth=oauth_check[provider]), category="error")
+ except NoResultFound:
+ app.logger.warning("oauth %s for user %d not fount" % (provider, current_user.id))
+ flash(_("Not linked to %(oauth)s.", oauth=oauth_check[provider]), category="error")
+ return redirect(url_for('profile'))
+
+
# notify on OAuth provider error
@oauth_error.connect_via(github_blueprint)
def github_error(blueprint, error, error_description=None, error_uri=None):
@@ -4129,6 +4171,12 @@ def github_login():
return redirect(url_for('login'))
+@app.route('/unlink/github', methods=["GET"])
+@login_required
+def github_login_unlink():
+ return unlink_oauth(github_blueprint.name)
+
+
@app.route('/google')
@google_oauth_required
def google_login():
@@ -4154,3 +4202,9 @@ def google_error(blueprint, error, error_description=None, error_uri=None):
uri=error_uri,
)
flash(msg, category="error")
+
+
+@app.route('/unlink/google', methods=["GET"])
+@login_required
+def google_login_unlink():
+ return unlink_oauth(google_blueprint.name)
From e1b6fa25e9cb80bc203245b3daac37215f2c0275 Mon Sep 17 00:00:00 2001
From: haseo
Date: Mon, 19 Nov 2018 15:54:28 +0800
Subject: [PATCH 006/349] A better solution to #681
---
cps/web.py | 7 +------
1 file changed, 1 insertion(+), 6 deletions(-)
diff --git a/cps/web.py b/cps/web.py
index 396066d9..5541ebcf 100644
--- a/cps/web.py
+++ b/cps/web.py
@@ -197,12 +197,7 @@ def get_locale():
if user.nickname != 'Guest': # if the account is the guest account bypass the config lang settings
return user.locale
translations = [item.language for item in babel.list_translations()] + ['en']
- preferred = [x.replace('-', '_') for x in request.accept_languages.values()]
-
- # In the case of Simplified Chinese, Accept-Language is "zh-CN", while our translation of Simplified Chinese is "zh_Hans_CN".
- # TODO: This is Not a good solution, should be improved.
- if "zh_CN" in preferred:
- return "zh_Hans_CN"
+ preferred = [str(LC.parse(x, '-')) for x in request.accept_languages.values()]
return negotiate_locale(preferred, translations)
From 863b77a5d7d342ac2f587c6f8d70679c707189a6 Mon Sep 17 00:00:00 2001
From: Ozzieisaacs
Date: Sun, 25 Nov 2018 11:25:20 +0100
Subject: [PATCH 007/349] Fix #711 Fixing for send to kindle after uploading
codecleaning
---
cps/helper.py | 25 +++++++--
cps/templates/detail.html | 8 ++-
cps/web.py | 104 ++++++++++++++++++--------------------
3 files changed, 73 insertions(+), 64 deletions(-)
diff --git a/cps/helper.py b/cps/helper.py
index 2834bad1..43fb8520 100644
--- a/cps/helper.py
+++ b/cps/helper.py
@@ -114,9 +114,9 @@ def send_registration_mail(e_mail, user_name, default_password, resend=False):
return
def check_send_to_kindle(entry):
- '''
+ """
returns all available book formats for sending to Kindle
- '''
+ """
if len(entry.data):
bookformats=list()
if ub.config.config_ebookconverter == 0:
@@ -156,6 +156,18 @@ def check_send_to_kindle(entry):
return None
+# Check if a reader is existing for any of the book formats, if not, return empty list, otherwise return
+# list with supported formats
+def check_read_formats(entry):
+ EXTENSIONS_READER = {'TXT', 'PDF', 'EPUB', 'ZIP', 'CBZ', 'TAR', 'CBT', 'RAR', 'CBR'}
+ bookformats = list()
+ if len(entry.data):
+ for ele in iter(entry.data):
+ if ele.format in EXTENSIONS_READER:
+ bookformats.append(ele.format.lower())
+ return bookformats
+
+
# Files are processed in the following order/priority:
# 1: If Mobi file is existing, it's directly send to kindle email,
# 2: If Epub file is existing, it's converted and send to kindle email,
@@ -336,6 +348,7 @@ def delete_book_gdrive(book, book_format):
error =_(u'Book path %(path)s not found on Google Drive', path=book.path) # file not found
return error
+
def generate_random_password():
s = "abcdefghijklmnopqrstuvwxyz01234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%&*()?"
passlen = 8
@@ -349,12 +362,14 @@ def update_dir_stucture(book_id, calibrepath):
else:
return update_dir_structure_file(book_id, calibrepath)
+
def delete_book(book, calibrepath, book_format):
if ub.config.config_use_google_drive:
return delete_book_gdrive(book, book_format)
else:
return delete_book_file(book, calibrepath, book_format)
+
def get_book_cover(cover_path):
if ub.config.config_use_google_drive:
try:
@@ -372,6 +387,7 @@ def get_book_cover(cover_path):
else:
return send_from_directory(os.path.join(ub.config.config_calibre_dir, cover_path), "cover.jpg")
+
# saves book cover to gdrive or locally
def save_cover(url, book_path):
img = requests.get(url)
@@ -384,7 +400,7 @@ def save_cover(url, book_path):
f = open(os.path.join(tmpDir, "uploaded_cover.jpg"), "wb")
f.write(img.content)
f.close()
- uploadFileToEbooksFolder(os.path.join(book_path, 'cover.jpg'), os.path.join(tmpDir, f.name))
+ gd.uploadFileToEbooksFolder(os.path.join(book_path, 'cover.jpg'), os.path.join(tmpDir, f.name))
web.app.logger.info("Cover is saved on Google Drive")
return True
@@ -394,6 +410,7 @@ def save_cover(url, book_path):
web.app.logger.info("Cover is saved")
return True
+
def do_download_file(book, book_format, data, headers):
if ub.config.config_use_google_drive:
startTime = time.time()
@@ -621,6 +638,7 @@ def get_current_version_info():
return {'hash': content[0], 'datetime': content[1]}
return False
+
def json_serial(obj):
"""JSON serializer for objects not serializable by default json code"""
@@ -628,6 +646,7 @@ def json_serial(obj):
return obj.isoformat()
raise TypeError ("Type %s not serializable" % type(obj))
+
def render_task_status(tasklist):
#helper function to apply localize status information in tasklist entries
renderedtasklist=list()
diff --git a/cps/templates/detail.html b/cps/templates/detail.html
index 27d73ae2..7631ce2f 100644
--- a/cps/templates/detail.html
+++ b/cps/templates/detail.html
@@ -57,17 +57,15 @@
{% endif %}
{% endif %}
- {% if entry.data|length %}
+ {% if reader_list %}
- {% for format in entry.data %}
- {%if format.format|lower == 'epub' or format.format|lower == 'txt' or format.format|lower == 'pdf' or format.format|lower == 'cbr' or format.format|lower == 'cbt' or format.format|lower == 'cbz' %}
-
ft8.4: Traceback (most recent call last):
+ File "/home/matthias/Entwicklung/calibre-web-test/test/test_login.py", line 81, in test_login_protected
+ self.assertTrue(self.fail_access_page("http://127.0.0.1:8083/config"))
+AssertionError: False is not true
From 64b1b830d60ee842ad1060fd113ed32abbf1dd19 Mon Sep 17 00:00:00 2001
From: Ozzieisaacs
Date: Sun, 25 Nov 2018 13:29:58 +0100
Subject: [PATCH 009/349] Fix #712
---
cps/helper.py | 8 +-
test/Calibre-Web TestSummary.html | 479 ++++++++++++++++++++++++------
2 files changed, 384 insertions(+), 103 deletions(-)
diff --git a/cps/helper.py b/cps/helper.py
index 43fb8520..d74318bf 100644
--- a/cps/helper.py
+++ b/cps/helper.py
@@ -123,13 +123,13 @@ def check_send_to_kindle(entry):
# no converter - only for mobi and pdf formats
for ele in iter(entry.data):
if 'MOBI' in ele.format:
- bookformats.append({'format':'Mobi','text':_('Send %(format)s to Kindle',format='Mobi')})
+ bookformats.append({'format':'Mobi','convert':0,'text':_('Send %(format)s to Kindle',format='Mobi')})
if 'PDF' in ele.format:
- bookformats.append({'format':'Pdf','text':_('Send %(format)s to Kindle',format='Pdf')})
+ bookformats.append({'format':'Pdf','convert':0,'text':_('Send %(format)s to Kindle',format='Pdf')})
if 'AZW' in ele.format:
- bookformats.append({'format':'Azw','text':_('Send %(format)s to Kindle',format='Azw')})
+ bookformats.append({'format':'Azw','convert':0,'text':_('Send %(format)s to Kindle',format='Azw')})
if 'AZW3' in ele.format:
- bookformats.append({'format':'Azw3','text':_('Send %(format)s to Kkindle',format='Azw3')})
+ bookformats.append({'format':'Azw3','convert':0,'text':_('Send %(format)s to Kindle',format='Azw3')})
else:
formats = list()
for ele in iter(entry.data):
diff --git a/test/Calibre-Web TestSummary.html b/test/Calibre-Web TestSummary.html
index e9503755..2feb61b8 100644
--- a/test/Calibre-Web TestSummary.html
+++ b/test/Calibre-Web TestSummary.html
@@ -32,15 +32,15 @@
-
Start Time: 2018-11-25 12:06:17.948456
+
Start Time: 2018-11-25 12:57:57.569980
-
Stop Time: 2018-11-25 12:24:09.768645
+
Stop Time: 2018-11-25 13:16:06.314449
-
Duration: 0:17:51.820189
+
Duration: 0:18:08.744469
@@ -1554,6 +1554,287 @@ AssertionError: 'The camicdemo' != '\n '
ft9.4: Traceback (most recent call last):
File "/home/matthias/Entwicklung/calibre-web-test/test/test_login.py", line 81, in test_login_protected
self.assertTrue(self.fail_access_page("http://127.0.0.1:8083/config"))
AssertionError: False is not true
@@ -1610,13 +1891,13 @@ AssertionError: False is not true
-
+
test_login_unicode_user_space_end_passwort
PASS
-
+
test_login_user_with_space_passwort_end_space
@@ -1629,48 +1910,13 @@ AssertionError: False is not true