Files
postgres-web/pgweb/account/migrations/0006_communityauth_sync.py
Magnus Hagander c1fb5de080 Implement synchronization for community authentication
This adds the concept of an apiurl to each site that uses community
authentication, that the main website server can make calls to and send
updates. This URL will receive POSTs from the main website when a user
account that has been used on this site gets updated, and can then
optionally update it's local entries with it (the django plugin sample
is updated to handle this fully).

Updates are only sent for users that have a history of having logged
into the specific site -- this way we avoid braodcasting user
information to sites requiring specific constent that the user hasn't
given, and also decreases the amount of updates that have to be sent.

Updates are queued by the system in a table and using listen/notify a
daemon that's running picks up what needs to be updated and posts it to
the endpoints. If this daemon is not running, obviously nothing gets
sent.

Updates are tracked using triggers in the database which push
information into this queue.
2020-08-11 11:33:46 +02:00

125 lines
5.4 KiB
Python

# -*- coding: utf-8 -*-
# Generated by Django 1.11.27 on 2020-08-06 13:36
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('account', '0005_secondaryemail'),
('core', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='communityauthsite',
name='apiurl',
field=models.URLField(max_length=200, null=False, blank=True),
),
migrations.AddField(
model_name='communityauthsite',
name='push_changes',
field=models.BooleanField(default=False, help_text='Supports receiving http POSTs with changes to accounts'),
),
migrations.AddField(
model_name='communityauthsite',
name='push_ssh',
field=models.BooleanField(default=False, help_text='Wants to receive SSH keys in push changes'),
),
migrations.RunSQL(
"""CREATE TABLE account_communityauthchangelog (
user_id int NOT NULL REFERENCES auth_user(id) DEFERRABLE INITIALLY DEFERRED,
site_id int NOT NULL REFERENCES account_communityauthsite (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
changedat timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT account_communityauthchangelog_pkey PRIMARY KEY (user_id, site_id)
)""",
"""DROP TABLE account_communityauthchangelog""",
),
# When a user entry is changed, propagate it to any community auth site that has push enabled, and that
# the user has at some point logged in to. We do this through a trigger on auth_user, to make sure we
# definitely catch all changes.
migrations.RunSQL(
"""CREATE FUNCTION account_cauth_changetrack () RETURNS trigger AS $$
BEGIN
IF NEW.username != OLD.username THEN
RAISE EXCEPTION 'Usernames cannot be changed';
END IF;
IF NEW.first_name != OLD.first_name OR NEW.last_name != OLD.last_name OR NEW.email != OLD.email THEN
INSERT INTO account_communityauthchangelog (user_id, site_id, changedat)
SELECT NEW.id, s.id, CURRENT_TIMESTAMP
FROM account_communityauthsite s
INNER JOIN account_communityauthlastlogin ll ON ll.site_id=s.id
WHERE s.push_changes AND ll.user_id=NEW.id
ON CONFLICT (user_id, site_id) DO UPDATE SET changedat=greatest(account_communityauthchangelog.changedat, CURRENT_TIMESTAMP);
NOTIFY communityauth_changetrack;
END IF;
RETURN NEW;
END;
$$ language 'plpgsql'""",
"""DROP FUNCTION account_cauth_changetrack""",
),
# We specifically don't use "UPDATE OF" to find columns because then we create a dependency on columns in
# auth_user, which is owned by django, and may block migrations in that app. So we make the check at runtime.
migrations.RunSQL(
"""CREATE TRIGGER account_cauth_changetrack_trg
AFTER UPDATE ON auth_user
FOR EACH ROW EXECUTE FUNCTION account_cauth_changetrack()""",
"""DROP TRIGGER account_cauth_changetrack_trg ON auth_user""",
),
# We also need to track when secondary email addresses are added/removed (if they are confirmed)
# We don't have to track INSERTs as they are always unconfirmed, but we do need to track deletes here.
migrations.RunSQL(
"""CREATE FUNCTION account_secondaryemail_changetrack () RETURNS trigger AS $$
BEGIN
INSERT INTO account_communityauthchangelog (user_id, site_id, changedat)
SELECT NEW.user_id, s.id, CURRENT_TIMESTAMP
FROM account_communityauthsite s
INNER JOIN account_communityauthlastlogin ll ON ll.site_id=s.id
WHERE s.push_changes AND ll.user_id=NEW.user_id
ON CONFLICT (user_id, site_id) DO UPDATE SET changedat=greatest(account_communityauthchangelog.changedat, CURRENT_TIMESTAMP);
NOTIFY communityauth_changetrack;
RETURN NEW;
END;
$$ language 'plpgsql'""",
"""DROP FUNCTION account_secondaryemail_changetrack""",
),
migrations.RunSQL(
"""CREATE TRIGGER account_secondaryemail_changetrack_trg
AFTER DELETE OR UPDATE ON account_secondaryemail
FOR EACH ROW EXECUTE FUNCTION account_secondaryemail_changetrack()""",
"""DROP TRIGGER account_Secondaryemail_changetrack_trg""",
),
migrations.RunSQL(
"""CREATE FUNCTION account_profile_changetrack () RETURNS trigger AS $$
BEGIN
IF NEW.sshkey != OLD.sshkey THEN
INSERT INTO account_communityauthchangelog (user_id, site_id, changedat)
SELECT NEW.user_id, s.id, CURRENT_TIMESTAMP
FROM account_communityauthsite s
INNER JOIN account_communityauthlastlogin ll ON ll.site_id=s.id
WHERE s.push_changes AND s.push_ssh AND ll.user_id=NEW.user_id
ON CONFLICT (user_id, site_id) DO UPDATE SET changedat=greatest(account_communityauthchangelog.changedat, CURRENT_TIMESTAMP);
NOTIFY communityauth_changetrack;
END IF;
RETURN NEW;
END;
$$ language 'plpgsql'""",
"""DROP FUNCTION account_secondaryemail_changetrack""",
),
migrations.RunSQL(
"""CREATE TRIGGER account_profile_changetrack_trg
AFTER DELETE OR UPDATE ON core_userprofile
FOR EACH ROW EXECUTE FUNCTION account_profile_changetrack()""",
"""DROP TRIGGER account_profile_changetrack_trg""",
),
]