Skip to content

Commit ab0a8b2

Browse files
committed
handy restore_access_rights script
1 parent ccc0777 commit ab0a8b2

File tree

1 file changed

+172
-0
lines changed

1 file changed

+172
-0
lines changed
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
# Copyright 2025 UW-IT, University of Washington
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
from django.core.management.base import BaseCommand
5+
from endorsement.models import AccessRecord, AccessRight, AccessRecordConflict
6+
from endorsement.dao.access import (
7+
get_accessee_model, store_access_record, set_delegate)
8+
from endorsement.dao.office import get_office_accessor
9+
from endorsement.exceptions import UnrecognizedUWNetid, UnrecognizedGroupID
10+
from uw_msca.delegate import get_all_delegates, _msca_get_delegate_url
11+
from uw_msca import get_resource
12+
import json
13+
import csv
14+
import logging
15+
16+
17+
logger = logging.getLogger(__name__)
18+
logger.setLevel(logging.INFO)
19+
20+
21+
class Command(BaseCommand):
22+
help = "Restore Office365 mailbox access from MSCA"
23+
24+
def add_arguments(self, parser):
25+
parser.add_argument(
26+
'--commit',
27+
action='store_true',
28+
default=False,
29+
help='Store access record changes',
30+
)
31+
parser.add_argument(
32+
'--netid',
33+
type=str,
34+
help='Netid to restore rights for')
35+
parser.add_argument(
36+
'--csv',
37+
type=str,
38+
help='CSV of accessor netid, accessor name, access right')
39+
40+
41+
def handle(self, *args, **options):
42+
self.commit_changes = options['commit']
43+
netid = options['netid']
44+
csv_file = options['csv']
45+
try:
46+
if netid:
47+
self.restore_netid_access(netid)
48+
elif csv_file:
49+
self.restore_csv_access(csv_file)
50+
except Exception as ex:
51+
logger.error("restore_access_rights: Exception: {}".format(ex))
52+
53+
54+
def restore_netid_access(self, netid):
55+
accessee = get_accessee_model(netid)
56+
for delegate, right in self.get_delegates_for_netid(netid).items():
57+
try:
58+
self.fix_access_record(accessee, delegate, right)
59+
except Exception as ex:
60+
logger.info(f"ERROR: assign delegate {delegate}: {ex}")
61+
continue
62+
63+
def get_delegates_for_netid(self, netid):
64+
url = _msca_get_delegate_url(netid)
65+
response = get_resource(url)
66+
json_response = json.loads(response)
67+
delegates = {}
68+
for delegation in json_response:
69+
if netid != delegation['netid']:
70+
raise Exception("netid mismatch")
71+
netid = delegation['netid']
72+
delegations = delegation['delegates']
73+
if isinstance(delegations, dict):
74+
user = delegations['User']
75+
if not user:
76+
continue
77+
78+
rights = delegations['AccessRights']
79+
delegates[user] = rights
80+
elif isinstance(delegations, list):
81+
for d in delegations:
82+
user = d['User']
83+
if not user:
84+
continue
85+
86+
rights = d['AccessRights']
87+
if isinstance(rights, list):
88+
if len(rights) == 1:
89+
delegates[user] = rights[0]
90+
else:
91+
raise Exception(f"multiple rights for {user}: {rights}")
92+
elif isinstance(rights, str):
93+
delegates[user] = rights
94+
else:
95+
raise Exception(f"unknown right type for {user}")
96+
return delegates
97+
98+
def fix_access_record(self, accessee, delegate, right):
99+
try:
100+
accessor = get_office_accessor(delegate)
101+
except Exception as ex:
102+
raise Exception(f"ERROR: get accessor {delegate}: {ex}")
103+
104+
try:
105+
ar = AccessRecord.objects.get(
106+
accessee=accessee, accessor=accessor)
107+
except AccessRecord.DoesNotExist:
108+
raise Exception(f"ERROR: no record: mailbox {netid} "
109+
f"delegate {delegate}")
110+
try:
111+
rr = AccessRight.objects.get(name=right)
112+
except AccessRight.DoesNotExist:
113+
raise Exception(f"ERROR: unknown right {right} ")
114+
115+
if ar.access_right != rr:
116+
if self.commit_changes:
117+
ar.access_right = rr
118+
ar.save()
119+
120+
logger.info(
121+
f"{'' if self.commit_changes else 'WOULD '}ASSIGN: "
122+
f"mailbox {ar.accessee.netid} "
123+
f"delegate {ar.accessor.name} "
124+
f"right '{ar.access_right.display_name if (self.commit_changes) else rr.display_name}'")
125+
126+
127+
def reconcile_csv_access(self, csv_file):
128+
delegations = {}
129+
with open(csv_file, 'r') as f:
130+
blank_reader = csv.reader(f)
131+
for i, line in enumerate(blank_reader):
132+
delegates = json.loads(line[1])
133+
if not delegates or delegates == 'null':
134+
continue
135+
136+
netid = line[0]
137+
if netid not in delegations:
138+
delegations[netid] = {}
139+
140+
if isinstance(delegates, dict):
141+
user = delegates['User']
142+
right = delegates['AccessRights']
143+
delegations[netid][user] = right
144+
else:
145+
for d in delegates:
146+
user = d['User']
147+
right = d['AccessRights']
148+
delegations[netid][user] = right
149+
150+
with open('/tmp/blanks_remaining.csv', 'r') as f:
151+
blank_reader = csv.reader(f)
152+
for i, line in enumerate(blank_reader):
153+
netid = line[0]
154+
if netid not in delegations:
155+
logger.info(f"mailbox {netid} has no delegates")
156+
continue
157+
158+
try:
159+
accessee = get_accessee_model(netid)
160+
except UnrecognizedUWNetid:
161+
logger.info(f"ERROR: get accessee {netid}: unrecognized")
162+
continue
163+
except Exception as ex:
164+
logger.info(f"ERROR: get accessee {netid}: {ex}")
165+
continue
166+
167+
for delegate, right in delegations[netid].items():
168+
try:
169+
fix_access_record(accessee, delegate, right)
170+
except Exception as ex:
171+
logger.info(f"ERROR: assign delegate {delegate}: {ex}")
172+
continue

0 commit comments

Comments
 (0)