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']); } }