Implementation for gmail server with OAuth2 started

This commit is contained in:
Ozzie Isaacs 2021-03-27 16:36:24 +01:00
parent 0ceb12f74f
commit 8f91437701
5 changed files with 150 additions and 51 deletions

View File

@ -39,7 +39,7 @@ from sqlalchemy.orm.attributes import flag_modified
from sqlalchemy.exc import IntegrityError, OperationalError, InvalidRequestError from sqlalchemy.exc import IntegrityError, OperationalError, InvalidRequestError
from sqlalchemy.sql.expression import func, or_ from sqlalchemy.sql.expression import func, or_
from . import constants, logger, helper, services from . import constants, logger, helper, services, gmail
from .cli import filepicker from .cli import filepicker
from . import db, calibre_db, ub, web_server, get_locale, config, updater_thread, babel, gdriveutils from . import db, calibre_db, ub, web_server, get_locale, config, updater_thread, babel, gdriveutils
from .helper import check_valid_domain, send_test_mail, reset_password, generate_password_hash from .helper import check_valid_domain, send_test_mail, reset_password, generate_password_hash
@ -1319,7 +1319,17 @@ def edit_mailsettings():
@admin_required @admin_required
def update_mailsettings(): def update_mailsettings():
to_save = request.form.to_dict() to_save = request.form.to_dict()
_config_int(to_save, "mail_server_type")
if to_save.get("invalidate_server"):
config.mail_gmail_token = {}
try:
flag_modified(config, "mail_gmail_token")
except AttributeError:
pass
elif to_save.get("gmail"):
config.mail_gmail_token = gmail.setup_gmail(config)
flash(_(u"G-Mail Account Verification Successfull"), category="success")
else:
_config_string(to_save, "mail_server") _config_string(to_save, "mail_server")
_config_int(to_save, "mail_port") _config_int(to_save, "mail_port")
_config_int(to_save, "mail_use_ssl") _config_int(to_save, "mail_use_ssl")
@ -1338,8 +1348,8 @@ def update_mailsettings():
if current_user.email: if current_user.email:
result = send_test_mail(current_user.email, current_user.name) result = send_test_mail(current_user.email, current_user.name)
if result is None: if result is None:
flash(_(u"Test e-mail queued for sending to %(email)s, please check Tasks for result", email=current_user.email), flash(_(u"Test e-mail queued for sending to %(email)s, please check Tasks for result",
category="info") email=current_user.email), category="info")
else: else:
flash(_(u"There was an error sending the Test e-mail: %(res)s", res=result), category="error") flash(_(u"There was an error sending the Test e-mail: %(res)s", res=result), category="error")
else: else:

View File

@ -39,7 +39,7 @@ class _Flask_Settings(_Base):
__tablename__ = 'flask_settings' __tablename__ = 'flask_settings'
id = Column(Integer, primary_key=True) id = Column(Integer, primary_key=True)
flask_session_key = Column(BLOB, default="") flask_session_key = Column(BLOB, default=b"")
def __init__(self, key): def __init__(self, key):
self.flask_session_key = key self.flask_session_key = key
@ -58,6 +58,8 @@ class _Settings(_Base):
mail_password = Column(String, default='mypassword') mail_password = Column(String, default='mypassword')
mail_from = Column(String, default='automailer <mail@example.com>') mail_from = Column(String, default='automailer <mail@example.com>')
mail_size = Column(Integer, default=25*1024*1024) mail_size = Column(Integer, default=25*1024*1024)
mail_server_type = Column(SmallInteger, default=0)
mail_gmail_token = Column(JSON, default={})
config_calibre_dir = Column(String) config_calibre_dir = Column(String)
config_port = Column(Integer, default=constants.DEFAULT_PORT) config_port = Column(Integer, default=constants.DEFAULT_PORT)
@ -246,7 +248,8 @@ class _ConfigSQL(object):
return {k:v for k, v in self.__dict__.items() if k.startswith('mail_')} return {k:v for k, v in self.__dict__.items() if k.startswith('mail_')}
def get_mail_server_configured(self): def get_mail_server_configured(self):
return not bool(self.mail_server == constants.DEFAULT_MAIL_SERVER) return bool((self.mail_server != constants.DEFAULT_MAIL_SERVER and self.mail_server_type == 0)
or (self.mail_gmail_token != b"" and self.mail_server_type == 1))
def set_from_dictionary(self, dictionary, field, convertor=None, default=None, encode=None): def set_from_dictionary(self, dictionary, field, convertor=None, default=None, encode=None):
@ -364,10 +367,14 @@ def _migrate_table(session, orm_class):
if isinstance(column.default.arg, bool): if isinstance(column.default.arg, bool):
column_default = ("DEFAULT %r" % int(column.default.arg)) column_default = ("DEFAULT %r" % int(column.default.arg))
else: else:
column_default = ("DEFAULT %r" % column.default.arg) column_default = ("DEFAULT '%r'" % column.default.arg)
if isinstance(column.type, JSON):
column_type = "JSON"
else:
column_type = column.type
alter_table = "ALTER TABLE %s ADD COLUMN `%s` %s %s" % (orm_class.__tablename__, alter_table = "ALTER TABLE %s ADD COLUMN `%s` %s %s" % (orm_class.__tablename__,
column_name, column_name,
column.type, column_type,
column_default) column_default)
log.debug(alter_table) log.debug(alter_table)
session.execute(alter_table) session.execute(alter_table)

64
cps/gmail.py Normal file
View File

@ -0,0 +1,64 @@
from __future__ import print_function
import os.path
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from .constants import BASE_DIR
import json
from datetime import datetime
subject = "Test"
msg = "Testnachricht"
sender = "matthias1.knopp@googlemail.com"
receiver = "matthias.knopp@web.de"
SCOPES = ['https://www.googleapis.com/auth/gmail.send']
def setup_gmail(config):
token = config.mail_gmail_token
# if config.mail_gmail_token != "{}":
# If there are no (valid) credentials available, let the user log in.
creds = None
if "token" in token:
creds = Credentials(
token=token['token'],
refresh_token=token['refresh_token'],
token_uri=token['token_uri'],
client_id=token['client_id'],
client_secret=token['client_secret'],
scopes=token['scopes'],
)
creds.expiry = datetime.fromisoformat(token['expiry'])
if not creds or not creds.valid:
# don't forget to dump one more time after the refresh
# also, some file-locking routines wouldn't be needless
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file(
os.path.join(BASE_DIR, 'gmail.json'), SCOPES)
creds = flow.run_local_server(port=0)
return {
'token': creds.token,
'refresh_token': creds.refresh_token,
'token_uri': creds.token_uri,
'client_id': creds.client_id,
'client_secret': creds.client_secret,
'scopes': creds.scopes,
'expiry': creds.expiry.isoformat(),
}
# implement your storage logic here, e.g. just good old json.dump() / json.load()
# service = build('gmail', 'v1', credentials=creds)
# message = MIMEText(msg)
# message['to'] = receiver
# message['from'] = sender
# message['subject'] = subject
# raw = base64.urlsafe_b64encode(message.as_bytes())
# raw = raw.decode()
# body = {'raw' : raw}
# message = (service.users().messages().send(userId='me', body=body).execute())

View File

@ -7,6 +7,23 @@
<div class="discover"> <div class="discover">
<h1>{{title}}</h1> <h1>{{title}}</h1>
<form role="form" class="col-md-10 col-lg-6" method="POST"> <form role="form" class="col-md-10 col-lg-6" method="POST">
<div class="form-group">
<label for="mail_server_type">{{_('Choose Server Type')}}</label>
<select name="mail_server_type" id="config_email_type" class="form-control" data-control="email-settings">
<option value="0" {% if content.mail_server_type == 0 %}selected{% endif %}>{{_('Use Standard E-Mail Account')}}</option>
<option value="1" {% if content.mail_server_type == 1 %}selected{% endif %}>{{_('G-Mail Account with OAuth2 Verfification')}}</option>
</select>
</div>
<div data-related="email-settings-1">
<div class="form-group">
{% if content.mail_gmail_token == {} %}
<button type="submit" id="gmail_server" name="gmail" value="submit" class="btn btn-default">{{_('Setup Gmail Account as E-Mail Server')}}</button>
{% else %}
<button type="submit" id="invalidate_server" name="invalidate" value="submit" class="btn btn-danger">{{_('Revoke G-Mail Access')}}</button>
{% endif %}
</div>
</div>
<div data-related="email-settings-0">
<div class="form-group"> <div class="form-group">
<label for="mail_server">{{_('SMTP Hostname')}}</label> <label for="mail_server">{{_('SMTP Hostname')}}</label>
<input type="text" class="form-control" name="mail_server" id="mail_server" value="{{content.mail_server}}"> <input type="text" class="form-control" name="mail_server" id="mail_server" value="{{content.mail_server}}">
@ -44,6 +61,7 @@
</div> </div>
<button type="submit" name="submit" value="submit" class="btn btn-default">{{_('Save')}}</button> <button type="submit" name="submit" value="submit" class="btn btn-default">{{_('Save')}}</button>
<button type="submit" name="test" value="test" class="btn btn-default">{{_('Save and Send Test E-mail')}}</button> <button type="submit" name="test" value="test" class="btn btn-default">{{_('Save and Send Test E-mail')}}</button>
</div>
<a href="{{ url_for('admin.admin') }}" id="back" class="btn btn-default">{{_('Cancel')}}</a> <a href="{{ url_for('admin.admin') }}" id="back" class="btn btn-default">{{_('Cancel')}}</a>
</form> </form>
{% if g.allow_registration %} {% if g.allow_registration %}

View File

@ -1,9 +1,9 @@
# GDrive Integration # GDrive Integration
google-api-python-client>=1.7.11,<1.13.0 google-api-python-client>=1.7.11,<2.1.0
gevent>20.6.0,<21.2.0 gevent>20.6.0,<21.2.0
greenlet>=0.4.17,<1.1.0 greenlet>=0.4.17,<1.1.0
httplib2>=0.9.2,<0.18.0 httplib2>=0.9.2,<0.18.0
oauth2client>=4.0.0,<4.1.4 # oauth2client>=4.0.0,<4.1.4
uritemplate>=3.0.0,<3.1.0 uritemplate>=3.0.0,<3.1.0
pyasn1-modules>=0.0.8,<0.3.0 pyasn1-modules>=0.0.8,<0.3.0
pyasn1>=0.1.9,<0.5.0 pyasn1>=0.1.9,<0.5.0