Science Score: 26.0%

This score indicates how likely this project is to be science-related based on various indicators:

  • CITATION.cff file
  • codemeta.json file
    Found codemeta.json file
  • .zenodo.json file
    Found .zenodo.json file
  • DOI references
  • Academic publication links
  • Committers with academic emails
  • Institutional organization owner
  • JOSS paper metadata
  • Scientific vocabulary similarity
    Low similarity (15.2%) to scientific vocabulary
Last synced: 10 months ago · JSON representation

Repository

Basic Info
  • Host: GitHub
  • Owner: okta
  • License: apache-2.0
  • Language: Python
  • Default Branch: master
  • Size: 5.9 MB
Statistics
  • Stars: 259
  • Watchers: 238
  • Forks: 154
  • Open Issues: 42
  • Releases: 23
Created about 11 years ago · Last pushed 11 months ago
Metadata Files
Readme Changelog Contributing License

README.md

License Support

PyPI PyPI - Python Version Code Style

Okta Python Management SDK

This repository contains the Okta management SDK for Python. This SDK can be used in your server-side code to interact with the Okta management API and

Requires Python version 3.7.0 or higher.

You can also learn more on the Okta + Python page in our documentation.

Release status

This library uses semantic versioning and follows Okta's Library Version Policy.

| Version | Status | | ------- | -------------------------------- | | 0.x | :warning: Beta Release (Retired) | | 1.x | :warning: Retired | | 2.x | :heavycheckmark: Release |

The latest release can always be found on the releases page.

Need help?

If you run into problems using the SDK, you can:

Getting started

To install the Okta Python SDK in your project:

sh pip install okta

You'll also need

Construct a client instance by passing it your Okta domain name and API token:

```py import asyncio

from okta.client import Client as OktaClient

Instantiating with a Python dictionary in the constructor

config = { 'orgUrl': 'https://{yourOktaDomain}', 'token': 'YOURAPITOKEN' } okta_client = OktaClient(config)

example of usage, list all users and print their first name and last name

async def main(): users, resp, err = await oktaclient.listusers() for user in users: print(user.profile.firstname, user.profile.lastname)

loop = asyncio.geteventloop() loop.rununtilcomplete(main()) ```

Another way to instantiate okta_client (config should be provided in other place as described below):

```py

Instantiating without in-text credentials

okta_client = OktaClient() ```

Http session was introduced within v2.3.0 to allow custom SSL contest. Starting with SDK v2.4.0 you can reuse http session to gain better performance:

```py import asyncio import aiohttp

from okta.client import Client as OktaClient

config = { 'orgUrl': 'https://{yourOktaDomain}', 'token': 'YOURAPITOKEN' }

async def main(): async with OktaClient(config) as client: # perform all queries within same session users, oktaresp, err = await client.listusers() user, oktaresp, err = await client.getuser(users[0].id)

loop = asyncio.geteventloop() loop.rununtilcomplete(main()) ```

Context Manager is a preferable way to use OktaClient to perform bunch of API requests.

Using a Python dictionary to hard-code the Okta domain and API token is encouraged for development; In production, you should use a more secure way of storing these values. This library supports a few different configuration sources, covered in the configuration reference section.

OAuth 2.0

Okta allows you to interact with Okta APIs using scoped OAuth 2.0 access tokens. Each access token enables the bearer to perform specific actions on specific Okta endpoints, with that ability controlled by which scopes the access token contains.

This SDK supports this feature (OAuth 2.0) only for service-to-service applications. Check out our guides to learn more about how to register a new service application using a private and public key pair.

When using this approach you won't need an API Token because the SDK will request an access token for you. In order to use OAuth 2.0, construct a client instance by passing the following parameters:

```py from okta.client import Client as OktaClient config = { 'orgUrl': 'https://{yourOktaDomain}', 'authorizationMode': 'PrivateKey', 'clientId': '{yourClientId}', 'scopes': ['okta.users.manage'], 'privateKey': 'YOURPRIVATEJWK', # this parameter should be type of str 'kid': 'YOURPRIVATEKEYID' # if a key ID needs to be provided, it can be provided here or part of the privateKey under "kid" } oktaclient = OktaClient(config)

example of usage, list all users and print their first name and last name

async def main(): users, resp, err = await oktaclient.listusers() for user in users: print(user.profile.firstname, user.profile.lastname)

loop = asyncio.geteventloop() loop.rununtilcomplete(main()) ```

Note, that privateKey can be passed in JWK format or in PEM format, i.e. (examples generated with https://mkjwk.org):

{ "p": "4VmEO2ztlIHvalMHX797rWhETbKgB6bRbdGevYpRLZH167hKHB5vsuRjIAXJdujQw8W3rnas9Z-_Ddv1TbR5Qnz0UmhnxQAIbdDDUE9r5P_LEholrjY9Jz0P-W4jey-7cDATeITYHb3t67HcIwVbxQF5fkRdJAhfO029RqkH3OE", "kty": "RSA", "q": "x8ngsUMrDGReVVpeGdlZzGTSFxrNP89DF4WEQZ7zCpSe3_GpuUPbzgslYQEiX6XJY5ssavavVNOmmQEAt0xsMcxxVOPYCYy7LBE8cJQiFb_bMf2H1-zTlPn_KF4D10h45cLXhu-xh4c52Rh9WDMYZmKWLkAJQ6L_eueGoZkIDmU", "d": "R38UamnZiEhOLxD7FYUN5AKj9mHQneRWizblxfNq2T1Nfk4matfZrrlMq_nz9tYZ3-TOCu3u-7k_igM0Tml365mbU_HzkfCrD-ou7cGSrqNgnipj_VQSgJfKRFKATEf4hMfdpKSd4rZzf8OJnq8s-kpRVC4kdHJtJjja59VvHEQRIrN_dkycNHSBWu5UjZbXOO5X3mjwuIh9gpLGZ-nHTqgTpT324q5BLVsH8_ywRGifIj-HQL1O5bJO2Q2_18iL1TbnMSbDwrKdb1edb4bgDuWB4o0xSTXsherTgeXu76gN9FY28tuAKSd34yqp7GZaYcjtkskbWPRtYhOID2cOgQ", "e": "AQAB", "use": "sig", "kid": "test", "qi": "FZGFuvW1W9VF31JyrMYJy_BH7vja3d9iZlhFzttNZ-wmiXG4irrI_fLJgmXK6dI3MfIhKPAYi9nnza2kcR1qEV9QObA4NV86RWnc8sAHbDGooe9VK5eJ5jjD7Tq_ZZiLiHGOZit3HylNilOb0k3VsgMcp0F3ZQaMbg35K9rSgZE", "dp": "i4D6HjupvCTQDNdHmluU-d2xYxQwg2we_EgnaBkHdhmEzx8wKcYhyfIe90T92jH4gymUM1neatQw1yiS7D7MTn_CVH2zt730ed8h-kageYxsr1EmgHmtU-w2RmiLaIg9Fg99Dj_W9lqMvjtGFxwLGqN2DdfOfS79nV3bzbF4X6E", "alg": "RS256", "dq": "CT79iBacsmkeuIKDIl0du8jatDkIULCt4TPLqCHMC6xPIfwUJ7_NN17qru-XgKeyh0qSJq0d9iYJasFSICmIRFG62PvmbqK1stdlXaxtW2ZSpaCfHc4XCKj9NwgK03bGKZP314XWSHhoo_RvMJrEwVBEtQU_qIKtoil-4JGtfsU", "n": "r95K3WIN8-4dB-tEKHjyTIIZZUMbHz8ad5oBX2BGiGxfPGfHbz2RH4QLT9ffzL-tgEo8IKs0Myh0VTwauiwz0cdHuS2gUTasK9OsosX1h1scSu_eZ-g-__lXBogU-SvBXBAgjv8hdcZjqWYQwmhJp2Ilv0CuXKxQwZyjso775PDjWDCH5HkVcSxHyUvpThLfWfkfz5PNDZvRpuPltv55ILRaVZhwPb7VXLAm2ebfeYUdybUKpGnEogKQdaL7TdNvP-HRnUSXTiYeXWHzU04FaXJ7yLmtXOQ52FT9dwkwLrCDOmDSBGafZ9asUtgOKhKN6wQW5mndhMK_1zThfjZyxQ" }

or

-----BEGIN PRIVATE KEY----- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCv3krdYg3z7h0H 60QoePJMghllQxsfPxp3mgFfYEaIbF88Z8dvPZEfhAtP19/Mv62ASjwgqzQzKHRV PBq6LDPRx0e5LaBRNqwr06yixfWHWxxK795n6D7/+VcGiBT5K8FcECCO/yF1xmOp ZhDCaEmnYiW/QK5crFDBnKOyjvvk8ONYMIfkeRVxLEfJS+lOEt9Z+R/Pk80Nm9Gm 4+W2/nkgtFpVmHA9vtVcsCbZ5t95hR3JtQqkacSiApB1ovtN028/4dGdRJdOJh5d YfNTTgVpcnvIua1c5DnYVP13CTAusIM6YNIEZp9n1qxS2A4qEo3rBBbmad2Ewr/X NOF+NnLFAgMBAAECggEAR38UamnZiEhOLxD7FYUN5AKj9mHQneRWizblxfNq2T1N fk4matfZrrlMq/nz9tYZ3+TOCu3u+7k/igM0Tml365mbU/HzkfCrD+ou7cGSrqNg nipj/VQSgJfKRFKATEf4hMfdpKSd4rZzf8OJnq8s+kpRVC4kdHJtJjja59VvHEQR IrN/dkycNHSBWu5UjZbXOO5X3mjwuIh9gpLGZ+nHTqgTpT324q5BLVsH8/ywRGif Ij+HQL1O5bJO2Q2/18iL1TbnMSbDwrKdb1edb4bgDuWB4o0xSTXsherTgeXu76gN 9FY28tuAKSd34yqp7GZaYcjtkskbWPRtYhOID2cOgQKBgQDhWYQ7bO2Uge9qUwdf v3utaERNsqAHptFt0Z69ilEtkfXruEocHm+y5GMgBcl26NDDxbeudqz1n78N2/VN tHlCfPRSaGfFAAht0MNQT2vk/8sSGiWuNj0nPQ/5biN7L7twMBN4hNgdve3rsdwj BVvFAXl+RF0kCF87Tb1GqQfc4QKBgQDHyeCxQysMZF5VWl4Z2VnMZNIXGs0/z0MX hYRBnvMKlJ7f8am5Q9vOCyVhASJfpcljmyxq9q9U06aZAQC3TGwxzHFU49gJjLss ETxwlCIVv9sx/YfX7NOU+f8oXgPXSHjlwteG77GHhznZGH1YMxhmYpYuQAlDov96 54ahmQgOZQKBgQCLgPoeO6m8JNAM10eaW5T53bFjFDCDbB78SCdoGQd2GYTPHzAp xiHJ8h73RP3aMfiDKZQzWd5q1DDXKJLsPsxOf8JUfbO3vfR53yH6RqB5jGyvUSaA ea1T7DZGaItoiD0WD30OP9b2Woy+O0YXHAsao3YN1859Lv2dXdvNsXhfoQKBgAk+ /YgWnLJpHriCgyJdHbvI2rQ5CFCwreEzy6ghzAusTyH8FCe/zTde6q7vl4CnsodK kiatHfYmCWrBUiApiERRutj75m6itbLXZV2sbVtmUqWgnx3OFwio/TcICtN2ximT 99eF1kh4aKP0bzCaxMFQRLUFP6iCraIpfuCRrX7FAoGAFZGFuvW1W9VF31JyrMYJ y/BH7vja3d9iZlhFzttNZ+wmiXG4irrI/fLJgmXK6dI3MfIhKPAYi9nnza2kcR1q EV9QObA4NV86RWnc8sAHbDGooe9VK5eJ5jjD7Tq/ZZiLiHGOZit3HylNilOb0k3V sgMcp0F3ZQaMbg35K9rSgZE= -----END PRIVATE KEY-----

Using a Python dictionary to hard-code the Okta domain and API token is encouraged for development; In production, you should use a more secure way of storing these values. This library supports a few different configuration sources, covered in the configuration reference section.

Extending the Client

When creating a new client, we allow for you to pass custom instances of okta.request_executor, okta.http_client and okta.cache.cache.

```py from okta.client import Client as OktaClient

Assuming implementations are in project.custom

from project.custom.requestexecutorimpl import RequestExecImpl from project.custom.httpclientimpl import HTTPClientImpl from project.custom.cache_impl import CacheImpl

config = { 'orgUrl': 'https://{yourOktaDomain}', 'token': 'YOURAPITOKEN', 'requestExecutor': RequestExecImpl, 'httpClient': HTTPClientImpl, 'cacheManager': CacheImpl(), # pass instance of CacheImpl 'cache': {'enabled': True} }

async def main(): client = OktaClient(config) userinfo, resp, err = await client.getuser({YOURUSERID}) print(user_info)

loop = asyncio.geteventloop() loop.rununtilcomplete(main()) ```

Extending or Creating New Classes

Example: You can create a custom cache driver by implementing okta.cache.cache

```py

Fully working example for Custom Cache class

from okta.cache.cache import Cache

class CacheImpl(Cache): def init(self): super().init() self.cache_dict = {}

def add(self, key, value):
    self.cache_dict[key] = value

def get(self, key):
    return self.cache_dict.get(key, None)

def contains(self, key):
    return key in self.cache_dict

def delete(self, key):
    if self.contains(key):
        del self.cache_dict[key]

```

A similar approach can be used to extend okta.request_executor:

```py from okta.request_executor import RequestExecutor

class RequestExecImpl(RequestExecutor): def init(self, config, cache, httpclient=None): super().init(config, cache, httpclient) # custom code

# Note, this method shoud be defined as async
async def create_request(self, method: str, url: str, body: dict = None,
                         headers: dict = {}, oauth=False):
    """
    Creates request for request executor's HTTP client.

    Args:
        method (str): HTTP Method to be used
        url (str): URL to send request to
        body (dict, optional): Request body. Defaults to None.
        headers (dict, optional): Request headers. Defaults to {}.

    Returns:
        dict, Exception: Tuple of Dictionary repr of HTTP request and
        exception raised during execution
    """
    # custom code

# Note, this method shoud be defined as async
async def execute(self, request, response_type=None):
    """
    This function is the high level request execution method. Performs the
    API call and returns a formatted response object

    Args:
        request (dict): dictionary object containing request details

    Returns:
        (OktaAPIResponse, Exception): Response obj for the Okta API, Error
    """
    # custom code

```

and okta.http_client:

```py from okta.http_client import HTTPClient

class HTTPClientImpl(HTTPClient): def init(self, httpconfig={}): super().init(httpconfig) # custom code

# Note, this method shoud be defined as async
async def send_request(self, request):
    """
    This method fires HTTP requests

    Arguments:
        request {dict} -- This dictionary contains all information needed
        for the request.
        - HTTP method (as str)
        - Headers (as dict)
        - Request body (as dict)

    Returns:
        Tuple(RequestInfo, ClientResponse, JSONBody, ErrorObject)
        -- A tuple containing the request and response of the HTTP call
    """
    # custom code

```

Usage guide

These examples will help you understand how to use this library.

Once you initialize a client, you can call methods to make requests to the Okta API. The client uses asynchronous methods to operate. Most methods are grouped by the API endpoint they belong to. For example, methods that call the Users API are organized under the User resource client (okta.resourceclients.userclient.py).

Asynchronous I/O is fairly new to Python after making its debut in Python 3.5. It's powered by the asyncio library which provides avenues to produce concurrent code. This allows developers to define async functions and await asynchronous calls within them. For more information, you can check out the Python docs.

Calls using await must be made in an async def function. That function must be called by asyncio (see example below).

```python from okta.client import Client as OktaClient import asyncio

async def main(): client = OktaClient() users, resp, err = await client.list_users() print(len(users))

loop = asyncio.geteventloop() loop.rununtilcomplete(main()) ```

Authenticate a User

This library should only be used with the Okta management API. To call the Authentication API, you should construct your own HTTP requests.

Assume the client is instantiated before each example below.

py from okta.client import Client as OktaClient import okta.models as models client = OktaClient({'orgUrl': 'https://test.okta.com', 'token': 'YOUR_API_TOKEN'})

Get and set custom attributes

Custom attributes must first be defined in the Okta profile editor. Then, you can work with custom attributes on a user:

Feature is fully supported with SDK version >= 1.2.0

```py """ Setting attributes """

Creating an instance through a Python Dictionary

from okta.models import UserProfile

user_profile = UserProfile({ 'firstName': 'John', 'lastName': 'Foe', 'email': 'John.Foe@okta.com', 'login': 'John.Foe@okta.com', 'customAttr': 'custom value' })

print(user_profile.customAttr)

Creating an empty object and using variables

userprofile = models.UserProfile() userprofile.firstname = 'John' userprofile.lastname = 'Doe' userprofile.email = 'John.Doe@okta.com' userprofile.login = 'John.Doe@okta.com' userprofile.customAttr = 'custom value' ```

NOTE: Custom Attributes case must be the same as defined in the Profile tab of your organization UI.

To maintain consistent code, you can use camelCase for all default attributes:

Feature is fully supported with SDK version >= 1.6.1

py user_profile = models.UserProfile() user_profile.firstName = 'John' user_profile.lastName = 'Doe' user_profile.email = 'John.Doe@okta.com' user_profile.login = 'John.Doe@okta.com' user_profile.customAttr = 'custom value'

Full example:

```py from okta.client import Client as OktaClient import asyncio

async def main(): client = OktaClient()

# create user with custom attribute
body = {
  "profile": {
    "firstName": "John",
    "lastName": "Smith",
    "email": "jsmith@matrix.com",
    "login": "jsmith@matrix.com",
    "customAttr": "custom value"
  },
  "credentials": {
    "password" : { "value": "Knock*knock*neo*111" }
  }
}
result = await client.create_user(body)

# create user without custom attribute
body = {
  "profile": {
    "firstName": "Neo",
    "lastName": "Anderson",
    "email": "nanderson@matrix.com",
    "login": "nanderson@matrix.com"
  },
  "credentials": {
    "password" : { "value": "Knock*knock*neo*111" }
  }
}
result = await client.create_user(body)

users, resp, err = await client.list_users()
for user in users:
    print(user.profile.first_name, user.profile.last_name)
    try:
        print(user.profile.customAttr)
    except:
        print('User has no customAttr')

loop = asyncio.geteventloop() loop.rununtilcomplete(main()) ```

Output should look like the following (removed pre-existing users from output):

sh John Smith custom value Neo Anderson User has no customAttr

Get and set custom headers

Feature appears in v1.3.0

It is possible to set custom headers, which will be sent with each request:

```py import asyncio

from okta.client import Client as OktaClient

async def main(): client = OktaClient()

# set custom headers
client.set_custom_headers({'Custom-Header': 'custom value'})

# perform different requests with custom headers
users, resp, err = await client.list_users()
for user in users:
    print(user.profile.first_name, user.profile.last_name)

# clear all custom headers
client.clear_custom_headers()

# output should be: {}
print(client.get_custom_headers())

loop = asyncio.geteventloop() loop.rununtilcomplete(main()) ```

Note, that custom headers will be overwritten with default headers with the same name. This doesn't allow breaking the client. Get default headers:

py client.get_default_headers()

Get a User

```py user, resp, err = await client.get_user(user.id)

OR using their login

user, resp, err = await client.get_user(user.profile.login) ```

List all Users

py users, resp, err = await client.list_users()

Filter or search for Users

```py

Query parameters are optional on methods that can use them!

Check the method definition for details on which query parameters are accepted.

queryparameters = {'filter': 'status eq "ACTIVE"'} users, resp, err = await client.listusers(query_parameters) ```

Create a User

```py

Create Password

password = models.PasswordCredential({ 'value': 'Password123' })

Create User Credentials

user_creds = models.UserCredentials({ 'password': password })

Create User Profile and CreateUser Request

userprofile = models.UserProfile() userprofile.firstname = 'John' userprofile.lastname = 'Doe' userprofile.email = 'John.Doe' user_profile.login = 'John.Doe'

createuserreq = models.CreateUserRequest({ 'credentials': usercreds, 'profile': userprofile })

Create User

user, resp, err = await client.createuser(createuser_req) ```

Update a User

```py

Assume user object saved to variable user

Craft new profile and get user object

newprofile = user.profile newprofile.nickname = 'Oktanaut' updateduserobj = models.User({'profile': newprofile})

Update User with new details

updateduser, _, err = await client.updateuser(user.id, updateduserobj) ```

Remove a User

You must first deactivate the user, and then you can delete the user.

```py

Assuming user starts off with a status of 'ACTIVE'

Deactivate

resp, err = await client.deactivateordelete_user(user.id)

Then delete

resp, err = await client.deactivateordelete_user(user.id) ```

List a User's Groups

py users_groups, resp, err = await client.list_user_groups(user.id)

Create a Group

```py

Create Group Model

groupprofile = models.GroupProfile({ 'name': 'Group-Test' }) groupmodel = models.Group({ 'profile': group_profile })

Create Group

group, resp, err = await client.creategroup(groupmodel) ```

Add a User to a Group

py resp, err = await client.add_user_to_group(group.id, user.id)

List a User's enrolled Factors

py supported_factors, resp, err = await client.list_supported_factors(user.id)

Enroll a User in a new Factor

```py

Create and enroll factor

smsfactor = models.SmsUserFactor({ 'profile': models.SmsUserFactorProfile({ 'phoneNumber': '+12345678901' }) }) enrolledfactor, , err = await client.enrollfactor(createduser.id, smsfactor) ```

Activate a Factor

py activate_factor_request = models.ActivateFactorRequest({ 'passCode': '123456' }) activated_factor, resp, err = await client.activate_factor(user.id, factor.id, activate_factor_request)

Verify a Factor

py verify_factor_request = models.ActivateFactorRequest({ 'passCode': '123456' }) verified_factor, resp, err = await client.activate_factor(user.id, factor.id, verify_factor_request)

List all Applications

py apps, resp, err = await client.list_applications()

Get an Application

py app, resp, err = await client.get_application(app.id)

Create a SWA Application

```py

Create SWA Application model and SWA Application in Okta

swaappsettings_app = models.SwaApplicationSettingsApplication({ 'buttonField': 'btn-login', 'passwordField': 'txt-box-password', 'usernameField': 'txt-box-username', 'url': 'https://example.com/login.html', 'loginUrlRegex': '^https://example.com/login.html$' })

swaappsettings = models.SwaApplicationSettings({ 'app': swaappsettings_app })

swaappmodel = models.SwaApplication({ 'label': 'SWA Test App', 'settings': swaappsettings, })

app, resp, err = await client.createapplication(swaapp_model) ```

Manage Group Schema custom atributes

There are 2 ways of creating custom attribute for Group Schema Profile:

  1. via UI of your ORG (Directory -> Profile Editor -> Groups)
  2. with the following request (create custom attribute with name "testCustomAttr"):

py definition = {'custom': {'id': '#custom', 'properties': {'testCustomAttr': {'description': 'Custom attribute for testing purposes', 'maxLength': 20, 'minLength': 1, 'permissions': [{'action': 'READ_WRITE', 'principal': 'SELF'}], 'required': False, 'title': 'Test Custom Attribute', 'type': 'string'}, 'required': []}, 'type': 'object' } } resp, _, err = await client.update_group_schema({'definitions': definition})

Update existing attribute:

```py

Get existing GroupSchema

resp, , err = await client.getgroup_schema()

Set new title for custom attribute 'testCustomAttr'

resp.definitions.custom.properties['testCustomAttr']['title'] = 'New Title'

Launch api request to update GroupSchema

resp, , err = await client.updategroup_schema(resp) ```

Call other API endpoints

Not every API endpoint is represented by a method in this library. You can call any Okta management API endpoint using this generic syntax:

```py

Example that doesn't return Object

request, error = await client.getrequestexecutor().createrequest( method='POST', url='/api/v1/users/USERID_HERE/lifecycle/activate', body={}, headers={}, oauth=False )

response, error = await client.getrequestexecutor().execute(request, None) responsebody = response.getbody()

Example that does return Object

request, error = await client.getrequestexecutor().createrequest( method='GET', url='/api/v1/users/USERID_HERE', body={}, headers={}, oauth=False )

response, error = await client.getrequestexecutor().execute(request, models.User)

responsebody = client.formresponsebody(response.getbody()) user = response.gettype()(responsebody) ```

Perform requests with empty parameters

Some group of api calls requires no empty parameters in request body, i.e. it is not allowed to pass within body the following samples {"some_var": ""} or {"some_var": []}. Meanwhile, other group of api calls allows setting empty parameters, which is used to clear value of related parameter on the server-side. By default all api calls are being performed without "empty" parameters, i.e. all empty parameters are being removed automatically before actual request. Starting from v2.1.0 users can control this behavior with parameter keep_empty_params:

This feature is supported with SDK version >= 2.1.0

```py

Default behavior, parameter "badgeNumber" won't be present in actual request

from okta.client import Client as OktaClient import asyncio

async def main(): client = OktaClient() userid = 'REDACTED' userparams = {"profile": {"phoneNumber": "1234567890", "badgeNumber": ""}} updateduser, resp, err = await client.partialupdateuser(userid, userparams) print(updateduser)

loop = asyncio.geteventloop() loop.rununtilcomplete(main()) ```

The same behavior when "keepemptyparams" is set to "False" (which is default for most methods, except UserSchema methods):

```py

Parameter "badgeNumber" won't be present in actual request

from okta.client import Client as OktaClient import asyncio

async def main(): client = OktaClient() userid = 'REDACTED' userparams = {"profile": {"phoneNumber": "1234567890", "badgeNumber": ""}} updateduser, resp, err = await client.partialupdateuser(userid, userparams, keepemptyparams=False) print(updateduser)

loop = asyncio.geteventloop() loop.rununtilcomplete(main()) ```

All empty parameters will be present in request when "keepemptyparams" is set to "True":

```py

Parameter "badgeNumber" will be present in actual request

from okta.client import Client as OktaClient import asyncio

async def main(): client = OktaClient() userid = 'REDACTED' userparams = {"profile": {"phoneNumber": "1234567890", "badgeNumber": ""}} updateduser, resp, err = await client.partialupdateuser(userid, userparams, keepemptyparams=True) print(updateduser)

loop = asyncio.geteventloop() loop.rununtilcomplete(main()) ```

Exceptions

Starting from v1.1.0 SDK introduces exceptions, which are disabled by default, thus feature is backward compatible. To force client raise an exception instead of returning custom error, option 'raiseException' should be provided:

```py import asyncio

from okta.client import Client as OktaClient from okta.exceptions import OktaAPIException

async def main(): config = {'orgUrl': 'https://{yourOktaDomain}', 'token': 'badtoken', 'raiseException': True} client = OktaClient(config) try: users, resp, err = await client.listusers() for user in users: print(user.profile.firstname, user.profile.lastname) except OktaAPIException as err: print(err)

loop = asyncio.geteventloop() loop.rununtilcomplete(main()) ```

Result should look like:

py {'errorCode': 'E0000011', 'errorSummary': 'Invalid token provided', 'errorLink': 'E0000011', 'errorId': 'oaeqWcqizEUQ_-iHc2hCbH9LA', 'errorCauses': []}

List of available exceptions: OktaAPIException, HTTPException (to raise instead of returning errors OktaAPIError and HTTPError respectively). It is possible to inherit and/or extend given exceptions:

```py from okta.exceptions import HTTPException

class MyHTTPException(HTTPException): pass

raise MyHTTPException('My HTTP Exception') ```

Pagination

If your request comes back with more than the default or set limit (resp.has_next() == True), you can request the next page.

Example of listing users 1 at a time:

```py queryparameters = {'limit': '1'} users, resp, err = await client.listusers(query_parameters)

Check if there more pages follow

if resp.has_next(): users, err = await resp.next() # Returns list of 1 user after the last retrieved user

Iterate through all of the rest of the pages

while resp.has_next(): users, err = await resp.next() # Do stuff with users in users

print(resp.has_next()) # False try: await resp.next() except StopAsyncIteration: # Handle Exception raised ```

Here's a complete example:

```python from okta.client import Client as OktaClient import asyncio

async def main(): client = OktaClient() users, resp, err = await client.listusers() while True: for user in users: print(user.profile.login) # Add more properties here. if resp.hasnext(): users, err = await resp.next() else: break

loop = asyncio.geteventloop() loop.rununtilcomplete(main()) ```

If you require access to the response headers during a pagination operation, set the includeResponse parameter to True in the call to the next method; this returns the response as a third tuple value. See the following example:

```python from okta.client import Client as OktaClient import asyncio

async def main(): client = OktaClient() users, resp, err = await client.listusers() while True: for user in users: print(user.profile.login) if resp.hasnext(): users, err, response = await resp.next(True) # Specify True to receive the response object as the third part of the tuple for further analysis else: break

loop = asyncio.geteventloop() loop.rununtilcomplete(main()) ```

Logging

Feature appears in version 1.5.0

SDK v1.5.0 introduces logging for debug purposes. Logs are disabled by default, thus SDK behavior remains the same. Logging should be enabled explicitly via client configuration or via a configuration file:

```py from okta.client import Client as OktaClient

config = {"logging": {"enabled": True}} client = OktaClient(config) ```

SDK utilizes the standard Python library logging. By default, log level INFO is set. You can set another log level via config:

```py from okta.client import Client as OktaClient import logging

config = {"logging": {"enabled": True, "logLevel": logging.DEBUG}} client = OktaClient(config) ```

NOTE: DO NOT SET DEBUG LEVEL IN PRODUCTION!

Here's a complete example:

```python from okta.client import Client as OktaClient import asyncio import logging

async def main(): config = {"logging": {"enabled": True, "logLevel": logging.DEBUG}} client = OktaClient(config) users, resp, err = await client.list_users() assert users is not None

loop = asyncio.geteventloop() loop.rununtilcomplete(main()) ```

You should now see logs in your console. Actual API Tokens will be logged to the console, so use caution and never use DEBUG level logging in production.

What it being logged: requests, http errors, caching responses.

Configuration reference

This library looks for configuration in the following sources:

  1. An okta.yaml file in a .okta folder in the current user's home directory (~/.okta/okta.yaml or %userprofile%\.okta\okta.yaml). See a sample YAML Configuration
  2. A okta.yaml file in the application or project's root directory. See a sample YAML Configuration
  3. Environment variables
  4. Configuration explicitly passed to the constructor (see the example in Getting started)

Only ONE source needs to be provided!

Higher numbers win. In other words, configuration passed via the constructor will OVERRIDE configuration found in environment variables, which will override configuration in the designated okta.yaml files.

YAML configuration

When you use an API Token instead of OAuth 2.0 the full YAML configuration looks like:

yaml okta: client: connectionTimeout: 30 # seconds orgUrl: "https://{yourOktaDomain}" proxy: port: { proxy_port } host: { proxy_host } username: { proxy_username } password: { proxy_password } token: "YOUR_API_TOKEN" requestTimeout: 0 # seconds rateLimit: maxRetries: 4 logging: enabled: true logLevel: INFO

When you use OAuth 2.0 the full YAML configuration looks like:

yaml okta: client: connectionTimeout: 30 # seconds orgUrl: "https://{yourOktaDomain}" proxy: port: { proxy_port } host: { proxy_host } username: { proxy_username } password: { proxy_password } authorizationMode: "PrivateKey" clientId: "YOUR_CLIENT_ID" scopes: - scope.1 - scope.2 privateKey: | -----BEGIN RSA PRIVATE KEY----- MIIEogIBAAKCAQEAl4F5CrP6Wu2kKwH1Z+CNBdo0iteHhVRIXeHdeoqIB1iXvuv4 THQdM5PIlot6XmeV1KUKuzw2ewDeb5zcasA4QHPcSVh2+KzbttPQ+RUXCUAr5t+r 0r6gBc5Dy1IPjCFsqsPJXFwqe3RzUb... -----END RSA PRIVATE KEY----- requestTimeout: 0 # seconds rateLimit: maxRetries: 4 logging: enabled: true logLevel: INFO

If a proxy is not going to be used for the SDK, you may omit the okta.client.proxy section from your okta.yaml file

Environment variables

Each one of the configuration values above can be turned into an environment variable name with the _ (underscore) character and UPPERCASE characters. The following are accepted:

  • OKTA_CLIENT_AUTHORIZATIONMODE
  • OKTA_CLIENT_ORGURL
  • OKTA_CLIENT_TOKEN
  • OKTA_CLIENT_CLIENTID
  • OKTA_CLIENT_SCOPES
  • OKTA_CLIENT_PRIVATEKEY
  • OKTA_CLIENT_USERAGENT
  • OKTA_CLIENT_CONNECTIONTIMEOUT
  • OKTA_CLIENT_REQUESTTIMEOUT
  • OKTA_CLIENT_CACHE_ENABLED
  • OKTA_CLIENT_CACHE_DEFAULTTTI
  • OKTA_CLIENT_CACHE_DEFAULTTTL
  • OKTA_CLIENT_PROXY_PORT
  • OKTA_CLIENT_PROXY_HOST
  • OKTA_CLIENT_PROXY_USERNAME
  • OKTA_CLIENT_PROXY_PASSWORD
  • OKTA_CLIENT_RATELIMIT_MAXRETRIES
  • OKTA_TESTING_TESTINGDISABLEHTTPSCHECK

Other configuration options

Starting with SDK v2.3.0 you can provide custom SSL context:

```py import asyncio import ssl

from okta.client import Client as OktaClient

async def main(): # create default context for demo purpose sslcontext = ssl.createdefaultcontext() client = OktaClient({"sslContext": sslcontext}) users, resp, err = await client.list_users() print(users)

loop = asyncio.geteventloop() loop.rununtilcomplete(main()) ```

Rate Limiting

The Okta API will return 429 responses if too many requests are made within a given time. Please see Rate Limiting at Okta for a complete list of which endpoints are rate limited. When a 429 error is received, the X-Rate-Limit-Reset header will tell you the time at which you can retry. This section discusses the method for handling rate limiting with this SDK.

Built-In Retry

This SDK uses the built-in retry strategy to automatically retry on 429 errors. You can use the default configuration options for the built-in retry strategy, or provide your desired values via the client configuration.

You can configure the following options when using the built-in retry strategy:

| Configuration Option | Description | | --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | client.requestTimeout | The waiting time in seconds for a request to be resolved by the client. Less than or equal to 0 means "no timeout". The default value is 0 (None). | | client.rateLimit.maxRetries | The number of times to retry. |

Check out the Configuration Reference section for more details about how to set these values via configuration.

Building the SDK

In most cases, you won't need to build the SDK from source. If you want to build it yourself, you'll need these prerequisites:

  • Clone the repo
  • Run python setup.py build from the root of the project (assuming Python is installed)
  • Ensure tests run succesfully. Install tox if not installed already using: pip install tox. Run tests using tox in the root directory of the project.

Contributing

We're happy to accept contributions and PRs! Please see the Contribution Guide to understand how to structure a contribution.

Owner

  • Name: Okta, Inc
  • Login: okta
  • Kind: organization

GitHub Events

Total
  • Create event: 16
  • Release event: 5
  • Issues event: 20
  • Watch event: 20
  • Delete event: 3
  • Issue comment event: 53
  • Push event: 26
  • Pull request review event: 12
  • Pull request event: 39
  • Fork event: 13
Last Year
  • Create event: 16
  • Release event: 5
  • Issues event: 20
  • Watch event: 20
  • Delete event: 3
  • Issue comment event: 53
  • Push event: 26
  • Pull request review event: 12
  • Pull request event: 39
  • Fork event: 13

Committers

Last synced: about 1 year ago

All Time
  • Total Commits: 406
  • Total Committers: 32
  • Avg Commits per committer: 12.688
  • Development Distribution Score (DDS): 0.495
Past Year
  • Commits: 16
  • Committers: 5
  • Avg Commits per committer: 3.2
  • Development Distribution Score (DDS): 0.563
Top Committers
Name Email Commits
Justin Abrokwah j****h@o****m 205
Serhii Buniak s****k@o****m 99
Brian Retterer b****r@g****m 27
Bryan Apellanes 6****a 13
BinoyOza-okta b****a@o****m 7
Gabriel Sroka g****a@g****m 7
lboyette-okta l****e@o****m 6
Jordan Melberg j****g@o****m 6
Ali McMaster a****1@g****m 3
Drew Carmichael d****l@o****m 3
Kees Hendriks k****s@f****l 3
Phong Nguyen 9****9 3
Vijet Mahabaleshwar v****r@o****m 3
James Stronz j****z@r****m 2
Joël Franusic j****a 2
Chaim Sanders c****t 1
Alan Scherger a****n@i****m 1
Robert Damphousse r****x@o****m 1
Oleksandr Kaleniuk o****k@t****m 1
Daniel del Hoyo d****o@s****m 1
Corentin Deret c****t@p****m 1
Corey Smith c****h@r****m 1
Cory Levine c****e@e****m 1
Gillen Burch 7****h 1
Gleb Radutsky g****y 1
Matt Cummins M****s@w****m 1
Nate Barbettini n****b@g****m 1
Nicolas Agustin Torres n****s@g****m 1
Omid Raha o****a 1
Tom Belote t****e@o****m 1
and 2 more...

Issues and Pull Requests

Last synced: 10 months ago

All Time
  • Total issues: 96
  • Total pull requests: 106
  • Average time to close issues: 6 months
  • Average time to close pull requests: 2 months
  • Total issue authors: 69
  • Total pull request authors: 39
  • Average comments per issue: 2.68
  • Average comments per pull request: 1.12
  • Merged pull requests: 69
  • Bot issues: 0
  • Bot pull requests: 1
Past Year
  • Issues: 11
  • Pull requests: 32
  • Average time to close issues: about 1 month
  • Average time to close pull requests: 8 days
  • Issue authors: 9
  • Pull request authors: 11
  • Average comments per issue: 1.73
  • Average comments per pull request: 0.5
  • Merged pull requests: 21
  • Bot issues: 0
  • Bot pull requests: 0
Top Authors
Issue Authors
  • bryanapellanes-okta (6)
  • molar-volume (4)
  • gabrielsroka (4)
  • exitcode0 (4)
  • saloni-canva (3)
  • leha007 (2)
  • kevinklitzke (2)
  • ghals13 (2)
  • RomainMichau (2)
  • qbui-os (2)
  • cmc333333 (2)
  • tjobarow (2)
  • calebevans (2)
  • bfrisbie-brex (2)
  • JimJohnsonColgate (2)
Pull Request Authors
  • bryanapellanes-okta (23)
  • BinoyOza-okta (14)
  • justinabrokwah-okta (11)
  • bretterer (10)
  • serhiibuniak-okta (7)
  • gabrielsroka (5)
  • jccaldw1 (4)
  • csanders-git (4)
  • andyj29 (3)
  • comrumino (2)
  • agburch (2)
  • David-Gilman (2)
  • nkhadka12 (2)
  • x972ff (2)
  • GraemeMeyerGT (2)
Top Labels
Issue Labels
viewed (5) bug (2) help wanted (2) priority-pending (2) OKTA-920838 (1) OKTA-899697 (1)
Pull Request Labels
dependencies (1)

Packages

  • Total packages: 1
  • Total downloads:
    • pypi 622,055 last-month
  • Total docker downloads: 6,733,335
  • Total dependent packages: 16
  • Total dependent repositories: 131
  • Total versions: 45
  • Total maintainers: 1
pypi.org: okta

Python SDK for the Okta Management API

  • Versions: 45
  • Dependent Packages: 16
  • Dependent Repositories: 131
  • Downloads: 622,055 Last month
  • Docker Downloads: 6,733,335
Rankings
Downloads: 0.4%
Docker downloads count: 0.7%
Dependent packages count: 0.8%
Dependent repos count: 1.3%
Average: 1.9%
Forks count: 3.7%
Stargazers count: 4.6%
Maintainers (1)
Last synced: 10 months ago

Dependencies

.github/workflows/python-package.yml actions
  • actions/checkout v2 composite
  • actions/setup-python v2 composite
openapi/package.json npm
  • @okta/openapi ^2.15.0
  • lodash ^4.17.21
openapi/yarn.lock npm
  • @apidevtools/json-schema-ref-parser 9.0.6
  • @apidevtools/openapi-schemas 2.1.0
  • @apidevtools/swagger-cli 4.0.4
  • @apidevtools/swagger-methods 3.0.2
  • @apidevtools/swagger-parser 10.1.0
  • @jsdevtools/ono 7.1.3
  • @okta/openapi 2.15.0
  • ajv 8.11.0
  • ajv-draft-04 1.0.0
  • ansi-regex 5.0.1
  • ansi-styles 4.3.0
  • argparse 1.0.10
  • call-me-maybe 1.0.2
  • camelcase 5.3.1
  • chalk 4.1.2
  • cliui 6.0.0
  • color-convert 2.0.1
  • color-name 1.1.4
  • commander 2.9.0
  • decamelize 1.2.0
  • emoji-regex 8.0.0
  • esprima 4.0.1
  • fast-deep-equal 3.1.3
  • find-up 4.1.0
  • fs-extra 3.0.1
  • get-caller-file 2.0.5
  • graceful-fs 4.2.10
  • graceful-readlink 1.0.1
  • handlebars 4.7.7
  • has-flag 4.0.0
  • is-fullwidth-code-point 3.0.0
  • js-yaml 3.14.1
  • json-schema-traverse 1.0.0
  • json-stable-stringify 1.0.1
  • jsonfile 3.0.1
  • jsonify 0.0.1
  • locate-path 5.0.0
  • lodash 4.17.21
  • minimist 1.2.7
  • neo-async 2.6.2
  • node-fetch 2.6.7
  • p-limit 2.3.0
  • p-locate 4.1.0
  • p-try 2.2.0
  • path-exists 4.0.0
  • punycode 2.1.1
  • require-directory 2.1.1
  • require-from-string 2.0.2
  • require-main-filename 2.0.0
  • set-blocking 2.0.0
  • source-map 0.6.1
  • sprintf-js 1.0.3
  • string-width 4.2.3
  • strip-ansi 6.0.1
  • supports-color 7.2.0
  • tr46 0.0.3
  • uglify-js 3.17.4
  • universalify 0.1.2
  • uri-js 4.4.1
  • webidl-conversions 3.0.1
  • whatwg-url 5.0.0
  • which-module 2.0.0
  • wordwrap 1.0.0
  • wrap-ansi 6.2.0
  • y18n 4.0.3
  • yargs 15.4.1
  • yargs-parser 18.1.3
requirements.txt pypi
  • aenum *
  • aiohttp *
  • flake8 *
  • flatdict *
  • pycryptodome *
  • pydash *
  • pyfakefs *
  • pytest *
  • pytest-asyncio *
  • pytest-mock *
  • pytest-recording *
  • python-jose *
  • pyyaml *
  • xmltodict *
  • yarl *
setup.py pypi
  • aenum *
  • aiohttp *
  • flatdict *
  • pycryptodome *
  • pydash *
  • python-jose *
  • pyyaml *
  • xmltodict *
  • yarl *