Projet

Général

Profil

0001-users-add-cronjob-to-delete-users-24430.patch

Benjamin Dauvergne, 31 août 2022 12:18

Télécharger (6,95 ko)

Voir les différences:

Subject: [PATCH] users: add cronjob to delete users (#24430)

 tests/test_users.py | 83 ++++++++++++++++++++++++++++++++++++++++++++-
 wcs/publisher.py    |  6 ++++
 wcs/sql.py          | 53 +++++++++++++++++++++++++++++
 3 files changed, 141 insertions(+), 1 deletion(-)
tests/test_users.py
1
import datetime
1 2
import shutil
3
import time
2 4

  
3 5
import pytest
4
from quixote import cleanup
6
from quixote import cleanup, get_publisher
5 7

  
6 8
from wcs import fields
7 9
from wcs.variables import LazyUser
......
92 94
    with pytest.raises(AttributeError):
93 95
        # noqa pylint: disable=pointless-statement
94 96
        user.xxx
97

  
98

  
99
def test_clean_deleted_users():
100
    from wcs.carddef import CardDef
101
    from wcs.formdef import FormDef
102
    from wcs.workflows import Evolution
103

  
104
    User = pub.user_class
105

  
106
    User.wipe()
107
    FormDef.wipe()
108
    CardDef.wipe()
109

  
110
    formdef = FormDef()
111
    formdef.name = 'foobar'
112
    formdef.url_name = 'foobar'
113
    formdef.fields = []
114
    formdef.store()
115
    data_class = formdef.data_class()
116

  
117
    carddef = CardDef()
118
    carddef.name = 'barfoo'
119
    carddef.url_name = 'barfoo'
120
    carddef.fields = []
121
    carddef.store()
122
    card_data_class = carddef.data_class()
123

  
124
    user1 = User()
125
    user1.name = 'Pierre'
126
    user1.deleted_timestamp = datetime.datetime.now()
127
    user1.store()
128

  
129
    user2 = User()
130
    user2.name = 'Jean'
131
    user2.deleted_timestamp = datetime.datetime.now()
132
    user2.store()
133

  
134
    user3 = User()
135
    user3.name = 'Michel'
136
    user3.deleted_timestamp = datetime.datetime.now()
137
    user3.store()
138

  
139
    user4 = User()
140
    user4.name = 'Martin'
141
    user4.deleted_timestamp = datetime.datetime.now()
142
    user4.store()
143

  
144
    user5 = User()
145
    user5.name = 'Alain'
146
    user5.deleted_timestamp = datetime.datetime.now()
147
    user5.store()
148

  
149
    formdata1 = data_class()
150
    formdata1.user_id = user1.id
151
    evo = Evolution()
152
    evo.time = time.localtime()
153
    evo.who = user4.id
154
    formdata1.evolution = [evo]
155
    formdata1.workflow_roles = {'_received': '_user:%s' % user5.id}
156
    formdata1.store()
157

  
158
    carddata1 = card_data_class()
159
    carddata1.user_id = user3.id
160
    carddata1.store()
161

  
162
    assert User.count() == 5
163

  
164
    get_publisher().clean_deleted_users()
165

  
166
    assert User.count() == 4
167

  
168
    assert {user.name for user in User.select()} == {'Pierre', 'Michel', 'Martin', 'Alain'}
169

  
170
    data_class.wipe()
171
    card_data_class.wipe()
172

  
173
    get_publisher().clean_deleted_users()
174

  
175
    assert User.count() == 0
wcs/publisher.py
123 123
        cls.register_cronjob(
124 124
            CronJob(cls.update_deprecations_report, name='update_deprecations_report', hours=[2], minutes=[0])
125 125
        )
126
        # once a day delete users without any formdata
127
        cls.register_cronjob(CronJob(cls.clean_deleted_users, name='clean_deleted_users', minutes=[0]))
126 128
        # other jobs
127 129
        data_sources.register_cronjob()
128 130
        formdef.register_cronjobs()
......
511 513
            return value_.get_value()
512 514
        return value_
513 515

  
516
    def clean_deleted_users(self):
517
        for user_id in self.user_class.get_to_delete_ids():
518
            self.user_class.remove_object(user_id)
519

  
514 520

  
515 521
set_publisher_class(WcsPublisher)
516 522
WcsPublisher.register_extra_dir(os.path.join(os.path.dirname(__file__), 'extra'))
wcs/sql.py
3296 3296

  
3297 3297
        return objects
3298 3298

  
3299
    @classmethod
3300
    def get_to_delete_ids(cls):
3301
        '''Retrieve ids of users which are deleted on the IdP and are no more referenced by any form or card.'''
3302
        from wcs.carddef import CardDef
3303
        from wcs.formdef import FormDef
3304

  
3305
        # fetch marked as deleted users
3306
        conn, cur = get_connection_and_cursor()
3307
        sql_statement = 'SELECT users.id FROM users WHERE users.deleted_timestamp IS NOT NULL'
3308
        cur.execute(sql_statement)
3309
        to_delete_ids = {user_id for user_id, in cur.fetchall()}
3310
        conn.commit()
3311

  
3312
        # iteratively reduce to_delete_ids by retaining only unreferenced users
3313
        for carddef in CardDef.select() + FormDef.select():
3314
            # keep only user.id not present in form/card_data.user_id
3315
            data_class = carddef.data_class()
3316
            sql_statement = '''SELECT users.id
3317
                                 FROM users LEFT JOIN %(table)s ON users.id = CAST(%(table)s.user_id AS INTEGER)
3318
                                WHERE users.deleted_timestamp IS NOT NULL
3319
                                AND %(table)s.id IS NULL
3320
                                AND users.id IN %%(to_delete_ids)s
3321
                                ''' % {
3322
                'table': data_class._table_name
3323
            }
3324
            cur.execute(sql_statement, {'to_delete_ids': tuple(to_delete_ids)})
3325
            to_delete_ids = {user_id for user_id, in cur.fetchall()}
3326
            # keep only user.id not present in form/card_evolutions.who columns
3327
            sql_statement = '''SELECT users.id
3328
                                 FROM users LEFT JOIN %(table)s ON users.id = CAST(%(table)s.who AS INTEGER)
3329
                                WHERE users.deleted_timestamp IS NOT NULL
3330
                                AND users.id IN %%(to_delete_ids)s
3331
                                AND %(table)s.id IS NULL''' % {
3332
                'table': '%s_evolutions' % data_class._table_name,
3333
            }
3334
            cur.execute(sql_statement, {'to_delete_ids': tuple(to_delete_ids)})
3335
            to_delete_ids = {user_id for user_id, in cur.fetchall()}
3336
            # keep only user.id not present in form/card_data.workflow_roles_array
3337
            sql_statement = '''SELECT users.id
3338
                                 FROM %(table)s AS data
3339
                                 JOIN UNNEST(data.workflow_roles_array) AS workflow_role ON 'true'
3340
                                 RIGHT JOIN users ON workflow_role.workflow_role = '_user:' || users.id
3341
                                 WHERE users.id IN %%(to_delete_ids)s
3342
                                 AND users.deleted_timestamp IS NOT NULL AND data.id IS NULL''' % {
3343
                'table': data_class._table_name,
3344
            }
3345
            cur.execute(sql_statement, {'to_delete_ids': tuple(to_delete_ids)})
3346
            to_delete_ids = {user_id for user_id, in cur.fetchall()}
3347
            conn.commit()
3348
        cur.close()
3349

  
3350
        return to_delete_ids
3351

  
3299 3352

  
3300 3353
class Role(SqlMixin, wcs.roles.Role):
3301 3354
    _table_name = 'roles'
3302
-