Skip to content

Commit

Permalink
Merge pull request #1016 from shankari/suspend_certain_subgroups
Browse files Browse the repository at this point in the history
💩 🔧 Enable suspending data collection for certain subgroups
  • Loading branch information
shankari authored Feb 3, 2025
2 parents c941e5d + cbb29e4 commit aaedcd3
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 17 deletions.
37 changes: 24 additions & 13 deletions emission/net/api/cfc_webapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
auth_method = config.get("WEBSERVER_AUTH", "skip")
aggregate_call_auth = config.get("WEBSERVER_AGGREGATE_CALL_AUTH", "no_auth")
not_found_redirect = config.get("WEBSERVER_NOT_FOUND_REDIRECT", "https://nrel.gov/openpath")
dynamic_config = None

BaseRequest.MEMFILE_MAX = 1024 * 1024 * 1024 # Allow the request size to be 1G
# to accomodate large section sizes
Expand Down Expand Up @@ -483,6 +484,25 @@ def after_request():
stats.store_server_api_time(request.params.user_uuid, "%s_%s_cputime" % (request.method, request.path),
msTimeNow, new_duration)

# Dynamic config BEGIN

def get_dynamic_config():
logging.debug(f"STUDY_CONFIG is {STUDY_CONFIG}")
download_url = "https://raw.githubusercontent.com/e-mission/nrel-openpath-deploy-configs/main/configs/" + STUDY_CONFIG + ".nrel-op.json"
logging.debug("About to download config from %s" % download_url)
r = requests.get(download_url)
if r.status_code != 200:
logging.debug(f"Unable to download study config for {STUDY_CONFIG=}, status code: {r.status_code}")
return None
else:
dynamic_config = json.loads(r.text)
logging.debug(f"Successfully downloaded config with version {dynamic_config['version']} "\
f"for {dynamic_config['intro']['translated_text']['en']['deployment_name']} "\
f"and data collection URL {dynamic_config.get('server', {}).get('connectUrl', 'OS DEFAULT')}")
return dynamic_config

# Dynamic config END

# Auth helpers BEGIN
# This should only be used by createUserProfile since we may not have a UUID
# yet. All others should use the UUID.
Expand Down Expand Up @@ -513,7 +533,7 @@ def get_user_or_aggregate_auth(request):

def getUUID(request, inHeader=False):
try:
retUUID = enaa.getUUID(request, auth_method, inHeader)
retUUID = enaa.getUUID(dynamic_config, request, auth_method, inHeader)
logging.debug("retUUID = %s" % retUUID)
if retUUID is None:
raise HTTPError(403, "token is valid, but no account found for user")
Expand All @@ -524,20 +544,10 @@ def getUUID(request, inHeader=False):

def resolve_auth(auth_method):
if auth_method == "dynamic":
logging.debug("auth_method is dynamic")
logging.debug(f"STUDY_CONFIG is {STUDY_CONFIG}")
download_url = "https://raw.githubusercontent.com/e-mission/nrel-openpath-deploy-configs/main/configs/" + STUDY_CONFIG + ".nrel-op.json"
logging.debug("About to download config from %s" % download_url)
r = requests.get(download_url)
if r.status_code != 200:
logging.debug(f"Unable to download study config, status code: {r.status_code}")
logging.debug("auth_method is dynamic, using dynamic config to find the actual auth method")
if dynamic_config is None:
sys.exit(1)
else:
dynamic_config = json.loads(r.text)
logging.debug(f"Successfully downloaded config with version {dynamic_config['version']} "\
f"for {dynamic_config['intro']['translated_text']['en']['deployment_name']} "\
f"and data collection URL {dynamic_config['server']['connectUrl']}")

if "opcode" in dynamic_config:
# New style config
if dynamic_config["opcode"]["autogen"] == True:
Expand Down Expand Up @@ -567,6 +577,7 @@ def resolve_auth(auth_method):

logging.config.dictConfig(webserver_log_config)
logging.debug("attempting to resolve auth_method")
dynamic_config = get_dynamic_config()
auth_method = resolve_auth(auth_method)

logging.debug(f"Using auth method {auth_method}")
Expand Down
38 changes: 37 additions & 1 deletion emission/net/auth/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,45 @@ def __getToken__(request, inHeader):

return userToken

def getUUID(request, authMethod, inHeader=False):
def getSubgroupFromToken(token, config):
if "opcode" in config:
# new style study, expects token with sub-group
tokenParts = token.split('_');
if len(tokenParts) <= 3:
# no subpart defined
raise ValueError(f"Not enough parts in {token=}, expected 3, found {tokenParts.length=}")
if "subgroups" in config.get("opcode", {}):
if tokenParts[2] not in config['opcode']['subgroups']:
# subpart not in config list
raise ValueError(f"Invalid subgroup {tokenParts[2]} not in {config.opcode.subgroups}")
else:
logging.debug('subgroup ' + tokenParts[2] + ' found in list ' + str(config['opcode']['subgroups']))
return tokenParts[2];
else:
if tokenParts[2] != 'default':
# subpart not in config list
raise ValueError(f"No subgroups found in config, but subgroup {tokenParts[2]} is not 'default'")
else:
logging.debug("no subgroups in config, 'default' subgroup found in token ");
return tokenParts[2];
else:
# old style study, expect token without subgroup
# nothing further to validate at this point
# only validation required is `nrelop_` and valid study name
# first is already handled in getStudyNameFromToken, second is handled
# by default since download will fail if it is invalid
logging.debug('Old-style study, expecting token without a subgroup...');
return None;

def getUUID(dynamicConfig, request, authMethod, inHeader=False):
retUUID = None
userToken = __getToken__(request, inHeader)
curr_subgroup = getSubgroupFromToken(userToken, dynamicConfig)
suspended_subgroups = dynamicConfig.get("opcode", {}).get("suspended_subgroups", [])
if request.path == "/usercache/put":
if curr_subgroup in suspended_subgroups:
logging.info(f"Received put message for subgroup {curr_subgroup} in {suspended_subgroups=}, returning uuid = None")
return None
retUUID = getUUIDFromToken(authMethod, userToken)
request.params.user_uuid = retUUID
return retUUID
Expand Down
6 changes: 3 additions & 3 deletions emission/tests/netTests/TestAuthSelection.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ def testGetUUIDSkipAuth(self):
logging.debug("Found request body = %s" % request.body.getvalue())
logging.debug("Found request headers = %s" % list(request.headers.keys()))
user = ecwu.User.register(self.test_email)
self.assertEqual(enaa.getUUID(request, "skip", inHeader=False), user.uuid)
self.assertEqual(enaa.getUUID({}, request, "skip", inHeader=False), user.uuid)
ecwu.User.unregister(self.test_email)

def testGetUUIDTokenAuthSuccess(self):
Expand All @@ -161,7 +161,7 @@ def testGetUUIDTokenAuthSuccess(self):
logging.debug("Found request body = %s" % request.body.getvalue())
logging.debug("Found request headers = %s" % list(request.headers.keys()))
user = ecwu.User.register(self.test_email)
self.assertEqual(enaa.getUUID(request, "token_list", inHeader=False), user.uuid)
self.assertEqual(enaa.getUUID({}, request, "token_list", inHeader=False), user.uuid)
ecwu.User.unregister(self.test_email)

def testGetUUIDTokenAuthFailure(self):
Expand All @@ -180,7 +180,7 @@ def testGetUUIDTokenAuthFailure(self):
user = ecwu.User.register(self.test_email)
ecwu.User.unregister(self.test_email)
with self.assertRaises(ValueError):
self.assertEqual(enaa.getUUID(request, "token_list", inHeader=False), user.uuid)
self.assertEqual(enaa.getUUID({}, request, "token_list", inHeader=False), user.uuid)

if __name__ == '__main__':
import emission.tests.common as etc
Expand Down
4 changes: 4 additions & 0 deletions emission/tests/netTests/TestWebserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,20 +60,24 @@ def test404Redirect(self):
@mock.patch.dict(os.environ, {"STUDY_CONFIG":"nrel-commute"}, clear=True)
def test_ResolveAuthWithEnvVar(self):
importlib.reload(enacw)
enacw.dynamic_config = enacw.get_dynamic_config()
self.assertEqual(enacw.resolve_auth("dynamic"),"skip")

@mock.patch.dict(os.environ, {"STUDY_CONFIG":"denver-casr"}, clear=True)
def test_ResolveAuthWithEnvVar(self):
importlib.reload(enacw)
enacw.dynamic_config = enacw.get_dynamic_config()
self.assertEqual(enacw.resolve_auth("dynamic"),"skip")

@mock.patch.dict(os.environ, {"STUDY_CONFIG":"stage-program"}, clear=True)
def test_ResolveAuthWithEnvVar(self):
importlib.reload(enacw)
enacw.dynamic_config = enacw.get_dynamic_config()
self.assertEqual(enacw.resolve_auth("dynamic"),"token_list")

def testResolveAuthNoEnvVar(self):
importlib.reload(enacw)
enacw.dynamic_config = enacw.get_dynamic_config()
self.assertEqual(enacw.resolve_auth("skip"),"skip")
self.assertEqual(enacw.resolve_auth("token_list"),"token_list")
self.assertEqual(enacw.resolve_auth("dynamic"),"token_list")
Expand Down

0 comments on commit aaedcd3

Please sign in to comment.