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

Legalpad - allow for legalpad documents to be required to be signed for using Phabricator

Summary: Fixes T7159.

Test Plan:
Created a legalpad document that needed a signature and I was required to sign it no matter what page I hit. Signed it and things worked! Added a new legalpad document and I had to sign again!

Ran unit tests and they passed!

Logged out as a user who was roadblocked into signing a bunch of stuff and it worked!

Reviewers: epriestley

Reviewed By: epriestley

Subscribers: Korvin, epriestley

Maniphest Tasks: T7159

Differential Revision: https://secure.phabricator.com/D11759
This commit is contained in:
Bob Trahan 2015-02-12 15:22:56 -08:00
parent d598edc5f3
commit d39da529ca
19 changed files with 219 additions and 28 deletions

View file

@ -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;

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_legalpad.legalpad_document
ADD KEY `key_required` (requireSignature, dateModified);

View file

@ -11,6 +11,10 @@ final class PhabricatorAuthFinishController
return true; return true;
} }
public function shouldAllowLegallyNonCompliantUsers() {
return true;
}
public function processRequest() { public function processRequest() {
$request = $this->getRequest(); $request = $this->getRequest();
$viewer = $request->getUser(); $viewer = $request->getUser();

View file

@ -11,6 +11,10 @@ final class PhabricatorAuthValidateController
return true; return true;
} }
public function shouldAllowLegallyNonCompliantUsers() {
return true;
}
public function processRequest() { public function processRequest() {
$request = $this->getRequest(); $request = $this->getRequest();
$viewer = $request->getUser(); $viewer = $request->getUser();

View file

@ -21,6 +21,10 @@ final class PhabricatorLogoutController
return true; return true;
} }
public function shouldAllowLegallyNonCompliantUsers() {
return true;
}
public function handleRequest(AphrontRequest $request) { public function handleRequest(AphrontRequest $request) {
$request = $this->getRequest(); $request = $this->getRequest();
$user = $request->getUser(); $user = $request->getUser();

View file

@ -134,6 +134,7 @@ final class PhabricatorAuthSessionEngine extends Phobject {
s.sessionStart AS s_sessionStart, s.sessionStart AS s_sessionStart,
s.highSecurityUntil AS s_highSecurityUntil, s.highSecurityUntil AS s_highSecurityUntil,
s.isPartial AS s_isPartial, s.isPartial AS s_isPartial,
s.signedLegalpadDocuments as s_signedLegalpadDocuments,
u.* u.*
FROM %T u JOIN %T s ON u.phid = s.userPHID FROM %T u JOIN %T s ON u.phid = s.userPHID
AND s.type = %s AND s.sessionKey = %s', AND s.type = %s AND s.sessionKey = %s',
@ -232,6 +233,7 @@ final class PhabricatorAuthSessionEngine extends Phobject {
->setSessionStart(time()) ->setSessionStart(time())
->setSessionExpires(time() + $session_ttl) ->setSessionExpires(time() + $session_ttl)
->setIsPartial($partial ? 1 : 0) ->setIsPartial($partial ? 1 : 0)
->setSignedLegalpadDocuments(0)
->save(); ->save();
$log = PhabricatorUserLog::initializeNewLog( $log = PhabricatorUserLog::initializeNewLog(
@ -553,6 +555,52 @@ final class PhabricatorAuthSessionEngine extends Phobject {
} }
/* -( 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 )------------------------------------------------ */ /* -( One Time Login URIs )------------------------------------------------ */

View file

@ -13,6 +13,7 @@ final class PhabricatorAuthSession extends PhabricatorAuthDAO
protected $sessionExpires; protected $sessionExpires;
protected $highSecurityUntil; protected $highSecurityUntil;
protected $isPartial; protected $isPartial;
protected $signedLegalpadDocuments;
private $identityObject = self::ATTACHABLE; private $identityObject = self::ATTACHABLE;
@ -26,6 +27,7 @@ final class PhabricatorAuthSession extends PhabricatorAuthDAO
'sessionExpires' => 'epoch', 'sessionExpires' => 'epoch',
'highSecurityUntil' => 'epoch?', 'highSecurityUntil' => 'epoch?',
'isPartial' => 'bool', 'isPartial' => 'bool',
'signedLegalpadDocuments' => 'bool',
), ),
self::CONFIG_KEY_SCHEMA => array( self::CONFIG_KEY_SCHEMA => array(
'sessionKey' => array( 'sessionKey' => array(

View file

@ -53,6 +53,10 @@ abstract class PhabricatorController extends AphrontController {
return PhabricatorEnv::getEnvConfig('security.require-multi-factor-auth'); return PhabricatorEnv::getEnvConfig('security.require-multi-factor-auth');
} }
public function shouldAllowLegallyNonCompliantUsers() {
return false;
}
public function willBeginExecution() { public function willBeginExecution() {
$request = $this->getRequest(); $request = $this->getRequest();
@ -221,6 +225,47 @@ abstract class PhabricatorController extends AphrontController {
} }
} }
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 // NOTE: We do this last so that users get a login page instead of a 403
// if they need to login. // if they need to login.
if ($this->shouldRequireAdmin() && !$user->getIsAdmin()) { if ($this->shouldRequireAdmin() && !$user->getIsAdmin()) {

View file

@ -170,7 +170,7 @@ final class PhabricatorAccessControlTestCase extends PhabricatorTestCase {
// Test public access. // Test public access.
$this->checkAccess( $this->checkAccess(
'No Login Required', 'Public Access',
id(clone $controller)->setConfig('public', true), id(clone $controller)->setConfig('public', true),
$request, $request,
array( array(

View file

@ -18,6 +18,10 @@ abstract class CelerityResourceController extends PhabricatorController {
return true; return true;
} }
public function shouldAllowLegallyNonCompliantUsers() {
return true;
}
abstract public function getCelerityResourceMap(); abstract public function getCelerityResourceMap();
protected function serveResource($path, $package_hash = null) { protected function serveResource($path, $package_hash = null) {

View file

@ -6,5 +6,6 @@ final class LegalpadTransactionType extends LegalpadConstants {
const TYPE_TEXT = 'text'; const TYPE_TEXT = 'text';
const TYPE_SIGNATURE_TYPE = 'legalpad:signature-type'; const TYPE_SIGNATURE_TYPE = 'legalpad:signature-type';
const TYPE_PREAMBLE = 'legalpad:premable'; const TYPE_PREAMBLE = 'legalpad:premable';
const TYPE_REQUIRE_SIGNATURE = 'legalpad:require-signature';
} }

View file

@ -2,17 +2,11 @@
final class LegalpadDocumentEditController extends LegalpadController { final class LegalpadDocumentEditController extends LegalpadController {
private $id; public function handleRequest(AphrontRequest $request) {
public function willProcessRequest(array $data) {
$this->id = idx($data, 'id');
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser(); $user = $request->getUser();
if (!$this->id) { $id = $request->getURIData('id');
if (!$id) {
$is_create = true; $is_create = true;
$this->requireApplicationCapability( $this->requireApplicationCapability(
@ -34,7 +28,7 @@ final class LegalpadDocumentEditController extends LegalpadController {
PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT, PhabricatorPolicyCapability::CAN_EDIT,
)) ))
->withIDs(array($this->id)) ->withIDs(array($id))
->executeOne(); ->executeOne();
if (!$document) { if (!$document) {
return new Aphront404Response(); return new Aphront404Response();
@ -48,6 +42,7 @@ final class LegalpadDocumentEditController extends LegalpadController {
$text = $document->getDocumentBody()->getText(); $text = $document->getDocumentBody()->getText();
$v_signature_type = $document->getSignatureType(); $v_signature_type = $document->getSignatureType();
$v_preamble = $document->getPreamble(); $v_preamble = $document->getPreamble();
$v_require_signature = $document->getRequireSignature();
$errors = array(); $errors = array();
$can_view = null; $can_view = null;
@ -97,6 +92,24 @@ final class LegalpadDocumentEditController extends LegalpadController {
->setTransactionType(LegalpadTransactionType::TYPE_PREAMBLE) ->setTransactionType(LegalpadTransactionType::TYPE_PREAMBLE)
->setNewValue($v_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) { if (!$errors) {
$editor = id(new LegalpadDocumentEditor()) $editor = id(new LegalpadDocumentEditor())
->setContentSourceFromRequest($request) ->setContentSourceFromRequest($request)
@ -133,11 +146,29 @@ final class LegalpadDocumentEditController extends LegalpadController {
->setName(pht('signatureType')) ->setName(pht('signatureType'))
->setValue($v_signature_type) ->setValue($v_signature_type)
->setOptions(LegalpadDocument::getSignatureTypeMap())); ->setOptions(LegalpadDocument::getSignatureTypeMap()));
$show_require = true;
} else { } else {
$form->appendChild( $form->appendChild(
id(new AphrontFormMarkupControl()) id(new AphrontFormMarkupControl())
->setLabel(pht('Who Should Sign?')) ->setLabel(pht('Who Should Sign?'))
->setValue($document->getSignatureTypeName())); ->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 $form

View file

@ -2,23 +2,16 @@
final class LegalpadDocumentSignController extends LegalpadController { final class LegalpadDocumentSignController extends LegalpadController {
private $id;
public function shouldAllowPublic() { public function shouldAllowPublic() {
return true; return true;
} }
public function willProcessRequest(array $data) { public function handleRequest(AphrontRequest $request) {
$this->id = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$viewer = $request->getUser(); $viewer = $request->getUser();
$document = id(new LegalpadDocumentQuery()) $document = id(new LegalpadDocumentQuery())
->setViewer($viewer) ->setViewer($viewer)
->withIDs(array($this->id)) ->withIDs(array($request->getURIData('id')))
->needDocumentBodies(true) ->needDocumentBodies(true)
->executeOne(); ->executeOne();
if (!$document) { if (!$document) {

View file

@ -2,13 +2,7 @@
final class LegalpadDocumentSignatureAddController extends LegalpadController { final class LegalpadDocumentSignatureAddController extends LegalpadController {
private $id; public function handleRequest(AphrontRequest $request) {
public function willProcessRequest(array $data) {
$this->id = $data['id'];
}
public function processRequest() {
$request = $this->getRequest(); $request = $this->getRequest();
$viewer = $request->getUser(); $viewer = $request->getUser();
@ -20,7 +14,7 @@ final class LegalpadDocumentSignatureAddController extends LegalpadController {
PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT, PhabricatorPolicyCapability::CAN_EDIT,
)) ))
->withIDs(array($this->id)) ->withIDs(array($request->getURIData('id')))
->executeOne(); ->executeOne();
if (!$document) { if (!$document) {
return new Aphront404Response(); return new Aphront404Response();

View file

@ -32,6 +32,7 @@ final class LegalpadDocumentEditor
$types[] = LegalpadTransactionType::TYPE_TEXT; $types[] = LegalpadTransactionType::TYPE_TEXT;
$types[] = LegalpadTransactionType::TYPE_SIGNATURE_TYPE; $types[] = LegalpadTransactionType::TYPE_SIGNATURE_TYPE;
$types[] = LegalpadTransactionType::TYPE_PREAMBLE; $types[] = LegalpadTransactionType::TYPE_PREAMBLE;
$types[] = LegalpadTransactionType::TYPE_REQUIRE_SIGNATURE;
return $types; return $types;
} }
@ -49,6 +50,8 @@ final class LegalpadDocumentEditor
return $object->getSignatureType(); return $object->getSignatureType();
case LegalpadTransactionType::TYPE_PREAMBLE: case LegalpadTransactionType::TYPE_PREAMBLE:
return $object->getPreamble(); return $object->getPreamble();
case LegalpadTransactionType::TYPE_REQUIRE_SIGNATURE:
return $object->getRequireSignature();
} }
} }
@ -61,6 +64,7 @@ final class LegalpadDocumentEditor
case LegalpadTransactionType::TYPE_TEXT: case LegalpadTransactionType::TYPE_TEXT:
case LegalpadTransactionType::TYPE_SIGNATURE_TYPE: case LegalpadTransactionType::TYPE_SIGNATURE_TYPE:
case LegalpadTransactionType::TYPE_PREAMBLE: case LegalpadTransactionType::TYPE_PREAMBLE:
case LegalpadTransactionType::TYPE_REQUIRE_SIGNATURE:
return $xaction->getNewValue(); return $xaction->getNewValue();
} }
} }
@ -87,12 +91,27 @@ final class LegalpadDocumentEditor
case LegalpadTransactionType::TYPE_PREAMBLE: case LegalpadTransactionType::TYPE_PREAMBLE:
$object->setPreamble($xaction->getNewValue()); $object->setPreamble($xaction->getNewValue());
break; break;
case LegalpadTransactionType::TYPE_REQUIRE_SIGNATURE:
$object->setRequireSignature($xaction->getNewValue());
break;
} }
} }
protected function applyCustomExternalTransaction( protected function applyCustomExternalTransaction(
PhabricatorLiskDAO $object, PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) { 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; return;
} }
@ -138,6 +157,7 @@ final class LegalpadDocumentEditor
case LegalpadTransactionType::TYPE_TEXT: case LegalpadTransactionType::TYPE_TEXT:
case LegalpadTransactionType::TYPE_SIGNATURE_TYPE: case LegalpadTransactionType::TYPE_SIGNATURE_TYPE:
case LegalpadTransactionType::TYPE_PREAMBLE: case LegalpadTransactionType::TYPE_PREAMBLE:
case LegalpadTransactionType::TYPE_REQUIRE_SIGNATURE:
return $v; return $v;
} }
@ -182,6 +202,7 @@ final class LegalpadDocumentEditor
case LegalpadTransactionType::TYPE_TEXT: case LegalpadTransactionType::TYPE_TEXT:
case LegalpadTransactionType::TYPE_TITLE: case LegalpadTransactionType::TYPE_TITLE:
case LegalpadTransactionType::TYPE_PREAMBLE: case LegalpadTransactionType::TYPE_PREAMBLE:
case LegalpadTransactionType::TYPE_REQUIRE_SIGNATURE:
return true; return true;
} }

View file

@ -10,6 +10,7 @@ final class LegalpadDocumentQuery
private $signerPHIDs; private $signerPHIDs;
private $dateCreatedAfter; private $dateCreatedAfter;
private $dateCreatedBefore; private $dateCreatedBefore;
private $signatureRequired;
private $needDocumentBodies; private $needDocumentBodies;
private $needContributors; private $needContributors;
@ -41,6 +42,11 @@ final class LegalpadDocumentQuery
return $this; return $this;
} }
public function withSignatureRequired($bool) {
$this->signatureRequired = $bool;
return $this;
}
public function needDocumentBodies($need_bodies) { public function needDocumentBodies($need_bodies) {
$this->needDocumentBodies = $need_bodies; $this->needDocumentBodies = $need_bodies;
return $this; return $this;
@ -204,6 +210,13 @@ final class LegalpadDocumentQuery
$this->contributorPHIDs); $this->contributorPHIDs);
} }
if ($this->signatureRequired !== null) {
$where[] = qsprintf(
$conn_r,
'd.requireSignature = %d',
$this->signatureRequired);
}
$where[] = $this->buildPagingClause($conn_r); $where[] = $this->buildPagingClause($conn_r);
return $this->formatWhereClause($where); return $this->formatWhereClause($where);

View file

@ -18,6 +18,7 @@ final class LegalpadDocument extends LegalpadDAO
protected $mailKey; protected $mailKey;
protected $signatureType; protected $signatureType;
protected $preamble; protected $preamble;
protected $requireSignature;
const SIGNATURE_TYPE_INDIVIDUAL = 'user'; const SIGNATURE_TYPE_INDIVIDUAL = 'user';
const SIGNATURE_TYPE_CORPORATION = 'corp'; const SIGNATURE_TYPE_CORPORATION = 'corp';
@ -44,6 +45,7 @@ final class LegalpadDocument extends LegalpadDAO
->attachSignatures(array()) ->attachSignatures(array())
->setSignatureType(self::SIGNATURE_TYPE_INDIVIDUAL) ->setSignatureType(self::SIGNATURE_TYPE_INDIVIDUAL)
->setPreamble('') ->setPreamble('')
->setRequireSignature(0)
->setViewPolicy($view_policy) ->setViewPolicy($view_policy)
->setEditPolicy($edit_policy); ->setEditPolicy($edit_policy);
} }
@ -61,11 +63,15 @@ final class LegalpadDocument extends LegalpadDAO
'mailKey' => 'bytes20', 'mailKey' => 'bytes20',
'signatureType' => 'text4', 'signatureType' => 'text4',
'preamble' => 'text', 'preamble' => 'text',
'requireSignature' => 'bool',
), ),
self::CONFIG_KEY_SCHEMA => array( self::CONFIG_KEY_SCHEMA => array(
'key_creator' => array( 'key_creator' => array(
'columns' => array('creatorPHID', 'dateModified'), 'columns' => array('creatorPHID', 'dateModified'),
), ),
'key_required' => array(
'columns' => array('requireSignature', 'dateModified'),
),
), ),
) + parent::getConfiguration(); ) + parent::getConfiguration();
} }

View file

@ -54,6 +54,17 @@ final class LegalpadTransaction extends PhabricatorApplicationTransaction {
return pht( return pht(
'%s updated the preamble.', '%s updated the preamble.',
$this->renderHandleLink($author_phid)); $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(); return parent::getTitle();

View file

@ -8,6 +8,7 @@ final class PhabricatorUserLog extends PhabricatorUserDAO
const ACTION_LOGIN_FULL = 'login-full'; const ACTION_LOGIN_FULL = 'login-full';
const ACTION_LOGOUT = 'logout'; const ACTION_LOGOUT = 'logout';
const ACTION_LOGIN_FAILURE = 'login-fail'; const ACTION_LOGIN_FAILURE = 'login-fail';
const ACTION_LOGIN_LEGALPAD = 'login-legalpad';
const ACTION_RESET_PASSWORD = 'reset-pass'; const ACTION_RESET_PASSWORD = 'reset-pass';
const ACTION_CREATE = 'create'; const ACTION_CREATE = 'create';
@ -53,6 +54,8 @@ final class PhabricatorUserLog extends PhabricatorUserDAO
self::ACTION_LOGIN_PARTIAL => pht('Login: Partial Login'), self::ACTION_LOGIN_PARTIAL => pht('Login: Partial Login'),
self::ACTION_LOGIN_FULL => pht('Login: Upgrade to Full'), self::ACTION_LOGIN_FULL => pht('Login: Upgrade to Full'),
self::ACTION_LOGIN_FAILURE => pht('Login: Failure'), self::ACTION_LOGIN_FAILURE => pht('Login: Failure'),
self::ACTION_LOGIN_LEGALPAD =>
pht('Login: Signed Required Legalpad Documents'),
self::ACTION_LOGOUT => pht('Logout'), self::ACTION_LOGOUT => pht('Logout'),
self::ACTION_RESET_PASSWORD => pht('Reset Password'), self::ACTION_RESET_PASSWORD => pht('Reset Password'),
self::ACTION_CREATE => pht('Create Account'), self::ACTION_CREATE => pht('Create Account'),