Skip to content

Commit 88d2936

Browse files
infispielJoshuaSBrownsourcery-ai[bot]
authored
[DAPS-1731] - Feature, scripts, compose - add scripts to generate globus credentials for web service (#1731)
Co-authored-by: Joshua S Brown <[email protected]> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
1 parent 78a6dbe commit 88d2936

File tree

8 files changed

+261
-22
lines changed

8 files changed

+261
-22
lines changed
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#!/bin/bash
2-
SCRIPT=$(realpath "$0")
2+
SCRIPT=$(realpath "${BASH_SOURCE[0]}")
33
SOURCE=$(dirname "$SCRIPT")
44
PROJECT_ROOT=$(realpath "${SOURCE}/../../")
55

6+
set -euf -o pipefail
7+
68
"${PROJECT_ROOT}/scripts/compose_generate_globus_files.sh" -d "$(pwd)"
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#!/bin/bash
2+
SCRIPT=$(realpath "${BASH_SOURCE[0]}")
3+
SOURCE=$(dirname "$SCRIPT")
4+
PROJECT_ROOT=$(realpath "${SOURCE}/../../")
5+
6+
set -euf -o pipefail
7+
8+
"${PROJECT_ROOT}/scripts/compose_generate_web_server_globus_credentials.sh" -d "$(pwd)"
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#!/bin/bash
2-
SCRIPT=$(realpath "$0")
2+
SCRIPT=$(realpath "${BASH_SOURCE[0]}")
33
SOURCE=$(dirname "$SCRIPT")
44
PROJECT_ROOT=$(realpath "${SOURCE}/../../")
55

6+
set -euf -o pipefail
7+
68
"${PROJECT_ROOT}/scripts/compose_generate_globus_files.sh" -d "$(pwd)"

scripts/compose_generate_globus_files.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#!/bin/bash
2-
SCRIPT=$(realpath "$0")
2+
SCRIPT=$(realpath "${BASH_SOURCE[0]}")
33
SOURCE=$(dirname "$SCRIPT")
44
PROJECT_ROOT=$(realpath "${SOURCE}/..")
55

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
#!/bin/bash
2+
SCRIPT=$(realpath "${BASH_SOURCE[0]}")
3+
SOURCE=$(dirname "$SCRIPT")
4+
PROJECT_ROOT=$(realpath "${SOURCE}/..")
5+
6+
source "${PROJECT_ROOT}/scripts/dependency_versions.sh"
7+
8+
# This script should be run after generating the .env file as it will pull
9+
# values from the .env file
10+
Help() {
11+
echo "$(basename $0) generate credentials for the DataFed Web Server."
12+
echo "Note the .env file must exist in the specified directory."
13+
echo
14+
echo "Syntax: $(basename $0) [-h|d]"
15+
echo "options:"
16+
echo "-h, --help Print this help message"
17+
echo "-d, --directory Directory where globus folder will be"
18+
echo " created."
19+
}
20+
21+
VALID_ARGS=$(getopt -o hd: --long 'help',directory: -- "$@")
22+
23+
DIRECTORY=""
24+
eval set -- "$VALID_ARGS"
25+
while [ : ]; do
26+
case "$1" in
27+
-h | --help)
28+
Help
29+
exit 0
30+
;;
31+
-d | --directory)
32+
DIRECTORY="$2"
33+
shift 2
34+
;;
35+
--)
36+
shift
37+
break
38+
;;
39+
\?) # incorrect option
40+
echo "Error: Invalid option"
41+
exit
42+
;;
43+
esac
44+
done
45+
46+
if [ ! -d "${DIRECTORY}" ]; then
47+
echo "The provided directory does not seem to exist: ${DIRECTORY}"
48+
fi
49+
50+
if [ ! -f "${DIRECTORY}/.env" ]; then
51+
echo "Missing . ${DIRECTORY}/.env file. This file needs to be"
52+
echo "created first"
53+
exit 1
54+
fi
55+
56+
# This script should be run after generating the .env file as it will pull
57+
# values from the .env file
58+
59+
if [ ! -f "${DIRECTORY}/.env" ]; then
60+
echo "Missing . ${DIRECTORY}/.env file. This file needs to be"
61+
echo "created first"
62+
exit 1
63+
fi
64+
65+
local_DATAFED_GLOBUS_KEY_DIR="${DIRECTORY}/globus"
66+
if [ ! -d "$local_DATAFED_GLOBUS_KEY_DIR" ]; then
67+
mkdir -p "$local_DATAFED_GLOBUS_KEY_DIR"
68+
fi
69+
70+
# Because docker compose honors spaces and reads in .env files as literals
71+
# we cannot include the quotes for variables that have spaces. So we need to
72+
# convert this file such that it is in a format that can be readable by bash
73+
# before loading it into the env
74+
75+
cp "${DIRECTORY}/.env" "${DIRECTORY}/.env_shell"
76+
77+
sed -i 's/=\([^"]*\)/="\1"/' "${DIRECTORY}/.env_shell"
78+
79+
. "${DIRECTORY}/.env_shell"
80+
81+
# Cleanup after loading env
82+
rm "${DIRECTORY}/.env_shell"
83+
84+
DATAFED_GLOBUS_REDIRECT_CRED_FILE_PATH="$DATAFED_GLOBUS_REDIRECT_CRED_FILE_PATH" \
85+
DATAFED_GCS_ROOT_NAME="$DATAFED_GCS_ROOT_NAME" \
86+
DATAFED_DOMAIN="$DATAFED_DOMAIN" \
87+
"python${DATAFED_PYTHON_VERSION}" "${PROJECT_ROOT}/scripts/globus/generate_web_server_credentials.py"
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import globus_sdk
2+
from globus_sdk.scopes import GroupsScopes
3+
from globus_sdk import AuthClient, GroupsClient
4+
5+
import utils
6+
7+
import os, sys
8+
9+
CLIENT_ID = "f8d0afca-7ac4-4a3c-ac05-f94f5d9afce8"
10+
11+
# The Globus project the GCS endpoint will be created in
12+
default_DATAFED_GCS_ROOT_NAME = "DataFed Repo"
13+
DATAFED_GCS_ROOT_NAME = os.getenv(
14+
"DATAFED_GCS_ROOT_NAME", default_DATAFED_GCS_ROOT_NAME
15+
)
16+
if len(DATAFED_GCS_ROOT_NAME) == 0:
17+
CRED_FILE_PATH = default_DATAFED_GCS_ROOT_NAME
18+
19+
default_PROJECT_NAME = f"{DATAFED_GCS_ROOT_NAME} Project"
20+
PROJECT_NAME = os.getenv("DATAFED_GLOBUS_PROJECT_NAME", default_PROJECT_NAME)
21+
if len(PROJECT_NAME) == 0:
22+
PROJECT_NAME = default_PROJECT_NAME
23+
24+
default_REDIRECT_CLIENT_NAME = DATAFED_GCS_ROOT_NAME + " Web Server Client"
25+
REDIRECT_CLIENT_NAME = os.getenv("DATAFED_GLOBUS_REDIRECT_CLIENT_NAME", default_REDIRECT_CLIENT_NAME)
26+
if len(REDIRECT_CLIENT_NAME) == 0:
27+
REDIRECT_CLIENT_NAME = default_REDIRECT_CLIENT_NAME
28+
29+
default_REDIRECT_CRED_NAME = DATAFED_GCS_ROOT_NAME + " Web Server Cred"
30+
REDIRECT_CRED_NAME = os.getenv("DATAFED_GLOBUS_REDIRECT_CRED_NAME", default_REDIRECT_CRED_NAME)
31+
if len(REDIRECT_CRED_NAME) == 0 :
32+
REDIRECT_CRED_NAME = default_REDIRECT_CRED_NAME
33+
34+
default_REDIRECT_CRED_FILE_PATH = os.path.abspath("./globus/globus_config.json")
35+
REDIRECT_CRED_FILE_PATH = os.getenv("DATAFED_GLOBUS_REDIRECT_CRED_FILE_PATH", default_REDIRECT_CRED_FILE_PATH)
36+
if len(REDIRECT_CRED_FILE_PATH) == 0:
37+
REDIRECT_CRED_FILE_PATH = default_REDIRECT_CRED_FILE_PATH
38+
39+
default_DOMAIN = "localhost"
40+
DOMAIN = os.getenv("DATAFED_DOMAIN", default_DOMAIN)
41+
if len(DOMAIN) == 0:
42+
DOMAIN = default_DOMAIN
43+
REDIRECT_PATH = os.path.join("https://", DOMAIN, "ui/authn")
44+
45+
# begin oauth
46+
client = globus_sdk.NativeAppAuthClient(CLIENT_ID)
47+
48+
group_scope = GroupsScopes.make_mutable("all")
49+
client.oauth2_start_flow(
50+
requested_scopes="openid profile email "
51+
"urn:globus:auth:scope:auth.globus.org:manage_projects "
52+
"urn:globus:auth:scope:auth.globus.org:view_identities " + str(group_scope),
53+
refresh_tokens=True,
54+
)
55+
56+
authorize_url = client.oauth2_get_authorize_url(query_params={"prompt": "login"})
57+
print("Please go to this URL and login: \n", authorize_url)
58+
auth_code = input("Please enter the authorization code: ")
59+
60+
token_response = client.oauth2_exchange_code_for_tokens(auth_code)
61+
# Extract the token
62+
refresh_token_auth = token_response.by_resource_server["auth.globus.org"][
63+
"refresh_token"
64+
]
65+
refresh_token_groups = token_response.by_resource_server["groups.api.globus.org"][
66+
"refresh_token"
67+
]
68+
rt_authorizer = globus_sdk.RefreshTokenAuthorizer(refresh_token_auth, client)
69+
70+
rt_authorizer_groups = globus_sdk.RefreshTokenAuthorizer(refresh_token_groups, client)
71+
72+
# auth_client_refresh_token
73+
ac_rt = AuthClient(authorizer=rt_authorizer)
74+
gr_rt = GroupsClient(authorizer=rt_authorizer_groups)
75+
76+
userinfo = ac_rt.oauth2_userinfo()
77+
# Will get the primary email and id
78+
identity_id = userinfo["sub"]
79+
email = userinfo["email"]
80+
username = userinfo["preferred_username"]
81+
print("username")
82+
print(username)
83+
print("userinfo")
84+
print(userinfo)
85+
organization = userinfo["identity_provider_display_name"]
86+
87+
# Need to determine the project uuid
88+
if utils.projectExists(ac_rt, PROJECT_NAME) is False:
89+
project_id = utils.createProject(ac_rt, PROJECT_NAME, userinfo)
90+
else:
91+
project_id = utils.getProjectId(ac_rt, PROJECT_NAME)
92+
93+
count = utils.countProjects(ac_rt, PROJECT_NAME)
94+
95+
if count != 1:
96+
print(
97+
"Something is wrong there should be at least one project with name"
98+
f" {PROJECT_NAME} instead there are {count} with that name"
99+
)
100+
sys.exit(1)
101+
102+
103+
print(f"Project id is {project_id}")
104+
105+
redirect_c_id, redirect_c_secret = utils.createClient(
106+
ac_rt, REDIRECT_CLIENT_NAME, project_id, REDIRECT_CRED_NAME, REDIRECT_CRED_FILE_PATH, REDIRECT_PATH
107+
)

scripts/globus/initialize_globus_endpoint.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
CRED_NAME = default_CRED_NAME
3636

3737
# Name of the file where we will store confidential client credentials
38-
default_CRED_FILE_PATH = os.path.abspath("./globus/client_cred.json")
38+
default_CRED_FILE_PATH = os.path.abspath("./globus/globus_config.json")
3939
CRED_FILE_PATH = os.getenv("DATAFED_GLOBUS_CRED_FILE_PATH", default_CRED_FILE_PATH)
4040
if len(CRED_FILE_PATH) == 0:
4141
CRED_FILE_PATH = default_CRED_FILE_PATH

scripts/globus/utils.py

Lines changed: 51 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,18 @@ def createNewClient(auth_client, client_name, project_id):
131131

132132
return client_id
133133

134+
def createNewRedirectClient(auth_client, client_name, project_id, redirect_uri):
135+
client_id = getClientId(auth_client, client_name, project_id)
136+
137+
client_exists = bool(client_id)
138+
if not client_exists:
139+
result = auth_client.create_client(
140+
client_name, project=project_id, public_client=False,
141+
redirect_uris=[redirect_uri]
142+
)
143+
client_id = result["client"]["id"]
144+
145+
return client_id
134146

135147
def getCredentialID(auth_client, client_id, cred_name):
136148
get_client_cred_result = auth_client.get_client_credentials(client_id)
@@ -179,29 +191,41 @@ def validFile(file_name):
179191
return file_exists, file_empty
180192

181193

182-
def getCredentialFromFile(cred_file_name, cred_id):
194+
def getCredentialFromFile(cred_file_name, cred_id, cred_type="setup"):
183195
# Check to see if the local secret is the same id and not just the same
184196
# name
185197
_, cred_empty = validFile(cred_file_name)
186198
if cred_empty is False:
187199
with open(cred_file_name, "r") as f:
188200
loaded_data = json.load(f)
201+
if cred_type in loaded_data.keys():
202+
loaded_data = loaded_data[cred_type]
203+
else :
204+
print(f"Failed to get credential from file '{cred_file_name}': credential type '{cred_type}' not found.")
189205
if loaded_data["client"] == cred_id:
190206
return loaded_data["secret"]
191207
return None
192208

193209

194-
def getClientIdFromCredFile(cred_file_name):
210+
def getClientIdFromCredFile(cred_file_name, cred_type="setup"):
195211
# Check to see if the local secret is the same id and not just the same
196212
# name
197213
_, cred_empty = validFile(cred_file_name)
198214
if cred_empty is False:
199215
with open(cred_file_name, "r") as f:
200216
loaded_data = json.load(f)
201-
return loaded_data["client"]
217+
if cred_type in loaded_data.keys():
218+
loaded_data = loaded_data[cred_type]
219+
return loaded_data["client"]
220+
else :
221+
print(f"Failed to get client ID from file '{cred_file_name}': credential type '{cred_type}' not found.")
202222
return None
203223

204224

225+
# Doesn't appear to be used.
226+
# Not removing until we have a chance to refactor the entire
227+
# globus configuration setup process.
228+
205229
def getEndpointIdFromFile(deployment_key_file_path):
206230
# Check to see if the local secret is the same id and not just the same
207231
# name
@@ -213,22 +237,24 @@ def getEndpointIdFromFile(deployment_key_file_path):
213237
return None
214238

215239

216-
def createNewCredential(auth_client, client_id, cred_name, cred_file):
217-
240+
def createNewCredential(auth_client, client_id, cred_name, cred_file, cred_type):
218241
get_client_cred_result = auth_client.get_client_credentials(client_id)
219242
for cred in get_client_cred_result["credentials"]:
220243
# Should have stored secret locally
221244
auth_client.delete_client_credential(client_id, cred["id"])
222245

223246
cred_result = auth_client.create_client_credential(client_id, cred_name)
224247
# Have to change this to a dict
225-
obj = {
226-
"client": cred_result["credential"]["client"],
227-
"id": cred_result["credential"]["id"],
228-
"name": cred_result["credential"]["name"],
229-
"secret": cred_result["credential"]["secret"],
230-
}
231248

249+
obj = { cred_type:
250+
{
251+
"client": cred_result["credential"]["client"],
252+
"id": cred_result["credential"]["id"],
253+
"name": cred_result["credential"]["name"],
254+
"secret": cred_result["credential"]["secret"],
255+
}
256+
}
257+
232258
# Check that the folder exists
233259
folder_path = os.path.dirname(cred_file)
234260
if not os.path.exists(folder_path):
@@ -246,9 +272,9 @@ def createNewCredential(auth_client, client_id, cred_name, cred_file):
246272
return cred_result["credential"]["secret"]
247273

248274

249-
def getClientSecret(auth_client, client_id, cred_name, cred_id, cred_file):
275+
def getClientSecret(auth_client, client_id, cred_name, cred_id, cred_file, cred_type):
250276

251-
client_secret = getCredentialFromFile(cred_file, cred_id)
277+
client_secret = getCredentialFromFile(cred_file, cred_id, cred_type)
252278

253279
create_new_credential = True
254280
remove_cached_credential = True
@@ -268,23 +294,30 @@ def getClientSecret(auth_client, client_id, cred_name, cred_id, cred_file):
268294
if create_new_credential:
269295
# Remove credentials from cloud
270296
client_secret = createNewCredential(
271-
auth_client, client_id, cred_name, cred_file
297+
auth_client, client_id, cred_name, cred_file, cred_type
272298
)
273299

274300
return client_secret
275301

276302

277-
def createClient(auth_client, client_name, project_id, cred_name, cred_file):
278-
client_id = createNewClient(auth_client, client_name, project_id)
303+
def createClient(auth_client, client_name, project_id, cred_name, cred_file, redirect_uri=None):
304+
if redirect_uri is not None:
305+
client_id = createNewRedirectClient(auth_client, client_name, project_id, redirect_uri)
306+
cred_type = "web"
307+
else :
308+
# if we haven't provided a redirect_uri, assume we're trying to
309+
# create a setup client, because that was previously the only
310+
# time this function was called.
311+
client_id = createNewClient(auth_client, client_name, project_id)
312+
cred_type = "setup"
279313

280314
cred_id = getCredentialID(auth_client, client_id, cred_name)
281315

282316
client_secret = getClientSecret(
283-
auth_client, client_id, cred_name, cred_id, cred_file
317+
auth_client, client_id, cred_name, cred_id, cred_file, cred_type
284318
)
285319
return client_id, client_secret
286320

287-
288321
def getGCSClientIDFromDeploymentFile(deployment_key_file):
289322
deployment_key_exists, deployment_key_empty = validFile(deployment_key_file)
290323

0 commit comments

Comments
 (0)