diff --git a/resources/sql/autopatches/20150212.legalpad.session.sql b/resources/sql/autopatches/20150212.legalpad.session.sql new file mode 100644 --- /dev/null +++ b/resources/sql/autopatches/20150212.legalpad.session.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/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', @@ -553,6 +554,49 @@ } +/* -( Legalpad Documents )-------------------------------------------------- */ + + + /** + * Upgrade a session to have all legalpad documents signed. + * + * @param PhabricatorUser Session to upgrade. + * @return void + * @task partial + */ + public function signLegalpadDocuments(PhabricatorUser $viewer) { + + 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()); + + $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( @@ -38,6 +40,9 @@ 'key_expires' => array( 'columns' => array('sessionExpires'), ), + 'key_legalpad' => array( + 'columns' => array('signedLegalpadDocuments'), + ), ), ) + parent::getConfiguration(); } 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,46 @@ } } + + if (!$this->shouldAllowLegallyNonCompliantUsers()) { + $legalpad_class = 'PhabricatorLegalpadApplication'; + $legalpad = id(new PhabricatorApplicationQuery()) + ->setViewer($user) + ->withClasses(array($legalpad_class)) + ->withInstalled(true) + ->executeOne(); + + $doc_query = id(new LegalpadDocumentQuery()) + ->setViewer($user) + ->withSignatureRequired(true) + ->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); + } + } + } + // 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__/PhabricatorTestController.php b/src/applications/base/controller/__tests__/PhabricatorTestController.php --- a/src/applications/base/controller/__tests__/PhabricatorTestController.php +++ b/src/applications/base/controller/__tests__/PhabricatorTestController.php @@ -33,6 +33,10 @@ return $this->getConfig('enabled', parent::shouldRequireEnabledUser()); } + public function shouldAllowLegallyNonCompliantUsers() { + return true; + } + public function processRequest() {} } 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,11 @@ ->setTransactionType(LegalpadTransactionType::TYPE_PREAMBLE) ->setNewValue($v_preamble); + $v_require_signature = $request->getBool('requireSignature', 0); + $xactions[] = id(new LegalpadTransaction()) + ->setTransactionType(LegalpadTransactionType::TYPE_REQUIRE_SIGNATURE) + ->setNewValue($v_require_signature); + if (!$errors) { $editor = id(new LegalpadDocumentEditor()) ->setContentSourceFromRequest($request) @@ -142,6 +142,14 @@ $form ->appendChild( + id(new AphrontFormCheckboxControl()) + ->setLabel(pht('Require Signature')) + ->addCheckbox( + 'requireSignature', + 'requireSignature', + pht('Should signing this document be required to use Phabricator?'), + $v_require_signature)) + ->appendChild( id(new PhabricatorRemarkupControl()) ->setUser($user) ->setID('preamble') 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,23 @@ 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: + // TODO - if we are requiring a new signature, then we need + // to invalidate all the sessions that claim to have signed + // all the docs...! + break; + } return; } @@ -138,6 +153,7 @@ case LegalpadTransactionType::TYPE_TEXT: case LegalpadTransactionType::TYPE_SIGNATURE_TYPE: case LegalpadTransactionType::TYPE_PREAMBLE: + case LegalpadTransactionType::TYPE_REQUIRE_SIGNATURE: return $v; } @@ -182,6 +198,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,12 @@ $this->contributorPHIDs); } + if ($this->signatureRequired === true) { + $where[] = qsprintf( + $conn_r, + 'd.requireSignature = 1'); + } + $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'),