okta-sdk-python icon indicating copy to clipboard operation
okta-sdk-python copied to clipboard

Exception raised when making an OAuth 2.0 API request without necessary scopes is unhelpful

Open noelleleigh opened this issue 2 years ago • 0 comments

Details

  • Python version: 3.11.5
  • Okta package version: 2.9.3
  • OS: macOS 13.5.1

I have an API client (okta.client.Client) that authenticates with my Okta organization using OAuth 2.0 via an API Service app integration. This Okta app is given the "Group Membership Administrator" admin assignment, and 2 Okta API scopes:

  • okta.groups.manage
  • okta.users.read

However, the API client only includes the okta.users.read scope in its configuration. It is also configured to raise exceptions on errors.

When the API client tries to add a user to a group, an exception is raised (as expected). However, that exception doesn't contain enough information to diagnose why the exception was raised. Here is what the stack trace looks like:

Traceback (most recent call last):
  File "/Users/noelle/dev/okta-sdk-error-repro/.venv/lib/python3.11/site-packages/okta/http_client.py", line 139, in check_response_for_error
    error = OktaAPIError(url, response_details, formatted_response)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/noelle/dev/okta-sdk-error-repro/.venv/lib/python3.11/site-packages/okta/errors/okta_api_error.py", line 7, in __init__
    self.error_code = response_body["errorCode"]
                      ~~~~~~~~~~~~~^^^^^^^^^^^^^
TypeError: string indices must be integers, not 'str'
Traceback (most recent call last):
  File "/Users/noelle/dev/okta-sdk-error-repro/.venv/lib/python3.11/site-packages/okta/http_client.py", line 139, in check_response_for_error
    error = OktaAPIError(url, response_details, formatted_response)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/noelle/dev/okta-sdk-error-repro/.venv/lib/python3.11/site-packages/okta/errors/okta_api_error.py", line 7, in __init__
    self.error_code = response_body["errorCode"]
                      ~~~~~~~~~~~~~^^^^^^^^^^^^^
TypeError: string indices must be integers, not 'str'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<frozen runpy>", line 198, in _run_module_as_main
  File "<frozen runpy>", line 88, in _run_code
  File "/Users/noelle/dev/okta-sdk-error-repro/__main__.py", line 20, in <module>
    asyncio.run(main())
  File "/Users/noelle/.pyenv/versions/3.11.5/lib/python3.11/asyncio/runners.py", line 190, in run
    return runner.run(main)
           ^^^^^^^^^^^^^^^^
  File "/Users/noelle/.pyenv/versions/3.11.5/lib/python3.11/asyncio/runners.py", line 118, in run
    return self._loop.run_until_complete(task)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/noelle/.pyenv/versions/3.11.5/lib/python3.11/asyncio/base_events.py", line 653, in run_until_complete
    return future.result()
           ^^^^^^^^^^^^^^^
  File "/Users/noelle/dev/okta-sdk-error-repro/__main__.py", line 16, in main
    response, error = await okta_client.add_user_to_group(GROUP_ID, USER_ID)
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/noelle/dev/okta-sdk-error-repro/.venv/lib/python3.11/site-packages/okta/resource_clients/group_client.py", line 1262, in add_user_to_group
    response, error = await self._request_executor\
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/noelle/dev/okta-sdk-error-repro/.venv/lib/python3.11/site-packages/okta/request_executor.py", line 174, in execute
    _, error = self._http_client.check_response_for_error(
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/noelle/dev/okta-sdk-error-repro/.venv/lib/python3.11/site-packages/okta/http_client.py", line 148, in check_response_for_error
    raise HTTPException(formatted_response)
okta.exceptions.exceptions.HTTPException

The API response from Okta has an empty body. The actual details for what went wrong are contained in the WWW-Authenticate HTTP header of the response:

Bearer authorization_uri="http://dev-22706224.okta.com/oauth2/v1/authorize", realm="http://dev-22706224.okta.com", scope="okta.groups.manage", error="insufficient_scope", error_description="The access token provided does not contain the required scopes.", resource="/api/v1/groups/00ga2wa4gsRli77iu5d7/users/00uawnm71r7bLPaPE5d7"

The SDK doesn't seem to be aware of this potential source of errors, leaving it to the developer to dig into the responses themself.

Repro

Reproduction details

okta.yaml

okta:
  client:
    raiseException: true
    connectionTimeout: 30 # seconds
    orgUrl: "https://dev-22706224.okta.com"
    authorizationMode: "PrivateKey"
    clientId: "0oab0pj9tsZzD9lqq5d7"
    scopes:
      # - okta.groups.manage # commented out to produce error
      - okta.users.read
    privateKey: |
      -----BEGIN PRIVATE KEY-----
      ...
      -----END PRIVATE KEY-----
    requestTimeout: 0 # seconds
    rateLimit:
      maxRetries: 4
    logging:
      enabled: true
      logLevel: INFO

__main__.py

import asyncio

from okta.client import Client as OktaClient


USER_ID = "foobar"
GROUP_ID = "foobar"

okta_client = OktaClient()


async def main():
    response, error = await okta_client.add_user_to_group(GROUP_ID, USER_ID)
    return response

if __name__ == "__main__":
    asyncio.run(main())

Steps

  1. Configure a new Okta application as an API service with Public key / Private key client authentication, add the okta.groups.manage, okta.users.read Okta API scopes, and add a "Group Membership Administrator" admin assignment to it.
  2. Copy okta.yaml and __main__.py into a new folder
  3. Fill in real privateKey in okta.yaml
  4. Run pip install okta==2.9.3
  5. Run python .

noelleleigh avatar Aug 29 '23 15:08 noelleleigh