1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-22 21:40:55 +01:00

Add conduit endpoints for querying legalpad

Summary:
This diff adds conduit methods for searching for legalpad documents and signatures. This is very helpful for auditing who's actually signed a document. It also fixes the "contributorPHIDs" constraint in the existing search engine.

In order to expose legalpad signatures through Conduit, this adds a `phid` column to the `legalpad_documentsignature` table. It includes a migration (in the style of many previous phid-adding migrations) to actually populate the column.

Test Plan: We run this on my company's internal fork and it seems to work okay. I don't think any other conduit methods anywhere have tests (???), but if you can point me at one I'm glad to write a unit test!

Reviewers: O1 Blessed Committers, speck

Reviewed By: O1 Blessed Committers, speck

Subscribers: 20after4, speck, tobiaswiese

Differential Revision: https://we.phorge.it/D25018
This commit is contained in:
James Brown 2021-09-04 13:51:06 -04:00 committed by Christopher Speck
parent 69cb760921
commit a4948ec800
13 changed files with 458 additions and 15 deletions

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_legalpad.legalpad_documentsignature
ADD phid VARBINARY(64) NOT NULL;

View file

@ -0,0 +1,79 @@
<?php
$phid_type = PhabricatorLegalpadDocumentSignaturePHIDType::TYPECONST;
$docsig_table = new LegalpadDocumentSignature();
$conn = $docsig_table->establishConnection('w');
$table_name = $docsig_table->getTableName();
$chunk_size = 4096;
$temporary_table = 'tmp_20210802_docsig_id_map';
try {
queryfx(
$conn,
'CREATE TEMPORARY TABLE %T (
docsig_id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
docsig_phid VARBINARY(64) NOT NULL)',
$temporary_table);
} catch (AphrontAccessDeniedQueryException $ex) {
throw new PhutilProxyException(
pht(
'Failed to "CREATE TEMPORARY TABLE". You may need to "GRANT" the '.
'current MySQL user this permission.'),
$ex);
}
$table_iterator = id(new LiskRawMigrationIterator($conn, $table_name))
->setPageSize($chunk_size);
$chunk_iterator = new PhutilChunkedIterator($table_iterator, $chunk_size);
foreach ($chunk_iterator as $chunk) {
$map = array();
foreach ($chunk as $docsig_row) {
$phid = $docsig_row['phid'];
if (strlen($phid)) {
continue;
}
$phid = PhabricatorPHID::generateNewPHID($phid_type);
$id = $docsig_row['id'];
$map[(int)$id] = $phid;
}
if (!$map) {
continue;
}
$sql = array();
foreach ($map as $docsig_id => $docsig_phid) {
$sql[] = qsprintf(
$conn,
'(%d, %s)',
$docsig_id,
$docsig_phid);
}
queryfx(
$conn,
'TRUNCATE TABLE %T',
$temporary_table);
queryfx(
$conn,
'INSERT INTO %T (docsig_id, docsig_phid) VALUES %LQ',
$temporary_table,
$sql);
queryfx(
$conn,
'UPDATE %T c JOIN %T x ON c.id = x.docsig_id
SET c.phid = x.docsig_phid',
$table_name,
$temporary_table);
}

View file

@ -1723,6 +1723,7 @@ phutil_register_library_map(array(
'LegalpadDocumentQuery' => 'applications/legalpad/query/LegalpadDocumentQuery.php',
'LegalpadDocumentRemarkupRule' => 'applications/legalpad/remarkup/LegalpadDocumentRemarkupRule.php',
'LegalpadDocumentRequireSignatureTransaction' => 'applications/legalpad/xaction/LegalpadDocumentRequireSignatureTransaction.php',
'LegalpadDocumentSearchConduitAPIMethod' => 'applications/legalpad/conduit/LegalpadDocumentSearchConduitAPIMethod.php',
'LegalpadDocumentSearchEngine' => 'applications/legalpad/query/LegalpadDocumentSearchEngine.php',
'LegalpadDocumentSignController' => 'applications/legalpad/controller/LegalpadDocumentSignController.php',
'LegalpadDocumentSignature' => 'applications/legalpad/storage/LegalpadDocumentSignature.php',
@ -1742,6 +1743,7 @@ phutil_register_library_map(array(
'LegalpadRequireSignatureHeraldAction' => 'applications/legalpad/herald/LegalpadRequireSignatureHeraldAction.php',
'LegalpadSchemaSpec' => 'applications/legalpad/storage/LegalpadSchemaSpec.php',
'LegalpadSignatureNeededByObjectEdgeType' => 'applications/legalpad/edge/LegalpadSignatureNeededByObjectEdgeType.php',
'LegalpadSignatureSearchConduitAPIMethod' => 'applications/legalpad/conduit/LegalpadSignatureSearchConduitAPIMethod.php',
'LegalpadTransaction' => 'applications/legalpad/storage/LegalpadTransaction.php',
'LegalpadTransactionComment' => 'applications/legalpad/storage/LegalpadTransactionComment.php',
'LegalpadTransactionQuery' => 'applications/legalpad/query/LegalpadTransactionQuery.php',
@ -3659,8 +3661,11 @@ phutil_register_library_map(array(
'PhabricatorLabelProfileMenuItem' => 'applications/search/menuitem/PhabricatorLabelProfileMenuItem.php',
'PhabricatorLanguageSettingsPanel' => 'applications/settings/panel/PhabricatorLanguageSettingsPanel.php',
'PhabricatorLegalpadApplication' => 'applications/legalpad/application/PhabricatorLegalpadApplication.php',
'PhabricatorLegalpadBodySearchEngineAttachment' => 'applications/legalpad/engineextension/PhabricatorLegalpadBodySearchEngineAttachment.php',
'PhabricatorLegalpadDocumentPHIDType' => 'applications/legalpad/phid/PhabricatorLegalpadDocumentPHIDType.php',
'PhabricatorLegalpadDocumentSignaturePHIDType' => 'applications/legalpad/phid/PhabricatorLegalpadDocumentSignaturePHIDType.php',
'PhabricatorLegalpadSignaturePolicyRule' => 'applications/legalpad/policyrule/PhabricatorLegalpadSignaturePolicyRule.php',
'PhabricatorLegalpadSignaturesSearchEngineAttachment' => 'applications/legalpad/engineextension/PhabricatorLegalpadSignaturesSearchEngineAttachment.php',
'PhabricatorLibraryTestCase' => '__tests__/PhabricatorLibraryTestCase.php',
'PhabricatorLinkProfileMenuItem' => 'applications/search/menuitem/PhabricatorLinkProfileMenuItem.php',
'PhabricatorLipsumArtist' => 'applications/lipsum/image/PhabricatorLipsumArtist.php',
@ -7992,6 +7997,7 @@ phutil_register_library_map(array(
'PhabricatorSubscribableInterface',
'PhabricatorApplicationTransactionInterface',
'PhabricatorDestructibleInterface',
'PhabricatorConduitResultInterface',
),
'LegalpadDocumentBody' => array(
'LegalpadDAO',
@ -8008,11 +8014,13 @@ phutil_register_library_map(array(
'LegalpadDocumentQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'LegalpadDocumentRemarkupRule' => 'PhabricatorObjectRemarkupRule',
'LegalpadDocumentRequireSignatureTransaction' => 'LegalpadDocumentTransactionType',
'LegalpadDocumentSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'LegalpadDocumentSearchEngine' => 'PhabricatorApplicationSearchEngine',
'LegalpadDocumentSignController' => 'LegalpadController',
'LegalpadDocumentSignature' => array(
'LegalpadDAO',
'PhabricatorPolicyInterface',
'PhabricatorConduitResultInterface',
),
'LegalpadDocumentSignatureAddController' => 'LegalpadController',
'LegalpadDocumentSignatureListController' => 'LegalpadController',
@ -8030,6 +8038,7 @@ phutil_register_library_map(array(
'LegalpadRequireSignatureHeraldAction' => 'HeraldAction',
'LegalpadSchemaSpec' => 'PhabricatorConfigSchemaSpec',
'LegalpadSignatureNeededByObjectEdgeType' => 'PhabricatorEdgeType',
'LegalpadSignatureSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'LegalpadTransaction' => 'PhabricatorModularTransaction',
'LegalpadTransactionComment' => 'PhabricatorApplicationTransactionComment',
'LegalpadTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
@ -10233,8 +10242,11 @@ phutil_register_library_map(array(
'PhabricatorLabelProfileMenuItem' => 'PhabricatorProfileMenuItem',
'PhabricatorLanguageSettingsPanel' => 'PhabricatorEditEngineSettingsPanel',
'PhabricatorLegalpadApplication' => 'PhabricatorApplication',
'PhabricatorLegalpadBodySearchEngineAttachment' => 'PhabricatorSearchEngineAttachment',
'PhabricatorLegalpadDocumentPHIDType' => 'PhabricatorPHIDType',
'PhabricatorLegalpadDocumentSignaturePHIDType' => 'PhabricatorPHIDType',
'PhabricatorLegalpadSignaturePolicyRule' => 'PhabricatorPolicyRule',
'PhabricatorLegalpadSignaturesSearchEngineAttachment' => 'PhabricatorSearchEngineAttachment',
'PhabricatorLibraryTestCase' => 'PhutilLibraryTestCase',
'PhabricatorLinkProfileMenuItem' => 'PhabricatorProfileMenuItem',
'PhabricatorLipsumArtist' => 'Phobject',

View file

@ -0,0 +1,18 @@
<?php
final class LegalpadDocumentSearchConduitAPIMethod
extends PhabricatorSearchEngineAPIMethod {
public function getAPIMethodName() {
return 'legalpad.document.search';
}
public function newSearchEngine() {
return new LegalpadDocumentSearchEngine();
}
public function getMethodSummary() {
return pht('Read information about legalpad documents.');
}
}

View file

@ -0,0 +1,18 @@
<?php
final class LegalpadSignatureSearchConduitAPIMethod
extends PhabricatorSearchEngineAPIMethod {
public function getAPIMethodName() {
return 'legalpad.signature.search';
}
public function newSearchEngine() {
return new LegalpadDocumentSignatureSearchEngine();
}
public function getMethodSummary() {
return pht('Read information about legalpad document signatures.');
}
}

View file

@ -0,0 +1,25 @@
<?php
final class PhabricatorLegalpadBodySearchEngineAttachment
extends PhabricatorSearchEngineAttachment {
public function getAttachmentName() {
return pht('Legalpad Document Body');
}
public function getAttachmentDescription() {
return pht('Get the full content for each document.');
}
public function willLoadAttachmentData($query, $spec) {
$query->needDocumentBodies(true);
}
public function getAttachmentForObject($object, $data, $spec) {
return array(
'body' => $object->getDocumentBody()->getText(),
'preamble' => $object->getPreamble(),
);
}
}

View file

@ -0,0 +1,38 @@
<?php
final class PhabricatorLegalpadSignaturesSearchEngineAttachment
extends PhabricatorSearchEngineAttachment {
public function getAttachmentName() {
return pht('Document signers');
}
public function getAttachmentDescription() {
return pht('Get the signer list for the project.');
}
public function willLoadAttachmentData($query, $spec) {
$query->needSignatures(true);
}
public function getAttachmentForObject($object, $data, $spec) {
$signatures = array();
foreach ($object->getSignatures() as $signature) {
$signatures[] = array(
'phid' => $signature->getPHID(),
'signerPHID' => $signature->getSignerPHID(),
'exemptionPHID' => $signature->getExemptionPHID(),
'isExemption' => $signature->getIsExemption(),
'signerName' => $signature->getSignerName(),
'signerEmail' => $signature->getSignerEmail(),
'documentVersion' => $signature->getDocumentVersion(),
'dateCreated' => (int)$signature->getDateCreated(),
);
}
return array(
'signatures' => $signatures,
);
}
}

View file

@ -0,0 +1,47 @@
<?php
final class PhabricatorLegalpadDocumentSignaturePHIDType
extends PhabricatorPHIDType {
const TYPECONST = 'LEGS';
public function getTypeName() {
return pht('Legalpad Signature');
}
public function getTypeIcon() {
return 'fa-file-text-o';
}
public function newObject() {
return new LegalpadDocumentSignature();
}
public function getPHIDTypeApplicationClass() {
return 'PhabricatorLegalpadApplication';
}
protected function buildQueryForObjects(
PhabricatorObjectQuery $query,
array $phids) {
return id(new LegalpadDocumentSignatureQuery())
->withPHIDs($phids);
}
public function loadHandles(
PhabricatorHandleQuery $query,
array $handles,
array $objects) {
foreach ($handles as $phid => $handle) {
$sig = $objects[$phid];
$id = $sig->getID();
$handle->setName('Signature '.$id);
$signer_name = $sig->getSignerName();
$handle->setFullName("Signature {$id} by {$signer_name}");
$handle->setURI("/legalpad/signature/{$id}");
}
}
}

View file

@ -53,7 +53,7 @@ final class LegalpadDocumentSearchEngine
}
if ($map['contributorPHIDs']) {
$query->withContributorPHIDs($map['creatorPHIDs']);
$query->withContributorPHIDs($map['contributorPHIDs']);
}
if ($map['creatorPHIDs']) {

View file

@ -4,6 +4,7 @@ final class LegalpadDocumentSignatureQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $documentPHIDs;
private $signerPHIDs;
private $documentVersions;
@ -16,6 +17,11 @@ final class LegalpadDocumentSignatureQuery
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withDocumentPHIDs(array $phids) {
$this->documentPHIDs = $phids;
return $this;
@ -46,20 +52,14 @@ final class LegalpadDocumentSignatureQuery
return $this;
}
public function newResultObject() {
return new LegalpadDocumentSignature();
}
protected function loadPage() {
$table = new LegalpadDocumentSignature();
$conn_r = $table->establishConnection('r');
$data = queryfx_all(
$conn_r,
'SELECT * FROM %T %Q %Q %Q',
$table->getTableName(),
$this->buildWhereClause($conn_r),
$this->buildOrderClause($conn_r),
$this->buildLimitClause($conn_r));
$table = $this->newResultObject();
$data = $this->loadStandardPageRows($table);
$signatures = $table->loadAllFromArray($data);
return $signatures;
}
@ -98,6 +98,13 @@ final class LegalpadDocumentSignatureQuery
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'phid IN (%Ls)',
$this->phids);
}
if ($this->documentPHIDs !== null) {
$where[] = qsprintf(
$conn,

View file

@ -5,6 +5,75 @@ final class LegalpadDocumentSignatureSearchEngine
private $document;
public function newQuery() {
return new LegalpadDocumentSignatureQuery();
}
protected function buildCustomSearchFields() {
return array(
id(new PhabricatorUsersSearchField())
->setLabel(pht('Signed By'))
->setKey('signerPHIDs')
->setAliases(array('signer', 'signers', 'signerPHID'))
->setDescription(
pht('Search for signatures by given users.')),
id(new PhabricatorPHIDsSearchField())
->setLabel(pht('Documents'))
->setKey('documentPHIDs')
->setAliases(array('document', 'documents', 'documentPHID'))
->setDescription(
pht('Search for signatures on the given documents')),
id(new PhabricatorSearchTextField())
->setLabel(pht('Name Contains'))
->setKey('nameContains')
->setDescription(
pht('Search for signatures with a name containing the '.
'given string.')),
id(new PhabricatorSearchTextField())
->setLabel(pht('Email Contains'))
->setKey('emailContains')
->setDescription(
pht('Search for signatures with an email containing the '.
'given string.')),
id(new PhabricatorSearchDateField())
->setLabel(pht('Created After'))
->setKey('createdStart'),
id(new PhabricatorSearchDateField())
->setLabel(pht('Created Before'))
->setKey('createdEnd'),
);
}
protected function buildQueryFromParameters(array $map) {
$query = $this->newQuery();
if ($map['signerPHIDs']) {
$query->withSignerPHIDs($map['signerPHIDs']);
}
if ($map['documentPHIDs']) {
$query->withDocumentPHIDs($map['documentPHIDs']);
}
if ($map['createdStart']) {
$query->withDateCreatedAfter($map['createdStart']);
}
if ($map['createdEnd']) {
$query->withDateCreatedAfter($map['createdStart']);
}
if ($map['nameContains']) {
$query->withNameContains($map['nameContains']);
}
if ($map['emailContains']) {
$query->withEmailContains($map['emailContains']);
}
return $query;
}
public function getResultTypeDescription() {
return pht('Legalpad Signatures');
}

View file

@ -5,7 +5,8 @@ final class LegalpadDocument extends LegalpadDAO
PhabricatorPolicyInterface,
PhabricatorSubscribableInterface,
PhabricatorApplicationTransactionInterface,
PhabricatorDestructibleInterface {
PhabricatorDestructibleInterface,
PhabricatorConduitResultInterface {
protected $title;
protected $contributorCount;
@ -159,6 +160,10 @@ final class LegalpadDocument extends LegalpadDAO
return idx($map, $type, 'fa-user grey');
}
public function getPreamble() {
return $this->preamble;
}
/* -( PhabricatorSubscribableInterface )----------------------------------- */
@ -167,6 +172,48 @@ final class LegalpadDocument extends LegalpadDAO
return ($this->creatorPHID == $phid);
}
/* -( PhabricatorConduitResultInterface )---------------------------------- */
public function getFieldSpecificationsForConduit() {
return array(
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('title')
->setType('string')
->setDescription(pht('The title of this document')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('creatorPHID')
->setType('phid')
->setDescription(pht('This user who created this document')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('versions')
->setType('int')
->setDescription(pht('The number of versions of this document')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('requireSignature')
->setType('bool')
->setDescription(pht(
'Whether signatures on this doc are required to use this install')),
);
}
public function getFieldValuesForConduit() {
return array(
'title' => $this->title,
'creatorPHID' => $this->creatorPHID,
'versions' => $this->versions,
'requireSignature' => (bool)$this->requireSignature,
);
}
public function getConduitSearchAttachments() {
return array(
id(new PhabricatorLegalpadBodySearchEngineAttachment())
->setAttachmentKey('body'),
id(new PhabricatorLegalpadSignaturesSearchEngineAttachment())
->setAttachmentKey('signatures'),
);
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */

View file

@ -2,7 +2,9 @@
final class LegalpadDocumentSignature
extends LegalpadDAO
implements PhabricatorPolicyInterface {
implements
PhabricatorPolicyInterface,
PhabricatorConduitResultInterface {
const VERIFIED = 0;
const UNVERIFIED = 1;
@ -23,6 +25,7 @@ final class LegalpadDocumentSignature
protected function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_SERIALIZATION => array(
'signatureData' => self::SERIALIZATION_JSON,
),
@ -51,6 +54,14 @@ final class LegalpadDocumentSignature
) + parent::getConfiguration();
}
public function getPHIDType() {
return PhabricatorLegalpadDocumentSignaturePHIDType::TYPECONST;
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID($this->getPHIDType());
}
public function save() {
if (!$this->getSecretKey()) {
$this->setSecretKey(Filesystem::readRandomCharacters(20));
@ -71,6 +82,76 @@ final class LegalpadDocumentSignature
return $this;
}
public function getSignerPHID() {
return $this->signerPHID;
}
public function getIsExemption() {
return (bool)$this->isExemption;
}
public function getExemptionPHID() {
return $this->exemptionPHID;
}
public function getSignerName() {
return $this->signerName;
}
public function getSignerEmail() {
return $this->signerEmail;
}
public function getDocumentVersion() {
return (int)$this->documentVersion;
}
/* -( PhabricatorConduitResultInterface )---------------------------------- */
public function getFieldSpecificationsForConduit() {
return array(
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('documentPHID')
->setType('phid')
->setDescription(pht('The PHID of the document')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('signerPHID')
->setType('phid?')
->setDescription(pht('The PHID of the signer')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('exemptionPHID')
->setType('phid?')
->setDescription(pht('The PHID of the user who granted the exemption')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('signerName')
->setType('string')
->setDescription(pht('The name used by the signer.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('signerEmail')
->setType('string')
->setDescription(pht('The email used by the signer.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('isExemption')
->setType('bool')
->setDescription(pht('Whether or not this signature is an exemption')),
);
}
public function getFieldValuesForConduit() {
return array(
'documentPHID' => $this->getDocumentPHID(),
'signerPHID' => $this->getSignerPHID(),
'exemptionPHID' => $this->getExemptionPHID(),
'signerName' => $this->getSignerName(),
'signerEmail' => $this->getSignerEmail(),
'isExemption' => $this->getIsExemption(),
);
}
public function getConduitSearchAttachments() {
return array();
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */