prompt: You will be provided with a partial code base and an issue statement explaining a problem to resolve.
  <issue>
  Improve checks in `oauth` `authorize` view
  We ran into a case while deploying #862 where our `auth_provider` model is configured with a `scope`, but the response from the Auth server doesn't actually contain the scope (probably still being set up).
  
  The `oauth` `authorize` function doesn't handle this case and raises an `AttributeError`
  ```
    File "/home/calitp/app/benefits/oauth/views.py", line 70, in authorize
      claim_flag = (userinfo.get(verifier_claim) if userinfo else "false").lower() == "true"
  AttributeError: 'NoneType' object has no attribute 'lower'
  ```
  
  Let's add a check and tests for this so that the logs for this are more helpful.
  
  </issue>
  <code>
  [start of benefits/oauth/views.py]
  1 import logging
  2 
  3 from django.shortcuts import redirect
  4 from django.urls import reverse
  5 from django.utils.decorators import decorator_from_middleware
  6 
  7 from benefits.core import session
  8 from benefits.core.middleware import VerifierSessionRequired
  9 from . import analytics, redirects
  10 from .client import oauth
  11 
  12 
  13 logger = logging.getLogger(__name__)
  14 
  15 
  16 ROUTE_AUTH = "oauth:authorize"
  17 ROUTE_START = "eligibility:start"
  18 ROUTE_CONFIRM = "eligibility:confirm"
  19 ROUTE_UNVERIFIED = "eligibility:unverified"
  20 ROUTE_POST_LOGOUT = "oauth:post_logout"
  21 
  22 
  23 @decorator_from_middleware(VerifierSessionRequired)
  24 def login(request):
  25     """View implementing OIDC authorize_redirect."""
  26     verifier = session.verifier(request)
  27     oauth_client = oauth.create_client(verifier.auth_provider.client_name)
  28 
  29     if not oauth_client:
  30         raise Exception(f"oauth_client not registered: {verifier.auth_provider.client_name}")
  31 
  32     route = reverse(ROUTE_AUTH)
  33     redirect_uri = redirects.generate_redirect_uri(request, route)
  34 
  35     logger.debug(f"OAuth authorize_redirect with redirect_uri: {redirect_uri}")
  36 
  37     analytics.started_sign_in(request)
  38 
  39     return oauth_client.authorize_redirect(request, redirect_uri)
  40 
  41 
  42 @decorator_from_middleware(VerifierSessionRequired)
  43 def authorize(request):
  44     """View implementing OIDC token authorization."""
  45     verifier = session.verifier(request)
  46     oauth_client = oauth.create_client(verifier.auth_provider.client_name)
  47 
  48     if not oauth_client:
  49         raise Exception(f"oauth_client not registered: {verifier.auth_provider.client_name}")
  50 
  51     logger.debug("Attempting to authorize OAuth access token")
  52     token = oauth_client.authorize_access_token(request)
  53 
  54     if token is None:
  55         logger.warning("Could not authorize OAuth access token")
  56         return redirect(ROUTE_START)
  57 
  58     logger.debug("OAuth access token authorized")
  59 
  60     # We store the id_token in the user's session. This is the minimal amount of information needed later to log the user out.
  61     id_token = token["id_token"]
  62 
  63     # We store the returned claim in case it can be used later in eligibility verification.
  64     verifier_claim = verifier.auth_provider.claim
  65     stored_claim = None
  66 
  67     if verifier_claim:
  68         userinfo = token.get("userinfo")
  69         # the claim comes back in userinfo like { "claim": "True" | "False" }
  70         claim_flag = (userinfo.get(verifier_claim) if userinfo else "false").lower() == "true"
  71         # if userinfo contains our claim and the flag is true, store the *claim*
  72         stored_claim = verifier_claim if claim_flag else None
  73 
  74     session.update(request, oauth_token=id_token, oauth_claim=stored_claim)
  75 
  76     analytics.finished_sign_in(request)
  77 
  78     return redirect(ROUTE_CONFIRM)
  79 
  80 
  81 def cancel(request):
  82     """View implementing cancellation of OIDC authorization."""
  83 
  84     analytics.canceled_sign_in(request)
  85 
  86     return redirect(ROUTE_UNVERIFIED)
  87 
  88 
  89 @decorator_from_middleware(VerifierSessionRequired)
  90 def logout(request):
  91     """View implementing OIDC and application sign out."""
  92     verifier = session.verifier(request)
  93     oauth_client = oauth.create_client(verifier.auth_provider.client_name)
  94 
  95     if not oauth_client:
  96         raise Exception(f"oauth_client not registered: {verifier.auth_provider.client_name}")
  97 
  98     analytics.started_sign_out(request)
  99 
  100     # overwrite the oauth session token, the user is signed out of the app
  101     token = session.oauth_token(request)
  102     session.logout(request)
  103 
  104     route = reverse(ROUTE_POST_LOGOUT)
  105     redirect_uri = redirects.generate_redirect_uri(request, route)
  106 
  107     logger.debug(f"OAuth end_session_endpoint with redirect_uri: {redirect_uri}")
  108 
  109     # send the user through the end_session_endpoint, redirecting back to
  110     # the post_logout route
  111     return redirects.deauthorize_redirect(oauth_client, token, redirect_uri)
  112 
  113 
  114 def post_logout(request):
  115     """View routes the user to their origin after sign out."""
  116 
  117     analytics.finished_sign_out(request)
  118 
  119     origin = session.origin(request)
  120     return redirect(origin)
  121
  [end of benefits/oauth/views.py]
  </code>
  
  I need you to solve this issue by generating a single patch file that I can apply directly to this repository using git apply. Please respond with a single patch file in the following format.
  <patch>
  diff --git a/file.py b/file.py
  --- a/file.py
  +++ b/file.py
  @@ -1,27 +1,35 @@
   def euclidean(a, b):
  -    while b:
  -        a, b = b, a % b
  -    return a
  +    if b == 0:
  +        return a
  +    return euclidean(b, a % b)
   
   
   def bresenham(x0, y0, x1, y1):
       points = []
       dx = abs(x1 - x0)
       dy = abs(y1 - y0)
  -    sx = 1 if x0 < x1 else -1
  -    sy = 1 if y0 < y1 else -1
  -    err = dx - dy
  +    x, y = x0, y0
  +    sx = -1 if x0 > x1 else 1
  +    sy = -1 if y0 > y1 else 1
   
  -    while True:
  -        points.append((x0, y0))
  -        if x0 == x1 and y0 == y1:
  -            break
  -        e2 = 2 * err
  -        if e2 > -dy:
  -            err -= dy
  -            x0 += sx
  -        if e2 < dx:
  -            err += dx
  -            y0 += sy
  +    if dx > dy:
  +        err = dx / 2.0
  +        while x != x1:
  +            points.append((x, y))
  +            err -= dy
  +            if err < 0:
  +                y += sy
  +                err += dx
  +            x += sx
  +    else:
  +        err = dy / 2.0
  +        while y != y1:
  +            points.append((x, y))
  +            err -= dx
  +            if err < 0:
  +                x += sx
  +                err += dy
  +            y += sy
  +
  +    points.append((x, y))
       return points
  
  </patch>
golden_diff: diff --git a/benefits/oauth/views.py b/benefits/oauth/views.py
  --- a/benefits/oauth/views.py
  +++ b/benefits/oauth/views.py
  @@ -66,10 +66,15 @@
   
       if verifier_claim:
           userinfo = token.get("userinfo")
  -        # the claim comes back in userinfo like { "claim": "True" | "False" }
  -        claim_flag = (userinfo.get(verifier_claim) if userinfo else "false").lower() == "true"
  -        # if userinfo contains our claim and the flag is true, store the *claim*
  -        stored_claim = verifier_claim if claim_flag else None
  +
  +        if userinfo:
  +            claim_value = userinfo.get(verifier_claim)
  +            # the claim comes back in userinfo like { "claim": "True" | "False" }
  +            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*
  +                stored_claim = verifier_claim
   
       session.update(request, oauth_token=id_token, oauth_claim=stored_claim)
  
verification_info: {"golden_diff": "diff --git a/benefits/oauth/views.py b/benefits/oauth/views.py\n--- a/benefits/oauth/views.py\n+++ b/benefits/oauth/views.py\n@@ -66,10 +66,15 @@\n \n     if verifier_claim:\n         userinfo = token.get(\"userinfo\")\n-        # the claim comes back in userinfo like { \"claim\": \"True\" | \"False\" }\n-        claim_flag = (userinfo.get(verifier_claim) if userinfo else \"false\").lower() == \"true\"\n-        # if userinfo contains our claim and the flag is true, store the *claim*\n-        stored_claim = verifier_claim if claim_flag else None\n+\n+        if userinfo:\n+            claim_value = userinfo.get(verifier_claim)\n+            # the claim comes back in userinfo like { \"claim\": \"True\" | \"False\" }\n+            if claim_value is None:\n+                logger.warning(f\"userinfo did not contain: {verifier_claim}\")\n+            elif claim_value.lower() == \"true\":\n+                # if userinfo contains our claim and the flag is true, store the *claim*\n+                stored_claim = verifier_claim\n \n     session.update(request, oauth_token=id_token, oauth_claim=stored_claim)\n", "issue": "Improve checks in `oauth` `authorize` view\nWe ran into a case while deploying #862 where our `auth_provider` model is configured with a `scope`, but the response from the Auth server doesn't actually contain the scope (probably still being set up).\r\n\r\nThe `oauth` `authorize` function doesn't handle this case and raises an `AttributeError`\r\n```\r\n  File \"/home/calitp/app/benefits/oauth/views.py\", line 70, in authorize\r\n    claim_flag = (userinfo.get(verifier_claim) if userinfo else \"false\").lower() == \"true\"\r\nAttributeError: 'NoneType' object has no attribute 'lower'\r\n```\r\n\r\nLet's add a check and tests for this so that the logs for this are more helpful.\n", "before_files": [{"content": "import logging\n\nfrom django.shortcuts import redirect\nfrom django.urls import reverse\nfrom django.utils.decorators import decorator_from_middleware\n\nfrom benefits.core import session\nfrom benefits.core.middleware import VerifierSessionRequired\nfrom . import analytics, redirects\nfrom .client import oauth\n\n\nlogger = logging.getLogger(__name__)\n\n\nROUTE_AUTH = \"oauth:authorize\"\nROUTE_START = \"eligibility:start\"\nROUTE_CONFIRM = \"eligibility:confirm\"\nROUTE_UNVERIFIED = \"eligibility:unverified\"\nROUTE_POST_LOGOUT = \"oauth:post_logout\"\n\n\n@decorator_from_middleware(VerifierSessionRequired)\ndef login(request):\n    \"\"\"View implementing OIDC authorize_redirect.\"\"\"\n    verifier = session.verifier(request)\n    oauth_client = oauth.create_client(verifier.auth_provider.client_name)\n\n    if not oauth_client:\n        raise Exception(f\"oauth_client not registered: {verifier.auth_provider.client_name}\")\n\n    route = reverse(ROUTE_AUTH)\n    redirect_uri = redirects.generate_redirect_uri(request, route)\n\n    logger.debug(f\"OAuth authorize_redirect with redirect_uri: {redirect_uri}\")\n\n    analytics.started_sign_in(request)\n\n    return oauth_client.authorize_redirect(request, redirect_uri)\n\n\n@decorator_from_middleware(VerifierSessionRequired)\ndef authorize(request):\n    \"\"\"View implementing OIDC token authorization.\"\"\"\n    verifier = session.verifier(request)\n    oauth_client = oauth.create_client(verifier.auth_provider.client_name)\n\n    if not oauth_client:\n        raise Exception(f\"oauth_client not registered: {verifier.auth_provider.client_name}\")\n\n    logger.debug(\"Attempting to authorize OAuth access token\")\n    token = oauth_client.authorize_access_token(request)\n\n    if token is None:\n        logger.warning(\"Could not authorize OAuth access token\")\n        return redirect(ROUTE_START)\n\n    logger.debug(\"OAuth access token authorized\")\n\n    # We store the id_token in the user's session. This is the minimal amount of information needed later to log the user out.\n    id_token = token[\"id_token\"]\n\n    # We store the returned claim in case it can be used later in eligibility verification.\n    verifier_claim = verifier.auth_provider.claim\n    stored_claim = None\n\n    if verifier_claim:\n        userinfo = token.get(\"userinfo\")\n        # the claim comes back in userinfo like { \"claim\": \"True\" | \"False\" }\n        claim_flag = (userinfo.get(verifier_claim) if userinfo else \"false\").lower() == \"true\"\n        # if userinfo contains our claim and the flag is true, store the *claim*\n        stored_claim = verifier_claim if claim_flag else None\n\n    session.update(request, oauth_token=id_token, oauth_claim=stored_claim)\n\n    analytics.finished_sign_in(request)\n\n    return redirect(ROUTE_CONFIRM)\n\n\ndef cancel(request):\n    \"\"\"View implementing cancellation of OIDC authorization.\"\"\"\n\n    analytics.canceled_sign_in(request)\n\n    return redirect(ROUTE_UNVERIFIED)\n\n\n@decorator_from_middleware(VerifierSessionRequired)\ndef logout(request):\n    \"\"\"View implementing OIDC and application sign out.\"\"\"\n    verifier = session.verifier(request)\n    oauth_client = oauth.create_client(verifier.auth_provider.client_name)\n\n    if not oauth_client:\n        raise Exception(f\"oauth_client not registered: {verifier.auth_provider.client_name}\")\n\n    analytics.started_sign_out(request)\n\n    # overwrite the oauth session token, the user is signed out of the app\n    token = session.oauth_token(request)\n    session.logout(request)\n\n    route = reverse(ROUTE_POST_LOGOUT)\n    redirect_uri = redirects.generate_redirect_uri(request, route)\n\n    logger.debug(f\"OAuth end_session_endpoint with redirect_uri: {redirect_uri}\")\n\n    # send the user through the end_session_endpoint, redirecting back to\n    # the post_logout route\n    return redirects.deauthorize_redirect(oauth_client, token, redirect_uri)\n\n\ndef post_logout(request):\n    \"\"\"View routes the user to their origin after sign out.\"\"\"\n\n    analytics.finished_sign_out(request)\n\n    origin = session.origin(request)\n    return redirect(origin)\n", "path": "benefits/oauth/views.py"}]}