Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F14035556
D7930.id18034.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
17 KB
Referenced Files
None
Subscribers
None
D7930.id18034.diff
View Options
Index: resources/sql/autopatches/20140113.legalpadsig.sql
===================================================================
--- /dev/null
+++ resources/sql/autopatches/20140113.legalpadsig.sql
@@ -0,0 +1,8 @@
+ALTER TABLE {$NAMESPACE}_legalpad.legalpad_documentsignature
+ ADD secretKey VARCHAR(20) NOT NULL COLLATE utf8_bin;
+
+ALTER TABLE {$NAMESPACE}_legalpad.legalpad_documentsignature
+ ADD verified TINYINT(1) DEFAULT 0;
+
+ALTER TABLE {$NAMESPACE}_legalpad.legalpad_documentsignature
+ ADD KEY `secretKey` (secretKey);
Index: src/__phutil_library_map__.php
===================================================================
--- src/__phutil_library_map__.php
+++ src/__phutil_library_map__.php
@@ -838,6 +838,7 @@
'LegalpadDocumentSearchEngine' => 'applications/legalpad/query/LegalpadDocumentSearchEngine.php',
'LegalpadDocumentSignController' => 'applications/legalpad/controller/LegalpadDocumentSignController.php',
'LegalpadDocumentSignature' => 'applications/legalpad/storage/LegalpadDocumentSignature.php',
+ 'LegalpadDocumentSignatureVerificationController' => 'applications/legalpad/controller/LegalpadDocumentSignatureVerificationController.php',
'LegalpadDocumentViewController' => 'applications/legalpad/controller/LegalpadDocumentViewController.php',
'LegalpadMockMailReceiver' => 'applications/legalpad/mail/LegalpadMockMailReceiver.php',
'LegalpadReplyHandler' => 'applications/legalpad/mail/LegalpadReplyHandler.php',
@@ -3357,6 +3358,7 @@
'LegalpadDocumentSearchEngine' => 'PhabricatorApplicationSearchEngine',
'LegalpadDocumentSignController' => 'LegalpadController',
'LegalpadDocumentSignature' => 'LegalpadDAO',
+ 'LegalpadDocumentSignatureVerificationController' => 'LegalpadController',
'LegalpadDocumentViewController' => 'LegalpadController',
'LegalpadMockMailReceiver' => 'PhabricatorObjectMailReceiver',
'LegalpadReplyHandler' => 'PhabricatorMailReplyHandler',
Index: src/applications/auth/query/PhabricatorExternalAccountQuery.php
===================================================================
--- src/applications/auth/query/PhabricatorExternalAccountQuery.php
+++ src/applications/auth/query/PhabricatorExternalAccountQuery.php
@@ -167,4 +167,35 @@
return 'PhabricatorApplicationPeople';
}
+ /**
+ * Attempts to find an external account and if none exists creates a new
+ * external account with a shiny new ID and PHID.
+ *
+ * NOTE: This function assumes the first item in various query parameters is
+ * the correct value to use in creating a new external account.
+ */
+ public function loadOneOrCreate() {
+ $account = $this->executeOne();
+ if (!$account) {
+ $account = new PhabricatorExternalAccount();
+ if ($this->accountIDs) {
+ $account->setAccountID(reset($this->accountIDs));
+ }
+ if ($this->accountTypes) {
+ $account->setAccountType(reset($this->accountTypes));
+ }
+ if ($this->accountDomains) {
+ $account->setAccountDomain(reset($this->accountDomains));
+ }
+ if ($this->accountSecrets) {
+ $account->setAccountSecret(reset($this->accountSecrets));
+ }
+ if ($this->userPHIDs) {
+ $account->setUserPHID(reset($this->userPHIDs));
+ }
+ $account->save();
+ }
+ return $account;
+ }
+
}
Index: src/applications/legalpad/application/PhabricatorApplicationLegalpad.php
===================================================================
--- src/applications/legalpad/application/PhabricatorApplicationLegalpad.php
+++ src/applications/legalpad/application/PhabricatorApplicationLegalpad.php
@@ -48,6 +48,8 @@
'edit/(?P<id>\d+)/' => 'LegalpadDocumentEditController',
'comment/(?P<id>\d+)/' => 'LegalpadDocumentCommentController',
'view/(?P<id>\d+)/' => 'LegalpadDocumentViewController',
+ 'verify/(?P<code>[^/]+)/' =>
+ 'LegalpadDocumentSignatureVerificationController',
'document/' => array(
'preview/' => 'PhabricatorMarkupPreviewController'),
));
Index: src/applications/legalpad/controller/LegalpadDocumentSignController.php
===================================================================
--- src/applications/legalpad/controller/LegalpadDocumentSignController.php
+++ src/applications/legalpad/controller/LegalpadDocumentSignController.php
@@ -7,6 +7,10 @@
private $id;
+ public function shouldRequireLogin() {
+ return false;
+ }
+
public function willProcessRequest(array $data) {
$this->id = $data['id'];
}
@@ -25,38 +29,70 @@
return new Aphront404Response();
}
- $signature = id(new LegalpadDocumentSignature())
- ->loadOneWhere(
- 'documentPHID = %s AND documentVersion = %d AND signerPHID = %s',
- $document->getPHID(),
- $document->getVersions(),
- $user->getPHID());
+ $signer_phid = null;
+ $signature = null;
+ $signature_data = array();
+ if ($user->isLoggedIn()) {
+ $signer_phid = $user->getPHID();
+ $signature_data = array(
+ 'email' => $user->loadPrimaryEmailAddress());
+ } else if ($request->isFormPost()) {
+ $email = new PhutilEmailAddress($request->getStr('email'));
+ $email_obj = id(new PhabricatorUserEmail())
+ ->loadOneWhere('address = %s', $email->getAddress());
+ if ($email_obj) {
+ return $this->signInResponse();
+ }
+ $external_account = id(new PhabricatorExternalAccountQuery())
+ ->setViewer($user)
+ ->withAccountTypes(array('email'))
+ ->withAccountDomains(array($email->getDomainName()))
+ ->withAccountIDs(array($email->getAddress()))
+ ->loadOneOrCreate();
+ if ($external_account->getUserPHID()) {
+ return $this->signInResponse();
+ }
+ $signer_phid = $external_account->getPHID();
+ }
+
+ if ($signer_phid) {
+ $signature = id(new LegalpadDocumentSignature())
+ ->loadOneWhere(
+ 'documentPHID = %s AND documentVersion = %d AND signerPHID = %s',
+ $document->getPHID(),
+ $document->getVersions(),
+ $signer_phid);
+ }
if (!$signature) {
$has_signed = false;
$error_view = null;
$signature = id(new LegalpadDocumentSignature())
- ->setSignerPHID($user->getPHID())
+ ->setSignerPHID($signer_phid)
->setDocumentPHID($document->getPHID())
- ->setDocumentVersion($document->getVersions());
- $data = array(
- 'name' => $user->getRealName(),
- 'email' => $user->loadPrimaryEmailAddress());
- $signature->setSignatureData($data);
+ ->setDocumentVersion($document->getVersions())
+ ->setSignatureData($signature_data);
} else {
$has_signed = true;
+ if ($signature->isVerified()) {
+ $title = pht('Already Signed');
+ $body = $this->getVerifiedSignatureBlurb();
+ } else {
+ $title = pht('Already Signed but...');
+ $body = $this->getUnverifiedSignatureBlurb();
+ }
$error_view = id(new AphrontErrorView())
->setSeverity(AphrontErrorView::SEVERITY_NOTICE)
- ->setTitle(pht('Already Signed'))
- ->appendChild(pht('Thank you for signing and agreeing'));
- $data = $signature->getSignatureData();
+ ->setTitle($title)
+ ->appendChild($body);
+ $signature_data = $signature->getSignatureData();
}
$e_name = true;
$e_email = true;
$e_address_1 = true;
$errors = array();
- if ($request->isFormPost()) {
+ if ($request->isFormPost() && !$has_signed) {
$name = $request->getStr('name');
$email = $request->getStr('email');
$address_1 = $request->getStr('address_1');
@@ -68,8 +104,9 @@
$e_name = pht('Required');
$errors[] = pht('Name field is required.');
}
- $data['name'] = $name;
+ $signature_data['name'] = $name;
+ $addr_obj = null;
if (!$email) {
$e_email = pht('Required');
$errors[] = pht('Email field is required.');
@@ -81,29 +118,47 @@
$errors[] = pht('A valid email is required.');
}
}
- $data['email'] = $email;
+ $signature_data['email'] = $email;
if (!$address_1) {
$e_address_1 = pht('Required');
$errors[] = pht('Address line 1 field is required.');
}
- $data['address_1'] = $address_1;
- $data['address_2'] = $address_2;
- $data['phone'] = $phone;
- $signature->setSignatureData($data);
+ $signature_data['address_1'] = $address_1;
+ $signature_data['address_2'] = $address_2;
+ $signature_data['phone'] = $phone;
+ $signature->setSignatureData($signature_data);
if (!$agree) {
$errors[] = pht(
'You must check "I agree to the terms laid forth above."');
}
+ $verified = LegalpadDocumentSignature::UNVERIFIED;
+ if ($user->isLoggedIn() && $addr_obj) {
+ $email_obj = id(new PhabricatorUserEmail())
+ ->loadOneWhere('address = %s', $addr_obj->getAddress());
+ if ($email_obj && $email_obj->getUserPHID() == $user->getPHID()) {
+ $verified = LegalpadDocumentSignature::VERIFIED;
+ }
+ }
+ $signature->setVerified($verified);
+
if (!$errors) {
$signature->save();
$has_signed = true;
+ if ($signature->isVerified()) {
+ $body = $this->getVerifiedSignatureBlurb();
+ } else {
+ $body = $this->getUnverifiedSignatureBlurb();
+ $this->sendVerifySignatureEmail(
+ $document,
+ $signature);
+ }
$error_view = id(new AphrontErrorView())
->setSeverity(AphrontErrorView::SEVERITY_NOTICE)
- ->setTitle(pht('Signature successful'))
- ->appendChild(pht('Thank you for signing and agreeing'));
+ ->setTitle(pht('Signature Successful'))
+ ->appendChild($body);
} else {
$error_view = id(new AphrontErrorView())
->setTitle(pht('Error in submission.'))
@@ -218,10 +273,61 @@
->setValue(pht('Sign and Agree'))
->setDisabled($has_signed));
- return id(new PHUIObjectBoxView())
+ $view = id(new PHUIObjectBoxView())
->setHeaderText(pht('Sign and Agree'))
- ->setErrorView($error_view)
->setForm($form);
+ if ($error_view) {
+ $view->setErrorView($error_view);
+ }
+ return $view;
+ }
+
+ private function getVerifiedSignatureBlurb() {
+ return pht('Thank you for signing and agreeing.');
+ }
+
+ private function getUnverifiedSignatureBlurb() {
+ return pht('Thank you for signing and agreeing. However, you must '.
+ 'verify your email address. Please check your email '.
+ 'and follow the instructions.');
+ }
+
+ private function sendVerifySignatureEmail(
+ LegalpadDocument $doc,
+ LegalpadDocumentSignature $signature) {
+
+ $signature_data = $signature->getSignatureData();
+ $email = new PhutilEmailAddress($signature_data['email']);
+ $doc_link = PhabricatorEnv::getProductionURI($doc->getMonogram());
+ $path = $this->getApplicationURI(sprintf(
+ '/verify/%s/',
+ $signature->getSecretKey()));
+ $link = PhabricatorEnv::getProductionURI($path);
+
+ $body = <<<EOBODY
+Hi {$signature_data['name']},
+
+This email address was used to sign a Legalpad document ({$doc_link}).
+Please verify you own this email address by clicking this link:
+
+ {$link}
+
+Your signature is invalid until you verify you own the email.
+EOBODY;
+
+ id(new PhabricatorMetaMTAMail())
+ ->addRawTos(array($email->getAddress()))
+ ->setSubject(pht('[Legalpad] Signature Verification'))
+ ->setBody($body)
+ ->setRelatedPHID($signature->getDocumentPHID())
+ ->saveAndSend();
+ }
+
+ private function signInResponse() {
+ return id(new Aphront403Response())
+ ->setForbiddenText(pht(
+ 'The email address specified is associated with an account. '.
+ 'Please login to that account and sign this document again.'));
}
}
Index: src/applications/legalpad/controller/LegalpadDocumentSignatureVerificationController.php
===================================================================
--- /dev/null
+++ src/applications/legalpad/controller/LegalpadDocumentSignatureVerificationController.php
@@ -0,0 +1,84 @@
+<?php
+
+final class LegalpadDocumentSignatureVerificationController
+extends LegalpadController {
+
+ private $code;
+
+ public function willProcessRequest(array $data) {
+ $this->code = $data['code'];
+ }
+
+ public function shouldRequireEmailVerification() {
+ return false;
+ }
+
+ public function shouldRequireLogin() {
+ return false;
+ }
+
+ public function processRequest() {
+ $request = $this->getRequest();
+ $user = $request->getUser();
+
+ $signature = id(new LegalpadDocumentSignature())
+ ->loadOneWhere('secretKey = %s', $this->code);
+
+ if (!$signature) {
+ $title = pht('Unable to Verify Signature');
+ $content = pht(
+ 'The verification code you provided is incorrect or the signature '.
+ 'has been removed. '.
+ 'Make sure you followed the link in the email correctly.');
+ $uri = $this->getApplicationURI();
+ $continue = pht('Rats!');
+ } else {
+ $document = id(new LegalpadDocumentQuery())
+ ->setViewer($user)
+ ->withPHIDs(array($signature->getDocumentPHID()))
+ ->executeOne();
+ // the document could be deleted or have its permissions changed
+ // 4oh4 time
+ if (!$document) {
+ return new Aphront404Response();
+ }
+ $uri = '/'.$document->getMonogram();
+ if ($signature->isVerified()) {
+ $title = pht('Signature Already Verified');
+ $content = pht(
+ 'This signature has already been verified.');
+ $continue = pht('Continue to Legalpad Document');
+ } else {
+ $guard = AphrontWriteGuard::beginScopedUnguardedWrites();
+ $signature
+ ->setVerified(LegalpadDocumentSignature::VERIFIED)
+ ->save();
+ unset($guard);
+ $title = pht('Signature Verified');
+ $content = pht('The signature is now verified.');
+ $continue = pht('Continue to Legalpad Document');
+ }
+ }
+
+ $dialog = id(new AphrontDialogView())
+ ->setUser($user)
+ ->setTitle($title)
+ ->setMethod('GET')
+ ->addCancelButton($uri, $continue)
+ ->appendChild($content);
+
+ $crumbs = $this->buildApplicationCrumbs();
+ $crumbs->addTextCrumb(pht('Verify Signature'));
+
+ return $this->buildApplicationPage(
+ array(
+ $crumbs,
+ $dialog,
+ ),
+ array(
+ 'title' => pht('Verify Signature'),
+ 'device' => true,
+ ));
+ }
+
+}
Index: src/applications/legalpad/storage/LegalpadDocument.php
===================================================================
--- src/applications/legalpad/storage/LegalpadDocument.php
+++ src/applications/legalpad/storage/LegalpadDocument.php
@@ -61,6 +61,10 @@
return parent::save();
}
+ public function getMonogram() {
+ return 'L'.$this->getID();
+ }
+
/* -( PhabricatorSubscribableInterface Implementation )-------------------- */
public function isAutomaticallySubscribed($phid) {
Index: src/applications/legalpad/storage/LegalpadDocumentSignature.php
===================================================================
--- src/applications/legalpad/storage/LegalpadDocumentSignature.php
+++ src/applications/legalpad/storage/LegalpadDocumentSignature.php
@@ -5,10 +5,15 @@
*/
final class LegalpadDocumentSignature extends LegalpadDAO {
+ const VERIFIED = 0;
+ const UNVERIFIED = 1;
+
protected $documentPHID;
protected $documentVersion;
protected $signerPHID;
protected $signatureData = array();
+ protected $verified;
+ protected $secretKey;
public function getConfiguration() {
return array(
@@ -18,6 +23,15 @@
) + parent::getConfiguration();
}
+ public function save() {
+ if (!$this->getSecretKey()) {
+ $this->setSecretKey(Filesystem::readRandomCharacters(20));
+ }
+ return parent::save();
+ }
+ public function isVerified() {
+ return $this->getVerified() != self::UNVERIFIED;
+ }
}
Index: src/applications/metamta/receiver/PhabricatorMailReceiver.php
===================================================================
--- src/applications/metamta/receiver/PhabricatorMailReceiver.php
+++ src/applications/metamta/receiver/PhabricatorMailReceiver.php
@@ -80,20 +80,13 @@
$email_key = 'phabricator.allow-email-users';
$allow_email_users = PhabricatorEnv::getEnvConfig($email_key);
if ($allow_email_users) {
- $xuser = id(new PhabricatorExternalAccount())->loadOneWhere(
- 'accountType = %s AND accountDomain = %s and accountID = %s',
- 'email',
- 'self',
- $from);
- if (!$xuser) {
- $xuser = id(new PhabricatorExternalAccount())
- ->setAccountID($from)
- ->setAccountType('email')
- ->setAccountDomain('self')
- ->setDisplayName($from)
- ->setEmail($from)
- ->save();
- }
+ $from_obj = new PhutilEmailAddress($from);
+ $xuser = id(new PhabricatorExternalAccountQuery())
+ ->setViewer($user)
+ ->withAccountTypes(array('email'))
+ ->withAccountDomains(array($from_obj->getDomainName(), 'self'))
+ ->withAccountIDs(array($from_obj->getAddress()))
+ ->loadOneOrCreate();
return $xuser->getPhabricatorUser();
} else {
$reasons[] = pht(
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Mon, Nov 11, 6:28 AM (2 d, 22 h ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6730874
Default Alt Text
D7930.id18034.diff (17 KB)
Attached To
Mode
D7930: Legalpad - make it work for not logged in users
Attached
Detach File
Event Timeline
Log In to Comment