provisionesta/okta-api-client

Laravel的Okta API客户端

4.1 2024-03-18 20:03 UTC

This package is auto-updated.

Last update: 2024-09-18 21:10:12 UTC


README

[[目录]]

概述

Okta API客户端是一个开源的Composer包,用于在Laravel应用程序中连接到Okta,以进行用户、组、应用程序和其他相关功能的配置和取消配置。

该包由开源社区维护,不由任何公司维护。请在自己的风险下使用,并创建合并请求来解决您遇到的任何错误。

问题陈述

我们不提供API文档中每个端点的SDK方法,而是采取了一种更简单的方法,通过提供一个通用的ApiClient,该客户端可以执行针对Okta API文档中找到的任何端点的GETPOSTPUTDELETE请求。

这基于Laravel HTTP客户端的简单性,该客户端由Guzzle HTTP客户端提供支持,以提供对Okta API响应的“最后一条代码解析”,以改善开发人员体验。

此API客户端的价值在于它为您处理API请求日志记录、响应分页、速率限制回退和4xx/5xx异常处理。

有关包含预构建Laravel Actions的完整SDK,包括控制台命令、服务类方法、可调度作业和API端点,请参阅provisionesta/okta-laravel-actions包。

示例用法

use Provisionesta\Okta\ApiClient;

// Get a list of records
// https://developer.okta.com/docs/reference/api/groups/#list-groups
$groups = ApiClient::get('groups');

// Search for records with a specific name
// This example uses positional arguments
// https://developer.okta.com/docs/reference/core-okta-api/#filter
// https://developer.okta.com/docs/reference/api/groups/#list-groups-with-search
$groups = ApiClient::get('groups', [
    'search' => 'profile.name eq "Hack the Planet Engineers"'
]);

// Search for users with a specific
// This example uses positional arguments
// https://developer.okta.com/docs/reference/api/users/#list-users-with-search
$users = ApiClient::get('users', [
    'search' => 'profile.firstName eq "Dade"'
]);

// Get a specific record
// https://developer.okta.com/docs/reference/api/groups/#get-group
$group = ApiClient::get('groups/00g1ab2c3D4E5F6G7h8i');

// {
//     +"id": "0og1ab2c3D4E5F6G7h8i",
//     +"created": "2023-01-01T00:00:00.000Z",
//     +"lastUpdated": "2023-02-01T00:00:00.000Z",
//     +"lastMembershipUpdated": "2023-03-15T00:00:00.000Z",
//     +"type": "OKTA_GROUP",
//     +"profile": {
//         +"name": "Hack the Planet Engineers",
//         +"description": "This group contains engineers that have proven they are elite enough to hack the Gibson.",
//     },
// }

$group_name = $group->data->profile->name;
// Hack the Planet Engineers

// Create a group
// https://developer.okta.com/docs/reference/api/groups/#add-group
// This example uses named arguments
$group = ApiClient::post(
    uri: 'groups',
    data: [
        'profile' => [
            'name' => 'Hack the Planet Engineers',
            'description' => 'This group contains engineers that have proven they are elite enough to hack the Gibson.'
        ]
    ]
);

// Update a group
// https://developer.okta.com/docs/reference/api/groups/#update-group
// This example uses named arguments
$group_id = '00g1ab2c3D4E5F6G7h8i';
$group = ApiClient::put(
    uri: 'groups/' . $group_id,
    data: [
        'profile' => [
            'description' => 'This group contains engineers that have liberated the garbage files.'
        ]
    ]
);

// Delete a group
// https://developer.okta.com/docs/reference/api/groups/#remove-group
$group_id = '00g1ab2c3D4E5F6G7h8i';
ApiClient::delete('groups/' . $group_id);

问题跟踪和错误报告

我们不维护功能请求路线图,但我们邀请您贡献,我们将很高兴审查您的合并请求。

请为错误报告创建问题

贡献

请参阅CONTRIBUTING.md以了解有关如何贡献的更多信息。

维护者

姓名GitLab处理电子邮件
Jeff Martin@jeffersonmartinprovisionesta [at] jeffersonmartin [dot] com

贡献者信用

安装

要求

要求版本
PHP^8.0
Laravel^8.0, ^9.0, ^10.0, ^11.0

升级指南

请参阅变更日志以获取发行说明。

仍在使用glamstack/okta-sdk(v2.x)?请参阅v3.0变更日志以获取升级说明。

仍在使用gitlab-it/okta-sdk(v3.x)?请参阅v4.0变更日志以获取升级说明。

添加Composer包

composer require provisionesta/okta-api-client:^4.0

如果您正在为此包做出贡献,请参阅CONTRIBUTING.md以了解如何配置带有符号链接的本地Composer包的说明。

发布配置文件

这是可选的。配置文件指定了API连接存储在哪个.env变量名中。如果您想重命名OKTA_API_* .env变量名,则必须发布配置文件。

php artisan vendor:publish --tag=okta-api-client

连接凭据

环境变量

将以下变量添加到您的.env文件中。您可以在文件中的任何位置添加这些变量,并在新行上,或者添加到文件底部(任选)。

OKTA_API_URL="https://dev-123456789.okta.com"
OKTA_API_TOKEN=""

如果您正在使用多个连接(例如,开发版和产品版),只需创建两个变量块,并注释掉其中一个。

# Development
OKTA_API_URL="https://dev-123456789.okta.com"
OKTA_API_TOKEN=""

# Production
# OKTA_API_URL="https://mycompany.okta.com"
# OKTA_API_TOKEN=""

如果您将连接密钥存储在数据库或密钥管理器中,可以覆盖config/okta-api-client.php配置或在每次请求上提供一个连接数组。有关更多信息,请参阅连接数组

URL

每个Okta客户都会为其公司提供一个子域。这有时也称为租户或${yourOktaDomain}(在API文档中)。您还可以使用Okta预览实例。

如果您是刚开始使用,建议您使用免费的Okta开发者账户

OKTA_API_URL="https://mycompany.okta.com"

OKTA_API_URL="https://mycompany.oktapreview.com"

OKTA_API_URL="https://dev-12345678.okta.com"

API令牌

请参阅Okta文档中的创建API令牌

请注意,API令牌使用它所属用户的权限,因此对于生产应用程序用例,创建一个服务账户(机器人)用户是一种最佳实践。

如果您是刚开始使用,则应在您的生产实例中添加只读管理员管理员角色,并根据需要添加其他自定义权限。出于安全原因,您不应将超级管理员管理员角色授予此服务账户用户。

任何30天内未进行API调用而处于非活动状态的令牌将自动过期。

只需在您的.env文件中设置OKTA_API_TOKEN即可。

OKTA_API_TOKEN="S3cr3tK3yG03sH3r3"

内部开发者注意:当API客户端使用时,API密钥会自动加上前缀SSWS。在定义变量值时无需包含它。

连接数组

除非您使用包含URL和API令牌或客户端ID和客户端密钥的数组的连接参数设置连接,否则默认使用您在.env文件中定义的变量。

安全警告:不要将硬编码的API令牌提交到您的代码库中。这仅应在使用存储在数据库或密钥管理器中的动态变量时使用。

$connection = [
    'url' => 'https://dev-12345678.okta.com',
    'token' => 'S3cr3tK3yG03sH3r3'
];
use Provisionesta\Okta\ApiClient;

class MyClass
{
    private array $connection;

    public function __construct($connection)
    {
        $this->connection = $connection;
    }

    public function getGroup($group_id)
    {
        return ApiClient::get(
            connection: $this->connection,
            uri: 'groups/' . $group_id
        )->data;
    }
}

安全最佳实践

不共享令牌

不要使用您已为其他目的创建的API令牌。您应该为每个用例生成一个新的API令牌。

这在安全事件期间很有帮助,当时需要在受损害的系统上吊销密钥,而您不希望使用相同用户或服务账户的其他系统受到影响,因为这些系统使用的是不同的密钥,该密钥未被吊销。

API令牌存储

不要将您的API令牌添加到任何config/*.php文件中,以避免将其提交到您的存储库(秘密泄露)。

所有API令牌都应在.env文件中定义,该文件包含在.gitignore中,并且不会提交到您的存储库。

对于高级用例,您可以将变量存储在CI/CD变量或密钥库(例如Ansible Vault、AWS Parameter Store、GCP Secrets Manager、HashiCorp Vault等)中。

API令牌权限

不同的Okta API操作需要不同的管理员权限级别。API令牌继承了创建它们的管理员账户的权限级别。因此,在创建API令牌时创建服务账户是一个好习惯,这样您就可以为令牌分配所需的特定权限级别。请参阅管理员文档以了解管理员账户类型以及每种账户的具体权限。

API请求

您可以向Okta REST API文档中的任何资源端点发送API请求。

刚开始吗?探索应用程序用户端点。

端点API文档
apps列出应用程序
apps/{id}获取应用程序
apps/{id}/users列出分配给应用程序的用户
apps/{id}/groups列出分配给应用程序的组
groups列出组
groups/{id}获取组
groups/{id}/users列出组成员
users列出用户
users/{id}获取用户
users/{id}/appLinks获取分配给用户的程序

依赖注入

如果您在每个类顶部包含完全限定的命名空间,您可以在执行API调用的方法中使用类名。

use Provisionesta\Okta\ApiClient;

class MyClass
{
    public function getGroup($group_id)
    {
        return ApiClient::get('groups/' . $group_id)->data;
    }
}

如果您不使用依赖注入,则在使用类时需要提供完全限定的命名空间。

class MyClass
{
    public function getGroup($group_id)
    {
        return \Provisionesta\Okta\ApiClient::get('groups/' . $group_id)->data;
    }
}

类实例化

我们已过渡到使用v4.0中的静态方法,您不需要实例化ApiClient类。

ApiClient::get('groups');
ApiClient::post('groups', []);
ApiClient::get('groups/00g1ab2c3D4E5F6G7h8i');
ApiClient::put('groups/00g1ab2c3D4E5F6G7h8i', []);
ApiClient::delete('groups/00g1ab2c3D4E5F6G7h8i');

命名参数与位置参数

您可以使用命名参数(PHP 8中引入)或位置函数参数。

建议使用命名参数来指定请求数据和/或使用连接数组。如果只指定URI,则可以使用位置参数。

函数参数命名参数和这篇有用的博客文章中了解更多信息。

// Named Arguments
ApiClient::get(
    uri: 'groups'
);

// Positional Arguments
ApiClient::get('groups');

GET请求

端点在/api/v1/之后没有前置的/。Okta API文档提供了完整的端点,因此复制粘贴端点时请删除/api/v1/。

以下示例以列出所有组 API文档为参考。

使用API客户端,您使用get()方法,将端点groups作为uri参数。

ApiClient::get('groups');

您还可以使用变量或数据库模型来获取构建端点所需的数据。

// Get a list of records
// https://developer.okta.com/docs/reference/api/groups/#list-groups
$records = ApiClient::get('groups');

// Use variable for endpoint
$endpoint = 'groups';
$records = ApiClient::get($endpoint);

// Get a specific record
// https://developer.okta.com/docs/reference/api/groups/#get-group
$group_id = '0og1ab2c3D4E5F6G7h8i';
$record = ApiClient::get('groups/' . $group_id);

// Get a specific record using a variable
// This assumes that you have a database column named `api_group_id` that
// contains the string with the Okta ID `0og1ab2c3D4E5F6G7h8i`.
$okta_group = \App\Models\OktaGroup::where('id', $id)->firstOrFail();
$record = ApiClient::get('groups/' . $okta_group->api_group_id);

带有查询字符串参数的GET请求

get()方法的第二个位置参数或data命名参数是一个可选的参数数组,由API客户端和Laravel HTTP客户端解析,并自动添加?和&转换为查询字符串。

API请求过滤

Okta API使用profile子数组为多个资源服务。您为用户或组定义的大多数元数据都将在配置文件中。在搜索值时,您使用点表示法(例如profile.name)来访问这些属性。更多内容请参阅过滤器文档。您将看到对filtersearch的引用,但建议对所有查询使用search

API响应过滤

您还可以使用Laravel集合来过滤和转换结果,无论是使用完整的数据集还是使用您通过API请求已过滤的数据。

有关更多信息,请参阅使用Laravel集合

搜索具有特定名称的记录

https://developer.okta.com/docs/reference/api/groups/#list-groups-with-search

// Named Arguments
$records = ApiClient::get(
    uri: 'groups',
    data: ['search' => 'profile.name eq "Hack the Planet Engineers"']
);

// Positional Arguments
$records = ApiClient::get('groups', [
    'search' => 'profile.name eq "Hack the Planet Engineers"'
]);

// This will parse the array and render the query string
// https://mycompany.okta.com/api/v1/groups?search=profile.name+eq+%22Hack%20the&%20Planet%20Engineers%22
列出所有已停用的用户

https://developer.okta.com/docs/reference/api/users/#list-users-with-search

$records = ApiClient::get(
    uri: 'users',
    data: ['search' => 'status eq "DEPROVISIONED"']
);

// This will parse the array and render the query string
// https://mycompany.okta.com/api/v1/groups?search=status+eq+%22DEPROVISIONED%22
列出特定部门的所有用户

https://developer.okta.com/docs/reference/api/users/#list-users-with-search

$records = ApiClient::get(
    uri: 'users',
    data: ['search' => 'profile.department eq "Engineering"']
);

// This will parse the array and render the query string
// https://mycompany.okta.com/api/v1/groups?search=profile.department+eq+%22Engineering%22

POST请求

post()方法几乎与带有参数数组的get()请求相同,但是参数是以application/json内容类型作为表单数据传递,而不是作为URL查询字符串。这是行业标准,并非特定于API客户端。

您可以在Laravel HTTP客户端文档中了解更多有关请求数据的信息。

// Create a group
// https://developer.okta.com/docs/reference/api/groups/#add-group
$record = ApiClient::post(
    uri: 'groups',
    data: [
        'profile' => [
            'name' => 'Hack the Planet Engineers',
            'description' => 'This group contains engineers that have proven they are elite enough to hack the Gibson.'
        ]
    ]
);

PATCH请求

并非所有端点都支持部分更新。例如,用户端点支持,但组端点不支持。对于不支持部分更新的端点,您需要提供所有属性(例如,整个配置文件)。这可能需要检索记录并覆盖数组中特定键的值,然后将整个数组作为参数传递给API客户端的data参数。

patch()方法用于更新现有记录的一个或多个属性。补丁用于部分更新。如果您想更新并替换现有记录的所有属性,应使用put()方法

您需要确保在第一个参数(URI)中提供了要更新的记录的ID。在大多数应用程序中,这将是从您的数据库或其他位置获取的变量,而不是硬编码的。

// Update a group
// https://developer.okta.com/docs/reference/api/groups/#update-group
$group_id = '00g1ab2c3D4E5F6G7h8i';
$record = ApiClient::put(
    uri: 'groups/' . $group_id,
    data: [
        'profile' => [
            'description' => 'This group contains engineers that have liberated the garbage files.'
        ]
    ]
);

内部开发者说明:Okta API不支持PATCH请求,并使用非标准POST请求进行部分更新。在Okta API客户端中,使用patch()方法是为了改善开发人员的体验,我们使用Laravel HTTP客户端的post()方法在幕后。您可以在Okta API客户端中无任何问题地使用post()方法来更新记录,这只是为了符合使用PATCH的行业惯例。

PUT请求

put()方法用于更新并替换现有记录的所有属性。如果您想更新一个或多个属性而无需更新整个现有记录,请使用patch()方法。对于大多数用例,您将想要使用patch()方法来更新记录。

您需要确保在第一个参数(URI)中提供了要更新的记录的ID。在大多数应用程序中,这将是从您的数据库或其他位置获取的变量,而不是硬编码的。

// Update a group
// https://developer.okta.com/docs/reference/api/groups/#update-group
$group_id = '00g1ab2c3D4E5F6G7h8i';
$record = ApiClient::put(
    uri: 'groups/' . $group_id,
    data: [
        'profile' => [
            'name' => 'Hack the Planet Engineers',
            'description' => 'This group contains engineers that have revealed to the world their elite skills.'
        ]
    ]
);

DELETE请求

delete()方法用于根据您提供的ID销毁资源的操作。

请注意,delete() 方法会根据供应商返回不同的状态码(例如:200、201、202、204 等)。Okta 的 API 在成功删除资源时会返回 204 状态码。您应该使用 $response->status->successful 布尔值来检查结果。

// Delete a group
// https://developer.okta.com/docs/reference/api/groups/#remove-group
$group_id = '00g1ab2c3D4E5F6G7h8i';
$record = ApiClient::delete('groups/' . $group_id);

类方法

上面的示例展示了适合大多数用例的基本内联使用方法。如果您更喜欢使用类和构造函数,下面的示例将会有所帮助。

<?php

use Provisionesta\Okta\ApiClient;
use Provisionesta\Okta\Exceptions\NotFoundException;

class OktaGroupService
{
    private $connection;

    public function __construct(array $connection = [])
    {
        // If connection is null, use the environment variables
        $this->connection = !empty($connection) ? $connection : config('okta-api-client');
    }

    public function listGroups($query = [])
    {
        $groups = ApiClient::get(
            connection: $this->connection,
            uri: 'groups',
            data: $query
        );

        return $groups->data;
    }

    public function getGroup($id, $query = [])
    {
        try {
            $group = ApiClient::get(
                connection: $this->connection,
                uri: 'groups/' . $id,
                data: $query
            );
        } catch (NotFoundException $e) {
            // Custom logic to handle a record not found. For example, you could
            // redirect to a page and flash an alert message.
        }

        return $group->data;
    }

    public function storeGroup($request_data)
    {
        $group = ApiClient::post(
            connection: $this->connection,
            uri: 'groups',
            data: $request_data
        );

        // To return an object with the newly created group
        return $group->data;

        // To return the ID of the newly created group
        // return $group->data->id;

        // To return the status code of the form request
        // return $group->status->code;

        // To return a bool with the status of the form request
        // return $group->status->successful;

        // To throw an exception if the request fails
        // throw_if(!$group->status->successful, new \Exception($group->error->message, $group->status->code));

        // To return the entire API response with the data, headers, and status
        // return $group;
    }

    public function updateGroup($id, $request_data)
    {
        try {
            $group = ApiClient::put(
                connection: $this->connection,
                uri: 'groups/' . $id,
                data: $request_data
            );
        } catch (NotFoundException $e) {
            // Custom logic to handle a record not found. For example, you could
            // redirect to a page and flash an alert message.
        }

        // To return an object with the updated group
        return $group->data;

        // To return a bool with the status of the form request
        // return $group->status->successful;
    }

    public function deleteGroup($id)
    {
        try {
            $group = ApiClient::delete(
                connection: $this->connection,
                uri: 'groups/' . $id
            );
        } catch (NotFoundException $e) {
            // Custom logic to handle a record not found. For example, you could
            // redirect to a page and flash an alert message.
        }

        return $group->status->successful;
    }
}

速率限制

大多数速率限制是由于大响应的分页(例如:/users 端点)导致的。如果您有大量数据集,您可能需要考虑使用 search 查询来过滤结果以获得更少的记录。

在 v4.0 版本中,我们添加了当剩余 20% 速率限制时自动回退。这通过在每个请求中实现 sleep(10) 来减缓请求。由于速率限制在 60 秒后重置,这将减缓接下来的 5-6 个请求,直到速率限制重置。

如果 Okta 的端点速率限制超过,将抛出 Provisionesta\Okta\Exceptions\RateLimitException

回退会减缓请求,但如果超过速率限制,请求将失败并终止。

API 响应

此 API 客户端使用 Provisionesta 标准 API 响应格式化。

// API Request
$group = ApiClient::get('groups/00g1ab2c3D4E5F6G7h8i');

// API Response
$group->data; // object
$group->headers; // array
$group->status; // object
$group->status->code; // int (ex. 200)
$group->status->ok; // bool (is 200 status)
$group->status->successful; // bool (is 2xx status)
$group->status->failed; // bool (is 4xx/5xx status)
$group->status->clientError; // bool (is 4xx status)
$group->status->serverError; // bool (is 5xx status)

响应数据

data 属性包含已解析的 Laravel HTTP 客户端 object() 方法的内容,并具有任何分页结果的最终合并输出。

$group = ApiClient::get('groups/00g1ab2c3D4E5F6G7h8i');
$group->data;
{
    +"id": "0og1ab2c3D4E5F6G7h8i",
    +"created": "2023-01-01T00:00:00.000Z",
    +"lastUpdated": "2023-02-01T00:00:00.000Z",
    +"lastMembershipUpdated": "2023-03-15T00:00:00.000Z",
    +"type": "OKTA_GROUP",
    +"profile": {
        +"name": "Hack the Planet Engineers",
        +"description": "This group contains engineers that have proven they are elite enough to hack the Gibson.",
    },
}

访问单个记录值

您可以使用对象符号访问这些变量。这是处理 API 响应的最常见用例。

$group = ApiClient::get('groups/00g1ab2c3D4E5F6G7h8i')->data;

$group_name = $group->profile->name;
// Hack the Planet Engineers

遍历记录

如果您有一组多个对象,您可以遍历记录。API 客户端会自动分页并合并记录数组,以改善开发人员体验。

$groups = ApiClient::get('groups')->data;

foreach($groups as $group) {
    dd($group->profile->name);
    // Hack the Planet Engineers
}

缓存响应

API 客户端不使用缓存,以避免您在缓存端点时遇到任何约束。

在调用 API 时,您可以将端点包装在缓存外观中。您可以在 Laravel Cache 文档中了解更多信息。

use Illuminate\Support\Facades\Cache;
use Provisionesta\Okta\ApiClient;

$groups = Cache::remember('okta_groups', now()->addHours(12), function () {
    return ApiClient::get('groups')->data;
});

foreach($groups as $group) {
    dd($group->profile->name);
    // Hack the Planet Engineers
}

在获取特定 ID 或传递附加参数时,请确保将变量传递到 use($var1, $var2)

$group_id = '00g1ab2c3D4E5F6G7h8i';

$groups = Cache::remember('okta_group_' . $group_id, now()->addHours(12), function () use ($group_id) {
    return ApiClient::get('groups/' . $group_id)->data;
});

日期格式化

您可以使用 Carbon 库来格式化日期并执行计算。

$created_date = Carbon::parse($group->data->created)->format('Y-m-d');
// 2023-01-01
$created_age_days = Carbon::parse($group->data->created)->diffInDays();
// 265

使用 Laravel 集合

您可以使用 Laravel 集合,它是一种强大的数组辅助工具,类似于您可能已经熟悉的数组搜索和 SQL 查询。

有关更多信息,请参阅 使用 Laravel 集合解析响应 文档。

响应头

由于键使用连字符,这些键与访问键和值的语法冲突,因此头信息以数组形式返回而不是对象。

$group = ApiClient::get('groups/00g1ab2c3D4E5F6G7h8i');
$group->headers;
[
    "Date" => "Sun, 30 Jan 2022 01:11:44 GMT",
    "Content-Type" => "application/json",
    "Transfer-Encoding" => "chunked",
    "Connection" => "keep-alive",
    "Server" => "nginx",
    "Public-Key-Pins-Report-Only" => "pin-sha256="REDACTED="; pin-sha256="REDACTED="; pin-sha256="REDACTED="; pin-sha256="REDACTED="; max-age=60; report-uri="https://okta.report-uri.com/r/default/hpkp/reportOnly"",
    "Vary" => "Accept-Encoding",
    "x-okta-request-id" => "A1b2C3D4e5@f6G7H8I9j0k1L2M3",
    "x-xss-protection" => "0",
    "p3p" => "CP="HONK"",
    "x-rate-limit-limit" => "1000",
    "x-rate-limit-remaining" => "998",
    "x-rate-limit-reset" => "1643505155",
    "cache-control" => "no-cache, no-store",
    "pragma" => "no-cache",
    "expires" => "0",
    "content-security-policy" => "default-src 'self' mycompany.okta.com *.oktacdn.com; connect-src 'self' mycompany.okta.com mycompany-admin.okta.com *.oktacdn.com *.mixpanel.com *.mapbox.com app.pendo.io data.pendo.io pendo-static-5634101834153984.storage.googleapis.com mycompany.kerberos.okta.com https://oinmanager.okta.com data:; script-src 'unsafe-inline' 'unsafe-eval' 'self' mycompany.okta.com *.oktacdn.com; style-src 'unsafe-inline' 'self' mycompany.okta.com *.oktacdn.com app.pendo.io cdn.pendo.io pendo-static-5634101834153984.storage.googleapis.com; frame-src 'self' mycompany.okta.com mycompany-admin.okta.com login.okta.com; img-src 'self' mycompany.okta.com *.oktacdn.com *.tiles.mapbox.com *.mapbox.com app.pendo.io data.pendo.io cdn.pendo.io pendo-static-5634101834153984.storage.googleapis.com data: blob:; font-src 'self' mycompany.okta.com data: *.oktacdn.com fonts.gstatic.com",
    "expect-ct" => "report-uri="https://oktaexpectct.report-uri.com/r/t/ct/reportOnly", max-age=0",
    "x-content-type-options" => "nosniff",
    "Strict-Transport-Security" => "max-age=315360000; includeSubDomains",
    "set-cookie" => "sid=""; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Path=/ autolaunch_triggered=""; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Path=/ JSESSIONID=E07ED763D2ADBB01B387772B9FB46EBF; Path=/; Secure; HttpOnly"
]

获取头值

$content_type = $group->headers['Content-Type'];
// application/json

响应状态

有关不同状态布尔值的信息,请参阅 Laravel HTTP 客户端文档

$group = ApiClient::get('groups/00g1ab2c3D4E5F6G7h8i');
$group->status;
{
  +"code": 200 // int (ex. 200)
  +"ok": true // bool (is 200 status)
  +"successful": true // bool (is 2xx status)
  +"failed": false // bool (is 4xx/5xx status)
  +"serverError": false // bool (is 4xx status)
  +"clientError": false // bool (is 5xx status)
}

API 响应状态码

$group = ApiClient::get('groups/00g1ab2c3D4E5F6G7h8i');

$status_code = $group->status->code;
// 200

错误响应

对于任何 4xx 或 5xx 响应都会抛出异常。所有响应都会自动记录。

异常

代码异常类
400Provisionesta\Okta\Exceptions\BadRequestException
401Provisionesta\Okta\Exceptions\UnauthorizedException
403Provisionesta\Okta\Exceptions\ForbiddenException
404Provisionesta\Okta\Exceptions\NotFoundException
412Provisionesta\Okta\Exceptions\PreconditionFailedException
422Provisionesta\Okta\Exceptions\UnprocessableException
429Provisionesta\Okta\Exceptions\RateLimitException
500Provisionesta\Okta\Exceptions\ServerErrorException

捕获异常

您可以捕获您想要静默处理的任何异常。任何未捕获的异常将出现在用户面前,并在您的监控软件中引起500错误。

use Provisionesta\Okta\Exceptions\NotFoundException;

try {
    $group = ApiClient::get('groups/00g1ab2c3D4E5F6G7h8i');
} catch (NotFoundException $e) {
    // Group is not found. You can create a log entry, throw an exception, or handle it another way.
    Log::error('Okta group could not be found', ['okta_group_id' => $group_id]);
}

禁用异常

如果您不希望抛出异常,您可以为Okta API客户端全局禁用异常,并自行处理每个请求的状态。只需在您的.env文件中设置OKTA_API_EXCEPTIONS=false

OKTA_API_EXCEPTIONS=false

使用Laravel集合解析响应

您可以使用 Laravel 集合,它是一种强大的数组辅助工具,类似于您可能已经熟悉的数组搜索和 SQL 查询。

$users = ApiClient::get('users');

$user_collection = collect($users->data)->where('profile.department', 'Security')->toArray();

// This will return an array of users that belong to the Security department based on their profile attribute

为了语法规范和可读性,您可以轻松将其折叠成一行。由于ApiClient自动处理任何4xx或5xx错误,您无需担心try/catch异常。

$users = collect(ApiClient::get('users')->data)
    ->where('profile.department', 'Security')
    ->toArray();

这种方法可以使您获得与执行SQL查询相同的优势,并且在您开始使用集合时将感觉非常熟悉。

SELECT * FROM users WHERE department='Security';

集合方法

对过滤数据有用的最常见方法包括

Laravel文档使用示例
count使用示例
countBy使用示例
exceptN/A
filterN/A
flipN/A
groupBy使用示例
keyByN/A
onlyN/A
pluck使用示例
sort使用示例
sortBy使用示例
sortKeys使用示例
toArrayN/A
transform使用示例
unique使用示例
values使用示例
whereN/A
whereInN/A
whereNotInN/A

简化数组集合

Pluck方法

您可以使用pluck方法通过集合获取特定属性。

// Get an array with email addresses
$user_job_titles = collect(ApiClient::get('users')->data)
    ->pluck('profile.email')
    ->toArray();

// [
//     0 => 'mspinka@example.com',
//     1 => 'rferry@example.com',
//     2 => 'sconnelly@example.com',
// ]

您还可以使用pluck方法获取两个属性,并将其中一个设置为数组键,另一个设置为数组值。

// Get an array with email address keys and job title values
$user_job_titles = collect(ApiClient::get('users')->data)
    ->pluck('profile.title', 'profile.email')
    ->toArray();

// [
//     'rferry@example.com' => 'Senior Frontend Engineer',
//     'mspinka@example.com' => 'Professional Services Engineer',
//     'sconnelly@example.com' => 'Frontend Engineer',
// ]

Unique方法

您可以使用unique方法获取唯一属性值列表(例如,职位名称)。

每个方法都可以链式调用,并且按顺序依次进行评估。

虽然从编程角度来看,先找到唯一的数组值然后解析它们可能更有效率,但使用集合时您有灵活性,可以根据可读性进行任意处理。速度提升和内存占用微乎其微(小于10%),并且由于这通常作为一个后台任务处理,建议您关注可读性和个人偏好。

// Get an array of unique job titles

// Option 1
$unique_job_titles = collect(ApiClient::get('users')->data)
    ->unique('profile.title')
    ->pluck('profile.title')
    ->toArray();

// Option 2 (marginally faster)
$unique_job_titles = collect(ApiClient::get('users')->data)
    ->pluck('profile.title')
    ->unique()
    ->toArray();

// [
//     236 => 'Professional Services Engineer',
//     511 => 'Senior Frontend Engineer',
//     988 => 'Frontend Engineer',
// ]

Values方法

当使用unique方法时,它使用找到的用户记录的键。您应该在最后添加values方法来根据您拥有的结果数量重置所有整数键。

// Get an array of unique job titles

// Option 1
$unique_job_titles = collect(ApiClient::get('users')->data)
    ->unique('profile.title')
    ->pluck('profile.title')
    ->values()
    ->toArray();

// Option 2
$unique_job_titles = collect(ApiClient::get('users')->data)
    ->pluck('profile.title')
    ->unique()
    ->values()
    ->toArray();

// [
//     0 => 'Professional Services Engineer',
//     1 => 'Senior Frontend Engineer',
//     2 => 'Frontend Engineer',
// ]

排序方法

您可以通过属性值进行字母排序。只需将属性提供给sortBy方法(支持嵌套数组值)。如果您已经使用了pluck方法,并且数组值是字符串,则可以使用不接收参数的sort方法。

// Get an array of unique job titles

// Option 1
$unique_job_titles = collect(ApiClient::get('users')->data)
    ->sortBy('profile.title')
    ->unique('profile.title')
    ->pluck('profile.title')
    ->values()
    ->toArray();

// Option 2
$unique_job_titles = collect(ApiClient::get('users')->data)
    ->pluck('profile.title')
    ->unique()
    ->sort()
    ->values()
    ->toArray();

// [
//     0 => 'Frontend Engineer',
//     1 => 'Professional Services Engineer',
//     2 => 'Senior Frontend Engineer',
// ]

如果您有数组键字符串,则可以使用sortKeys方法按字母顺序对结果数组键进行排序。

// Get an array with email address keys and job title values
$user_job_titles = collect(ApiClient::get('users')->data)
    ->pluck('profile.title', 'profile.email')
    ->sortKeys()
    ->toArray();

// [
//     'mspinka@example.com' => 'Professional Services Engineer',
//     'rferry@example.com' => 'Senior Frontend Engineer',
//     'sconnelly@example.com' => 'Frontend Engineer',
// ]

计数方法

您可以使用count方法获取在应用所有方法后的结果总数。这作为toArray的替代方案,以便您得到一个整数值,而无需执行count($collection_array)

// Get a count of unique job titles
$unique_job_titles = collect(ApiClient::get('users')->data)
    ->pluck('profile.title')
    ->unique()
    ->count();

// 376

您可以使用countBy方法获取唯一属性值的计数。您应该使用sortKeys方法按字母顺序对结果数组键进行排序。

// Get a count of unique job titles
$unique_job_titles = collect(ApiClient::get('users')->data)
    ->countBy('profile.title')
    ->sortKeys()
    ->toArray();

// [
//     'Frontend Engineer' => 8,
//     'Professional Services Engineer' => 4,
//     'Senior Frontend Engineer' => 44,
// ]

转换数组

在使用API返回的记录时,您将拥有很多当前用例中不需要的数据。

原始响应

// Disclaimer: This is anonymized fake data.
[
    {
      +"id": "00ue2xov9e5xiQmuL5d7",
      +"status": "ACTIVE",
      +"created": "2023-12-23T16:49:49.000Z",
      +"activated": "2023-12-23T16:49:50.000Z",
      +"statusChanged": "2023-12-23T16:49:50.000Z",
      +"lastLogin": null,
      +"lastUpdated": "2023-12-23T16:49:50.000Z",
      +"passwordChanged": "2023-12-23T16:49:50.000Z",
      +"type": {
        +"id": "otye2ebqn49728Yfb5d7",
      },
      +"profile": {
        +"lastName": "Howe",
        +"costCenter": "Sales",
        +"displayName": "Angelica Howe",
        +"secondEmail": null,
        +"managerId": "5f9632",
        +"hire_date": "2020-12-19",
        +"title": "Senior Channel Sales Manager",
        +"login": "ahowe@example.com",
        +"employeeNumber": "aee562",
        +"division": "Sales",
        +"firstName": "Angelica",
        +"management_level": "Individual Contributor",
        +"mobilePhone": null,
        +"department": "Channel Sales",
        +"email": "ahowe@example.com",
      },
    },
    {
      +"id": "00ue2xp1yybaQEE2o5d7",
      +"status": "ACTIVE",
      +"created": "2023-12-23T16:49:12.000Z",
      +"activated": "2023-12-23T16:49:12.000Z",
      +"statusChanged": "2023-12-23T16:49:12.000Z",
      +"lastLogin": null,
      +"lastUpdated": "2023-12-23T16:49:12.000Z",
      +"passwordChanged": "2023-12-23T16:49:12.000Z",
      +"type": {
        +"id": "otye2ebqn49728Yfb5d7",
      },
      +"profile": {
        +"lastName": "O'Kon",
        +"costCenter": "Sales",
        +"displayName": "Earlene O'Kon",
        +"secondEmail": null,
        +"managerId": "2410f0",
        +"hire_date": "2019-03-01",
        +"title": "Manager, Deal Desk",
        +"login": "eo'kon@example.com",
        +"employeeNumber": "0561bc",
        +"division": "Sales",
        +"firstName": "Earlene",
        +"management_level": "Manager",
        +"mobilePhone": null,
        +"department": "Sales Operations",
        +"email": "eo'kon@example.com",
      },
    },
  ]

基本转换

您可以使用 transform 方法对每条记录执行foreach循环,并创建一个包含您想要的具体字段的新数组。

您可以将 $item 变量视为 foreach($users as $item) { },它包含特定记录的所有元数据。

transform方法使用一个函数(也称为闭包)来返回应成为此特定数组键的新值的数组。

// Get all Okta users
$users = collect(ApiClient::get('users')->data)
    ->transform(function($item) {
        return [
            'id' => $item->id,
            'displayName' => $item->profile->displayName,
            'email' => $item->profile->email,
            'title' => $item->profile->title,
            'department' => $item->profile->department
        ];
    })->toArray();

// [
//     "id" => "00ue2xov9e5xiQmuL5d7",
//     "displayName" => "Angelica Howe",
//     "email" => "ahowe@example.com",
//     "title" => "Senior Channel Sales Manager",
//     "department" => "Channel Sales",
// ],
// [
//     "id" => "00ue2xp1yybaQEE2o5d7",
//     "displayName" => "Earlene O'Kon",
//     "email" => "eo'kon@example.com",
//     "title" => "Manager, Deal Desk",
//     "department" => "Sales Operations",
// ],
检查属性是否存在

当使用transform方法时,您确实需要使用 isset 来检查值是否存在,或将它们设置为null,对于并非每个记录都有的字段。如果您使用 null coalescing operators,您将遇到额外的调试问题,因此建议坚持使用 isset()。使用 三元运算符 是一种最佳实践,以确保语法的一致性。

$users = collect(ApiClient::get('users')->data)
    ->transform(function($item) {
        return [
            'id' => $item->id,
            'displayName' => $item->profile->displayName,
            'email' => $item->profile->email,
            'title' => isset($item->profile->title) ? $item->profile->title : null,
            'department' => isset($item->profile->department) ? $item->profile->department : null
        ];
    })->toArray();
箭头函数

如果您的所有转换都可以在数组内完成,并且不需要定义额外的变量(参见 高级转换),则可以使用简写的箭头函数。这是一个个人偏好,并不是一个要求。

$users = collect(ApiClient::get('users')->data)
    ->transform(fn($item) => [
        'id' => $item->id,
        'displayName' => $item->profile->displayName,
        'email' => $item->profile->email,
        'title' => isset($item->profile->title) ? $item->profile->title : null,
        'department' => isset($item->profile->department) ? $item->profile->department : null
    ])->toArray();

高级转换

您还可以在将值传递给数组之前在transform函数中执行额外的计算。这为您提供了最大的灵活性、自由和力量,以完成所需的所有操作。

是否定义变量或在行内执行计算取决于您。

use Carbon\Carbon;

$users = collect(ApiClient::get('users')->data)
    ->transform(function($item) {
        // Calculate dates using Carbon (https://carbon.nesbot.com/docs/)
        $created_date = Carbon::parse($item->created)->format('Y-m-d');
        $created_date_age = Carbon::parse($item->created)->diffInDays();

        // It is recommended to use match statements instead of if/else statements for string matching use cases
        $elevated_permissions = match($item->profile->department) {
            'Infrastructure' => true,
            'IT' => true,
            'Security' => true,
            default => false
        };

        return [
            'id' => $item->id,
            'displayName' => $item->profile->displayName,
            'email' => $item->profile->email,
            'title' => isset($item->profile->title) ? $item->profile->title : null,
            'department' => isset($item->profile->department) ? $item->profile->department : null,
            'created_date' => $created_date,
            'new_user' => ($created_date_age < 60 ? true : false),
            'elevated_permissions' => $elevated_permissions
        ];
    })->toArray();

// [
//     "id" => "00ue2xov9e5xiQmuL5d7",
//     "displayName" => "Angelica Howe",
//     "email" => "ahowe@example.com",
//     "title" => "Senior Channel Sales Manager",
//     "department" => "Channel Sales",
//     "created_date" => "2023-12-23",
//     "new_user" => true,
//     "elevated_permissions" => false,
// ],
// [
//     "id" => "00ue2xp1yybaQEE2o5d7",
//     "displayName" => "Earlene O'Kon",
//     "email" => "eo'kon@example.com",
//     "title" => "Manager, Deal Desk",
//     "department" => "Sales Operations",
//     "created_date" => "2023-12-23",
//     "new_user" => true,
//     "elevated_permissions" => false,
// ],

分组方法

尽管您可以使用 groupBy 方法与原始响应一起使用,但一旦数据被分组,就很难操作数据,因此建议您在转换数据后,将 groupBy('attribute_name') 添加到您的集合链的末尾。请注意,当您转换数据时,您已重命名了数组值键(属性),因此您想使用新的数组键。在示例中,我们定义了一个新的 department 属性,因此 profile.department 将不再可访问。

$users = collect(ApiClient::get('users')->data)
    ->transform(fn($item) => [
        'id' => $item->id,
        'displayName' => $item->profile->displayName,
        'email' => $item->profile->email,
        'title' => isset($item->profile->title) ? $item->profile->title : null,
        'department' => isset($item->profile->department) ? $item->profile->department : null
    ])->groupBy('department')
    ->toArray();

// "Channel Sales" => [
//     [
//         "id" => "00ue2xov9e5xiQmuL5d7",
//         "displayName" => "Angelica Howe",
//         "email" => "ahowe@example.com",
//         "title" => "Senior Channel Sales Manager",
//         "department" => "Channel Sales",
//     ],
// ],
// "Sales Operations" => [
//     [
//         "id" => "00ue2xp1yybaQEE2o5d7",
//         "displayName" => "Earlene O'Kon",
//         "email" => "eo'kon@example.com",
//         "title" => "Manager, Deal Desk",
//         "department" => "Sales Operations",
//     ],
//     [
//         "id" => "00ue2xpoh6h5rfN315d7",
//         "displayName" => "Rylee Veum",
//         "email" => "rveum@example.com",
//         "title" => "Senior Program Manager, Customer Programs",
//         "department" => "Sales Operations",
//     ],
// ],

阅读更多内容

有关其他用法,请参阅 Laravel Collections 文档。有关其他真实世界的示例,请参阅 provisionesta/okta-laravel-actions 包。

日志示例

此包使用 provisionesta/audit 包进行标准化日志。

事件类型

应使用 event_type 键进行任何分类和日志搜索。

  • 格式: okta.api.{method}.{result/log_level}.{reason?}
  • 方法: get|post|patch|put|delete
状态码事件类型日志级别
N/Aokta.api.test.successDEBUG
N/Aokta.api.test.error.{okta_error_code}CRITICAL
N/Aokta.api.test.error.unknownCRITICAL
N/Aokta.api.validate.errorCRITICAL
N/Aokta.api.get.process.pagination.startedDEBUG
N/Aokta.api.get.process.pagination.finishedDEBUG
N/Aokta.api.rate-limit.approachingCRITICAL
N/Aokta.api.rate-limit.exceeded (Pre-Exception)CRITICAL
N/Aokta.api.{method}.error.http.exceptionERROR
200okta.api.{method}.successDEBUG
201okta.api.{method}.successDEBUG
202okta.api.{method}.successDEBUG
204okta.api.{method}.successDEBUG
400okta.api.{method}.warning.bad-requestWARNING
401okta.api.{method}.error.unauthorizedERROR
403okta.api.{method}.error.forbiddenERROR
404okta.api.{method}.warning.not-foundWARNING
405okta.api.{method}.error.method-not-allowedERROR
412okta.api.{method}.error.precondition-failedDEBUG
422okta.api.{method}.error.unprocessableDEBUG
429okta.api.{method}.critical.rate-limitCRITICAL
500okta.api.{method}.critical.server-errorCRITICAL
501okta.api.{method}.error.not-implementedERROR
503okta.api.{method}.critical.server-unavailableCRITICAL

成功的请求

GET 请求日志

[YYYY-MM-DD HH:II:SS] local.DEBUG: ApiClient::get Success {"event_type":"okta.api.get.success","method":"Provisionesta\\Okta\\ApiClient::get","event_ms":453,"metadata":{"okta_request_id":"REDACTED","rate_limit_remaining":"499","url":"https://dev-12345678.okta.com/api/v1/org"}}

GET 分页请求日志

[YYYY-MM-DD HH:II:SS] local.DEBUG: ApiClient::get Success {"event_type":"okta.api.get.success","method":"Provisionesta\\Okta\\ApiClient::get","count_records":200,"event_ms":1081,"event_ms_per_record":5,"metadata":{"okta_request_id":"REDACTED","rate_limit_remaining":"299","url":"https://dev-12345678.okta.com/api/v1/users?limit=200&search=status+eq+%22ACTIVE%22+or+%28status+eq+%22DEPROVISIONED%22+and+statusChanged+ge+%222023-10-01T15%3A02%3A15.491037Z%22%29"}}
[YYYY-MM-DD HH:II:SS] local.DEBUG: ApiClient::get Paginated Results Process Started {"event_type":"okta.api.get.process.pagination.started","method":"Provisionesta\\Okta\\ApiClient::get","metadata":{"okta_request_id":"REDACTED","uri":"users"}}
[YYYY-MM-DD HH:II:SS] local.DEBUG: ApiClient::getPaginatedResults Success {"event_type":"okta.api.getPaginatedResults.success","method":"Provisionesta\\Okta\\ApiClient::getPaginatedResults","count_records":200,"event_ms":2346,"event_ms_per_record":11,"metadata":{"okta_request_id":"REDACTED","rate_limit_remaining":"298","url":"https://dev-12345678.okta.com/api/v1/users?after=00uREDACTED&limit=200&search=status+eq+%22ACTIVE%22+or+%28status+eq+%22DEPROVISIONED%22+and+statusChanged+ge+%222023-10-01T15%3A02%3A15.491037Z%22%29"}}
[YYYY-MM-DD HH:II:SS] local.DEBUG: ApiClient::getPaginatedResults Success {"event_type":"okta.api.getPaginatedResults.success","method":"Provisionesta\\Okta\\ApiClient::getPaginatedResults","count_records":200,"event_ms":1577,"event_ms_per_record":7,"metadata":{"okta_request_id":"REDACTED","rate_limit_remaining":"297","url":"https://dev-12345678.okta.com/api/v1/users?after=00uREDACTED&limit=200&search=status+eq+%22ACTIVE%22+or+%28status+eq+%22DEPROVISIONED%22+and+statusChanged+ge+%222023-10-01T15%3A02%3A15.491037Z%22%29"}}
[YYYY-MM-DD HH:II:SS] local.DEBUG: ApiClient::getPaginatedResults Success {"event_type":"okta.api.getPaginatedResults.success","method":"Provisionesta\\Okta\\ApiClient::getPaginatedResults","count_records":200,"event_ms":1115,"event_ms_per_record":5,"metadata":{"okta_request_id":"REDACTED","rate_limit_remaining":"296","url":"https://dev-12345678.okta.com/api/v1/users?after=00uREDACTED&limit=200&search=status+eq+%22ACTIVE%22+or+%28status+eq+%22DEPROVISIONED%22+and+statusChanged+ge+%222023-10-01T15%3A02%3A15.491037Z%22%29"}}
[YYYY-MM-DD HH:II:SS] local.DEBUG: ApiClient::getPaginatedResults Success {"event_type":"okta.api.getPaginatedResults.success","method":"Provisionesta\\Okta\\ApiClient::getPaginatedResults","count_records":200,"event_ms":1108,"event_ms_per_record":5,"metadata":{"okta_request_id":"REDACTED","rate_limit_remaining":"295","url":"https://dev-12345678.okta.com/api/v1/users?after=00uREDACTED&limit=200&search=status+eq+%22ACTIVE%22+or+%28status+eq+%22DEPROVISIONED%22+and+statusChanged+ge+%222023-10-01T15%3A02%3A15.491037Z%22%29"}}
[YYYY-MM-DD HH:II:SS] local.DEBUG: ApiClient::getPaginatedResults Success {"event_type":"okta.api.getPaginatedResults.success","method":"Provisionesta\\Okta\\ApiClient::getPaginatedResults","count_records":200,"event_ms":1067,"event_ms_per_record":5,"metadata":{"okta_request_id":"REDACTED","rate_limit_remaining":"294","url":"https://dev-12345678.okta.com/api/v1/users?after=00uREDACTED&limit=200&search=status+eq+%22ACTIVE%22+or+%28status+eq+%22DEPROVISIONED%22+and+statusChanged+ge+%222023-10-01T15%3A02%3A15.491037Z%22%29"}}
[YYYY-MM-DD HH:II:SS] local.DEBUG: ApiClient::getPaginatedResults Success {"event_type":"okta.api.getPaginatedResults.success","method":"Provisionesta\\Okta\\ApiClient::getPaginatedResults","count_records":200,"event_ms":1295,"event_ms_per_record":6,"metadata":{"okta_request_id":"REDACTED","rate_limit_remaining":"293","url":"https://dev-12345678.okta.com/api/v1/users?after=00uREDACTED&limit=200&search=status+eq+%22ACTIVE%22+or+%28status+eq+%22DEPROVISIONED%22+and+statusChanged+ge+%222023-10-01T15%3A02%3A15.491037Z%22%29"}}
[YYYY-MM-DD HH:II:SS] local.DEBUG: ApiClient::getPaginatedResults Success {"event_type":"okta.api.getPaginatedResults.success","method":"Provisionesta\\Okta\\ApiClient::getPaginatedResults","count_records":200,"event_ms":994,"event_ms_per_record":4,"metadata":{"okta_request_id":"REDACTED","rate_limit_remaining":"292","url":"https://dev-12345678.okta.com/api/v1/users?after=00uREDACTED&limit=200&search=status+eq+%22ACTIVE%22+or+%28status+eq+%22DEPROVISIONED%22+and+statusChanged+ge+%222023-10-01T15%3A02%3A15.491037Z%22%29"}}
[YYYY-MM-DD HH:II:SS] local.DEBUG: ApiClient::getPaginatedResults Success {"event_type":"okta.api.getPaginatedResults.success","method":"Provisionesta\\Okta\\ApiClient::getPaginatedResults","count_records":200,"event_ms":949,"event_ms_per_record":4,"metadata":{"okta_request_id":"REDACTED","rate_limit_remaining":"291","url":"https://dev-12345678.okta.com/api/v1/users?after=00uREDACTED&limit=200&search=status+eq+%22ACTIVE%22+or+%28status+eq+%22DEPROVISIONED%22+and+statusChanged+ge+%222023-10-01T15%3A02%3A15.491037Z%22%29"}}
[YYYY-MM-DD HH:II:SS] local.DEBUG: ApiClient::getPaginatedResults Success {"event_type":"okta.api.getPaginatedResults.success","method":"Provisionesta\\Okta\\ApiClient::getPaginatedResults","count_records":200,"event_ms":820,"event_ms_per_record":4,"metadata":{"okta_request_id":"REDACTED","rate_limit_remaining":"290","url":"https://dev-12345678.okta.com/api/v1/users?after=00uREDACTED&limit=200&search=status+eq+%22ACTIVE%22+or+%28status+eq+%22DEPROVISIONED%22+and+statusChanged+ge+%222023-10-01T15%3A02%3A15.491037Z%22%29"}}
[YYYY-MM-DD HH:II:SS] local.DEBUG: ApiClient::getPaginatedResults Success {"event_type":"okta.api.getPaginatedResults.success","method":"Provisionesta\\Okta\\ApiClient::getPaginatedResults","count_records":200,"event_ms":1060,"event_ms_per_record":5,"metadata":{"okta_request_id":"REDACTED","rate_limit_remaining":"289","url":"https://dev-12345678.okta.com/api/v1/users?after=00uREDACTED&limit=200&search=status+eq+%22ACTIVE%22+or+%28status+eq+%22DEPROVISIONED%22+and+statusChanged+ge+%222023-10-01T15%3A02%3A15.491037Z%22%29"}}
[YYYY-MM-DD HH:II:SS] local.DEBUG: ApiClient::getPaginatedResults Success {"event_type":"okta.api.getPaginatedResults.success","method":"Provisionesta\\Okta\\ApiClient::getPaginatedResults","count_records":200,"event_ms":741,"event_ms_per_record":3,"metadata":{"okta_request_id":"REDACTED","rate_limit_remaining":"288","url":"https://dev-12345678.okta.com/api/v1/users?after=00uREDACTED&limit=200&search=status+eq+%22ACTIVE%22+or+%28status+eq+%22DEPROVISIONED%22+and+statusChanged+ge+%222023-10-01T15%3A02%3A15.491037Z%22%29"}}
[YYYY-MM-DD HH:II:SS] local.DEBUG: ApiClient::getPaginatedResults Success {"event_type":"okta.api.getPaginatedResults.success","method":"Provisionesta\\Okta\\ApiClient::getPaginatedResults","count_records":90,"event_ms":407,"event_ms_per_record":4,"metadata":{"okta_request_id":"REDACTED","rate_limit_remaining":"287","url":"https://dev-12345678.okta.com/api/v1/users?after=00uREDACTED&limit=200&search=status+eq+%22ACTIVE%22+or+%28status+eq+%22DEPROVISIONED%22+and+statusChanged+ge+%222023-10-01T15%3A02%3A15.491037Z%22%29"}}
[YYYY-MM-DD HH:II:SS] local.DEBUG: ApiClient::get Paginated Results Process Complete {"event_type":"okta.api.get.process.pagination.finished","method":"Provisionesta\\Okta\\ApiClient::get","duration_ms":14573,"metadata":{"okta_request_id":"REDACTED","uri":"users"}}

POST 请求日志

[YYYY-MM-DD HH:II:SS] local.DEBUG: ApiClient::post Success {"event_type":"okta.api.post.success","method":"Provisionesta\\Okta\\ApiClient::post","event_ms":349,"metadata":{"okta_request_id":"REDACTED","rate_limit_remaining":"49","url":"https://dev-12345678.okta.com/api/v1/groups"}}

PATCH 请求日志

[YYYY-MM-DD HH:II:SS] local.DEBUG: ApiClient::patch Success {"event_type":"okta.api.patch.success","method":"Provisionesta\\Okta\\ApiClient::patch","event_ms":522,"metadata":{"okta_request_id":"0235f183fa446f5a2ae369ebfa8e8c5f","rate_limit_remaining":"49","url":"https://dev-12345678.okta.com/api/v1/users/00u1b2c3d4e5f6g7h8i9"}}

如果端点不支持使用带有 PATCH 覆盖方法的 POST 请求进行部分更新,请使用 PUT 请求。

[YYYY-MM-DD HH:II:SS] local.ERROR: ApiClient::patch Client Error {"event_type":"okta.api.patch.error.method-not-allowed","method":"Provisionesta\\Okta\\ApiClient::patch","errors":{"error_code":"E0000022","error_message":"The endpoint does not support the provided HTTP method","status_code":405},"event_ms":225,"metadata":{"okta_request_id":null,"rate_limit_remaining":null,"url":"https://dev-12345678.okta.com/api/v1/groups/00g1b2c3d4e5f6g7h8i9"}}

PUT 成功日志

[YYYY-MM-DD HH:II:SS] local.DEBUG: ApiClient::put Success {"event_type":"okta.api.put.success","method":"Provisionesta\\Okta\\ApiClient::put","event_ms":287,"metadata":{"okta_request_id":"REDACTED","rate_limit_remaining":"49","url":"https://dev-12345678.okta.com/api/v1/groups/00g1b2c3d4e5f6g7h8i9"}}

DELETE 成功日志

[YYYY-MM-DD HH:II:SS] local.DEBUG: ApiClient::delete Success {"event_type":"okta.api.delete.success","method":"Provisionesta\\Okta\\ApiClient::delete","event_ms":577,"metadata":{"okta_request_id":"REDACTED","rate_limit_remaining":"49","url":"https://dev-12345678.okta.com/api/v1/groups/00g1b2c3d4e5f6g7h8i9"}}

错误

400 错误请求

[YYYY-MM-DD HH:II:SS] local.WARNING: ApiClient::post Client Error {"event_type":"okta.api.post.warning.bad-request","method":"Provisionesta\\Okta\\ApiClient::post","errors":{"error_code":"E0000003","error_message":"The request body was not well-formed.","status_code":400},"event_ms":128,"metadata":{"okta_request_id":"REDACTED","rate_limit_remaining":"49","url":"https://dev-12345678.okta.com/api/v1/groups"}}

401 未授权

环境变量未设置
[YYYY-MM-DD HH:II:SS] local.CRITICAL: ApiClient::validateConnection Error {"event_type":"okta.api.validate.error","method":"Provisionesta\\Okta\\ApiClient::validateConnection","errors":["The url field is required.","The token field is required."]}
无效令牌
[YYYY-MM-DD HH:II:SS] local.ERROR: ApiClient::get Client Error {"event_type":"okta.api.get.error.unauthorized","method":"Provisionesta\\Okta\\ApiClient::get","errors":{"error_code":"E0000011","error_message":"Invalid token provided","status_code":401},"event_ms":261,"metadata":{"okta_request_id":"REDACTED","rate_limit_remaining":null,"url":"https://dev-12345678.okta.com/api/v1/org"}}

404 未找到

[YYYY-MM-DD HH:II:SS] local.WARNING: ApiClient::get Client Error {"event_type":"okta.api.get.warning.not-found","method":"Provisionesta\\Okta\\ApiClient::get","errors":{"error_code":"E0000007","error_message":"Not found: Resource not found: 00u1b2c3d4e5f6g7h8i9 (User)","status_code":404},"event_ms":614,"metadata":{"okta_request_id":"REDACTED","rate_limit_remaining":"49","url":"https://dev-12345678.okta.com/api/v1/users/00u1b2c3d4e5f6g7h8i9"}}