diff --git a/config/filter.php b/config/filter.php
index 7d1f8fcd60..e35d666e1f 100644
--- a/config/filter.php
+++ b/config/filter.php
@@ -371,8 +371,8 @@ $filter->repo->diff->cookie['repoPairs'] = 'array';
$filter->repo->view->cookie['repoPairs'] = 'array';
$filter->repo->ajaxsynccommit->cookie['syncBranch'] = 'reg::any';
-$filter->webhook->bind->get['selectedDepts'] = 'reg::checked';
-$filter->webhook->bind->cookie['selectedDepts'] = 'reg::checked';
+$filter->webhook->bind->get['selectedDepts'] = 'reg::any';
+$filter->webhook->bind->cookie['selectedDepts'] = 'reg::any';
$filter->search->index->get['words'] = 'reg::any';
$filter->search->index->get['type'] = 'code';
diff --git a/config/zentaopms.php b/config/zentaopms.php
index 8508db61cd..47748473ca 100644
--- a/config/zentaopms.php
+++ b/config/zentaopms.php
@@ -142,6 +142,9 @@ $config->openMethods[] = 'my.changepassword';
$config->openMethods[] = 'my.profile';
$config->openMethods[] = 'my.settutorialconfig';
$config->openMethods[] = 'doc.selectlibtype';
+$config->openMethods[] = 'sso.getfeishusso';
+$config->openMethods[] = 'sso.feishuauthen';
+$config->openMethods[] = 'sso.feishulogin';
/* Define the tables. */
define('TABLE_COMPANY', '`' . $config->db->prefix . 'company`');
diff --git a/lib/dingapi/dingapi.class.php b/lib/dingapi/dingapi.class.php
index a9d2e2d388..dacf0a52b7 100644
--- a/lib/dingapi/dingapi.class.php
+++ b/lib/dingapi/dingapi.class.php
@@ -136,7 +136,7 @@ class dingapi
$node['id'] = $deptID;
$node['pId'] = $parentID;
$node['name'] = $deptName;
- if($parentID == 0) $node['open'] = true;
+ $node['open'] = true;
$tree[] = $node;
}
diff --git a/lib/feishuapi/feishuapi.class.php b/lib/feishuapi/feishuapi.class.php
index 31b26e85c1..a7a3daccfe 100644
--- a/lib/feishuapi/feishuapi.class.php
+++ b/lib/feishuapi/feishuapi.class.php
@@ -52,17 +52,23 @@ class feishuapi
* @access public
* @return array
*/
- public function getAllUsers()
+ public function getAllUsers($selectedDepts = '')
{
+ $selectedDepts = trim($selectedDepts);
+ if(empty($selectedDepts)) return array('result' => 'fail', 'message' => 'nodept');
+
set_time_limit(0);
$users = array();
- $depts = $this->getDepts();
+ $depts = explode(',', $selectedDepts);
+ $depts = array_flip($depts);
+ unset($depts[1]);
+ if(empty($depts)) return array('result' => 'fail', 'message' => 'nodept');
/* Get users by dept. */
foreach($depts as $deptID => $count)
{
- if($deptID and empty($count)) continue;
+ if(empty($deptID)) continue;
$pageToken = '';
while(true)
@@ -138,6 +144,46 @@ class feishuapi
return $depts;
}
+ /**
+ * Get department tree structure.
+ *
+ * @access public
+ * @return array
+ */
+ public function getDeptTree()
+ {
+ $depts = array('result' => 'success', 'data' => array());
+
+ /* Gets the enterprise name. */
+ $response = $this->queryAPI($this->apiUrl . "tenant/v2/tenant/query", '', array(CURLOPT_CUSTOMREQUEST => "GET"));
+ $company = array('id' => '1', 'pId' => '0', 'name' => $response->data->tenant->name, 'open' => 1);
+ $data = array($company);
+
+ /* Get depts by parent dept. */
+ $pageToken = '';
+ while(true)
+ {
+ $response = $this->queryAPI($this->apiUrl . "contact/v3/departments?parent_department_id=0" . ($pageToken ? "&page_token={$pageToken}" : '') . "&fetch_child=true", '', array(CURLOPT_CUSTOMREQUEST => "GET"));
+ if(isset($response->data->items))
+ {
+ foreach($response->data->items as $key => $dept)
+ {
+ $key ++;
+ $data[$key]['id'] = $dept->open_department_id;
+ $data[$key]['pId'] = empty($dept->parent_department_id) ? 1 : $dept->parent_department_id;
+ $data[$key]['name'] = $dept->name;
+ $data[$key]['open'] = 1;
+ }
+ }
+
+ if(!isset($response->data->page_token)) break;
+ $pageToken = $response->data->page_token;
+ }
+
+ $depts['data'] = $data;
+ return $depts;
+ }
+
/**
* Send message
*
@@ -179,6 +225,11 @@ class feishuapi
if(empty($response)) $this->errors = $errors;
if(isset($response->code)) $this->errors[$response->code] = "Errcode:{$response->code}, Errmsg:{$response->msg}";
+ if(!empty($this->errors))
+ {
+ echo js::error(array_shift($this->errors));
+ die(js::locate(helper::createLink('webhook', 'browse')));
+ }
return false;
}
diff --git a/module/sso/control.php b/module/sso/control.php
index ad3cf3d03c..23cd3d59f1 100644
--- a/module/sso/control.php
+++ b/module/sso/control.php
@@ -305,4 +305,127 @@ class sso extends control
$datas['bug'] = $this->dao->select("id, title")->from(TABLE_BUG)->where('assignedTo')->eq($account)->andWhere('status')->eq('active')->andWhere('deleted')->eq(0)->fetchPairs();
die(json_encode($datas));
}
+
+ /**
+ * Get the link to the Feishu single sign-on configuration.
+ *
+ * @access public
+ * @return void
+ */
+ public function getFeishuSSO()
+ {
+ $httpType = ((isset($_SERVER['HTTPS']) and $_SERVER['HTTPS'] == 'on') or (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) and $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https')) ? 'https://' : 'http://';
+ $applicationHome = $httpType . $_SERVER['HTTP_HOST'] . $this->createLink('sso', 'feishuAuthen');
+ $redirectLink = $httpType . $_SERVER['HTTP_HOST'] . $this->createLink('sso', 'feishuLogin');
+
+ echo $this->lang->sso->homeURL . $applicationHome;
+ echo '
';
+ echo $this->lang->sso->redirectURL . $redirectLink;
+ }
+
+ /**
+ * Get the pre-authorization code for Feishu code.
+ *
+ * @access public
+ * @return void
+ */
+ public function feishuAuthen()
+ {
+ $httpType = ((isset($_SERVER['HTTPS']) and $_SERVER['HTTPS'] == 'on') or (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) and $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https')) ? 'https://' : 'http://';
+ $redirectURI = $httpType . $_SERVER['HTTP_HOST'] . $this->createLink('sso', 'feishuLogin');
+ $redirectURI = urlencode($redirectURI);
+
+ $feishuConfig = $this->loadModel('webhook')->getByType('feishuuser');
+ if(empty($feishuConfig)) $this->showError($this->lang->sso->feishuConfigEmpty);
+
+ $appConfig = json_decode($feishuConfig->secret);
+ $appID = $appConfig->appId;
+
+ $url = "https://open.feishu.cn/open-apis/authen/v1/index?redirect_uri=%s&app_id=%s";
+ $url = sprintf($url, $redirectURI, $appID, $state);
+ header("location: $url");
+ }
+
+ /**
+ * Get the identity of the logged-in user.
+ *
+ * @param string $code
+ * @access public
+ * @return void
+ */
+ public function feishuLogin($code = '')
+ {
+ if($this->config->requestType == 'PATH_INFO')
+ {
+ $params = $_SERVER["QUERY_STRING"];
+ parse_str($params, $params);
+ if(isset($params['code'])) $code = $params['code'];
+ }
+
+ $feishuConfig = $this->loadModel('webhook')->getByType('feishuuser');
+ if(empty($feishuConfig)) $this->showError($this->lang->sso->feishuConfigEmpty);
+ $appConfig = json_decode($feishuConfig->secret);
+
+ /* Obtain the access credentials of the Feishu app. */
+ $appUrl = 'https://open.feishu.cn/open-apis/auth/v3/app_access_token/internal';
+ $appParams = array('app_id' => $appConfig->appId, 'app_secret' => $appConfig->appSecret);
+ $appResult = $this->sso->http($appUrl, $appParams, 'POST', 'json');
+
+ if(empty($appResult)) $this->showError($this->lang->sso->feishuResponseEmpty);
+ $appInfo = json_decode($appResult);
+
+ if(!isset($appInfo->msg) or $appInfo->msg != 'ok') $this->showError($appResult);
+ $accessToken = $appInfo->app_access_token;
+
+ /* Verify the identity of the logged in user. */
+ $tokenUrl = 'https://open.feishu.cn/open-apis/authen/v1/refresh_access_token';
+ $tokenHeaders = array('Authorization: Bearer ' . $accessToken);
+ $tokenParams = array('grant_type' => 'authorization_code', 'code' => $code);
+ $tokenResult = $this->sso->http($tokenUrl, $tokenParams, 'POST', 'json', array(), $tokenHeaders);
+
+ if(empty($tokenResult)) $this->showError($this->lang->sso->feishuResponseEmpty);
+ $tokenInfo = json_decode($tokenResult);
+
+ if(!isset($tokenInfo->msg) or $tokenInfo->msg != 'success') $this->showError($tokenResult);
+ $userToken = $tokenInfo->data->access_token;
+
+ /* Get login user information. */
+ $userUrl = 'https://open.feishu.cn/open-apis/authen/v1/user_info';
+ $userHeaders = array('Authorization: Bearer ' . $userToken);
+ $userResult = $this->sso->http($userUrl, array(), 'GET', 'json', array(), $userHeaders);
+
+ if(empty($userResult)) $this->showError($this->lang->sso->feishuResponseEmpty);
+ $userInfo = json_decode($userResult);
+
+ if(!isset($userInfo->msg) or $userInfo->msg != 'success') $this->showError($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)) $this->showError($this->lang->sso->unbound);
+
+ $user = $this->loadModel('user')->getById($account);
+ $password = $user->password;
+ $this->session->set('rand', '');
+ $user = $this->user->identify($account, $password);
+ $this->user->login($user);
+
+ $indexUrl = $this->createLink('my', 'index');
+ header("location: $indexUrl");
+ }
+
+ /**
+ * Display the error message.
+ *
+ * @param string $message
+ * @access public
+ * @return void
+ */
+ public function showError($message = '')
+ {
+ $this->view->title = $this->lang->sso->deny;
+ $this->view->message = $message;
+ $this->display('sso', 'error');
+ die();
+ }
}
diff --git a/module/sso/lang/zh-cn.php b/module/sso/lang/zh-cn.php
index c8c3fbd11b..2cad5b1681 100644
--- a/module/sso/lang/zh-cn.php
+++ b/module/sso/lang/zh-cn.php
@@ -33,7 +33,14 @@ $lang->sso->help = <<
2、代号和密钥必须与ZDOO后台设置的一致。
EOD; +$lang->sso->deny = '访问受限'; $lang->sso->bindNotice = '添加的新用户暂时没有权限,需要联系禅道管理员,给该用户分配权限。'; $lang->sso->bindNoPassword = '密码不能为空'; $lang->sso->bindNoUser = '该用户的登录密码错误,或该用户不存在!'; $lang->sso->bindHasAccount = '该用户名已经存在,请更换用户名,或直接绑定到该用户。'; + +$lang->sso->homeURL = '飞书主页配置URL:'; +$lang->sso->redirectURL = '飞书重定向配置URL:'; +$lang->sso->feishuConfigEmpty = '请在[后台][通知][Webhook]功能中配置(飞书工作消息通知)'; +$lang->sso->feishuResponseEmpty = '请求响应信息为空'; +$lang->sso->unbound = '当前飞书用户未在禅道webhook功能中进行用户关系绑定'; diff --git a/module/sso/model.php b/module/sso/model.php index 80a73e9938..851972e18b 100644 --- a/module/sso/model.php +++ b/module/sso/model.php @@ -105,4 +105,60 @@ class ssoModel extends model if(dao::isError()) return array('status' => 'fail', 'data' => dao::getError()); return array('status' => 'success', 'id' => $this->dao->lastInsertId()); } + + /** + * Initiate a request. + * + * @param string $url + * @param array $data + * @param string $mehtod GET|POST|PATCH + * @param string $dataType data|json + * @param array $options This is option and value pair, like CURLOPT_HEADER => true. Use curl_setopt function to set options. + * @param array $headers Set request headers. + * @static + * @access public + * @return string + */ + public function http($url, $data = array(), $method = 'POST', $dataType = 'data', $options = array(), $headers = array()) + { + global $lang, $app; + if(!extension_loaded('curl')) die($this->lang->error->noCurlExt); + + if(!is_array($headers)) $headers = (array)$headers; + $headers[] = "API-RemoteIP: " . zget($_SERVER, 'REMOTE_ADDR', ''); + if($dataType == 'json') + { + $headers[] = 'Content-Type: application/json;charset=utf-8'; + if(!empty($data)) $data = json_encode($data); + } + + $curl = curl_init(); + curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); + curl_setopt($curl, CURLOPT_USERAGENT, 'Sae T OAuth2 v0.1'); + curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 30); + curl_setopt($curl, CURLOPT_TIMEOUT, 30); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE); + curl_setopt($curl, CURLOPT_ENCODING, ""); + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE); + curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, FALSE); + curl_setopt($curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); + curl_setopt($curl, CURLOPT_HEADER, FALSE); + curl_setopt($curl, CURLINFO_HEADER_OUT, TRUE); + curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); + curl_setopt($curl, CURLOPT_URL, $url); + + if(!empty($data)) + { + if($method == 'POST') curl_setopt($curl, CURLOPT_POST, true); + if($method == 'PATCH') curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'PATCH'); + curl_setopt($curl, CURLOPT_POSTFIELDS, $data); + } + + if($options) curl_setopt_array($curl, $options); + + $response = curl_exec($curl); + curl_close($curl); + + return $response; + } } diff --git a/module/sso/view/error.html.php b/module/sso/view/error.html.php new file mode 100644 index 0000000000..cd6e33b62c --- /dev/null +++ b/module/sso/view/error.html.php @@ -0,0 +1,26 @@ + + * @package ZenTaoPMS + * @version $Id: deny.html.php 4129 2013-01-18 01:58:14Z wwccss $ + */ +include '../../common/view/header.lite.html.php'; +?> +