powersystem / cake-cognito
CognitoSDK插件用于CakePHP
Requires
- composer-plugin-api: 2.3.0
- aws/aws-sdk-php: ^3.54
- cakephp/cakephp: 4.*
- friendsofcake/search: ^6.2
- lcobucci/jwt: ^3.2
- muffin/footprint: ^3.0
- powersystem/cake-s3upload: 1.0.01
- powersystem/cakeapigateway: 1.0.05
- ralouphie/mimey: ^1.0
Requires (Dev)
- phpunit/phpunit: ^9.5
README
此插件允许从应用程序面板管理AWS Cognito用户,确保与本地副本保持同步。
此插件假设与AWS Cognito单向通信通道,其中同步字段的更改首先在应用程序中发生,然后由插件传播到AWS Cognito。
变更日志
日期:2018-10-11
- 重写了整个文档以反映最近的变化
文档
- 功能
- 注意
- 安装
- 结构
- PowerSystem\CognitoSDK\Model\Entity\ApiUser
- PowerSystem\CognitoSDK\Model\Table\ApiUsersTable
- PowerSystem\CognitoSDK\Model\Traits\AwsCognitoSaveTrait
- PowerSystem\CognitoSDK\Model\Behavior\AwsCognitoBehavior
- PowerSystem\CognitoSDK\Model\Behavior\ImportApiUsersBehavior
- PowerSystem\CognitoSDK\Controller\ApiUsersController
- PowerSystem\CognitoSDK\Controller\Api\ApiUsersController
- PowerSystem\CognitoSDK\Controller\Traits\BaseApiEndpointsTrait
- 配置变量
- 测试
- 扩展插件
功能
实现
- 用户注册。向用户发送包含临时密码的邀请电子邮件
- 用户注销,确保也从Cognito中删除
- 通过“活动”复选框禁用/启用用户(阻止登录)
- 密码重置
- 更改电子邮件,可选择是否发送验证码
- 重新发送邀请电子邮件(创建账户)
- 用户配置文件指示用户是否与Cognito正确同步
- 将用户头像上传到AWS S3
- JSON Web Tokens (AwsCognitoJwtAuthenticate)认证器用于认证Cognito用户
- 为非管理员用户提供的API端点
- 以CSV格式批量导入用户,预先进行验证
待办事项
- 配置使用电话、电子邮件或两者进行用户注册/访问(目前仅支持电子邮件)
- 如果用户导入失败,用户导入无法回滚编辑。请仅用于创建新用户。
- 允许管理员更改用户头像
注意
- 如果创建用户时用户被创建为“禁用”状态(active = 0),则插件将在创建用户后尝试在Cognito中禁用该用户(因为Cognito无法创建禁用用户)。如果失败,插件将优先考虑一致性,并创建一个启用状态的用户。
安装
要使用composer获取插件,需要在composer.json中添加以下内容
- 在
"require"对象中添加插件:"PowerSystem/cake-cognito": "dev-master" - 在
"repositories"数组中添加对象:{"type": "vcs", "url": "git@bitbucket.org:PowerSystem/cake-cognito.git"} - 运行
composer update
注意:请确保在存储库中具有正确的访问/部署权限。
一旦在存储库中安装了插件
- 将以下行添加到
config/bootstrap.php中,以加载插件:Plugin::load('PowerSystem/CognitoSDK', ['bootstrap' => true, 'routes' => true]); - 请确保存在
api_users表。插件自带迁移脚本,如果需要:运行bin/cake migrations migrate -p PowerSystem/CognitoSDK - 在Cognito中创建一个User Pool。请注意
- 用户池应只包含访问所需的数据。额外的字段应仅存在于本地表中。
- 请确保Cognito中创建用户所需字段的验证与本地表验证相同或更宽松,否则在创建用户时可能会发生错误,这些错误不会正确地显示给用户。
- 不允许用户通过Cognito更改其电子邮件或电话,这可能会导致数据不同步。除密码更改外,所有用户更改均应通过插件提供的API进行。
- 在加载插件之前,将以下行添加到 config/bootstrap.php 中:
Configure::write('CognitoSDK.config', ['awscognito']); - 将文件
vendor/PowerSystem/cake-cognito/config/awscognito.php复制到app/config/awscognito.php。 - 修改文件以配置插件。(请参阅“配置变量”部分)
- 默认路由如下(要更改此设置,请参阅“扩展插件”部分。要在
Plugin::load中禁用默认路由,请使用'routes' => false)- 插件的用户控制器应可通过
/aws-cognito/api-users访问。 - API端点可通过
/aws-cognito/api/api-users/:action访问。
- 插件的用户控制器应可通过
结构
PowerSystem\CognitoSDK\Model\Entity\ApiUser
属性
- aws_cognito_id (字符 36) (隐藏,不可变)
- aws_cognito_username (可变字符 255) (隐藏,不可变)
- email (可变字符 255)
- active (小整数 1)
- role (可变字符 50)
- first_name (可变字符 50)
- last_name (可变字符 50)
- created (日期时间)
- modified (日期时间)
虚拟属性
- full_name (
"${first_name} ${last_name}")
PowerSystem\CognitoSDK\Model\Table\ApiUsersTable
- 受保护 $searchQueryFields
- 用于表搜索配置的字段数组。
- 抽象为受保护属性以方便扩展。
- 公共 initialize()
- 配置
Creators与Users表的created_by关联以及Modifiers与Users表的modified_by关联。要更改使用的表,请扩展该表,请参阅“扩展插件”部分。 - 添加
Timestamp行为以填充created_at和modified_at字段。 - 添加
Muffin/Footprint.Footprint行为以填充created_by和modified_by字段。 - 添加
PowerSystem/CognitoSDK.CognitoSDK行为以启用Cognito功能。 - 添加
PowerSystem/CognitoSDK.ImportApiUsers行为以添加批量导入功能。 - 添加
PowerSystem/S3UploadSDK.S3UploadSDK行为以将用户头像上传到AWS S3。 - 添加
Search.Search行为以启用搜索。
- 配置
- 公共 validationDefault(Validator $validator)
- 基本验证字段(id,first_name,last_name)的验证。
- 确保
role字段存在并属于配置的列表(要更改此设置,请扩展验证,请参阅“扩展插件”部分)。
- 公共 getRoles()
- 返回配置的角色的数组。
- 如果配置不存在或无效,则抛出异常。
PowerSystem\CognitoSDK\Model\Traits\AwsCognitoSaveTrait
此特质由ApiUsersTable使用。其功能是
- 覆盖 saveMany 方法以捕获在尝试一次性保存多个用户时发生的任何Cognito异常或保存错误。
- 在这种情况下,负责撤销Cognito中发生的任何更改,必要时删除新用户。
- 将来也负责撤销编辑。
PowerSystem\CognitoSDK\Model\Behavior\AwsCognitoBehavior
此行为负责启用所有与Cognito相关的功能。其方法如下
- 公共 validationResendInvitationEmail(Validator $validator)
- 包含用于重新发送邀请电子邮件操作的验证器
- 公共 validationChangeEmail(Validator $validator)
- 包含用于电子邮件更改操作的验证器
- public buildValidator(Event $event, Validator $validator, $name)
- 验证器的回调函数
- 确保密码字段是必需的,并按相应方式进行验证
- public buildRules(Event $event, RulesChecker $rules)
- 添加应用规则
- 确保Cognito用户名是唯一的
- 确保电子邮件是唯一的
- 阻止编辑Cognito用户名
- 阻止编辑Cognito ID
- 阻止通过传统方式编辑电子邮件(提供changeEmail和resendInvitationEmail方法来更改电子邮件的选项)
- public beforeSave(Event $event, EntityInterface $entity, ArrayObject $options)
- 在保存用户之前,此回调函数执行以下操作
- 如果用户是新的,则调用
createCognitoUser - 如果用户正在编辑且
active字段已更改,则根据情况调用disableCognitoUser或enableCognitoUser
- 如果用户是新的,则调用
- 在保存用户之前,此回调函数执行以下操作
- public beforeDelete(Event $event, EntityInterface $entity, ArrayObject $options)
- 在从数据库中删除用户之前,此回调函数调用
deleteCognitoUser以在Cognito中删除用户。
- 在从数据库中删除用户之前,此回调函数调用
- public changeEmail(EntityInterface $entity, $new_email, bool $require_verification = true)
- 此方法封装了用户的电子邮件更改,以便从Controller中方便地调用。
- 在Cognito中更改电子邮件,如果成功,则将更改保存到数据库中。
- 可以通过
$require_verification参数选择电子邮件是否需要验证。 - 如果需要验证,Cognito将向用户发送带有验证码的电子邮件。客户端开发者可以使用此验证电子邮件。
- public resendInvitationEmail(EntityInterface $entity, $new_email)
- 在账户已过期(刷新账户)或电子邮件地址错误(允许更改)的情况下,重新发送邀请电子邮件。
- public getWithCognitoData($id, $options = [])
- 在数据库中查找用户,并添加Cognito信息
- 调用Cognito添加字段,因此性能不高。
- 在Controller中客户详情时使用。
- 添加以下字段
- bool
aws_cognito_synced: 指示用户是否已将所有相关字段正确同步到Cognito。通常不应为假。 - array
aws_cognito_attributes: Cognito额外属性的列表 - array
aws_cognito_status: 包含显示Cognito状态的字段,已解析以便于阅读aws_cognito_status['code']: 状态代码aws_cognito_status['title']: 状态标题aws_cognito_status['description']: 状态描述
- bool
- 如果在Cognito中找不到用户,不会抛出异常,而是字段为空,且
aws_cognito_synced将为假。
- public resetCognitoPassword(EntityInterface $entity)
- 清除密码。
- 将在用户下次登录时启动密码更改过程。
- 如果用户从未登录过,或者如果电子邮件/电话未验证以发送验证消息,则无法清除密码。
- public deleteCognitoUser(EntityInterface $entity)
- 删除Cognito用户。
- 在从本地表删除用户时自动使用。
- 方法公开,因为对于批量处理和事务过程很有用。
- protected createCognitoClient()
- 用于初始化与Cognito连接的内部方法。
- 受保护的 processCognitoUser(结果 $cognito_user)
- 处理由 Cognito SDK 返回的用户数据,并将其转换为更符合 CakePHP 的数据。
- 受保护的 titleForUserStatus($status)
- 返回用户的 Cognito 状态标题
- 受保护的 descriptionForUserStatus($status)
- 返回用户的 Cognito 状态描述
- 受保护的 createCognitoUser(实体接口 $entity, $message_action = null)
- 用于在 Cognito 中创建新用户或重新发送邀请邮件
- 受保护的 awsExceptionToEntityErrors(AwsException $exception, 实体接口 $entity)
- 解析某些 Cognito 异常,并将它们转换为实体的验证错误
- 涵盖电子邮件和用户无效或重复的情况
- 由其他方法使用,以使过程更符合 CakePHP
- 受保护的 disableCognitoUser(实体接口 $entity)
- 禁用 Cognito 中的用户,阻止其登录
- 受保护的 enableCognitoUser(实体接口 $entity)
- 启用 Cognito 中的用户,恢复其登录
PowerSystem\CognitoSDK\Model\Behavior\ImportApiUsersBehavior
此行为负责添加用户导入功能。目前,这包括批量导入用户的能力,格式为 CSV。其方法有
- 公共的 validateMany(数组 $rows, $max_errors = false, 数组 $options = []): 数组
$rows是用户数组,可能包含 CakePHP 实体或格式化以符合 CakePHP 的用户数据数组- 此方法将数组
$rows中的所有用户转换为 CakePHP 实体 - 使用用户名搜索预存在的实体,并使用输入的更改进行修补。其余的将被创建。
- 使用实体验证和应用程序规则验证所有实体
- 同时验证它们之间是否存在重复
- 返回一个应用了所有验证的实体数组。可以调用
$entity->getErrors()来查看错误
- 公共的 csvDataToAssociativeArray(字符串 $csv_data, 数组 $fields = []): 数组
- 将输入的 CSV 数据格式 (
$csv_data) 转换为 CakePHP 格式的用户数组。 - 期望字段按特定顺序排列
- 默认期望基本字段 (
aws_cognito_username,email,first_name,last_name) - 要更改顺序或字段数量,请使用参数 $fields。
- 将输入的 CSV 数据格式 (
PowerSystem\CognitoSDK\Controller\ApiUsersController
使用以下特质
BaseCrudTraitImportApiUsersTraitAwsCognitoTrait
PowerSystem\CognitoSDK\Controller\Traits\BaseCrudTrait
提供以下基本管理员用户操作
- /aws-cognito/api-users/index (GET)
- 用户列表
- /aws-cognito/api-users/view/:id (GET)
- 用户详情
- 包含 Cognito 数据
- /aws-cognito/api-users/add (GET, POST)
- 添加用户
- /aws-cognito/api-users/edit/:id (GET, POST)
- 编辑用户的基本字段(first_name, last_name 等)
- /aws-cognito/api-users/change-email/:id (GET, POST)
- 更改用户电子邮件
- 允许决定电子邮件是否需要验证(在这种情况下,将发送验证电子邮件)
- /aws-cognito/api-users/delete/:id (POST, DELETE)
- 删除用户
PowerSystem\CognitoSDK\Controller\Traits\ImportApiUsersTrait
提供用户导入操作
- /aws-cognito/api-users/import (GET, POST)
- 允许通过 CSV 格式的文本字段批量导入用户
PowerSystem\CognitoSDK\Controller\Traits\AwsCognitoTrait
提供与 Cognito 功能相关的相关操作
- /aws-cognito/api-users/activate/:id (POST)
- 激活用户(允许登录)
- /aws-cognito/api-users/deactivate/:id (POST)
- 禁用用户(阻止登录)
- /aws-cognito/api-users/reset-password/:id (POST)
- 清除用户密码
- 将在用户下次登录时启动密码更改过程。
- 如果用户从未登录过,或者如果电子邮件/电话未验证以发送验证消息,则无法清除密码。
- /aws-cognito/api-users/resend-invitation-email/:id (GET, POST)
- 重新发送邀请邮件
- 刷新账户过期时间
- 允许更改电子邮件
PowerSystem\CognitoSDK\Controller\Api\ApiUsersController
使用 BaseApiEndpointsTrait。
PowerSystem\CognitoSDK\Controller\Traits\BaseApiEndpointsTrait
为客户端开发者提供以下端点
- /aws-cognito/api/api-users/profile (GET)
- 用户详细资料
- /aws-cognito/api/api-users/profile (POST)
- 允许编辑用户资料(仅限基本字段,如first_name和last_name)
- /aws-cognito/api/api-users/change-email (POST)
- 允许更改用户电子邮件
- 将发送带有验证码的电子邮件到新地址。开发者必须使用它来与cognito通信并验证电子邮件。
- /aws-cognito/api/api-users/upload-avatar (POST)
- 允许更改用户头像
- 期望在HTTP请求体中发送原始图像二进制文件
- 将头像上传到S3
配置变量
配置变量与其他配置(默认为config/app.php)一起保存在应用程序配置数组中。
可用的配置有
'CognitoSDK' => [ 'AccessKeys' => [ 'id' => 'NSWPXE30F49XAOF', 'secret' => 'QIQNxRO2425bb040e4adc8cc02fae05063c3c' ], 'UserPool' => [ 'id' => 'us-east-2_rjaob1HaR', ], 'IdentityProviderClient' => [ 'settings' => [], //https://docs.aws.amazon.com/sdkforruby/api/Aws/CognitoIdentityProvider/Client.html#initialize-instance_method ], ], 'ApiUsers' => [ /* available user roles */ 'roles' => [ 'user' => __d('PowerSystem/CognitoSDK', 'API User'), ], /* the max amount of errors alloweds before the validation process of the imported data is halted */ 'import_max_errors' => 10, /* the limit of accepted rows in the importing CSV data */ 'import_max_rows' => 500, ]
测试
目前插件大约有90%的行覆盖率,70%的方法和函数覆盖率。
要运行测试,必须单独下载仓库(以便composer注册开发模式)。运行composer install来安装依赖项,然后使用vendor/bin/phpunit运行测试。
扩展插件
更改路由
要更改插件的默认路由(/aws-cognito/api-users),可以在config/routes.php中添加以下内容,必须在bootstrap.php中加载插件时将'routes' => false设置为Plugin::load。
然后,您可以在config/routes.php中的基础作用域(/)内使用以下内容。
- 将路由
/aws-cognito/api-users替换为/api-users$routes->connect('/api-users/*', [ 'plugin' => 'PowerSystem/CognitoSDK', 'controller' => 'ApiUsers' ]); $routes->connect('/api-users/:action', [ 'plugin' => 'PowerSystem/CognitoSDK', 'controller' => 'ApiUsers', 'action' => ':action' ]); $routes->connect('/api-users/:action/:id', [ 'plugin' => 'PowerSystem/CognitoSDK', 'controller' => 'ApiUsers', 'action' => ':action' ], ['id' => '\d+', 'pass' => ['id']]);
- 对于REST API,可以按以下方式替换
Router::prefix('api', function ($routes) { $routes->extensions(['json']); /* Api Users */ $routes->connect('/me', [ 'plugin' => 'PowerSystem/CognitoSDK', 'prefix' => 'Api', 'controller' => 'ApiUsers', 'action' => 'profile', '_method' => 'GET' ]); $routes->connect('/me', [ 'plugin' => 'PowerSystem/CognitoSDK', 'prefix' => 'Api', 'controller' => 'ApiUsers', 'action' => 'editProfile', '_method' => 'PATCH' ]); $routes->connect('/me/avatar', [ 'plugin' => 'PowerSystem/CognitoSDK', 'prefix' => 'Api', 'controller' => 'ApiUsers', 'action' => 'uploadAvatar', '_method' => 'PUT' ]); $routes->connect('/me/email', [ 'plugin' => 'PowerSystem/CognitoSDK', 'prefix' => 'Api', 'controller' => 'ApiUsers', 'action' => 'changeEmail', '_method' => 'PUT' ]); });
覆盖视图
为了在不扩展控制器的情况下覆盖模板,必须将新模板放在以下位置
src/Template/Plugin/PowerSystem/CognitoSDK/ApiUsers/*
例如,为了替换index模板
src/Template/Plugin/PowerSystem/CognitoSDK/ApiUsers/index.ctp
扩展控制器/模型
如果需要,可以扩展控制器和模型(例如,为了向ApiUsers模型添加新关联),以下是最优方法
首先,扩展ApiUsersTable
//archivo: src/Model/Table/ApiUsersTable.php namespace App\Model\Table; use PowerSystem\CognitoSDK\Model\Entity\ApiUser; use PowerSystem\CognitoSDK\Model\Table\ApiUsersTable as AwsApiUsersTable; class ApiUsersTable extends AwsApiUsersTable { //esto solo es necesario si no se va a extender la Entidad protected $_entityClass = 'PowerSystem\CognitoSDK\Model\Entity\ApiUser'; public function initialize(array $config) { parent::initialize($config); //cambiando la tabla de usuarioa administradores $this->association('Creators')->className('AppUsers'); $this->association('Modifiers')->className('AppUsers'); //agregar nuevas asociaciones aca $this->belongsToMany('FunctionalUnits', [ 'through' => 'ApiUsersFunctionalUnits', 'saveStrategy' =>'append', ]); //y podemos facilmente reconfigurar el search cambiando estos valores $this->searchQueryFields = [ 'ApiUsers.aws_cognito_username', 'ApiUsers.email', 'ApiUsers.phone', 'ApiUsers.first_name', 'ApiUsers.last_name', ]; } //extendiendo la validación por defecto public function validationDefault(Validator $validator) { $validator = parent::validationDefault($validator); //se puede remover la validacion de un campo si no se usa $validator->remove('role'); //y agregar campos nuevos $validator ->scalar('phone') ->allowEmpty('phone'); return $validator; } }
然后扩展ApiUsersController
//archivo: src/Controller/ApiUsersController.php namespace App\Controller; use PowerSystem\CognitoSDK\Controller\ApiUsersController as AwsApiUsersController; use App\Model\Table\ApiUsersTable; class ApiUsersController extends AwsApiUsersController { //aca se pueden agregar nuevas acciones o reemplazar las existentes //por ejemplo el index: public function index() { $this->paginate['contain'] = ['PointsOfSale']; $this->set('api_users', $this->paginate('ApiUsers')); $this->set('_serialize', ['api_users']); } }
可选地可以扩展实体ApiUser
//archivo: src/Model/Entity/ApiUser.php namespace App\Model\Entity; use PowerSystem\CognitoSDK\Model\Entity\ApiUser as AwsApiUser; class ApiUser extends AwsApiUser { protected $_accessible = [ '*' => true, 'id' => false, 'role' => false, //cognito fields: 'aws_cognito_username' => false, 'aws_cognito_id' => false, 'email' => false, //dejando lo demás como viene, podemos agregar nuevos campos 'phone' => false ]; //podemos agregar nuevas virtual properties aca }
现在,为了保留插件的模板并只需添加新模板(或要替换的模板),可以修改路由,以便新动作使用此控制器,而其他动作则重定向到插件内的控制器
//el index lleva al nuevo controller $routes->connect('/api-users', ['controller' => 'ApiUsers', 'action' => 'index']); $routes->connect('/api-users/index', ['controller' => 'ApiUsers', 'action' => 'index']); //las demas rutas llevan al controller del plugin $routes->connect('/api-users/*', [ 'plugin' => 'PowerSystem/CognitoSDK', 'controller' => 'ApiUsers' ]); $routes->connect('/api-users/:action', [ 'plugin' => 'PowerSystem/CognitoSDK', 'controller' => 'ApiUsers', 'action' => ':action' ]); $routes->connect('/api-users/:action/:id', [ 'plugin' => 'PowerSystem/CognitoSDK', 'controller' => 'ApiUsers', 'action' => ':action' ], ['id' => '\d+', 'pass' => ['id']]);