Path: blob/master/src/applications/auth/provider/PhabricatorJIRAAuthProvider.php
12256 views
<?php12final class PhabricatorJIRAAuthProvider3extends PhabricatorOAuth1AuthProvider4implements DoorkeeperRemarkupURIInterface {56public function getProviderName() {7return pht('JIRA');8}910public function getDescriptionForCreate() {11return pht('Configure JIRA OAuth. NOTE: Only supports JIRA 6.');12}1314public function getConfigurationHelp() {15return $this->getProviderConfigurationHelp();16}1718protected function getProviderConfigurationHelp() {19if ($this->isSetup()) {20return pht(21"**Step 1 of 2**: Provide the name and URI for your JIRA install.\n\n".22"In the next step, you will configure JIRA.");23} else {24$login_uri = PhabricatorEnv::getURI($this->getLoginURI());25return pht(26"**Step 2 of 2**: In this step, you will configure JIRA.\n\n".27"**Create a JIRA Application**: Log into JIRA and go to ".28"**Administration**, then **Add-ons**, then **Application Links**. ".29"Click the button labeled **Add Application Link**, and use these ".30"settings to create an application:\n\n".31" - **Server URL**: `%s`\n".32" - Then, click **Next**. On the second page:\n".33" - **Application Name**: `%s`\n".34" - **Application Type**: `Generic Application`\n".35" - Then, click **Create**.\n\n".36"**Configure Your Application**: Find the application you just ".37"created in the table, and click the **Configure** link under ".38"**Actions**. Select **Incoming Authentication** and click the ".39"**OAuth** tab (it may be selected by default). Then, use these ".40"settings:\n\n".41" - **Consumer Key**: Set this to the \"Consumer Key\" value in the ".42"form above.\n".43" - **Consumer Name**: `%s`\n".44" - **Public Key**: Set this to the \"Public Key\" value in the ".45"form above.\n".46" - **Consumer Callback URL**: `%s`\n".47"Click **Save** in JIRA. Authentication should now be configured, ".48"and this provider should work correctly.",49PhabricatorEnv::getProductionURI('/'),50PlatformSymbols::getPlatformServerName(),51PlatformSymbols::getPlatformServerName(),52$login_uri);53}54}5556protected function newOAuthAdapter() {57$config = $this->getProviderConfig();5859return id(new PhutilJIRAAuthAdapter())60->setAdapterDomain($config->getProviderDomain())61->setJIRABaseURI($config->getProperty(self::PROPERTY_JIRA_URI))62->setPrivateKey(63new PhutilOpaqueEnvelope(64$config->getProperty(self::PROPERTY_PRIVATE_KEY)));65}6667protected function getLoginIcon() {68return 'Jira';69}7071private function isSetup() {72return !$this->getProviderConfig()->getID();73}7475const PROPERTY_JIRA_NAME = 'oauth1:jira:name';76const PROPERTY_JIRA_URI = 'oauth1:jira:uri';77const PROPERTY_PUBLIC_KEY = 'oauth1:jira:key:public';78const PROPERTY_PRIVATE_KEY = 'oauth1:jira:key:private';79const PROPERTY_REPORT_LINK = 'oauth1:jira:report:link';80const PROPERTY_REPORT_COMMENT = 'oauth1:jira:report:comment';818283public function readFormValuesFromProvider() {84$config = $this->getProviderConfig();85$uri = $config->getProperty(self::PROPERTY_JIRA_URI);8687return array(88self::PROPERTY_JIRA_NAME => $this->getProviderDomain(),89self::PROPERTY_JIRA_URI => $uri,90);91}9293public function readFormValuesFromRequest(AphrontRequest $request) {94$is_setup = $this->isSetup();95if ($is_setup) {96$name = $request->getStr(self::PROPERTY_JIRA_NAME);97} else {98$name = $this->getProviderDomain();99}100101return array(102self::PROPERTY_JIRA_NAME => $name,103self::PROPERTY_JIRA_URI => $request->getStr(self::PROPERTY_JIRA_URI),104self::PROPERTY_REPORT_LINK =>105$request->getInt(self::PROPERTY_REPORT_LINK, 0),106self::PROPERTY_REPORT_COMMENT =>107$request->getInt(self::PROPERTY_REPORT_COMMENT, 0),108);109}110111public function processEditForm(112AphrontRequest $request,113array $values) {114$errors = array();115$issues = array();116117$is_setup = $this->isSetup();118119$key_name = self::PROPERTY_JIRA_NAME;120$key_uri = self::PROPERTY_JIRA_URI;121122if (!strlen($values[$key_name])) {123$errors[] = pht('JIRA instance name is required.');124$issues[$key_name] = pht('Required');125} else if (!preg_match('/^[a-z0-9.]+\z/', $values[$key_name])) {126$errors[] = pht(127'JIRA instance name must contain only lowercase letters, digits, and '.128'period.');129$issues[$key_name] = pht('Invalid');130}131132if (!strlen($values[$key_uri])) {133$errors[] = pht('JIRA base URI is required.');134$issues[$key_uri] = pht('Required');135} else {136$uri = new PhutilURI($values[$key_uri]);137if (!$uri->getProtocol()) {138$errors[] = pht(139'JIRA base URI should include protocol (like "https://").');140$issues[$key_uri] = pht('Invalid');141}142}143144if (!$errors && $is_setup) {145$config = $this->getProviderConfig();146147$config->setProviderDomain($values[$key_name]);148149$consumer_key = 'phjira.'.Filesystem::readRandomCharacters(16);150list($public, $private) = PhutilJIRAAuthAdapter::newJIRAKeypair();151152$config->setProperty(self::PROPERTY_PUBLIC_KEY, $public);153$config->setProperty(self::PROPERTY_PRIVATE_KEY, $private);154$config->setProperty(self::PROPERTY_CONSUMER_KEY, $consumer_key);155}156157return array($errors, $issues, $values);158}159160public function extendEditForm(161AphrontRequest $request,162AphrontFormView $form,163array $values,164array $issues) {165166if (!function_exists('openssl_pkey_new')) {167// TODO: This could be a bit prettier.168throw new Exception(169pht(170"The PHP 'openssl' extension is not installed. You must install ".171"this extension in order to add a JIRA authentication provider, ".172"because JIRA OAuth requests use the RSA-SHA1 signing algorithm. ".173"Install the 'openssl' extension, restart everything, and try ".174"again."));175}176177$form->appendRemarkupInstructions(178pht(179'NOTE: This provider **only supports JIRA 6**. It will not work with '.180'JIRA 5 or earlier.'));181182$is_setup = $this->isSetup();183$viewer = $request->getViewer();184185$e_required = $request->isFormPost() ? null : true;186187$v_name = $values[self::PROPERTY_JIRA_NAME];188if ($is_setup) {189$e_name = idx($issues, self::PROPERTY_JIRA_NAME, $e_required);190} else {191$e_name = null;192}193194$v_uri = $values[self::PROPERTY_JIRA_URI];195$e_uri = idx($issues, self::PROPERTY_JIRA_URI, $e_required);196197if ($is_setup) {198$form199->appendRemarkupInstructions(200pht(201"**JIRA Instance Name**\n\n".202"Choose a permanent name for this instance of JIRA. This name is ".203"used internally to keep track of this particular instance of ".204"JIRA, in case the URL changes later.\n\n".205"Use lowercase letters, digits, and period. For example, ".206"`jira`, `jira.mycompany` or `jira.engineering` are reasonable ".207"names."))208->appendChild(209id(new AphrontFormTextControl())210->setLabel(pht('JIRA Instance Name'))211->setValue($v_name)212->setName(self::PROPERTY_JIRA_NAME)213->setError($e_name));214} else {215$form216->appendChild(217id(new AphrontFormStaticControl())218->setLabel(pht('JIRA Instance Name'))219->setValue($v_name));220}221222$form223->appendChild(224id(new AphrontFormTextControl())225->setLabel(pht('JIRA Base URI'))226->setValue($v_uri)227->setName(self::PROPERTY_JIRA_URI)228->setCaption(229pht(230'The URI where JIRA is installed. For example: %s',231phutil_tag('tt', array(), 'https://jira.mycompany.com/')))232->setError($e_uri));233234if (!$is_setup) {235$config = $this->getProviderConfig();236237238$ckey = $config->getProperty(self::PROPERTY_CONSUMER_KEY);239$ckey = phutil_tag('tt', array(), $ckey);240241$pkey = $config->getProperty(self::PROPERTY_PUBLIC_KEY);242$pkey = phutil_escape_html_newlines($pkey);243$pkey = phutil_tag('tt', array(), $pkey);244245$form246->appendRemarkupInstructions(247pht(248'NOTE: **To complete setup**, copy and paste these keys into JIRA '.249'according to the instructions below.'))250->appendChild(251id(new AphrontFormStaticControl())252->setLabel(pht('Consumer Key'))253->setValue($ckey))254->appendChild(255id(new AphrontFormStaticControl())256->setLabel(pht('Public Key'))257->setValue($pkey));258259$form260->appendRemarkupInstructions(261pht(262'= Integration Options = '."\n".263'Configure how to record Revisions on JIRA tasks.'."\n\n".264'Note you\'ll have to restart the daemons for this to take '.265'effect.'))266->appendChild(267id(new AphrontFormCheckboxControl())268->addCheckbox(269self::PROPERTY_REPORT_LINK,2701,271new PHUIRemarkupView(272$viewer,273pht(274'Create **Issue Link** to the Revision, as an "implemented '.275'in" relationship.')),276$this->shouldCreateJIRALink()))277->appendChild(278id(new AphrontFormCheckboxControl())279->addCheckbox(280self::PROPERTY_REPORT_COMMENT,2811,282new PHUIRemarkupView(283$viewer,284pht(285'**Post a comment** in the JIRA task.')),286$this->shouldCreateJIRAComment()));287}288289}290291/**292* JIRA uses a setup step to generate public/private keys.293*/294public function hasSetupStep() {295return true;296}297298public static function getJIRAProvider() {299$providers = self::getAllEnabledProviders();300301foreach ($providers as $provider) {302if ($provider instanceof PhabricatorJIRAAuthProvider) {303return $provider;304}305}306307return null;308}309310public function newJIRAFuture(311PhabricatorExternalAccount $account,312$path,313$method,314$params = array()) {315316$adapter = clone $this->getAdapter();317$adapter->setToken($account->getProperty('oauth1.token'));318$adapter->setTokenSecret($account->getProperty('oauth1.token.secret'));319320return $adapter->newJIRAFuture($path, $method, $params);321}322323public function shouldCreateJIRALink() {324$config = $this->getProviderConfig();325return $config->getProperty(self::PROPERTY_REPORT_LINK, true);326}327328public function shouldCreateJIRAComment() {329$config = $this->getProviderConfig();330return $config->getProperty(self::PROPERTY_REPORT_COMMENT, true);331}332333/* -( DoorkeeperRemarkupURIInterface )------------------------------------- */334335public function getDoorkeeperURIRef(PhutilURI $uri) {336$uri_string = phutil_string_cast($uri);337338$pattern = '((https?://\S+?)/browse/([A-Z][A-Z0-9]*-[1-9]\d*))';339$matches = null;340if (!preg_match($pattern, $uri_string, $matches)) {341return null;342}343344if (strlen($uri->getFragment())) {345return null;346}347348if ($uri->getQueryParamsAsPairList()) {349return null;350}351352$domain = $matches[1];353$issue = $matches[2];354355$config = $this->getProviderConfig();356$base_uri = $config->getProperty(self::PROPERTY_JIRA_URI);357358if ($domain !== rtrim($base_uri, '/')) {359return null;360}361362return id(new DoorkeeperURIRef())363->setURI($uri)364->setApplicationType(DoorkeeperBridgeJIRA::APPTYPE_JIRA)365->setApplicationDomain($this->getProviderDomain())366->setObjectType(DoorkeeperBridgeJIRA::OBJTYPE_ISSUE)367->setObjectID($issue);368}369370}371372373