Skip to content

Commit c45af27

Browse files
committed
reconcile_access refactor
1 parent c9cd02b commit c45af27

File tree

3 files changed

+154
-107
lines changed

3 files changed

+154
-107
lines changed

endorsement/exceptions.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,33 @@ class SharedDriveRecordNotFound(Exception):
5757

5858
class ITBillSubscriptionNotFound(Exception):
5959
pass
60+
61+
62+
class NoAccessRecordException(Exception):
63+
pass
64+
65+
66+
class NullDelegateException(Exception):
67+
pass
68+
69+
70+
class AccessRecordException(Exception):
71+
def __init__(self, *args, **kwargs):
72+
self.record = kwargs.pop('record', None)
73+
super().__init__(*args, **kwargs)
74+
75+
76+
class DeletedAccessRecordException(AccessRecordException):
77+
pass
78+
79+
80+
class TooManyRightsException(AccessRecordException):
81+
pass
82+
83+
84+
class EmptyDelegateRightsException(AccessRecordException):
85+
pass
86+
87+
88+
class DelegateRightMismatchException(AccessRecordException):
89+
pass

endorsement/reconcile_access.py

Lines changed: 123 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -5,126 +5,148 @@
55
from endorsement.dao.access import (
66
get_accessee_model, store_access_record, set_delegate)
77
from endorsement.dao.office import get_office_accessor
8-
from endorsement.exceptions import UnrecognizedUWNetid, UnrecognizedGroupID
9-
from uw_msca.delegate import get_all_delegates
8+
from endorsement.exceptions import (
9+
UnrecognizedUWNetid, UnrecognizedGroupID, NoAccessRecordException,
10+
NullDelegateException, AccessRecordException, DeletedAccessRecordException,
11+
TooManyRightsException, EmptyDelegateRightsException,
12+
DelegateRightMismatchException)
13+
from uw_msca.delegate import get_delegates, get_all_delegates
1014
import json
1115
import csv
1216
import logging
1317

1418

1519
logger = logging.getLogger(__name__)
1620
logger.setLevel(logging.INFO)
21+
MISSING_DELEGATES_THRESHOLD = 1024
1722

1823

1924
def reconcile_access(commit_changes=False):
2025
delegates = get_all_delegates()[1:]
2126
delegate_count = len(delegates)
2227
# make sure an empty response (likely an MSCA error condition) doesn't
2328
# cause all delegations to be deleted
24-
if delegate_count < 1024:
29+
if delegate_count < MISSING_DELEGATES_THRESHOLD:
2530
logger.error(
2631
"Possible malformed delegates response: {}".format(delegates))
2732
return
2833

29-
logger.info("Reconcile: {} delegates reported".format(delegate_count))
30-
31-
accessee_mailboxes = list(AccessRecord.objects.filter(
32-
is_deleted__isnull=True).values(
33-
'accessee__netid').distinct().values_list(
34-
'accessee__netid', flat=True))
34+
# all active record ids
35+
record_ids = list(AccessRecord.objects.filter(
36+
is_deleted__isnull=True).values_list('id', flat=True))
3537

3638
for row in csv.reader(delegates, delimiter=","):
3739
if len(row) != 2:
38-
logger.error("Reconcile: malformed row: {}".format(row))
40+
logger.error(f"Reconcile: malformed row: {row}")
3941
continue
4042

4143
netid = strip_domain(row[0])
4244
accessee = get_accessee_model(netid)
43-
records = list(
44-
AccessRecord.objects.get_access_for_accessee(accessee))
45-
46-
try:
47-
accessee_mailboxes.remove(netid)
48-
except ValueError:
49-
pass
50-
51-
for delegate, rights in get_delegates(row[1]).items():
52-
# loop thru office mailbox delegates
53-
# 1) catch dupes and record them
54-
# 2) add records for delegations without one
55-
# 3) delete records without a matching delegation
56-
if not delegate or delegate.lower() == 'null':
45+
46+
for delegate, rights in get_delegations(row[1]).items():
47+
try:
48+
record = reconcile_delegation(accessee, delegate, rights)
49+
clear_record_id(record_ids, record.id)
50+
except NullDelegateException:
5751
logger.info(
58-
f"mailbox {netid} with null delegate has rights: {rights}")
59-
continue
52+
f"NULL DELEGATE: mailbox {netid} delegate null "
53+
f"with rights: {rights}")
54+
except NoAccessRecordException:
55+
logger.info(f"NO ACCESS RECORD FOR: mailbox {netid} "
56+
f"delegate {delegate} rights: {rights}")
57+
if commit_changes:
58+
new_access_record(accessee, delegate, right_record)
59+
except DeletedAccessRecordException as ex:
60+
record = None
61+
# try "live" query since report may have outdated data
62+
for d in get_delegates(netid):
63+
if d.delegate == delegate:
64+
record = ex.record
65+
break
6066

61-
record, i = get_accessor_record(records, delegate)
62-
if len(rights) > 1:
6367
if record:
64-
if commit_changes:
65-
# stash existing access record
66-
revoke_record(record)
67-
68-
records.remove(record)
68+
logger.info(f"DELETED ACCESS RECORD: "
69+
f"mailbox {netid} delegate {delegate} "
70+
f"right: {record.access_right.name} on "
71+
f"{record.datetime_expired}")
6972

73+
if commit_changes:
74+
right = next(iter(rights))
75+
right_record = get_access_right(right)
76+
assign_access_right(record, right_record)
77+
except EmptyDelegateRightsException as ex:
78+
record = ex.record
79+
logger.info(f"NO RIGHTS FOR DELEGATION: "
80+
f"mailbox {netid} delegate {delegate} BUT "
81+
f"record has right: {record.access_right.name}")
82+
clear_record_id(record_ids, record.id)
83+
except TooManyRightsException as ex:
84+
logger.info(
85+
f"CONFLICT: mailbox {netid} delegate {delegate} "
86+
f"rights: {rights}")
87+
record = ex.record
7088
if commit_changes:
89+
revoke_record(record)
7190
save_conflict_record(accessee, record, delegate, rights)
72-
else:
73-
logger.info(
74-
f"CONFLICT: mailbox {netid} delegate {delegate} "
75-
f"rights: {rights}")
7691

77-
elif len(rights) == 1:
92+
clear_record_id(record_ids, record.id)
93+
except DelegateRightMismatchException as ex:
94+
record = ex.record
7895
right = next(iter(rights))
79-
right_record = get_access_right(right)
80-
if record:
81-
if record.access_right.name != right:
82-
if commit_changes:
83-
assign_access_right(record, right_record)
84-
else:
85-
logger.info(
86-
f"CHANGED mailbox {netid} delegate {delegate}"
87-
f" ({record.access_right.name}) to {right}")
88-
# else delegate right and record match
89-
90-
records.remove(record)
91-
else:
92-
# create record for unrecognized delegate right
93-
if commit_changes:
94-
new_access_record(accessee, delegate, right_record)
95-
else:
96-
logger.info(
97-
"MISSING ACCESS RECORD: "
98-
f"mailbox {accessee.netid} "
99-
f"delegate {delegate} ({right_record.name})")
100-
else:
101-
logger.info(f"EMPTY RIGHTS: mailbox {netid} "
102-
f"empty rights for {delegate}")
103-
104-
for record in records:
105-
# delegations that were reported, but for which we have no
106-
# access record
107-
if commit_changes:
108-
assign_delegation(accessee, record)
109-
else:
110-
logger.info("MISSING DELEGATION: "
111-
f"mailbox {record.accessee.netid} "
112-
f"delegation {record.accessor.name} "
113-
f"({record.access_right.name}) on "
114-
f"{record.datetime_granted}")
115-
116-
for mailbox in accessee_mailboxes:
117-
# access records for which no delegation was reported
118-
accessee = get_accessee_model(mailbox)
119-
for record in AccessRecord.objects.get_access_for_accessee(accessee):
120-
if commit_changes:
121-
assign_delegation(accessee, record)
122-
else:
123-
logger.info(f"MISSING DELEGATION: mailbox {accessee.netid} "
124-
f"delegation {record.accessor.name} "
125-
f"({record.access_right.name})"
126-
f" on {record.datetime_granted} not "
127-
"assigned in Outlook")
96+
97+
logger.info(
98+
f"DELEGATION CHANGE: mailbox {netid} delegate {delegate}"
99+
f" ({record.access_right.name}) to {right}")
100+
101+
if commit_changes:
102+
right_record = get_access_right(right)
103+
assign_access_right(record, right_record)
104+
105+
clear_record_id(record_ids, record.id)
106+
107+
# access records for which no delegation was reported
108+
for record in AccessRecord.objects.filter(id__in=record_ids):
109+
if commit_changes:
110+
assign_delegation(accessee, record)
111+
else:
112+
logger.info(f"MISSING DELEGATION: mailbox {accessee.netid} "
113+
f"delegate {record.accessor.name} "
114+
f"({record.access_right.name})"
115+
f" on {record.datetime_granted} not "
116+
"assigned in Outlook")
117+
118+
119+
def clear_record_id(record_ids, record_id):
120+
try:
121+
record_ids.remove(record_id)
122+
except ValueError:
123+
pass
124+
125+
126+
def reconcile_delegation(accessee, delegate, rights):
127+
if not delegate or delegate.lower() == 'null':
128+
raise NullDelegateException()
129+
130+
try:
131+
record = AccessRecord.objects.get(
132+
accessee=accessee, accessor__name=delegate)
133+
except AccessRecord.DoesNotExist:
134+
raise NoAccessRecordException()
135+
136+
if len(rights) > 1:
137+
raise TooManyRightsException(record=record)
138+
139+
if record.is_deleted:
140+
raise DeletedAccessRecordException(record=record)
141+
142+
if len(rights) < 1:
143+
raise EmptyDelegateRightsException(record=record)
144+
145+
right = next(iter(rights))
146+
if record.access_right.name != right:
147+
raise DelegateRightMismatchException(record=record)
148+
149+
return record
128150

129151

130152
def get_access_right(right):
@@ -134,7 +156,7 @@ def get_access_right(right):
134156

135157
def new_access_record(accessee, delegate, right):
136158
logger.info(
137-
f"CREATE mailbox {accessee.netid} "
159+
f"CREATE RECORD: mailbox {accessee.netid} "
138160
f"delegate {delegate} ({right.name})")
139161

140162
logger.info("FAILSAFE HIT")
@@ -153,20 +175,21 @@ def new_access_record(accessee, delegate, right):
153175

154176

155177
def assign_delegation(accessee, record):
156-
157178
logger.info('commit set delegate ')
179+
180+
logger.info("FAILSAFE HIT")
158181
return
159182

160183
try:
161184
set_delegate(accessee.netid, record.accessor.name,
162185
record.access_right.name)
163-
logger.info(f"mailbox {accessee.netid} delegation "
186+
logger.info(f"SET DELGATION: mailbox {accessee.netid} delegation "
164187
f"{record.accessor.name} "
165-
f"({record.access_right.name}) assigned")
188+
f"({record.access_right.name})")
166189
except Exception as ex:
167-
logger.error("set delegate {record.accessor.name} "
190+
logger.error("SET DELEGATE FAILED: {record.accessor.name} "
168191
f"({record.access_right.name}) on "
169-
f"{accessee.netid} failed: {ex}")
192+
f"{accessee.netid} reason: {ex}")
170193

171194

172195
def revoke_record(record):
@@ -209,22 +232,16 @@ def save_conflict_record(accessee, record, delegate, rights):
209232
conflict.save()
210233

211234

212-
def get_accessor_record(records, delegate):
213-
for i, record in enumerate(records):
214-
if record.accessor.name == delegate:
215-
return record, i
216-
217-
return None, -1
218-
219-
220-
def get_delegates(raw):
235+
def get_delegations(raw):
221236
delegates = {}
222237
cooked = json.loads(raw)
223238
for right in [cooked] if isinstance(cooked, dict) else cooked:
224-
try:
225-
delegates[right["User"]].append(right['AccessRights'])
226-
except KeyError:
227-
delegates[right["User"]] = [right['AccessRights']]
239+
user = right["User"]
240+
if user and user.lower() != 'null':
241+
try:
242+
delegates[user].append(right['AccessRights'])
243+
except KeyError:
244+
delegates[user] = [right['AccessRights']]
228245

229246
return delegates
230247

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
'UW-RestClients-UWNetID~=1.1.2',
4646
'UW-RestClients-Django-Utils~=2.1.5',
4747
'UW-RestClients-ITBill~=0.1',
48-
'UW-RestClients-MSCA~=0.1.2',
48+
'UW-RestClients-MSCA~=0.1.3',
4949
'Django-Safe-EmailBackend~=1.2',
5050
'UW-Django-SAML2>=1.3.8,<2.0',
5151
'django-pyscss',

0 commit comments

Comments
 (0)