- modify PhutilSlackAuthAdapter is easier than add Lark adapter/provider alone.
- need change makeTokenRequest to protected in PhutilOAuthAuthAdapter.php
- need override getContentSecurityPolicyFormActions with lark redirect uri domain in provider
Created
September 24, 2020 04:02
-
-
Save 0x1628/3553711e07f0bef8097cc6a9296d5722 to your computer and use it in GitHub Desktop.
Add Lark OAuth login for phabricator
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* Authentication adapter for Lark OAuth2. | |
*/ | |
final class PhutilLarkAuthAdapter extends PhutilOAuthAuthAdapter { | |
private $appAccessToken; | |
public function getAdapterType() { | |
return 'Lark'; | |
} | |
public function getAdapterDomain() { | |
return 'feishu.cn'; | |
} | |
public function getAuthenticateURI() { | |
$params = array( | |
'app_id' => $this->getClientID(), | |
'redirect_uri' => $this->getRedirectURI(), | |
'state' => $this->getState(), | |
) + $this->getExtraAuthenticateParameters(); | |
$uri = new PhutilURI($this->getAuthenticateBaseURI(), $params); | |
return phutil_string_cast($uri); | |
} | |
private function getAppAccessToken() { | |
if ($this->appAccessToken === null) { | |
$this->appAccessToken = $this->loadAppAccessToken(); | |
} | |
return $this->appAccessToken; | |
} | |
private function loadAppAccessToken() { | |
$query_data = array( | |
'app_id' => $this->getClientID(), | |
'app_secret' => $this->getClientSecret()->openEnvelope(), | |
); | |
$future = new HTTPSFuture('https://open.feishu.cn/open-apis/auth/v3/app_access_token/internal/'); | |
$future->addHeader('Content-Type', 'application/json'); | |
$future->setData(phutil_json_encode($query_data)); | |
list($body) = $future->resolvex(); | |
$body = json_decode($body, true); | |
return $body['app_access_token']; | |
} | |
protected function makeTokenRequest(array $params) { | |
$app_access_token = $this->getAppAccessToken(); | |
$uri = $this->getTokenBaseURI(); | |
$query_data = array( | |
'app_access_token' => $app_access_token, | |
'grant_type' => 'authorization_code', | |
) + $params; | |
$future = new HTTPSFuture($uri); | |
$future->setMethod('POST'); | |
$future->addHeader('Content-Type', 'application/json'); | |
$future->setData(phutil_json_encode($query_data)); | |
list($body) = $future->resolvex(); | |
$data = $this->readAccessTokenResponse($body); | |
if (isset($data['expires_in'])) { | |
$data['expires_epoch'] = $data['expires_in']; | |
} else if (isset($data['expires'])) { | |
$data['expires_epoch'] = $data['expires']; | |
} | |
// If we got some "expires" value back, interpret it as an epoch timestamp | |
// if it's after the year 2010 and as a relative number of seconds | |
// otherwise. | |
if (isset($data['expires_epoch'])) { | |
if ($data['expires_epoch'] < (60 * 60 * 24 * 365 * 40)) { | |
$data['expires_epoch'] += time(); | |
} | |
} | |
if (isset($data['error'])) { | |
throw new Exception(pht('Access token error: %s', $data['error'])); | |
} | |
return $data; | |
} | |
protected function readAccessTokenResponse($body) { | |
// NOTE: Most providers either return JSON or HTTP query strings, so try | |
// both mechanisms. If your provider does something else, override this | |
// method. | |
$data = json_decode($body, true); | |
if (empty($data['data'])) { | |
throw new Exception(pht('Failed to decode %s', $body)); | |
} | |
$data = $data['data']; | |
if (empty($data['access_token']) && | |
empty($data['error'])) { | |
throw new Exception( | |
pht('Failed to decode OAuth access token response: %s', $body)); | |
} | |
return $data; | |
} | |
public function getAccountID() { | |
$user = $this->getOAuthAccountData('data'); | |
return idx($user, 'user_id'); | |
} | |
public function getAccountEmail() { | |
$user = $this->getOAuthAccountData('data'); | |
return idx($user, 'email'); | |
} | |
public function getAccountImageURI() { | |
$user = $this->getOAuthAccountData('data'); | |
return idx($user, 'avatar_big'); | |
} | |
public function getAccountRealName() { | |
$user = $this->getOAuthAccountData('data'); | |
return idx($user, 'name'); | |
} | |
protected function getAuthenticateBaseURI() { | |
return 'https://open.feishu.cn/open-apis/authen/v1/index'; | |
} | |
protected function getTokenBaseURI() { | |
return 'https://open.feishu.cn/open-apis/authen/v1/access_token'; | |
} | |
public function getScope() { | |
return 'identity.basic,identity.team,identity.avatar'; | |
} | |
public function getExtraAuthenticateParameters() { | |
return array( | |
'response_type' => 'code', | |
); | |
} | |
protected function loadOAuthAccountData() { | |
$uri = new PhutilURI('https://open.feishu.cn/open-apis/authen/v1/user_info'); | |
$future = new HTTPSFuture($uri); | |
$future->addHeader('Content-Type', 'application/json'); | |
$future->addHeader('Authorization', 'Bearer ' . $this->getAccessToken()); | |
list($status, $body) = $future->resolve(); | |
if ($status->isError()) { | |
throw $status; | |
} | |
try { | |
$result = phutil_json_decode($body); | |
} catch (PhutilJSONParserException $ex) { | |
throw new PhutilProxyException( | |
pht('Expected valid JSON response from Lark account data request.'), | |
$ex); | |
} | |
return $result; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thanks a lot.
After add these php file, you need to execute
arc liberate
in phabricator server to load them.And in step 3, lark redirect url of
getContentSecurityPolicyFormActions
ishttps://open.zjurl.cn/
andhttps://www.feishu.cn/
in China, which you can inspect in browser when login phabricator by Lark.