516 lines
16 KiB
PHP
Executable File
516 lines
16 KiB
PHP
Executable File
<?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同时为false;bind为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']);
|
||
}
|
||
|
||
} |