Commit a784c6bd authored by Ozzieisaacs's avatar Ozzieisaacs

Fixes for oauth login after programming basic tests

parent ce4f1258
...@@ -43,6 +43,11 @@ try: ...@@ -43,6 +43,11 @@ try:
except ImportError: except ImportError:
unidecode_version = _(u'not installed') unidecode_version = _(u'not installed')
try:
from flask_dance import __version__ as flask_danceVersion
except ImportError:
flask_danceVersion = None
from . import services from . import services
about = flask.Blueprint('about', __name__) about = flask.Blueprint('about', __name__)
...@@ -68,10 +73,11 @@ _VERSIONS = OrderedDict( ...@@ -68,10 +73,11 @@ _VERSIONS = OrderedDict(
iso639=isoLanguages.__version__, iso639=isoLanguages.__version__,
pytz=pytz.__version__, pytz=pytz.__version__,
Unidecode = unidecode_version, Unidecode = unidecode_version,
Flask_SimpleLDAP = u'installed' if bool(services.ldap) else u'not installed', Flask_SimpleLDAP = u'installed' if bool(services.ldap) else None,
python_LDAP = services.ldapVersion if bool(services.ldapVersion) else u'not installed', python_LDAP = services.ldapVersion if bool(services.ldapVersion) else None,
Goodreads = u'installed' if bool(services.goodreads_support) else u'not installed', Goodreads = u'installed' if bool(services.goodreads_support) else None,
jsonschema = services.SyncToken.__version__ if bool(services.SyncToken) else u'not installed', jsonschema = services.SyncToken.__version__ if bool(services.SyncToken) else None,
flask_dance = flask_danceVersion
) )
_VERSIONS.update(uploader.get_versions()) _VERSIONS.update(uploader.get_versions())
......
...@@ -622,23 +622,22 @@ def _configuration_update_helper(): ...@@ -622,23 +622,22 @@ def _configuration_update_helper():
active_oauths = 0 active_oauths = 0
for element in oauthblueprints: for element in oauthblueprints:
if to_save["config_" + str(element['id']) + "_oauth_client_id"] != element['oauth_client_id'] \
or to_save["config_" + str(element['id']) + "_oauth_client_secret"] != element['oauth_client_secret']:
reboot_required = True
element['oauth_client_id'] = to_save["config_" + str(element['id']) + "_oauth_client_id"]
element['oauth_client_secret'] = to_save["config_" + str(element['id']) + "_oauth_client_secret"]
if to_save["config_"+str(element['id'])+"_oauth_client_id"] \ if to_save["config_"+str(element['id'])+"_oauth_client_id"] \
and to_save["config_"+str(element['id'])+"_oauth_client_secret"]: and to_save["config_"+str(element['id'])+"_oauth_client_secret"]:
active_oauths += 1 active_oauths += 1
element["active"] = 1 element["active"] = 1
ub.session.query(ub.OAuthProvider).filter(ub.OAuthProvider.id == element['id']).update(
{"oauth_client_id":to_save["config_"+str(element['id'])+"_oauth_client_id"],
"oauth_client_secret":to_save["config_"+str(element['id'])+"_oauth_client_secret"],
"active":1})
if to_save["config_" + str(element['id']) + "_oauth_client_id"] != element['oauth_client_id'] \
or to_save["config_" + str(element['id']) + "_oauth_client_secret"] != element['oauth_client_secret']:
reboot_required = True
element['oauth_client_id'] = to_save["config_"+str(element['id'])+"_oauth_client_id"]
element['oauth_client_secret'] = to_save["config_"+str(element['id'])+"_oauth_client_secret"]
else: else:
ub.session.query(ub.OAuthProvider).filter(ub.OAuthProvider.id == element['id']).update(
{"active":0})
element["active"] = 0 element["active"] = 0
ub.session.query(ub.OAuthProvider).filter(ub.OAuthProvider.id == element['id']).update(
{"oauth_client_id":to_save["config_"+str(element['id'])+"_oauth_client_id"],
"oauth_client_secret":to_save["config_"+str(element['id'])+"_oauth_client_secret"],
"active":element["active"]})
reboot_required |= _config_int("config_log_level") reboot_required |= _config_int("config_log_level")
reboot_required |= _config_string("config_logfile") reboot_required |= _config_string("config_logfile")
......
...@@ -70,7 +70,7 @@ try: ...@@ -70,7 +70,7 @@ try:
use_provider_user_id = True use_provider_user_id = True
if self.user_required and not u and not uid and not use_provider_user_id: 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") # raise ValueError("Cannot get OAuth token without an associated user")
return None return None
# check for user ID # check for user ID
if hasattr(self.model, "user_id") and uid: if hasattr(self.model, "user_id") and uid:
...@@ -95,7 +95,7 @@ try: ...@@ -95,7 +95,7 @@ try:
def set(self, blueprint, token, user=None, user_id=None): def set(self, blueprint, token, user=None, user_id=None):
uid = first([user_id, self.user_id, blueprint.config.get("user_id")]) uid = first([user_id, self.user_id, blueprint.config.get("user_id")])
u = first(_get_real_user(ref, self.anon_user) u = first(_get_real_user(ref, self.anon_user)
for ref in (user, self.user, blueprint.config.get("user"))) for ref in (user, self.user, blueprint.config.get("user")))
if self.user_required and not u and not uid: if self.user_required and not u and not uid:
raise ValueError("Cannot set OAuth token without an associated user") raise ValueError("Cannot set OAuth token without an associated user")
......
...@@ -36,7 +36,6 @@ from sqlalchemy.orm.exc import NoResultFound ...@@ -36,7 +36,6 @@ from sqlalchemy.orm.exc import NoResultFound
from . import constants, logger, config, app, ub from . import constants, logger, config, app, ub
from .web import login_required from .web import login_required
from .oauth import OAuthBackend from .oauth import OAuthBackend
# from .web import github_oauth_required
oauth_check = {} oauth_check = {}
...@@ -59,29 +58,29 @@ def oauth_required(f): ...@@ -59,29 +58,29 @@ def oauth_required(f):
return inner return inner
def register_oauth_blueprint(id, show_name): def register_oauth_blueprint(cid, show_name):
oauth_check[id] = show_name oauth_check[cid] = show_name
def register_user_with_oauth(user=None): def register_user_with_oauth(user=None):
all_oauth = {} all_oauth = {}
for oauth in oauth_check.keys(): for oauth_key in oauth_check.keys():
if str(oauth) + '_oauth_user_id' in session and session[str(oauth) + '_oauth_user_id'] != '': if str(oauth_key) + '_oauth_user_id' in session and session[str(oauth_key) + '_oauth_user_id'] != '':
all_oauth[oauth] = oauth_check[oauth] all_oauth[oauth_key] = oauth_check[oauth_key]
if len(all_oauth.keys()) == 0: if len(all_oauth.keys()) == 0:
return return
if user is None: if user is None:
flash(_(u"Register with %(provider)s", provider=", ".join(list(all_oauth.values()))), category="success") flash(_(u"Register with %(provider)s", provider=", ".join(list(all_oauth.values()))), category="success")
else: else:
for oauth in all_oauth.keys(): for oauth_key in all_oauth.keys():
# Find this OAuth token in the database, or create it # Find this OAuth token in the database, or create it
query = ub.session.query(ub.OAuth).filter_by( query = ub.session.query(ub.OAuth).filter_by(
provider=oauth, provider=oauth_key,
provider_user_id=session[str(oauth) + "_oauth_user_id"], provider_user_id=session[str(oauth_key) + "_oauth_user_id"],
) )
try: try:
oauth = query.one() oauth_key = query.one()
oauth.user_id = user.id oauth_key.user_id = user.id
except NoResultFound: except NoResultFound:
# no found, return error # no found, return error
return return
...@@ -93,39 +92,40 @@ def register_user_with_oauth(user=None): ...@@ -93,39 +92,40 @@ def register_user_with_oauth(user=None):
def logout_oauth_user(): def logout_oauth_user():
for oauth in oauth_check.keys(): for oauth_key in oauth_check.keys():
if str(oauth) + '_oauth_user_id' in session: if str(oauth_key) + '_oauth_user_id' in session:
session.pop(str(oauth) + '_oauth_user_id') session.pop(str(oauth_key) + '_oauth_user_id')
if ub.oauth_support: if ub.oauth_support:
oauthblueprints =[] oauthblueprints = []
if not ub.session.query(ub.OAuthProvider).count(): if not ub.session.query(ub.OAuthProvider).count():
oauth = ub.OAuthProvider() oauthProvider = ub.OAuthProvider()
oauth.provider_name = "github" oauthProvider.provider_name = "github"
oauth.active = False oauthProvider.active = False
ub.session.add(oauth) ub.session.add(oauthProvider)
ub.session.commit() ub.session.commit()
oauth = ub.OAuthProvider() oauthProvider = ub.OAuthProvider()
oauth.provider_name = "google" oauthProvider.provider_name = "google"
oauth.active = False oauthProvider.active = False
ub.session.add(oauth) ub.session.add(oauthProvider)
ub.session.commit() ub.session.commit()
oauth_ids = ub.session.query(ub.OAuthProvider).all() oauth_ids = ub.session.query(ub.OAuthProvider).all()
ele1=dict(provider_name='github', ele1 = dict(provider_name='github',
id=oauth_ids[0].id, id=oauth_ids[0].id,
active=oauth_ids[0].active, active=oauth_ids[0].active,
oauth_client_id=oauth_ids[0].oauth_client_id, oauth_client_id=oauth_ids[0].oauth_client_id,
scope=None, scope=None,
oauth_client_secret=oauth_ids[0].oauth_client_secret, oauth_client_secret=oauth_ids[0].oauth_client_secret,
obtain_link='https://github.com/settings/developers') obtain_link='https://github.com/settings/developers')
ele2=dict(provider_name='google', ele2 = dict(provider_name='google',
id=oauth_ids[1].id, id=oauth_ids[1].id,
active=oauth_ids[1].active, active=oauth_ids[1].active,
scope=["https://www.googleapis.com/auth/plus.me", "https://www.googleapis.com/auth/userinfo.email"], scope=["https://www.googleapis.com/auth/plus.me", "https://www.googleapis.com/auth/userinfo.email"],
oauth_client_id=oauth_ids[1].oauth_client_id, oauth_client_id=oauth_ids[1].oauth_client_id,
oauth_client_secret=oauth_ids[1].oauth_client_secret, oauth_client_secret=oauth_ids[1].oauth_client_secret,
obtain_link='https://github.com/settings/developers') obtain_link='https://github.com/settings/developers')
oauthblueprints.append(ele1) oauthblueprints.append(ele1)
oauthblueprints.append(ele2) oauthblueprints.append(ele2)
...@@ -138,9 +138,9 @@ if ub.oauth_support: ...@@ -138,9 +138,9 @@ if ub.oauth_support:
client_id=element['oauth_client_id'], client_id=element['oauth_client_id'],
client_secret=element['oauth_client_secret'], client_secret=element['oauth_client_secret'],
redirect_to="oauth."+element['provider_name']+"_login", redirect_to="oauth."+element['provider_name']+"_login",
scope = element['scope'] scope=element['scope']
) )
element['blueprint']=blueprint element['blueprint'] = blueprint
app.register_blueprint(blueprint, url_prefix="/login") app.register_blueprint(blueprint, url_prefix="/login")
element['blueprint'].backend = OAuthBackend(ub.OAuth, ub.session, str(element['id']), element['blueprint'].backend = OAuthBackend(ub.OAuth, ub.session, str(element['id']),
user=current_user, user_required=True) user=current_user, user_required=True)
...@@ -190,17 +190,17 @@ if ub.oauth_support: ...@@ -190,17 +190,17 @@ if ub.oauth_support:
provider_user_id=provider_user_id, provider_user_id=provider_user_id,
) )
try: try:
oauth = query.one() oauth_entry = query.one()
# update token # update token
oauth.token = token oauth_entry.token = token
except NoResultFound: except NoResultFound:
oauth = ub.OAuth( oauth_entry = ub.OAuth(
provider=provider_id, provider=provider_id,
provider_user_id=provider_user_id, provider_user_id=provider_user_id,
token=token, token=token,
) )
try: try:
ub.session.add(oauth) ub.session.add(oauth_entry)
ub.session.commit() ub.session.commit()
except Exception as e: except Exception as e:
log.exception(e) log.exception(e)
...@@ -216,25 +216,25 @@ if ub.oauth_support: ...@@ -216,25 +216,25 @@ if ub.oauth_support:
provider_user_id=provider_user_id, provider_user_id=provider_user_id,
) )
try: try:
oauth = query.one() oauth_entry = query.one()
# already bind with user, just login # already bind with user, just login
if oauth.user: if oauth_entry.user:
login_user(oauth.user) login_user(oauth_entry.user)
return redirect(url_for('web.index')) return redirect(url_for('web.index'))
else: else:
# bind to current user # bind to current user
if current_user and current_user.is_authenticated: if current_user and current_user.is_authenticated:
oauth.user = current_user oauth_entry.user = current_user
try: try:
ub.session.add(oauth) ub.session.add(oauth_entry)
ub.session.commit() ub.session.commit()
except Exception as e: except Exception as e:
log.exception(e) log.exception(e)
ub.session.rollback() ub.session.rollback()
return redirect(url_for('web.login')) return redirect(url_for('web.login'))
#if config.config_public_reg: # if config.config_public_reg:
# return redirect(url_for('web.register')) # return redirect(url_for('web.register'))
#else: # else:
# flash(_(u"Public registration is not enabled"), category="error") # flash(_(u"Public registration is not enabled"), category="error")
# return redirect(url_for(redirect_url)) # return redirect(url_for(redirect_url))
except NoResultFound: except NoResultFound:
...@@ -248,8 +248,8 @@ if ub.oauth_support: ...@@ -248,8 +248,8 @@ if ub.oauth_support:
) )
try: try:
oauths = query.all() oauths = query.all()
for oauth in oauths: for oauth_entry in oauths:
status.append(int(oauth.provider)) status.append(int(oauth_entry.provider))
return status return status
except NoResultFound: except NoResultFound:
return None return None
...@@ -263,11 +263,11 @@ if ub.oauth_support: ...@@ -263,11 +263,11 @@ if ub.oauth_support:
user_id=current_user.id, user_id=current_user.id,
) )
try: try:
oauth = query.one() oauth_entry = query.one()
if current_user and current_user.is_authenticated: if current_user and current_user.is_authenticated:
oauth.user = current_user oauth_entry.user = current_user
try: try:
ub.session.delete(oauth) ub.session.delete(oauth_entry)
ub.session.commit() ub.session.commit()
logout_oauth_user() logout_oauth_user()
flash(_(u"Unlink to %(oauth)s success.", oauth=oauth_check[provider]), category="success") flash(_(u"Unlink to %(oauth)s success.", oauth=oauth_check[provider]), category="success")
...@@ -292,7 +292,7 @@ if ub.oauth_support: ...@@ -292,7 +292,7 @@ if ub.oauth_support:
error=error, error=error,
description=error_description, description=error_description,
uri=error_uri, uri=error_uri,
) # ToDo: Translate ) # ToDo: Translate
flash(msg, category="error") flash(msg, category="error")
...@@ -338,7 +338,7 @@ if ub.oauth_support: ...@@ -338,7 +338,7 @@ if ub.oauth_support:
error=error, error=error,
description=error_description, description=error_description,
uri=error_uri, uri=error_uri,
) # ToDo: Translate ) # ToDo: Translate
flash(msg, category="error") flash(msg, category="error")
......
...@@ -25,19 +25,21 @@ ...@@ -25,19 +25,21 @@
<a href="{{url_for('web.remote_login')}}" class="pull-right">{{_('Log in with Magic Link')}}</a> <a href="{{url_for('web.remote_login')}}" class="pull-right">{{_('Log in with Magic Link')}}</a>
{% endif %} {% endif %}
{% if config.config_login_type == 2 %} {% if config.config_login_type == 2 %}
<a href="{{url_for('oauth.github_login')}}" class="pull-right"> {% if 1 in oauth_check %}
<svg height="32" class="octicon octicon-mark-github" viewBox="0 0 16 16" version="1.1" width="32" aria-hidden="true"> <a href="{{url_for('oauth.github_login')}}" class="pull-right github">
<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> <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> </svg>
</a> </a>
{% endif %} {% endif %}
{% if config.config_login_type == 3 %} {% if 2 in oauth_check %}
<a href="{{url_for('oauth.google_login')}}" class="pull-right"> <a href="{{url_for('oauth.google_login')}}" class="pull-right google">
<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" <svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
width="40" height="40" width="40" height="40"
viewBox="0 3 48 49" 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> 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> </a>
{% endif %}
{% endif %} {% endif %}
</form> </form>
</div> </div>
......
...@@ -35,10 +35,12 @@ ...@@ -35,10 +35,12 @@
</thead> </thead>
<tbody> <tbody>
{% for library,version in versions.items() %} {% for library,version in versions.items() %}
{% if version %}
<tr> <tr>
<th>{{library}}</th> <th>{{library}}</th>
<td>{{_(version)}}</td> <td>{{_(version)}}</td>
</tr> </tr>
{% endif %}
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
......
...@@ -161,7 +161,7 @@ ...@@ -161,7 +161,7 @@
</div> </div>
<div class="modal-body">...</div> <div class="modal-body">...</div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">{{_('Close')}}</button> <button type="button" id="kobo_close" class="btn btn-default" data-dismiss="modal">{{_('Close')}}</button>
</div> </div>
</div> </div>
</div> </div>
......
This diff is collapsed.
...@@ -18,7 +18,7 @@ python-Levenshtein>=0.12.0,<0.13.0 ...@@ -18,7 +18,7 @@ python-Levenshtein>=0.12.0,<0.13.0
# ldap login # ldap login
python_ldap>=3.0.0,<3.3.0 python_ldap>=3.0.0,<3.3.0
flask-simpleldap>1.3.0,<1.5.0 flask-simpleldap>=1.4.0,<1.5.0
#oauth #oauth
flask-dance>=1.4.0,<3.1.0 flask-dance>=1.4.0,<3.1.0
......
This diff is collapsed.
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