Skip to content

Commit a99f06e

Browse files
Merge pull request #21 from williamnswanson/SOFTWARE-5619.assign-new-project-gids
Change osggid assigner script to do project group setup (SOFTWARE-5619) Also create a library for various API calls and reused methods called "comanage_utils.py"
2 parents 0a263e3 + 894007e commit a99f06e

File tree

7 files changed

+569
-644
lines changed

7 files changed

+569
-644
lines changed

.github/workflows/python-linters.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ jobs:
3636
run: |
3737
python -m pip install --upgrade pip
3838
pip --cache-dir ~/pip-cache install pylint
39+
pip --cache-dir ~/pip-cache install ldap3
3940
- name: Run Pylint
4041
env:
4142
PYTHON_FILES: ${{ needs.python-files.outputs.filelist }}

comanage_utils.py

Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
#!/usr/bin/env python3
2+
3+
import os
4+
import re
5+
import sys
6+
import json
7+
import urllib.error
8+
import urllib.request
9+
from ldap3 import Server, Connection, ALL, ALL_ATTRIBUTES, SAFE_SYNC
10+
11+
#PRODUCTION VALUES
12+
13+
PRODUCTION_ENDPOINT = "https://registry.cilogon.org/registry/"
14+
PRODUCTION_LDAP_SERVER = "ldaps://ldap.cilogon.org"
15+
PRODUCTION_LDAP_USER = "uid=readonly_user,ou=system,o=OSG,o=CO,dc=cilogon,dc=org"
16+
PRODUCTION_OSG_CO_ID = 7
17+
PRODUCTION_UNIX_CLUSTER_ID = 1
18+
PRODUCTION_LDAP_TARGET_ID = 6
19+
20+
#TEST VALUES
21+
22+
TEST_ENDPOINT = "https://registry-test.cilogon.org/registry/"
23+
TEST_LDAP_SERVER = "ldaps://ldap-test.cilogon.org"
24+
TEST_LDAP_USER ="uid=registry_user,ou=system,o=OSG,o=CO,dc=cilogon,dc=org"
25+
TEST_OSG_CO_ID = 8
26+
TEST_UNIX_CLUSTER_ID = 10
27+
TEST_LDAP_TARGET_ID = 9
28+
29+
30+
MIN_TIMEOUT = 5
31+
MAX_TIMEOUT = 625
32+
TIMEOUTMULTIPLE = 5
33+
34+
35+
GET = "GET"
36+
PUT = "PUT"
37+
POST = "POST"
38+
DELETE = "DELETE"
39+
40+
41+
def getpw(user, passfd, passfile):
42+
if ":" in user:
43+
user, pw = user.split(":", 1)
44+
elif passfd is not None:
45+
pw = os.fdopen(passfd).readline().rstrip("\n")
46+
elif passfile is not None:
47+
pw = open(passfile).readline().rstrip("\n")
48+
elif "PASS" in os.environ:
49+
pw = os.environ["PASS"]
50+
else:
51+
raise PermissionError
52+
#when script needs to say PASS required, raise a permission error
53+
#usage("PASS required")
54+
return user, pw
55+
56+
57+
def mkauthstr(user, passwd):
58+
from base64 import encodebytes
59+
raw_authstr = "%s:%s" % (user, passwd)
60+
return encodebytes(raw_authstr.encode()).decode().replace("\n", "")
61+
62+
63+
def mkrequest(method, target, data, endpoint, authstr, **kw):
64+
url = os.path.join(endpoint, target)
65+
if kw:
66+
url += "?" + "&".join("{}={}".format(k,v) for k,v in kw.items())
67+
req = urllib.request.Request(url, json.dumps(data).encode("utf-8"))
68+
req.add_header("Authorization", "Basic %s" % authstr)
69+
req.add_header("Content-Type", "application/json")
70+
req.get_method = lambda: method
71+
return req
72+
73+
74+
def call_api(target, endpoint, authstr, **kw):
75+
return call_api2(GET, target, endpoint, authstr, **kw)
76+
77+
78+
def call_api2(method, target, endpoint, authstr, **kw):
79+
return call_api3(method, target, data=None, endpoint=endpoint, authstr=authstr, **kw)
80+
81+
82+
def call_api3(method, target, data, endpoint, authstr, **kw):
83+
req = mkrequest(method, target, data, endpoint, authstr, **kw)
84+
trying = True
85+
currentTimeout = MIN_TIMEOUT
86+
while trying:
87+
try:
88+
resp = urllib.request.urlopen(req, timeout=currentTimeout)
89+
payload = resp.read()
90+
trying = False
91+
except urllib.error.URLError as exception:
92+
if currentTimeout < MAX_TIMEOUT:
93+
currentTimeout *= TIMEOUTMULTIPLE
94+
else:
95+
sys.exit(
96+
f"Exception raised after maximum number of retries and/or timeout {MAX_TIMEOUT} seconds reached. "
97+
+ f"Exception reason: {exception.reason}.\n Request: {req.full_url}"
98+
)
99+
100+
return json.loads(payload) if payload else None
101+
102+
103+
def get_osg_co_groups(osg_co_id, endpoint, authstr):
104+
return call_api("co_groups.json", endpoint, authstr, coid=osg_co_id)
105+
106+
107+
def get_co_group_identifiers(gid, endpoint, authstr):
108+
return call_api("identifiers.json", endpoint, authstr, cogroupid=gid)
109+
110+
111+
def get_co_group_members(gid, endpoint, authstr):
112+
return call_api("co_group_members.json", endpoint, authstr, cogroupid=gid)
113+
114+
115+
def get_co_person_identifiers(pid, endpoint, authstr):
116+
return call_api("identifiers.json", endpoint, authstr, copersonid=pid)
117+
118+
119+
def get_co_group(gid, endpoint, authstr):
120+
resp_data = call_api("co_groups/%s.json" % gid, endpoint, authstr)
121+
grouplist = get_datalist(resp_data, "CoGroups")
122+
if not grouplist:
123+
raise RuntimeError("No such CO Group Id: %s" % gid)
124+
return grouplist[0]
125+
126+
127+
def get_identifier(id_, endpoint, authstr):
128+
resp_data = call_api("identifiers/%s.json" % id_, endpoint, authstr)
129+
idfs = get_datalist(resp_data, "Identifiers")
130+
if not idfs:
131+
raise RuntimeError("No such Identifier Id: %s" % id_)
132+
return idfs[0]
133+
134+
135+
def get_unix_cluster_groups(ucid, endpoint, authstr):
136+
return call_api("unix_cluster/unix_cluster_groups.json", endpoint, authstr, unix_cluster_id=ucid)
137+
138+
139+
def get_unix_cluster_groups_ids(ucid, endpoint, authstr):
140+
unix_cluster_groups = get_unix_cluster_groups(ucid, endpoint, authstr)
141+
return set(group["CoGroupId"] for group in unix_cluster_groups["UnixClusterGroups"])
142+
143+
144+
def delete_identifier(id_, endpoint, authstr):
145+
return call_api2(DELETE, "identifiers/%s.json" % id_, endpoint, authstr)
146+
147+
148+
def get_datalist(data, listname):
149+
return data[listname] if data else []
150+
151+
152+
def get_ldap_groups(ldap_server, ldap_user, ldap_authtok):
153+
ldap_group_osggids = set()
154+
server = Server(ldap_server, get_info=ALL)
155+
connection = Connection(server, ldap_user, ldap_authtok, client_strategy=SAFE_SYNC, auto_bind=True)
156+
_, _, response, _ = connection.search("ou=groups,o=OSG,o=CO,dc=cilogon,dc=org", "(cn=*)", attributes=ALL_ATTRIBUTES)
157+
for group in response:
158+
ldap_group_osggids.add(group["attributes"]["gidNumber"])
159+
return ldap_group_osggids
160+
161+
162+
def identifier_from_list(id_list, id_type):
163+
id_type_list = [id["Type"] for id in id_list]
164+
try:
165+
id_index = id_type_list.index(id_type)
166+
return id_list[id_index]["Identifier"]
167+
except ValueError:
168+
return None
169+
170+
171+
def identifier_matches(id_list, id_type, regex_string):
172+
pattern = re.compile(regex_string)
173+
value = identifier_from_list(id_list, id_type)
174+
return (value is not None) and (pattern.match(value) is not None)
175+
176+
177+
def rename_co_group(gid, group, newname, endpoint, authstr):
178+
# minimal edit CoGroup Request includes Name+CoId+Status+Version
179+
new_group_info = {
180+
"Name" : newname,
181+
"CoId" : group["CoId"],
182+
"Status" : group["Status"],
183+
"Version" : group["Version"]
184+
}
185+
data = {
186+
"CoGroups" : [new_group_info],
187+
"RequestType" : "CoGroups",
188+
"Version" : "1.0"
189+
}
190+
return call_api3(PUT, "co_groups/%s.json" % gid, data, endpoint, authstr)
191+
192+
193+
def add_identifier_to_group(gid, type, identifier_value, endpoint, authstr):
194+
new_identifier_info = {
195+
"Version": "1.0",
196+
"Type": type,
197+
"Identifier": identifier_value,
198+
"Login": False,
199+
"Person": {"Type": "Group", "Id": str(gid)},
200+
"Status": "Active",
201+
}
202+
data = {
203+
"RequestType": "Identifiers",
204+
"Version": "1.0",
205+
"Identifiers": [new_identifier_info],
206+
}
207+
return call_api3(POST, "identifiers.json", data, endpoint, authstr)
208+
209+
210+
def add_unix_cluster_group(gid, ucid, endpoint, authstr):
211+
data = {
212+
"RequestType": "UnixClusterGroups",
213+
"Version": "1.0",
214+
"UnixClusterGroups": [{"Version": "1.0", "UnixClusterId": ucid, "CoGroupId": gid}],
215+
}
216+
return call_api3(POST, "unix_cluster/unix_cluster_groups.json", data, endpoint, authstr)
217+
218+
219+
def provision_group(gid, provision_target, endpoint, authstr):
220+
path = f"co_provisioning_targets/provision/{provision_target}/cogroupid:{gid}.json"
221+
data = {
222+
"RequestType" : "CoGroupProvisioning",
223+
"Version" : "1.0",
224+
"Synchronous" : True
225+
}
226+
return call_api3(POST, path, data, endpoint, authstr)
227+
228+
def provision_group_members(gid, prov_id, endpoint, authstr):
229+
data = {
230+
"RequestType" : "CoPersonProvisioning",
231+
"Version" : "1.0",
232+
"Synchronous" : True
233+
}
234+
responses = {}
235+
for member in get_co_group_members(gid, endpoint, authstr)["CoGroupMembers"]:
236+
if member["Person"]["Type"] == "CO":
237+
pid = member["Person"]["Id"]
238+
path = f"co_provisioning_targets/provision/{prov_id}/copersonid:{pid}.json"
239+
responses[pid] = call_api3(POST, path, data, endpoint, authstr)
240+
return responses

0 commit comments

Comments
 (0)