228 lines
8 KiB
PHP
228 lines
8 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
/**
|
|
* The zen file of sso module of ZenTaoPMS.
|
|
*
|
|
* @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.zentao.net)
|
|
* @license ZPL(https://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
|
|
* @author Yidong Wang <yidong@easycorp.ltd>
|
|
* @package sso
|
|
* @link https://www.zentao.net
|
|
*/
|
|
class ssoZen extends sso
|
|
{
|
|
/**
|
|
* Get feishu accessToken.
|
|
*
|
|
* @param object $appConfig
|
|
* @access protected
|
|
* @return array
|
|
*/
|
|
protected function getFeishuAccessToken(object $appConfig): array
|
|
{
|
|
$appUrl = $this->config->sso->feishuAppInfoAPI;
|
|
$appParams = array('app_id' => $appConfig->appId, 'app_secret' => $appConfig->appSecret);
|
|
$appResult = common::http($appUrl, $appParams, array(), array(), 'json');
|
|
|
|
if(empty($appResult)) return array('result' => 'fail', 'message' => $this->lang->sso->feishuResponseEmpty);
|
|
|
|
$appInfo = json_decode($appResult);
|
|
if(!isset($appInfo->msg) || $appInfo->msg != 'ok') return array('result' => 'fail', 'message' => $appResult);
|
|
return array('result' => 'success', 'token' => $appInfo->app_access_token);
|
|
}
|
|
|
|
/**
|
|
* Get feishu userToken.
|
|
*
|
|
* @param string $code
|
|
* @param string $accessToken
|
|
* @access protected
|
|
* @return array
|
|
*/
|
|
protected function getFeishuUserToken(string $code, string $accessToken): array
|
|
{
|
|
$tokenUrl = $this->config->sso->feishuTokenAPI;
|
|
$tokenHeaders = array('Authorization: Bearer ' . $accessToken);
|
|
$tokenParams = array('grant_type' => 'authorization_code', 'code' => $code);
|
|
$tokenResult = common::http($tokenUrl, $tokenParams, array(), $tokenHeaders, 'json');
|
|
|
|
if(empty($tokenResult)) return array('result' => 'fail', 'message' => $this->lang->sso->feishuResponseEmpty);
|
|
|
|
$tokenInfo = json_decode($tokenResult);
|
|
if(!isset($tokenInfo->msg) or $tokenInfo->msg != 'success') return array('result' => 'fail', 'message' => $tokenResult);
|
|
return array('result' => 'success', 'token' => $tokenInfo->data->access_token);
|
|
}
|
|
|
|
/**
|
|
* Get bind feishu user.
|
|
*
|
|
* @param string $userToken
|
|
* @param object $feishuConfig
|
|
* @access protected
|
|
* @return array
|
|
*/
|
|
protected function getBindFeishuUser(string $userToken, object $feishuConfig): array
|
|
{
|
|
$userUrl = $this->config->sso->feishuUserInfoAPI;
|
|
$userHeaders = array('Authorization: Bearer ' . $userToken);
|
|
$userResult = common::http($userUrl, array(), array(), $userHeaders, 'json');
|
|
|
|
if(empty($userResult)) return array('result' => 'fail', 'message' => $this->lang->sso->feishuResponseEmpty);
|
|
|
|
$userInfo = json_decode($userResult);
|
|
if(!isset($userInfo->msg) or $userInfo->msg != 'success') return array('result' => 'fail', 'message' => $userResult);
|
|
|
|
$openID = $userInfo->data->open_id;
|
|
|
|
/* Get the user relationship bound in webhook. */
|
|
$account = $this->loadModel('webhook')->getBindAccount($feishuConfig->id, 'webhook', $openID);
|
|
if(empty($account)) return array('result' => 'fail', 'message' => $this->lang->sso->unbound);
|
|
|
|
$user = $this->loadModel('user')->getById($account);
|
|
return array('result' => 'success', 'user' => $user);
|
|
}
|
|
|
|
/**
|
|
* Build user data for createUser method.
|
|
*
|
|
* @access protected
|
|
* @return object
|
|
*/
|
|
protected function buildUserForCreate(): object
|
|
{
|
|
return form::data($this->config->sso->form->createUser)->setDefault('ranzhi', $this->post->account)->get();
|
|
}
|
|
|
|
/**
|
|
* Idenfy from SSO.
|
|
*
|
|
* @param string $locate
|
|
* @access protected
|
|
* @return bool
|
|
*/
|
|
protected function idenfyFromSSO(string $locate): bool
|
|
{
|
|
if($this->get->status != 'success' || md5($this->get->data) != $this->get->md5) return false;
|
|
|
|
$userIP = helper::getRemoteIp();
|
|
$last = $this->server->request_time;
|
|
$data = json_decode(base64_decode($this->get->data));
|
|
|
|
if($data->auth != $this->computeAuth($data->token)) return false;
|
|
|
|
$user = $this->sso->getBindUser($data->account);
|
|
if(!$user)
|
|
{
|
|
$this->session->set('ssoData', $data);
|
|
return $this->locate($this->createLink('sso', 'bind', "referer=" . helper::safe64Encode($locate)));
|
|
}
|
|
|
|
if($this->loadModel('user')->isLogon() and $this->session->user->account == $user->account) return $this->locate($locate);
|
|
|
|
$user->last = date(DT_DATETIME1, (int)$last);
|
|
$user->lastTime = $user->last;
|
|
$user = $this->user->checkNeedModifyPassword($user, 0);
|
|
$this->dao->update(TABLE_USER)->set('visits = visits + 1')->set('ip')->eq($userIP)->set('last')->eq($last)->where('account')->eq($user->account)->exec();
|
|
|
|
$this->user->login($user);
|
|
|
|
return $this->locate($locate);
|
|
}
|
|
|
|
/**
|
|
* Locate notify link.
|
|
*
|
|
* @param string $location
|
|
* @param string $referer
|
|
* @access protected
|
|
* @return void
|
|
*/
|
|
protected function locateNotifyLink(string $location, string $referer): void
|
|
{
|
|
$isGet = strpos($location, '&') !== false;
|
|
$requestType = $this->get->requestType;
|
|
if(isset($requestType)) $isGet = $requestType == 'GET' ? true : false;
|
|
|
|
if($isGet) $location = $this->buildLocationByGET($location, $referer);
|
|
if(!$isGet) $location = $this->buildLocationByPATHINFO($location, $referer);
|
|
|
|
if(!empty($_GET['sessionid']))
|
|
{
|
|
$sessionConfig = json_decode(base64_decode($this->get->sessionid), false);
|
|
$location .= '&' . $sessionConfig->session_name . '=' . $sessionConfig->session_id;
|
|
}
|
|
$this->locate($location);
|
|
}
|
|
|
|
/**
|
|
* Build location by GET.
|
|
*
|
|
* @param string $location
|
|
* @param string $referer
|
|
* @access private
|
|
* @return string
|
|
*/
|
|
private function buildLocationByGET(string $location, string $referer): string
|
|
{
|
|
if(strpos($location, '&') === false)
|
|
{
|
|
$position = strrpos($location, '/') + 1;
|
|
$uri = substr($location, 0 ,$position);
|
|
$param = str_replace('.html', '', substr($location, $position));
|
|
list($module, $method) = explode('-', $param);
|
|
$location = $uri . 'index.php?m=' . $module . '&f=' . $method;
|
|
}
|
|
return rtrim($location, '&') . '&' . $this->buildSSOParams($referer);
|
|
}
|
|
|
|
/**
|
|
* Build location by PATH_INFO.
|
|
*
|
|
* @param string $location
|
|
* @param string $referer
|
|
* @access private
|
|
* @return string
|
|
*/
|
|
private function buildLocationByPATHINFO(string $location, string $referer): string
|
|
{
|
|
if(strpos($location, '&') !== false)
|
|
{
|
|
list($uri, $param) = explode('index.php', $location);
|
|
$param = substr($param, 1);
|
|
parse_str($param, $result);
|
|
$location = $uri . $result['m'] . '-' . $result['f'] . '.html';
|
|
}
|
|
return rtrim($location, '?') . '?' . $this->buildSSOParams($referer);
|
|
}
|
|
|
|
/**
|
|
* Build SSO params.
|
|
*
|
|
* @param string $referer
|
|
* @access private
|
|
* @return string
|
|
*/
|
|
private function buildSSOParams(string $referer): string
|
|
{
|
|
$userIP = helper::getRemoteIp();
|
|
$token = $this->get->token;
|
|
$auth = $this->computeAuth($token);
|
|
$callback = urlencode(common::getSysURL() . inlink('login', "type=return"));
|
|
return "token=$token&auth=$auth&userIP=$userIP&callback=$callback&referer=$referer";
|
|
}
|
|
|
|
/**
|
|
* Compute auth.
|
|
*
|
|
* @access private
|
|
* @return string
|
|
*/
|
|
private function computeAuth(string $token): string
|
|
{
|
|
$userIP = helper::getRemoteIp();
|
|
$code = $this->config->sso->code;
|
|
$key = $this->config->sso->key;
|
|
return md5($code . $userIP . $token . $key);
|
|
}
|
|
}
|