2024-08-31 01:03:37 +08:00

516 lines
16 KiB
PHP
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/**
* 第三方账号绑定
*/
class oauthBindIndex extends Controller {
const BIND_META_INFO = 'Info';
const BIND_META_UNIONID = 'Unionid';
public function __construct() {
parent::__construct();
$this->pluginName = 'oauthPlugin';
$this->typeList = array(
'qq' => array('type' => 'qq', 'title' => 'QQ'),
'weixin' => array('type' => 'wx', 'title' => LNG('common.wechat')),
'github' => array('type' => 'gh', 'title' => 'GitHub'),
'google' => array('type' => 'gg', 'title' => 'Google'),
'facebook' => array('type' => 'fb', 'title' => 'Facebook'),
);
$this->_checkAuth();
}
// 检查是否有用户编辑权限
private function _checkAuth(){
if(!KodUser::isLogin()) return;
$check = array(
'bind',
'bindApi',
'unbind',
'oauth',
'bindWithApp',
);
$action = Input::get('method', 'require');
$action = strtolower($action);
if(!in_array($action,$check)) return;
if(!Action('user.authRole')->authCan('user.edit')){
show_json(LNG('explorer.noPermissionAction'),false);
}
}
/**
* 第三方验证
* data string {type;openid;unionid;nickName;sex;avatar}
* type string qq|weixin|github
*/
public function bindApi() {
// api固定参数:type、sign、kodid、timestamp、data
$input = Input::getArray(array(
'type' => array('check' => 'require'),
'kodid' => array('check' => 'require'),
'timestamp' => array('check' => 'require'),
'data' => array('check' => 'require')
));
$type = $input['type'];
if (!isset($this->typeList[$type])) {
$this->bindHtml($type, array(), false, array('bind', LNG('common.invalidRequest')));
}
// 验证签名
$sign = Input::get('sign','require');
$_sign = $this->makeSign($input['kodid'], $input);
if ($sign !== $_sign) {
$this->bindHtml($type, array(), false, array('bind', LNG('user.signError')));
}
// 解析data参数
$data = json_decode(@base64_decode($input['data']), true);
if (!$data && is_string($input['data'])) {
$msg = LNG('common.invalidParam');
if (isset($this->in['info']) && $this->in['info'] == '40003') {
//Model('SystemOption')->set('systemSecret', '');
$msg = 'sign_error';
}
return $this->bindHtml($type, $data, false, array('bind', $msg));
}
if (empty($data['unionid'])) {
$msg = isset($data['data']) && is_string($data['data']) ? $data['data'] : LNG('explorer.dataError');
return $this->bindHtml($type, $data, false, array('bind', $msg));
}
return $this->bindDisplay($type, $data);
}
/**
* 第三方绑定返回信息
* @param type $type qq|github|weixin|google|facebook
* @param type $data
*/
public function bindDisplay($type, $data) {
$this->updateAvatar($data);
$unionid = $data['unionid'];
$client = Input::get('client','require',1); // 前后端
$data['client'] = $client;
// 判断是否已绑定
$bind = $this->isBind($type, $unionid, $client);
if (!$bind) {
// 未绑定,前、后端分别处理
$function = $client ? 'bindFront' : 'bindBack';
return $this->$function($type, $data);
}
// 已绑定,前端:直接跳转登录;后端:已绑定(别的账号),提示绑定失败
if ($client) {
$data['bind'] = true;
if(is_array($bind) && $bind[0]){
$success = true;
$msg = array('login'); // 已绑定且已启用,直接登录
}else{
$msg = array('invalid'); // 未启用
$success = false;
}
} else {
$data['bind'] = false; // 可不传
$msg = array('bind', 'bind_others', $bind);
$success = false; // $bind=true说明已绑定其他账号——update:bind=name
}
return $this->bindHtml($type, $data, $success, $msg);
}
/**
* 根据unionid判断对应账号是否已绑定
* @param type $key
* @param type $unionid
* @param type $client
* @return boolean
*/
private function isBind($key, $unionid, $client = 1) {
// 根据metadata.unionid获取用户信息
$user = Model('User')->getInfoByMeta($key . self::BIND_META_UNIONID, $unionid);
if (empty($user)) return false;
// 后端,要判断是否已经绑定了其他账号
// 通过绑定信息获取到的用户,不是当前登录用户,说明已绑定其他账号
if (!$client) {
if($user['userID'] != Session::get("kodUser.userID")) {
return $user['nickName'] ? $user['nickName'] : $user['name'];
}
return false;
}
// 前端,用户存在,则直接登录
if($user['status']){
Action('user.index')->loginSuccessUpdate($user);
}
return array($user['status']); // true
}
/**
*
* @param type $type
* @param type $data
* @param type $success
* @param type $msg
*/
private function bindHtml($type, $data, $success, $msg) {
$return = array(
'type' => $type, // 绑定类型
'success' => (int) $success, // 绑定结果
'bind' => isset($data['bind']) ? $data['bind'] : false, // 是否已绑定
'client' => (int) $data['client'], // 前后端
'name' => isset($data['nickName']) ? $data['nickName'] : '',
'avatar' => isset($data['avatar']) ? $data['avatar'] : '', // 头像
'title' => $success ? LNG('explorer.success') : LNG('explorer.error'), // 结果标题
'msg' => $this->_bindInfo($type, $success, $msg), // 结果说明
);
$this->updateAvatar($return);
if ($return['bind']) Hook::trigger('user.bind.log', 'bind', $return);
return show_json($return);
}
/**
* api返回操作结果信息
* @param type $type // qq|github|weixin
* @param type $succ //
* @param type $msg // sign_error|update_error|bind_others
* @return type
*/
private function _bindInfo($type, $success, $msg = array()) {
$action = $msg[0]; // connect|bind|login
$title = $this->typeList[$type]['title'];
if ($success) {
return LNG('common.congrats') . $title . LNG('common.' . $action . 'Success');
}
$errTitle = LNG('common.sorry') . $title;
if ($action == 'login') {
return $errTitle . LNG('common.loginError') . ';'.$title . LNG('user.thirdBindFirst');
}
// 2.2 尚未启用
if ($action == 'invalid') {
return $errTitle . LNG('common.loginError') . ';' . LNG('user.userEnabled');
}
// 2.3 其他失败
$errList = array(
'sign_error' => LNG('user.bindSignError'),
'update_error' => LNG('user.bindUpdateError'),
'bind_others' => $title . LNG('user.bindOthers') . (isset($msg[2]) ? "[{$msg[2]}]" : '')
);
$msgKey = isset($msg[1]) ? $msg[1] : '';
return $errTitle . LNG('common.bindError') .';' . (isset($errList[$msgKey]) ? $errList[$msgKey] : $msgKey);
}
/**
* 第三方绑定返回信息-前端
* @param type $type
* @param type $data
*/
private function bindFront($type, $data) {
$data['bind'] = false;
// 1.判断是否开放了注册
$regist = Model('SystemOption')->get('regist');
if(!(int) $regist['openRegist']){
return $this->bindHtml($type, $data, false, array('login'));
}
// 2. 自动注册
$regist = $this->bindRegist($type, $data);
if(!$regist['code']){
return $this->bindHtml($type, $data, false, array('bind', $regist['data']));
}
// 3.自动登录
$userID = $regist['data'];
$user = Model("User")->getInfo($userID);
if($user['status']) {
Action('user.index')->loginSuccessUpdate($user);
}
if($this->withApp) { // bindHtml会直接打印故在此return
return array('code' => true, 'data' => array('success' => true));
}
$data['bind'] = true;
return $this->bindHtml($type, $data, true, array('connect'));
}
/**
* 绑定(自动)注册
*/
private function bindRegist($type, $data){
$prev = $this->typeList[$type]['type'];
// 判断昵称是否重复
$data['nickName'] = $this->nickNameAuto($data['nickName']);
// 1.写入用户主信息
$param = array(
'name' => $prev . substr(guid(), 0, 10),
'nickName' => $data['nickName'],
'password' => 'K0d' . rand_string(5),
'sex' => isset($data['sex']) ? $data['sex'] : 1,
'avatar' => $data['avatar'],
);
if (isset($data['email'])) $param['email'] = $data['email'];
$res = Action("user.regist")->addUser($param);
if (!$res['code']) return $res;
$userID = $res['info'];
// 2.更新账户名、密码置为空
$update = array('name' => strtoupper($prev) . '1' . str_pad($userID, 8, 0, STR_PAD_LEFT));
Model('User')->where(array("userID"=>$userID))->save(array('password'=>''));// 密码置空
Model('User')->userEdit($userID, $update);
Model('User')->metaSet($userID, 'passwordSalt','');
// 3.写入用户meta信息
if (!$this->bindSave($data, $userID)) {
return array('code' => false, 'data' => LNG('user.bindUpdateError'));
}
$this->addUser = true;
$data = array_merge($param, array(
'userID' => $userID,
'name' => $update['name'],
'password' => '',
'type' => $type
));
Action($this->pluginName)->logAdd('regist', $data);
return array('code' => true, 'data' => $userID);
}
// 获取昵称
private function nickNameAuto($nickName){
if (!$nickName) return '';
$where = array('nickName' => array('like', $nickName.'%'));
$cnt = Model('User')->where($where)->count();
if(!$cnt) return $nickName;
return $nickName . str_pad(($cnt + 1), 2, '0', STR_PAD_LEFT);
}
/**
* 第三方绑定返回信息-后端
* @param type $type
* @param type $data
*/
private function bindBack($type, $data) {
$data['bind'] = true;
// 绑定信息存储
$user = Session::get("kodUser");
if (!$ret = $this->bindSave($data, $user['userID'])) {
$data['bind'] = false;
return $this->bindHtml($type, $data, false, array('bind', 'update_error'));
}
$update = array("avatar" => $data['avatar']);
if (isset($data['email']) && empty($user['email'])) {
$update['email'] = $data['email'];
}
Model("User")->userEdit($user['userID'], $update);
return $this->bindHtml($type, $data, true, array('bind'));
}
/**
* 第三方信息绑定保存
*/
private function bindSave($data, $userID) {
if(!$userID) show_json(LNG('oauth.main.notLogin'),false);
// 更新meta信息
$metadata = array(
$data['type'] . self::BIND_META_UNIONID => $data['unionid'],
$data['type'] . self::BIND_META_INFO => json_encode($data)
);
$ret = Model('User')->metaSet($userID, $metadata);
if ($ret && !$data['client']) {
$this->updateUserInfo($userID); // 后端绑定,更新用户信息
}
return $ret;
}
/**
* 请求kodapi url参数处理——前端第三方登录、后端绑定
*/
public function oauth() {
$data = Input::getArray(array(
'type' => array('check' => 'require'),
'action' => array('check' => 'require'),
'state' => array('default' => 'open'),
'client' => array('default' => 1),
));
if (!isset($this->typeList[$data['type']])) {
show_json(LNG('common.invalidParam'), false);
}
$client = isset($data['client']) ? "&client={$data['client']}" : "";
$link = Input::get('link');
// $link = !$link ? APP_HOST . '#user/bindInfo' : $link;
$link = !$link ? APP_HOST . '?plugin/oauth/callback' : $link;
$post = array(
"type" => $data['type'],
'kodid' => md5(BASIC_PATH . Model('SystemOption')->get('systemPassword')),
'timestamp' => time(),
"data" => json_encode(array(
'action' => $data['type'] . '_' . $data['action'],
'link' => $link . $client,
'isJson' => 1, // 返回数据不使用序列化,此参数为兼容旧版
))
);
$post['sign'] = $this->makeSign($post['kodid'], $post);
$appId = '';
// 微信授权分公众号微信内和开放平台对应appid需要分别获取
if ($data['type'] == 'weixin') {
$data = array('type' => 'weixin', 'state' => $data['state']);
$res = $this->apiRequest('appid', $data);
if (!$res['code']) {
show_json(LNG('user.bindWxConfigError').'.'.$res['data'], false);
}
$appId = $res['data'];
}
show_json(http_build_query($post), true, $appId);
}
/**
* app端后台绑定
*/
public function bind(){
$data = Input::getArray(array(
'type' => array('check' => 'in', 'param' => array_keys($this->typeList)),
'openid' => array('check' => 'require'),
'unionid' => array('check' => 'require'),
'nickName' => array('check' => 'require', 'default' => ''),
'sex' => array('check' => 'require', 'default' => 1),
'avatar' => array('check' => 'require', 'default' => ''),
'email' => array('default' => null),
));
$this->in['client'] = 0;
$res = ActionCallHook($this->pluginName.'.bind.index.bindDisplay', $data['type'], $data);
$msg = $res['data'];
if(isset($msg['msg'])){
$msg = explode(';', $msg['msg']);
$msg = isset($msg[1]) ? $msg[1] : $msg[0];
}
// 操作失败
if(!$res['code']) show_json($msg, false);
// success/bind同时为falsebind为true——更新用户信息失败
if(!$res['data']['success']){ // bind
show_json($msg, false);
}
$this->bindToServer($data);
Hook::trigger('user.bind.log', 'bind', $data);
show_json(LNG('common.bindSuccess'));
}
/**
* app前端绑定
*/
public function bindWithApp($data){
$this->withApp = true;
if(empty($data['openid'])) {
show_json(LNG('common.invalidParam') . ':openid', false);
}
if(empty($data['unionid'])) {
show_json(LNG('common.invalidParam') . ':unionid', false);
}
$res = ActionCallHook($this->pluginName.'.bind.index.bindDisplay', $data['type'], $data);
$msg = $res['data']; // bindHtml前就已经报错时只打印了第一个错误信息
if(isset($msg['msg'])){
$msg = explode(';', $msg['msg']);
$msg = isset($msg[1]) ? $msg[1] : $msg[0];
}
// 操作失败
if(!$res['code']) show_json($msg, false);
// 绑定成功但结果失败-未启用
if(!$res['data']['success']){
$code = $res['data']['bind'] ? ERROR_CODE_USER_INVALID : false;
if ($code) Hook::trigger('user.bind.log', 'bind', $data);
show_json($msg, $code);
}
Hook::trigger('user.bind.log', 'bind', $data);
// 未注册用户,直接返回登录
if(!$this->addUser) return true;
$this->bindToServer($data);
return true;
}
// app端绑定信息写入api服务器
private function bindToServer($data){
// 写入api服务器
$param = array(
'type' => $data['type'],
'nickName' => $data['name'],
'avatar' => isset($data['avatar']) ? $data['avatar'] : '',
'sex' => isset($data['sex']) ? $data['sex'] : 1,
'openid' => $data['openid'],
'unionid' => $data['unionid'],
);
$this->updateAvatar($param);
$this->apiRequest('bind', $param); // 这里不管成功与否,登录信息已存储
}
// 头像地址替换
private function updateAvatar(&$data){
if (empty($data['avatar'])) return;
if (substr($data['avatar'],0,7) == 'http://') {
$data['avatar'] = 'https://'.substr($data['avatar'], 7);
}
}
/**
* 第三方账号解绑-后端
*/
public function unbind() {
$type = Input::get('type','require', '');
if(!isset($this->typeList[$type])){
show_json(LNG('user.bindTypeError'), false);
}
$info = Session::get('kodUser');
if($this->isEmptyPwd($info['userID'])) show_json(LNG('user.unbindWarning'), false);
Model('User')->startTrans();
$del = Model('User')->metaSet($info['userID'], $type . self::BIND_META_INFO, null);
$del = Model('User')->metaSet($info['userID'], $type . self::BIND_META_UNIONID, null);
Model('User')->commit();
if ($del === false) {
show_json(LNG('explorer.error'), false);
}
$this->updateUserInfo($info['userID']);
Hook::trigger('user.bind.log', 'unbind', array('type' => $type));
show_json(LNG('explorer.success'), true);
}
// 更新userInfo缓存
private function updateUserInfo($id) {
Model('User')->cacheFunctionClear('getInfo',$id);
Session::set('kodUser', Model('User')->getInfo($id));
}
/**
* 请求Kodapi服务器
* @param type $type
* @param type $data
* @return type
*/
private function apiRequest($type, $data = array()) {
return Action('user.bind')->apiRequest($type, $data);
}
/**
* kodapi请求参数签名
* @param type $kodid
* @param type $post
* @return type
*/
private function makeSign($kodid, $post) {
return Action('user.bind')->makeSign($kodid, $post);
}
/**
* 用户绑定信息
*/
public function bindInfo(){
$userInfo = Session::get('kodUser');
$metaInfo = $userInfo['metaInfo'];
$bindInfo = array();
foreach ($this->typeList as $type => $value) {
$bindInfo[$type.'Bind'] = isset($metaInfo[$type . 'Unionid']) ? 1 : 0;
}
// 密码是否为空
$data = array('bind' => $bindInfo, 'emptyPwd' => 0);
// if(array_sum($bindInfo)){
$data['emptyPwd'] = (int) $this->isEmptyPwd($userInfo['userID']);
// }
show_json($data);
}
private function isEmptyPwd($userID){
$info = Model('User')->getInfoSimple($userID);
return empty($info['password']);
}
}