From 6948ddca49aa70a1977d31a5a902f0cf491f7ad6 Mon Sep 17 00:00:00 2001 From: Julian Rother <julian@cccv.de> Date: Fri, 25 Feb 2022 15:06:11 +0100 Subject: [PATCH] Fix "Migrate OAuth2 and API clients to database" (again) The original change completely broke single logout support. The migration now uses the correct hashing algorithm (unsalted SHA512 instead of salted SHA512) for OAuth2/API secrets/passwords. --- ...9d3f7dac9db_move_api_and_oauth2_clients_to_db.py | 13 +++++-------- uffd/oauth2/models.py | 5 +++++ uffd/oauth2/templates/oauth2/logout.html | 9 +++------ uffd/oauth2/views.py | 2 +- 4 files changed, 14 insertions(+), 15 deletions(-) diff --git a/uffd/migrations/versions/b9d3f7dac9db_move_api_and_oauth2_clients_to_db.py b/uffd/migrations/versions/b9d3f7dac9db_move_api_and_oauth2_clients_to_db.py index 5cfaf114..34c1a4fd 100644 --- a/uffd/migrations/versions/b9d3f7dac9db_move_api_and_oauth2_clients_to_db.py +++ b/uffd/migrations/versions/b9d3f7dac9db_move_api_and_oauth2_clients_to_db.py @@ -19,12 +19,9 @@ down_revision = '09d2edcaf0cc' branch_labels = None depends_on = None -def hash_ssha512(password): - salt = secrets.token_bytes(8) - ctx = hashlib.new('sha512') - ctx.update(password.encode()) - ctx.update(salt) - return '{ssha512}' + base64.b64encode(ctx.digest() + salt).decode() +def hash_sha512(password): + ctx = hashlib.new('sha512', password.encode()) + return '{sha512}' + base64.b64encode(ctx.digest()).decode() def upgrade(): used_service_names = set() @@ -129,7 +126,7 @@ def upgrade(): sa.UniqueConstraint('auth_username', name=op.f('uq_api_client_auth_username')) ) for service_name, auth_username, auth_password, perm_users, perm_checkpassword, perm_mail_aliases in api_clients: - op.execute(api_client_table.insert().values(service_id=sa.select([service_table.c.id]).where(service_table.c.name==service_name).as_scalar(), auth_username=auth_username, auth_password=hash_ssha512(auth_password), perm_users=perm_users, perm_checkpassword=perm_checkpassword, perm_mail_aliases=perm_mail_aliases)) + op.execute(api_client_table.insert().values(service_id=sa.select([service_table.c.id]).where(service_table.c.name==service_name).as_scalar(), auth_username=auth_username, auth_password=hash_sha512(auth_password), perm_users=perm_users, perm_checkpassword=perm_checkpassword, perm_mail_aliases=perm_mail_aliases)) oauth2client_table = op.create_table('oauth2client', sa.Column('db_id', sa.Integer(), autoincrement=True, nullable=False), @@ -156,7 +153,7 @@ def upgrade(): sa.PrimaryKeyConstraint('id', name=op.f('pk_oauth2redirect_uri')) ) for service_name, client_id, client_secret, redirect_uris, logout_uris in oauth2_clients: - op.execute(oauth2client_table.insert().values(service_id=sa.select([service_table.c.id]).where(service_table.c.name==service_name).as_scalar(), client_id=client_id, client_secret=hash_ssha512(client_secret))) + op.execute(oauth2client_table.insert().values(service_id=sa.select([service_table.c.id]).where(service_table.c.name==service_name).as_scalar(), client_id=client_id, client_secret=hash_sha512(client_secret))) for method, uri, in logout_uris: op.execute(oauth2logout_uri_table.insert().values(client_db_id=sa.select([oauth2client_table.c.db_id]).where(oauth2client_table.c.client_id==client_id).as_scalar(), method=method, uri=uri)) for uri in redirect_uris: diff --git a/uffd/oauth2/models.py b/uffd/oauth2/models.py index 4621f114..6ed91097 100644 --- a/uffd/oauth2/models.py +++ b/uffd/oauth2/models.py @@ -1,4 +1,5 @@ import datetime +import json from sqlalchemy import Column, Integer, String, DateTime, Text, ForeignKey from sqlalchemy.orm import relationship @@ -41,6 +42,10 @@ class OAuth2Client(db.Model): def access_allowed(self, user): return self.service.has_access(user) + @property + def logout_uris_json(self): + return json.dumps([[item.method, item.uri] for item in self.logout_uris]) + class OAuth2RedirectURI(db.Model): __tablename__ = 'oauth2redirect_uri' id = Column(Integer, primary_key=True, autoincrement=True) diff --git a/uffd/oauth2/templates/oauth2/logout.html b/uffd/oauth2/templates/oauth2/logout.html index e45a9268..02363882 100644 --- a/uffd/oauth2/templates/oauth2/logout.html +++ b/uffd/oauth2/templates/oauth2/logout.html @@ -11,9 +11,9 @@ </noscript> <p>{{_('While you successfully logged out of the Single-Sign-On service, you may still be logged in on these services:')}}</p> <ul> - {% for client in clients if client.logout_urls %} - <li class="client" data-urls='{{ client.logout_urls|tojson }}'> - {{ client.client_id }} + {% for client in clients if client.logout_uris %} + <li class="client" data-urls='{{ client.logout_uris_json }}'> + {{ client.service.name }} <span class="status-active spinner-border spinner-border-sm d-none" role="status" aria-hidden="true"></span> <i class="status-success fas fa-check d-none"></i> <i class="status-failed fas fa-exclamation d-none"></i> @@ -53,7 +53,6 @@ function logout_services() { }); }); p = p.then(function () { - console.log('done', elem); elem.find('.status-active').addClass('d-none'); elem.find('.status-success').removeClass('d-none'); elem.removeClass('client'); @@ -61,13 +60,11 @@ function logout_services() { .catch(function (err) { elem.find('.status-active').addClass('d-none'); elem.find('.status-failed').removeClass('d-none'); - console.log(err); throw err; }); all_promises.push(p); }); Promise.allSettled(all_promises).then(function (results) { - console.log(results); for (result of results) { if (result.status == 'rejected') throw result.reason; diff --git a/uffd/oauth2/views.py b/uffd/oauth2/views.py index cfb089b3..d13fd42d 100644 --- a/uffd/oauth2/views.py +++ b/uffd/oauth2/views.py @@ -249,5 +249,5 @@ def logout(): if not request.values.get('client_ids'): return secure_local_redirect(request.values.get('ref', '/')) client_ids = request.values['client_ids'].split(',') - clients = [OAuth2Client.query.filter_by(name=client_id).one() for client_id in client_ids] + clients = [OAuth2Client.query.filter_by(client_id=client_id).one() for client_id in client_ids] return render_template('oauth2/logout.html', clients=clients) -- GitLab