provisionesta / okta-api-client
Laravel的Okta API客户端
Requires
- php: ^8.0
- illuminate/config: ^8.0 || ^9.0 || ^10.0 || ^11.0
- illuminate/http: ^8.0 || ^9.0 || ^10.0 || ^11.0
- illuminate/log: ^8.0 || ^9.0 || ^10.0 || ^11.0
- illuminate/support: ^8.0 || ^9.0 || ^10.0 || ^11.0
- provisionesta/audit: ^1.0
Requires (Dev)
- larastan/larastan: ^2.7
- orchestra/testbench: ^6.23 || ^7.0 || ^8.0
Suggests
- provisionesta/okta-laravel-actions: Collection of Laravel Actions API/Console/Job/Service CRUD classes for Okta apps, groups, and users endpoints.
README
[[目录]]
概述
Okta API客户端是一个开源的Composer包,用于在Laravel应用程序中连接到Okta,以进行用户、组、应用程序和其他相关功能的配置和取消配置。
该包由开源社区维护,不由任何公司维护。请在自己的风险下使用,并创建合并请求来解决您遇到的任何错误。
问题陈述
我们不提供API文档中每个端点的SDK方法,而是采取了一种更简单的方法,通过提供一个通用的ApiClient,该客户端可以执行针对Okta API文档中找到的任何端点的GET、POST、PUT和DELETE请求。
这基于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 | @jeffersonmartin | provisionesta [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)来访问这些属性。更多内容请参阅过滤器文档。您将看到对filter和search的引用,但建议对所有查询使用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 响应都会抛出异常。所有响应都会自动记录。
异常
| 代码 | 异常类 |
|---|---|
| 400 | Provisionesta\Okta\Exceptions\BadRequestException |
| 401 | Provisionesta\Okta\Exceptions\UnauthorizedException |
| 403 | Provisionesta\Okta\Exceptions\ForbiddenException |
| 404 | Provisionesta\Okta\Exceptions\NotFoundException |
| 412 | Provisionesta\Okta\Exceptions\PreconditionFailedException |
| 422 | Provisionesta\Okta\Exceptions\UnprocessableException |
| 429 | Provisionesta\Okta\Exceptions\RateLimitException |
| 500 | Provisionesta\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 | 使用示例 |
| except | N/A |
| filter | N/A |
| flip | N/A |
| groupBy | 使用示例 |
| keyBy | N/A |
| only | N/A |
| pluck | 使用示例 |
| sort | 使用示例 |
| sortBy | 使用示例 |
| sortKeys | 使用示例 |
| toArray | N/A |
| transform | 使用示例 |
| unique | 使用示例 |
| values | 使用示例 |
| where | N/A |
| whereIn | N/A |
| whereNotIn | N/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/A | okta.api.test.success | DEBUG |
| N/A | okta.api.test.error.{okta_error_code} | CRITICAL |
| N/A | okta.api.test.error.unknown | CRITICAL |
| N/A | okta.api.validate.error | CRITICAL |
| N/A | okta.api.get.process.pagination.started | DEBUG |
| N/A | okta.api.get.process.pagination.finished | DEBUG |
| N/A | okta.api.rate-limit.approaching | CRITICAL |
| N/A | okta.api.rate-limit.exceeded (Pre-Exception) | CRITICAL |
| N/A | okta.api.{method}.error.http.exception | ERROR |
| 200 | okta.api.{method}.success | DEBUG |
| 201 | okta.api.{method}.success | DEBUG |
| 202 | okta.api.{method}.success | DEBUG |
| 204 | okta.api.{method}.success | DEBUG |
| 400 | okta.api.{method}.warning.bad-request | WARNING |
| 401 | okta.api.{method}.error.unauthorized | ERROR |
| 403 | okta.api.{method}.error.forbidden | ERROR |
| 404 | okta.api.{method}.warning.not-found | WARNING |
| 405 | okta.api.{method}.error.method-not-allowed | ERROR |
| 412 | okta.api.{method}.error.precondition-failed | DEBUG |
| 422 | okta.api.{method}.error.unprocessable | DEBUG |
| 429 | okta.api.{method}.critical.rate-limit | CRITICAL |
| 500 | okta.api.{method}.critical.server-error | CRITICAL |
| 501 | okta.api.{method}.error.not-implemented | ERROR |
| 503 | okta.api.{method}.critical.server-unavailable | CRITICAL |
成功的请求
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"}}