Skip to content

Instantly share code, notes, and snippets.

@0x1628
Created September 24, 2020 04:02
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save 0x1628/3553711e07f0bef8097cc6a9296d5722 to your computer and use it in GitHub Desktop.
Save 0x1628/3553711e07f0bef8097cc6a9296d5722 to your computer and use it in GitHub Desktop.
Add Lark OAuth login for phabricator
  1. modify PhutilSlackAuthAdapter is easier than add Lark adapter/provider alone.
  2. need change makeTokenRequest to protected in PhutilOAuthAuthAdapter.php
  3. need override getContentSecurityPolicyFormActions with lark redirect uri domain in provider
<?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;
}
}
@zyf0330
Copy link

zyf0330 commented Dec 1, 2020

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 is https://open.zjurl.cn/ and https://www.feishu.cn/ in China, which you can inspect in browser when login phabricator by Lark.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment