diff --git a/resources/sql/autopatches/20150212.legalpad.session.1.sql b/resources/sql/autopatches/20150212.legalpad.session.1.sql new file mode 100644 --- /dev/null +++ b/resources/sql/autopatches/20150212.legalpad.session.1.sql @@ -0,0 +1,5 @@ +ALTER TABLE {$NAMESPACE}_user.phabricator_session + ADD signedLegalpadDocuments BOOL NOT NULL DEFAULT 0; + +ALTER TABLE {$NAMESPACE}_legalpad.legalpad_document + ADD requireSignature BOOL NOT NULL DEFAULT 0; diff --git a/resources/sql/autopatches/20150212.legalpad.session.2.sql b/resources/sql/autopatches/20150212.legalpad.session.2.sql new file mode 100644 --- /dev/null +++ b/resources/sql/autopatches/20150212.legalpad.session.2.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_legalpad.legalpad_document + ADD KEY `key_required` (requireSignature, dateModified); diff --git a/src/applications/auth/controller/PhabricatorAuthFinishController.php b/src/applications/auth/controller/PhabricatorAuthFinishController.php --- a/src/applications/auth/controller/PhabricatorAuthFinishController.php +++ b/src/applications/auth/controller/PhabricatorAuthFinishController.php @@ -11,6 +11,10 @@ return true; } + public function shouldAllowLegallyNonCompliantUsers() { + return true; + } + public function processRequest() { $request = $this->getRequest(); $viewer = $request->getUser(); diff --git a/src/applications/auth/controller/PhabricatorAuthValidateController.php b/src/applications/auth/controller/PhabricatorAuthValidateController.php --- a/src/applications/auth/controller/PhabricatorAuthValidateController.php +++ b/src/applications/auth/controller/PhabricatorAuthValidateController.php @@ -11,6 +11,10 @@ return true; } + public function shouldAllowLegallyNonCompliantUsers() { + return true; + } + public function processRequest() { $request = $this->getRequest(); $viewer = $request->getUser(); diff --git a/src/applications/auth/controller/PhabricatorLogoutController.php b/src/applications/auth/controller/PhabricatorLogoutController.php --- a/src/applications/auth/controller/PhabricatorLogoutController.php +++ b/src/applications/auth/controller/PhabricatorLogoutController.php @@ -21,6 +21,10 @@ return true; } + public function shouldAllowLegallyNonCompliantUsers() { + return true; + } + public function handleRequest(AphrontRequest $request) { $request = $this->getRequest(); $user = $request->getUser(); diff --git a/src/applications/auth/engine/PhabricatorAuthSessionEngine.php b/src/applications/auth/engine/PhabricatorAuthSessionEngine.php --- a/src/applications/auth/engine/PhabricatorAuthSessionEngine.php +++ b/src/applications/auth/engine/PhabricatorAuthSessionEngine.php @@ -134,6 +134,7 @@ s.sessionStart AS s_sessionStart, s.highSecurityUntil AS s_highSecurityUntil, s.isPartial AS s_isPartial, + s.signedLegalpadDocuments as s_signedLegalpadDocuments, u.* FROM %T u JOIN %T s ON u.phid = s.userPHID AND s.type = %s AND s.sessionKey = %s', @@ -232,6 +233,7 @@ ->setSessionStart(time()) ->setSessionExpires(time() + $session_ttl) ->setIsPartial($partial ? 1 : 0) + ->setSignedLegalpadDocuments(0) ->save(); $log = PhabricatorUserLog::initializeNewLog( @@ -553,6 +555,52 @@ } +/* -( Legalpad Documents )-------------------------------------------------- */ + + + /** + * Upgrade a session to have all legalpad documents signed. + * + * @param PhabricatorUser User whose session should upgrade. + * @param array LegalpadDocument objects + * @return void + * @task partial + */ + public function signLegalpadDocuments(PhabricatorUser $viewer, array $docs) { + + if (!$viewer->hasSession()) { + throw new Exception( + pht('Signing session legalpad documents of user with no session!')); + } + + $session = $viewer->getSession(); + + if ($session->getSignedLegalpadDocuments()) { + throw new Exception(pht( + 'Session has already signed required legalpad documents!')); + } + + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); + $session->setSignedLegalpadDocuments(1); + + queryfx( + $session->establishConnection('w'), + 'UPDATE %T SET signedLegalpadDocuments = %d WHERE id = %d', + $session->getTableName(), + 1, + $session->getID()); + + if (!empty($docs)) { + $log = PhabricatorUserLog::initializeNewLog( + $viewer, + $viewer->getPHID(), + PhabricatorUserLog::ACTION_LOGIN_LEGALPAD); + $log->save(); + } + unset($unguarded); + } + + /* -( One Time Login URIs )------------------------------------------------ */ diff --git a/src/applications/auth/storage/PhabricatorAuthSession.php b/src/applications/auth/storage/PhabricatorAuthSession.php --- a/src/applications/auth/storage/PhabricatorAuthSession.php +++ b/src/applications/auth/storage/PhabricatorAuthSession.php @@ -13,6 +13,7 @@ protected $sessionExpires; protected $highSecurityUntil; protected $isPartial; + protected $signedLegalpadDocuments; private $identityObject = self::ATTACHABLE; @@ -26,6 +27,7 @@ 'sessionExpires' => 'epoch', 'highSecurityUntil' => 'epoch?', 'isPartial' => 'bool', + 'signedLegalpadDocuments' => 'bool', ), self::CONFIG_KEY_SCHEMA => array( 'sessionKey' => array( diff --git a/src/applications/base/controller/PhabricatorController.php b/src/applications/base/controller/PhabricatorController.php --- a/src/applications/base/controller/PhabricatorController.php +++ b/src/applications/base/controller/PhabricatorController.php @@ -53,6 +53,10 @@ return PhabricatorEnv::getEnvConfig('security.require-multi-factor-auth'); } + public function shouldAllowLegallyNonCompliantUsers() { + return false; + } + public function willBeginExecution() { $request = $this->getRequest(); @@ -221,6 +225,47 @@ } } + + if (!$this->shouldAllowLegallyNonCompliantUsers()) { + $legalpad_class = 'PhabricatorLegalpadApplication'; + $legalpad = id(new PhabricatorApplicationQuery()) + ->setViewer($user) + ->withClasses(array($legalpad_class)) + ->withInstalled(true) + ->execute(); + $legalpad = head($legalpad); + + $doc_query = id(new LegalpadDocumentQuery()) + ->setViewer($user) + ->withSignatureRequired(1) + ->needViewerSignatures(true); + + if ($user->hasSession() && + !$user->getSession()->getIsPartial() && + !$user->getSession()->getSignedLegalpadDocuments() && + $user->isLoggedIn() && + $legalpad) { + + $sign_docs = $doc_query->execute(); + $must_sign_docs = array(); + foreach ($sign_docs as $sign_doc) { + if (!$sign_doc->getUserSignature($user->getPHID())) { + $must_sign_docs[] = $sign_doc; + } + } + if ($must_sign_docs) { + $controller = new LegalpadDocumentSignController(); + $this->getRequest()->setURIMap(array( + 'id' => head($must_sign_docs)->getID(),)); + $this->setCurrentApplication($legalpad); + return $this->delegateToController($controller); + } else { + $engine = id(new PhabricatorAuthSessionEngine()) + ->signLegalpadDocuments($user, $sign_docs); + } + } + } + // NOTE: We do this last so that users get a login page instead of a 403 // if they need to login. if ($this->shouldRequireAdmin() && !$user->getIsAdmin()) { diff --git a/src/applications/base/controller/__tests__/PhabricatorAccessControlTestCase.php b/src/applications/base/controller/__tests__/PhabricatorAccessControlTestCase.php --- a/src/applications/base/controller/__tests__/PhabricatorAccessControlTestCase.php +++ b/src/applications/base/controller/__tests__/PhabricatorAccessControlTestCase.php @@ -170,7 +170,7 @@ // Test public access. $this->checkAccess( - 'No Login Required', + 'Public Access', id(clone $controller)->setConfig('public', true), $request, array( diff --git a/src/applications/celerity/controller/CelerityResourceController.php b/src/applications/celerity/controller/CelerityResourceController.php --- a/src/applications/celerity/controller/CelerityResourceController.php +++ b/src/applications/celerity/controller/CelerityResourceController.php @@ -18,6 +18,10 @@ return true; } + public function shouldAllowLegallyNonCompliantUsers() { + return true; + } + abstract public function getCelerityResourceMap(); protected function serveResource($path, $package_hash = null) { diff --git a/src/applications/legalpad/constants/LegalpadTransactionType.php b/src/applications/legalpad/constants/LegalpadTransactionType.php --- a/src/applications/legalpad/constants/LegalpadTransactionType.php +++ b/src/applications/legalpad/constants/LegalpadTransactionType.php @@ -6,5 +6,6 @@ const TYPE_TEXT = 'text'; const TYPE_SIGNATURE_TYPE = 'legalpad:signature-type'; const TYPE_PREAMBLE = 'legalpad:premable'; + const TYPE_REQUIRE_SIGNATURE = 'legalpad:require-signature'; } diff --git a/src/applications/legalpad/controller/LegalpadDocumentEditController.php b/src/applications/legalpad/controller/LegalpadDocumentEditController.php --- a/src/applications/legalpad/controller/LegalpadDocumentEditController.php +++ b/src/applications/legalpad/controller/LegalpadDocumentEditController.php @@ -2,17 +2,11 @@ final class LegalpadDocumentEditController extends LegalpadController { - private $id; - - public function willProcessRequest(array $data) { - $this->id = idx($data, 'id'); - } - - public function processRequest() { - $request = $this->getRequest(); + public function handleRequest(AphrontRequest $request) { $user = $request->getUser(); - if (!$this->id) { + $id = $request->getURIData('id'); + if (!$id) { $is_create = true; $this->requireApplicationCapability( @@ -34,7 +28,7 @@ PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) - ->withIDs(array($this->id)) + ->withIDs(array($id)) ->executeOne(); if (!$document) { return new Aphront404Response(); @@ -48,6 +42,7 @@ $text = $document->getDocumentBody()->getText(); $v_signature_type = $document->getSignatureType(); $v_preamble = $document->getPreamble(); + $v_require_signature = $document->getRequireSignature(); $errors = array(); $can_view = null; @@ -97,6 +92,24 @@ ->setTransactionType(LegalpadTransactionType::TYPE_PREAMBLE) ->setNewValue($v_preamble); + $v_require_signature = $request->getBool('requireSignature', 0); + if ($v_require_signature) { + if (!$user->getIsAdmin()) { + $errors[] = pht('Only admins may require signature.'); + } + $corp = LegalpadDocument::SIGNATURE_TYPE_CORPORATION; + if ($v_signature_type == $corp) { + $errors[] = pht( + 'Only documents with signature type "individual" may require '. + 'signing to use Phabricator.'); + } + } + if ($user->getIsAdmin()) { + $xactions[] = id(new LegalpadTransaction()) + ->setTransactionType(LegalpadTransactionType::TYPE_REQUIRE_SIGNATURE) + ->setNewValue($v_require_signature); + } + if (!$errors) { $editor = id(new LegalpadDocumentEditor()) ->setContentSourceFromRequest($request) @@ -133,11 +146,29 @@ ->setName(pht('signatureType')) ->setValue($v_signature_type) ->setOptions(LegalpadDocument::getSignatureTypeMap())); + $show_require = true; } else { $form->appendChild( id(new AphrontFormMarkupControl()) ->setLabel(pht('Who Should Sign?')) ->setValue($document->getSignatureTypeName())); + $individual = LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL; + $show_require = $document->getSignatureType() == $individual; + } + + if ($show_require) { + $form + ->appendChild( + id(new AphrontFormCheckboxControl()) + ->setDisabled(!$user->getIsAdmin()) + ->setLabel(pht('Require Signature')) + ->addCheckbox( + 'requireSignature', + 'requireSignature', + pht( + 'Should signing this document be required to use Phabricator? '. + 'Applies to invidivuals only.'), + $v_require_signature)); } $form diff --git a/src/applications/legalpad/controller/LegalpadDocumentSignController.php b/src/applications/legalpad/controller/LegalpadDocumentSignController.php --- a/src/applications/legalpad/controller/LegalpadDocumentSignController.php +++ b/src/applications/legalpad/controller/LegalpadDocumentSignController.php @@ -2,23 +2,16 @@ final class LegalpadDocumentSignController extends LegalpadController { - private $id; - public function shouldAllowPublic() { return true; } - public function willProcessRequest(array $data) { - $this->id = $data['id']; - } - - public function processRequest() { - $request = $this->getRequest(); + public function handleRequest(AphrontRequest $request) { $viewer = $request->getUser(); $document = id(new LegalpadDocumentQuery()) ->setViewer($viewer) - ->withIDs(array($this->id)) + ->withIDs(array($request->getURIData('id'))) ->needDocumentBodies(true) ->executeOne(); if (!$document) { diff --git a/src/applications/legalpad/controller/LegalpadDocumentSignatureAddController.php b/src/applications/legalpad/controller/LegalpadDocumentSignatureAddController.php --- a/src/applications/legalpad/controller/LegalpadDocumentSignatureAddController.php +++ b/src/applications/legalpad/controller/LegalpadDocumentSignatureAddController.php @@ -2,13 +2,7 @@ final class LegalpadDocumentSignatureAddController extends LegalpadController { - private $id; - - public function willProcessRequest(array $data) { - $this->id = $data['id']; - } - - public function processRequest() { + public function handleRequest(AphrontRequest $request) { $request = $this->getRequest(); $viewer = $request->getUser(); @@ -20,7 +14,7 @@ PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) - ->withIDs(array($this->id)) + ->withIDs(array($request->getURIData('id'))) ->executeOne(); if (!$document) { return new Aphront404Response(); diff --git a/src/applications/legalpad/editor/LegalpadDocumentEditor.php b/src/applications/legalpad/editor/LegalpadDocumentEditor.php --- a/src/applications/legalpad/editor/LegalpadDocumentEditor.php +++ b/src/applications/legalpad/editor/LegalpadDocumentEditor.php @@ -32,6 +32,7 @@ $types[] = LegalpadTransactionType::TYPE_TEXT; $types[] = LegalpadTransactionType::TYPE_SIGNATURE_TYPE; $types[] = LegalpadTransactionType::TYPE_PREAMBLE; + $types[] = LegalpadTransactionType::TYPE_REQUIRE_SIGNATURE; return $types; } @@ -49,6 +50,8 @@ return $object->getSignatureType(); case LegalpadTransactionType::TYPE_PREAMBLE: return $object->getPreamble(); + case LegalpadTransactionType::TYPE_REQUIRE_SIGNATURE: + return $object->getRequireSignature(); } } @@ -61,6 +64,7 @@ case LegalpadTransactionType::TYPE_TEXT: case LegalpadTransactionType::TYPE_SIGNATURE_TYPE: case LegalpadTransactionType::TYPE_PREAMBLE: + case LegalpadTransactionType::TYPE_REQUIRE_SIGNATURE: return $xaction->getNewValue(); } } @@ -87,12 +91,27 @@ case LegalpadTransactionType::TYPE_PREAMBLE: $object->setPreamble($xaction->getNewValue()); break; + case LegalpadTransactionType::TYPE_REQUIRE_SIGNATURE: + $object->setRequireSignature($xaction->getNewValue()); + break; } } protected function applyCustomExternalTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { + + switch ($xaction->getTransactionType()) { + case LegalpadTransactionType::TYPE_REQUIRE_SIGNATURE: + if ($xaction->getNewValue()) { + $session = new PhabricatorAuthSession(); + queryfx( + $session->establishConnection('w'), + 'UPDATE %T SET signedLegalpadDocuments = 0', + $session->getTableName()); + } + break; + } return; } @@ -138,6 +157,7 @@ case LegalpadTransactionType::TYPE_TEXT: case LegalpadTransactionType::TYPE_SIGNATURE_TYPE: case LegalpadTransactionType::TYPE_PREAMBLE: + case LegalpadTransactionType::TYPE_REQUIRE_SIGNATURE: return $v; } @@ -182,6 +202,7 @@ case LegalpadTransactionType::TYPE_TEXT: case LegalpadTransactionType::TYPE_TITLE: case LegalpadTransactionType::TYPE_PREAMBLE: + case LegalpadTransactionType::TYPE_REQUIRE_SIGNATURE: return true; } diff --git a/src/applications/legalpad/query/LegalpadDocumentQuery.php b/src/applications/legalpad/query/LegalpadDocumentQuery.php --- a/src/applications/legalpad/query/LegalpadDocumentQuery.php +++ b/src/applications/legalpad/query/LegalpadDocumentQuery.php @@ -10,6 +10,7 @@ private $signerPHIDs; private $dateCreatedAfter; private $dateCreatedBefore; + private $signatureRequired; private $needDocumentBodies; private $needContributors; @@ -41,6 +42,11 @@ return $this; } + public function withSignatureRequired($bool) { + $this->signatureRequired = $bool; + return $this; + } + public function needDocumentBodies($need_bodies) { $this->needDocumentBodies = $need_bodies; return $this; @@ -204,6 +210,13 @@ $this->contributorPHIDs); } + if ($this->signatureRequired !== null) { + $where[] = qsprintf( + $conn_r, + 'd.requireSignature = %d', + $this->signatureRequired); + } + $where[] = $this->buildPagingClause($conn_r); return $this->formatWhereClause($where); diff --git a/src/applications/legalpad/storage/LegalpadDocument.php b/src/applications/legalpad/storage/LegalpadDocument.php --- a/src/applications/legalpad/storage/LegalpadDocument.php +++ b/src/applications/legalpad/storage/LegalpadDocument.php @@ -18,6 +18,7 @@ protected $mailKey; protected $signatureType; protected $preamble; + protected $requireSignature; const SIGNATURE_TYPE_INDIVIDUAL = 'user'; const SIGNATURE_TYPE_CORPORATION = 'corp'; @@ -44,6 +45,7 @@ ->attachSignatures(array()) ->setSignatureType(self::SIGNATURE_TYPE_INDIVIDUAL) ->setPreamble('') + ->setRequireSignature(0) ->setViewPolicy($view_policy) ->setEditPolicy($edit_policy); } @@ -61,11 +63,15 @@ 'mailKey' => 'bytes20', 'signatureType' => 'text4', 'preamble' => 'text', + 'requireSignature' => 'bool', ), self::CONFIG_KEY_SCHEMA => array( 'key_creator' => array( 'columns' => array('creatorPHID', 'dateModified'), ), + 'key_required' => array( + 'columns' => array('requireSignature', 'dateModified'), + ), ), ) + parent::getConfiguration(); } diff --git a/src/applications/legalpad/storage/LegalpadTransaction.php b/src/applications/legalpad/storage/LegalpadTransaction.php --- a/src/applications/legalpad/storage/LegalpadTransaction.php +++ b/src/applications/legalpad/storage/LegalpadTransaction.php @@ -54,6 +54,17 @@ return pht( '%s updated the preamble.', $this->renderHandleLink($author_phid)); + case LegalpadTransactionType::TYPE_REQUIRE_SIGNATURE: + if ($new) { + $text = pht( + '%s set the document to require signatures.', + $this->renderHandleLink($author_phid)); + } else { + $text = pht( + '%s set the document to not require signatures.', + $this->renderHandleLink($author_phid)); + } + return $text; } return parent::getTitle(); diff --git a/src/applications/people/storage/PhabricatorUserLog.php b/src/applications/people/storage/PhabricatorUserLog.php --- a/src/applications/people/storage/PhabricatorUserLog.php +++ b/src/applications/people/storage/PhabricatorUserLog.php @@ -8,6 +8,7 @@ const ACTION_LOGIN_FULL = 'login-full'; const ACTION_LOGOUT = 'logout'; const ACTION_LOGIN_FAILURE = 'login-fail'; + const ACTION_LOGIN_LEGALPAD = 'login-legalpad'; const ACTION_RESET_PASSWORD = 'reset-pass'; const ACTION_CREATE = 'create'; @@ -53,6 +54,8 @@ self::ACTION_LOGIN_PARTIAL => pht('Login: Partial Login'), self::ACTION_LOGIN_FULL => pht('Login: Upgrade to Full'), self::ACTION_LOGIN_FAILURE => pht('Login: Failure'), + self::ACTION_LOGIN_LEGALPAD => + pht('Login: Signed Required Legalpad Documents'), self::ACTION_LOGOUT => pht('Logout'), self::ACTION_RESET_PASSWORD => pht('Reset Password'), self::ACTION_CREATE => pht('Create Account'),