Commit 80582573 authored by Ozzieisaacs's avatar Ozzieisaacs

Merge remote-tracking branch 'oauth/oauth' into Develop

# Conflicts:
#	cps/templates/config_edit.html
#	cps/ub.py
#	cps/web.py
parents 3683e4e7 4b76b840
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from flask import session
from flask_dance.consumer.backend.sqla import SQLAlchemyBackend, first, _get_real_user
from sqlalchemy.orm.exc import NoResultFound
class OAuthBackend(SQLAlchemyBackend):
"""
Stores and retrieves OAuth tokens using a relational database through
the `SQLAlchemy`_ ORM.
.. _SQLAlchemy: http://www.sqlalchemy.org/
"""
def __init__(self, model, session,
user=None, user_id=None, user_required=None, anon_user=None,
cache=None):
super(OAuthBackend, self).__init__(model, session, user, user_id, user_required, anon_user, cache)
def get(self, blueprint, user=None, user_id=None):
if blueprint.name + '_oauth_token' in session and session[blueprint.name + '_oauth_token'] != '':
return session[blueprint.name + '_oauth_token']
# check cache
cache_key = self.make_cache_key(blueprint=blueprint, user=user, user_id=user_id)
token = self.cache.get(cache_key)
if token:
return token
# if not cached, make database queries
query = (
self.session.query(self.model)
.filter_by(provider=blueprint.name)
)
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")))
use_provider_user_id = False
if blueprint.name + '_oauth_user_id' in session and session[blueprint.name + '_oauth_user_id'] != '':
query = query.filter_by(provider_user_id=session[blueprint.name + '_oauth_user_id'])
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")
return None
# check for user ID
if hasattr(self.model, "user_id") and uid:
query = query.filter_by(user_id=uid)
# check for user (relationship property)
elif hasattr(self.model, "user") and u:
query = query.filter_by(user=u)
# if we have the property, but not value, filter by None
elif hasattr(self.model, "user_id"):
query = query.filter_by(user_id=None)
# run query
try:
token = query.one().token
except NoResultFound:
token = None
# cache the result
self.cache.set(cache_key, token)
return token
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")))
if self.user_required and not u and not uid:
raise ValueError("Cannot set OAuth token without an associated user")
# if there was an existing model, delete it
existing_query = (
self.session.query(self.model)
.filter_by(provider=blueprint.name)
)
# check for user ID
has_user_id = hasattr(self.model, "user_id")
if has_user_id and uid:
existing_query = existing_query.filter_by(user_id=uid)
# check for user (relationship property)
has_user = hasattr(self.model, "user")
if has_user and u:
existing_query = existing_query.filter_by(user=u)
# queue up delete query -- won't be run until commit()
existing_query.delete()
# create a new model for this token
kwargs = {
"provider": blueprint.name,
"token": token,
}
if has_user_id and uid:
kwargs["user_id"] = uid
if has_user and u:
kwargs["user"] = u
self.session.add(self.model(**kwargs))
# commit to delete and add simultaneously
self.session.commit()
# invalidate cache
self.cache.delete(self.make_cache_key(
blueprint=blueprint, user=user, user_id=user_id
))
def delete(self, blueprint, user=None, user_id=None):
query = (
self.session.query(self.model)
.filter_by(provider=blueprint.name)
)
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")))
if self.user_required and not u and not uid:
raise ValueError("Cannot delete OAuth token without an associated user")
# check for user ID
if hasattr(self.model, "user_id") and uid:
query = query.filter_by(user_id=uid)
# check for user (relationship property)
elif hasattr(self.model, "user") and u:
query = query.filter_by(user=u)
# if we have the property, but not value, filter by None
elif hasattr(self.model, "user_id"):
query = query.filter_by(user_id=None)
# run query
query.delete()
self.session.commit()
# invalidate cache
self.cache.delete(self.make_cache_key(
blueprint=blueprint, user=user, user_id=user_id,
))
...@@ -185,6 +185,34 @@ ...@@ -185,6 +185,34 @@
<input type="text" class="form-control" id="config_ldap_dn" name="config_ldap_dn" value="{% if content.config_use_ldap != None %}{{ content.config_ldap_dn }}{% endif %}" autocomplete="off"> <input type="text" class="form-control" id="config_ldap_dn" name="config_ldap_dn" value="{% if content.config_use_ldap != None %}{{ content.config_ldap_dn }}{% endif %}" autocomplete="off">
</div> </div>
</div> </div>
<input type="checkbox" id="config_use_github_oauth" name="config_use_github_oauth" data-control="github-oauth-settings" {% if content.config_use_github_oauth %}checked{% endif %}>
<label for="config_use_github_oauth">{{_('Use')}} GitHub OAuth</label>
<a href="https://github.com/settings/developers" target="_blank" style="margin-left: 5px">{{_('Obtain GitHub OAuth Credentail')}}</a>
</div>
<div data-related="github-oauth-settings">
<div class="form-group">
<label for="config_github_oauth_client_id">{{_('GitHub OAuth Client Id')}}</label>
<input type="text" class="form-control" id="config_github_oauth_client_id" name="config_github_oauth_client_id" value="{% if content.config_github_oauth_client_id != None %}{{ content.config_github_oauth_client_id }}{% endif %}" autocomplete="off">
</div>
<div class="form-group">
<label for="config_github_oauth_client_secret">{{_('GitHub OAuth Client Secret')}}</label>
<input type="text" class="form-control" id="config_github_oauth_client_secret" name="config_github_oauth_client_secret" value="{% if content.config_github_oauth_client_secret != None %}{{ content.config_github_oauth_client_secret }}{% endif %}" autocomplete="off">
</div>
</div>
<div class="form-group">
<input type="checkbox" id="config_use_google_oauth" name="config_use_google_oauth" data-control="google-oauth-settings" {% if content.config_use_google_oauth %}checked{% endif %}>
<label for="config_use_google_oauth">{{_('Use')}} Google OAuth</label>
<a href="https://console.developers.google.com/apis/credentials" target="_blank" style="margin-left: 5px">{{_('Obtain Google OAuth Credentail')}}</a>
</div>
<div data-related="google-oauth-settings">
<div class="form-group">
<label for="config_google_oauth_client_id">{{_('Google OAuth Client Id')}}</label>
<input type="text" class="form-control" id="config_google_oauth_client_id" name="config_google_oauth_client_id" value="{% if content.config_google_oauth_client_id != None %}{{ content.config_google_oauth_client_id }}{% endif %}" autocomplete="off">
</div>
<div class="form-group">
<label for="config_google_oauth_client_secret">{{_('Google OAuth Client Secret')}}</label>
<input type="text" class="form-control" id="config_google_oauth_client_secret" name="config_google_oauth_client_secret" value="{% if content.config_google_oauth_client_secret != None %}{{ content.config_google_oauth_client_secret }}{% endif %}" autocomplete="off">
</div>
</div> </div>
</div> </div>
</div> </div>
......
...@@ -18,9 +18,24 @@ ...@@ -18,9 +18,24 @@
</label> </label>
</div> </div>
<button type="submit" name="submit" class="btn btn-default">{{_('Submit')}}</button> <button type="submit" name="submit" class="btn btn-default">{{_('Submit')}}</button>
{% if remote_login %} {% if config.config_remote_login %}
<a href="{{url_for('remote_login')}}" class="pull-right">{{_('Log in with magic link')}}</a> <a href="{{url_for('remote_login')}}" class="pull-right">{{_('Log in with magic link')}}</a>
{% endif %} {% endif %}
{% if config.config_use_github_oauth %}
<a href="{{url_for('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">
<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_use_google_oauth %}
<a href="{{url_for('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>
{% endif %}
</form> </form>
</div> </div>
{% if error %} {% if error %}
......
...@@ -12,6 +12,21 @@ ...@@ -12,6 +12,21 @@
<input type="email" class="form-control" id="email" name="email" placeholder="{{_('Your email address')}}" required> <input type="email" class="form-control" id="email" name="email" placeholder="{{_('Your email address')}}" required>
</div> </div>
<button type="submit" id="submit" class="btn btn-primary">{{_('Register')}}</button> <button type="submit" id="submit" class="btn btn-primary">{{_('Register')}}</button>
{% if config.config_use_github_oauth %}
<a href="{{url_for('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">
<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_use_google_oauth %}
<a href="{{url_for('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>
{% endif %}
</form> </form>
</div> </div>
{% if error %} {% if error %}
......
...@@ -44,6 +44,21 @@ ...@@ -44,6 +44,21 @@
{% endfor %} {% endfor %}
</select> </select>
</div> </div>
{% if registered_oauth.keys()| length > 0 %}
<div class="form-group">
<label for="oauth_settings">{{_('OAuth Settings')}}</label>
<br/>
{% for oauth, name in registered_oauth.iteritems() %}
<label for="config_{{ oauth }}_oauth" style="margin-left: 15px">{{ name }}</label>
{% if oauth not in oauth_status %}
<a href="/{{ oauth }}" target="_blank" style="margin-left: 5px">Link</a>
{% else %}
<a href="/unlink/{{ oauth }}" style="margin-left: 5px">Unlink</a>
{% endif %}
<br/>
{% endfor %}
</div>
{% endif %}
<div class="col-sm-6"> <div class="col-sm-6">
<div class="form-group"> <div class="form-group">
<input type="checkbox" name="show_random" id="show_random" {% if content.show_random_books() %}checked{% endif %}> <input type="checkbox" name="show_random" id="show_random" {% if content.show_random_books() %}checked{% endif %}>
......
...@@ -23,6 +23,7 @@ from sqlalchemy import exc ...@@ -23,6 +23,7 @@ from sqlalchemy import exc
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import * from sqlalchemy.orm import *
from flask_login import AnonymousUserMixin from flask_login import AnonymousUserMixin
from flask_dance.consumer.backend.sqla import OAuthConsumerMixin
import sys import sys
import os import os
import logging import logging
...@@ -197,6 +198,12 @@ class User(UserBase, Base): ...@@ -197,6 +198,12 @@ class User(UserBase, Base):
mature_content = Column(Boolean, default=True) mature_content = Column(Boolean, default=True)
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 # Class for anonymous user is derived from User base and completly overrides methods and properties for the
# anonymous user # anonymous user
class Anonymous(AnonymousUserMixin, UserBase): class Anonymous(AnonymousUserMixin, UserBase):
...@@ -337,6 +344,12 @@ class Settings(Base): ...@@ -337,6 +344,12 @@ class Settings(Base):
config_use_ldap = Column(Boolean) config_use_ldap = Column(Boolean)
config_ldap_provider_url = Column(String) config_ldap_provider_url = Column(String)
config_ldap_dn = Column(String) config_ldap_dn = 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_mature_content_tags = Column(String)
config_logfile = Column(String) config_logfile = Column(String)
config_ebookconverter = Column(Integer, default=0) config_ebookconverter = Column(Integer, default=0)
...@@ -414,6 +427,12 @@ class Config: ...@@ -414,6 +427,12 @@ class Config:
self.config_use_ldap = data.config_use_ldap self.config_use_ldap = data.config_use_ldap
self.config_ldap_provider_url = data.config_ldap_provider_url self.config_ldap_provider_url = data.config_ldap_provider_url
self.config_ldap_dn = data.config_ldap_dn self.config_ldap_dn = data.config_ldap_dn
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: if data.config_mature_content_tags:
self.config_mature_content_tags = data.config_mature_content_tags self.config_mature_content_tags = data.config_mature_content_tags
else: else:
...@@ -722,6 +741,22 @@ def migrate_Database(): ...@@ -722,6 +741,22 @@ def migrate_Database():
conn.execute("ALTER TABLE Settings ADD column `config_updatechannel` INTEGER DEFAULT 0") conn.execute("ALTER TABLE Settings ADD column `config_updatechannel` INTEGER DEFAULT 0")
session.commit() 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 # Remove login capability of user Guest
conn = engine.connect() conn = engine.connect()
conn.execute("UPDATE user SET password='' where nickname = 'Guest' and password !=''") conn.execute("UPDATE user SET password='' where nickname = 'Guest' and password !=''")
......
This diff is collapsed.
...@@ -13,3 +13,5 @@ SQLAlchemy>=1.1.0 ...@@ -13,3 +13,5 @@ SQLAlchemy>=1.1.0
tornado>=4.1 tornado>=4.1
Wand>=0.4.4 Wand>=0.4.4
unidecode>=0.04.19 unidecode>=0.04.19
flask-dance>=0.13.0
sqlalchemy_utils>=0.33.5
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