Repository: cal-itp/benefits

Issue:
Refactor claims handling for integer claims
During the OAuth `authorize` flow, we look for [boolean claim values](https://github.com/cal-itp/benefits/blob/dev/benefits/oauth/views.py#L75) to determine if the user is eligible.

IdG is changing their claims implementation to cut down on the size of the token being sent to Benefits. Instead of booleans, they will use integers to indicate claim values:

* `0` will indicate `False` (i.e. the claim indicates eligibility failed)
* `1` will indicate `True` (i.e. the claim indicates eligibility succeeded)
* Any other integer `>= 10` will indicate an error code

**Note:** the claim values are transmitted in the token as `str`, and should be parsed to `int` before usage.

## Acceptance Criteria

<!-- Remember to consider edge cases -->

- [ ] `authorize` processes integer claims as described above

## Additional context

While we work to implement this change, existing flows for Older Adults and Veterans will use both claim styles. New flows for CalFresh and the new Veterans API will ~only use the newer integer claim style, so this refactor is necessary for supporting those flows.~ also support both styles to allow us time to implement and cut over. There are an entirely new set of scopes created for the integer-based claims so as not to interfere with the existing implementation.

Once we have this change tested and deployed, IdG will cutover all flows to use the integer style only.

Mapping error codes to error messages and analytics will be handled in #2049.

See [this Slack thread](https://cal-itp.slack.com/archives/C037Y3UE71P/p1714434750536319) from @johnatstate for more context.


Discussion:

Resolving patch:
diff --git a/benefits/oauth/views.py b/benefits/oauth/views.py
--- a/benefits/oauth/views.py
+++ b/benefits/oauth/views.py
@@ -69,11 +69,12 @@ def authorize(request):
 
         if userinfo:
             claim_value = userinfo.get(verifier_claim)
-            # the claim comes back in userinfo like { "claim": "True" | "False" }
+            # the claim comes back in userinfo like { "claim": "1" | "0" }
+            claim_value = int(claim_value) if claim_value else None
             if claim_value is None:
                 logger.warning(f"userinfo did not contain: {verifier_claim}")
-            elif claim_value.lower() == "true":
-                # if userinfo contains our claim and the flag is true, store the *claim*
+            elif claim_value == 1:
+                # if userinfo contains our claim and the flag is 1 (true), store the *claim*
                 stored_claim = verifier_claim
 
     session.update(request, oauth_token=id_token, oauth_claim=stored_claim)


Test patch:
diff --git a/tests/pytest/oauth/test_views.py b/tests/pytest/oauth/test_views.py
--- a/tests/pytest/oauth/test_views.py
+++ b/tests/pytest/oauth/test_views.py
@@ -99,14 +99,11 @@ def test_authorize_success(mocked_oauth_create_client, mocked_analytics_module,
 
 @pytest.mark.django_db
 @pytest.mark.usefixtures("mocked_analytics_module")
-@pytest.mark.parametrize("flag", ["true", "True", "tRuE"])
-def test_authorize_success_with_claim_true(
-    mocked_session_verifier_auth_required, mocked_oauth_create_client, app_request, flag
-):
+def test_authorize_success_with_claim_true(mocked_session_verifier_auth_required, mocked_oauth_create_client, app_request):
     verifier = mocked_session_verifier_auth_required.return_value
     verifier.auth_provider.claim = "claim"
     mocked_oauth_client = mocked_oauth_create_client.return_value
-    mocked_oauth_client.authorize_access_token.return_value = {"id_token": "token", "userinfo": {"claim": flag}}
+    mocked_oauth_client.authorize_access_token.return_value = {"id_token": "token", "userinfo": {"claim": "1"}}
 
     result = authorize(app_request)
 
@@ -118,14 +115,15 @@ def test_authorize_success_with_claim_true(
 
 @pytest.mark.django_db
 @pytest.mark.usefixtures("mocked_analytics_module")
-@pytest.mark.parametrize("flag", ["false", "False", "fAlSe"])
 def test_authorize_success_with_claim_false(
-    mocked_session_verifier_auth_required, mocked_oauth_create_client, app_request, flag
+    mocked_session_verifier_auth_required,
+    mocked_oauth_create_client,
+    app_request,
 ):
     verifier = mocked_session_verifier_auth_required.return_value
     verifier.auth_provider.claim = "claim"
     mocked_oauth_client = mocked_oauth_create_client.return_value
-    mocked_oauth_client.authorize_access_token.return_value = {"id_token": "token", "userinfo": {"claim": flag}}
+    mocked_oauth_client.authorize_access_token.return_value = {"id_token": "token", "userinfo": {"claim": "0"}}
 
     result = authorize(app_request)