public function handleRequest(AphrontRequest $request) { $viewer = $this->getViewer(); // If the user already has a full session, just kick them out of here. $has_partial_session = $viewer->hasSession() && $viewer->getSession()->getIsPartial(); if (!$has_partial_session) { return id(new AphrontRedirectResponse())->setURI('/'); } $engine = new PhabricatorAuthSessionEngine(); // If this cookie is set, the user is headed into a high security area // after login (normally because of a password reset) so if they are // able to pass the checkpoint we just want to put their account directly // into high security mode, rather than prompt them again for the same // set of credentials. $jump_into_hisec = $request->getCookie(PhabricatorCookies::COOKIE_HISEC); try { $token = $engine->requireHighSecuritySession($viewer, $request, '/logout/', $jump_into_hisec); } catch (PhabricatorAuthHighSecurityRequiredException $ex) { $form = id(new PhabricatorAuthSessionEngine())->renderHighSecurityForm($ex->getFactors(), $ex->getFactorValidationResults(), $viewer, $request); return $this->newDialog()->setTitle(pht('Provide Multi-Factor Credentials'))->setShortTitle(pht('Multi-Factor Login'))->setWidth(AphrontDialogView::WIDTH_FORM)->addHiddenInput(AphrontRequest::TYPE_HISEC, true)->appendParagraph(pht('Welcome, %s. To complete the login process, provide your ' . 'multi-factor credentials.', phutil_tag('strong', array(), $viewer->getUsername())))->appendChild($form->buildLayoutView())->setSubmitURI($request->getPath())->addCancelButton($ex->getCancelURI())->addSubmitButton(pht('Continue')); } // Upgrade the partial session to a full session. $engine->upgradePartialSession($viewer); // TODO: It might be nice to add options like "bind this session to my IP" // here, even for accounts without multi-factor auth attached to them. $next = PhabricatorCookies::getNextURICookie($request); $request->clearCookie(PhabricatorCookies::COOKIE_NEXTURI); $request->clearCookie(PhabricatorCookies::COOKIE_HISEC); if (!PhabricatorEnv::isValidLocalURIForLink($next)) { $next = '/'; } return id(new AphrontRedirectResponse())->setURI($next); }
public function handleRequest(AphrontRequest $request) { $viewer = $request->getUser(); $document = id(new LegalpadDocumentQuery())->setViewer($viewer)->withIDs(array($request->getURIData('id')))->needDocumentBodies(true)->executeOne(); if (!$document) { return new Aphront404Response(); } $information = $this->readSignerInformation($document, $request); if ($information instanceof AphrontResponse) { return $information; } list($signer_phid, $signature_data) = $information; $signature = null; $type_individual = LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL; $is_individual = $document->getSignatureType() == $type_individual; switch ($document->getSignatureType()) { case LegalpadDocument::SIGNATURE_TYPE_NONE: // nothing to sign means this should be true $has_signed = true; // this is a status UI element $signed_status = null; break; case LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL: if ($signer_phid) { // TODO: This is odd and should probably be adjusted after // grey/external accounts work better, but use the omnipotent // viewer to check for a signature so we can pick up // anonymous/grey signatures. $signature = id(new LegalpadDocumentSignatureQuery())->setViewer(PhabricatorUser::getOmnipotentUser())->withDocumentPHIDs(array($document->getPHID()))->withSignerPHIDs(array($signer_phid))->executeOne(); if ($signature && !$viewer->isLoggedIn()) { return $this->newDialog()->setTitle(pht('Already Signed'))->appendParagraph(pht('You have already signed this document!'))->addCancelButton('/' . $document->getMonogram(), pht('Okay')); } } $signed_status = null; if (!$signature) { $has_signed = false; $signature = id(new LegalpadDocumentSignature())->setSignerPHID($signer_phid)->setDocumentPHID($document->getPHID())->setDocumentVersion($document->getVersions()); // If the user is logged in, show a notice that they haven't signed. // If they aren't logged in, we can't be as sure, so don't show // anything. if ($viewer->isLoggedIn()) { $signed_status = id(new PHUIInfoView())->setSeverity(PHUIInfoView::SEVERITY_WARNING)->setErrors(array(pht('You have not signed this document yet.'))); } } else { $has_signed = true; $signature_data = $signature->getSignatureData(); // In this case, we know they've signed. $signed_at = $signature->getDateCreated(); if ($signature->getIsExemption()) { $exemption_phid = $signature->getExemptionPHID(); $handles = $this->loadViewerHandles(array($exemption_phid)); $exemption_handle = $handles[$exemption_phid]; $signed_text = pht('You do not need to sign this document. ' . '%s added a signature exemption for you on %s.', $exemption_handle->renderLink(), phabricator_datetime($signed_at, $viewer)); } else { $signed_text = pht('You signed this document on %s.', phabricator_datetime($signed_at, $viewer)); } $signed_status = id(new PHUIInfoView())->setSeverity(PHUIInfoView::SEVERITY_NOTICE)->setErrors(array($signed_text)); } $field_errors = array('name' => true, 'email' => true, 'agree' => true); $signature->setSignatureData($signature_data); break; case LegalpadDocument::SIGNATURE_TYPE_CORPORATION: $signature = id(new LegalpadDocumentSignature())->setDocumentPHID($document->getPHID())->setDocumentVersion($document->getVersions()); if ($viewer->isLoggedIn()) { $has_signed = false; $signed_status = null; } else { // This just hides the form. $has_signed = true; $login_text = pht('This document requires a corporate signatory. You must log in to ' . 'accept this document on behalf of a company you represent.'); $signed_status = id(new PHUIInfoView())->setSeverity(PHUIInfoView::SEVERITY_WARNING)->setErrors(array($login_text)); } $field_errors = array('name' => true, 'address' => true, 'contact.name' => true, 'email' => true); $signature->setSignatureData($signature_data); break; } $errors = array(); if ($request->isFormOrHisecPost() && !$has_signed) { // Require two-factor auth to sign legal documents. if ($viewer->isLoggedIn()) { $engine = new PhabricatorAuthSessionEngine(); $engine->requireHighSecuritySession($viewer, $request, '/' . $document->getMonogram()); } list($form_data, $errors, $field_errors) = $this->readSignatureForm($document, $request); $signature_data = $form_data + $signature_data; $signature->setSignatureData($signature_data); $signature->setSignatureType($document->getSignatureType()); $signature->setSignerName((string) idx($signature_data, 'name')); $signature->setSignerEmail((string) idx($signature_data, 'email')); $agree = $request->getExists('agree'); if (!$agree) { $errors[] = pht('You must check "I agree to the terms laid forth above."'); $field_errors['agree'] = pht('Required'); } if ($viewer->isLoggedIn() && $is_individual) { $verified = LegalpadDocumentSignature::VERIFIED; } else { $verified = LegalpadDocumentSignature::UNVERIFIED; } $signature->setVerified($verified); if (!$errors) { $signature->save(); // If the viewer is logged in, signing for themselves, send them to // the document page, which will show that they have signed the // document. Unless of course they were required to sign the // document to use Phabricator; in that case try really hard to // re-direct them to where they wanted to go. // // Otherwise, send them to a completion page. if ($viewer->isLoggedIn() && $is_individual) { $next_uri = '/' . $document->getMonogram(); if ($document->getRequireSignature()) { $request_uri = $request->getRequestURI(); $next_uri = (string) $request_uri; } } else { $this->sendVerifySignatureEmail($document, $signature); $next_uri = $this->getApplicationURI('done/'); } return id(new AphrontRedirectResponse())->setURI($next_uri); } } $document_body = $document->getDocumentBody(); $engine = id(new PhabricatorMarkupEngine())->setViewer($viewer); $engine->addObject($document_body, LegalpadDocumentBody::MARKUP_FIELD_TEXT); $engine->process(); $document_markup = $engine->getOutput($document_body, LegalpadDocumentBody::MARKUP_FIELD_TEXT); $title = $document_body->getTitle(); $manage_uri = $this->getApplicationURI('view/' . $document->getID() . '/'); $can_edit = PhabricatorPolicyFilter::hasCapability($viewer, $document, PhabricatorPolicyCapability::CAN_EDIT); // Use the last content update as the modified date. We don't want to // show that a document like a TOS was "updated" by an incidental change // to a field like the preamble or privacy settings which does not acutally // affect the content of the agreement. $content_updated = $document_body->getDateCreated(); // NOTE: We're avoiding `setPolicyObject()` here so we don't pick up // extra UI elements that are unnecessary and clutter the signature page. // These details are available on the "Manage" page. $header = id(new PHUIHeaderView())->setHeader($title)->setUser($viewer)->setEpoch($content_updated)->addActionLink(id(new PHUIButtonView())->setTag('a')->setIcon(id(new PHUIIconView())->setIconFont('fa-pencil'))->setText(pht('Manage'))->setHref($manage_uri)->setDisabled(!$can_edit)->setWorkflow(!$can_edit)); $preamble_box = null; if (strlen($document->getPreamble())) { $preamble_text = PhabricatorMarkupEngine::renderOneObject(id(new PhabricatorMarkupOneOff())->setContent($document->getPreamble()), 'default', $viewer); // NOTE: We're avoiding `setObject()` here so we don't pick up extra UI // elements like "Subscribers". This information is available on the // "Manage" page, but just clutters up the "Signature" page. $preamble = id(new PHUIPropertyListView())->setUser($viewer)->addSectionHeader(pht('Preamble'))->addTextContent($preamble_text); $preamble_box = new PHUIPropertyGroupView(); $preamble_box->addPropertyList($preamble); } $content = id(new PHUIDocumentViewPro())->addClass('legalpad')->setHeader($header)->appendChild(array($signed_status, $preamble_box, $document_markup)); $signature_box = null; if (!$has_signed) { $error_view = null; if ($errors) { $error_view = id(new PHUIInfoView())->setErrors($errors); } $signature_form = $this->buildSignatureForm($document, $signature, $field_errors); switch ($document->getSignatureType()) { default: break; case LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL: case LegalpadDocument::SIGNATURE_TYPE_CORPORATION: $box = id(new PHUIObjectBoxView())->setHeaderText(pht('Agree and Sign Document'))->setForm($signature_form); if ($error_view) { $box->setInfoView($error_view); } $signature_box = phutil_tag_div('phui-document-view-pro-box', $box); break; } } $crumbs = $this->buildApplicationCrumbs(); $crumbs->setBorder(true); $crumbs->addTextCrumb($document->getMonogram()); return $this->buildApplicationPage(array($crumbs, $content, $signature_box), array('title' => $title, 'pageObjects' => array($document->getPHID()))); }
public function processRequest() { $request = $this->getRequest(); $viewer = $request->getUser(); $document = id(new LegalpadDocumentQuery())->setViewer($viewer)->withIDs(array($this->id))->needDocumentBodies(true)->executeOne(); if (!$document) { return new Aphront404Response(); } list($signer_phid, $signature_data) = $this->readSignerInformation($document, $request); $signature = null; $type_individual = LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL; $is_individual = $document->getSignatureType() == $type_individual; if ($is_individual) { if ($signer_phid) { // TODO: This is odd and should probably be adjusted after grey/external // accounts work better, but use the omnipotent viewer to check for a // signature so we can pick up anonymous/grey signatures. $signature = id(new LegalpadDocumentSignatureQuery())->setViewer(PhabricatorUser::getOmnipotentUser())->withDocumentPHIDs(array($document->getPHID()))->withSignerPHIDs(array($signer_phid))->executeOne(); if ($signature && !$viewer->isLoggedIn()) { return $this->newDialog()->setTitle(pht('Already Signed'))->appendParagraph(pht('You have already signed this document!'))->addCancelButton('/' . $document->getMonogram(), pht('Okay')); } } $signed_status = null; if (!$signature) { $has_signed = false; $signature = id(new LegalpadDocumentSignature())->setSignerPHID($signer_phid)->setDocumentPHID($document->getPHID())->setDocumentVersion($document->getVersions()); // If the user is logged in, show a notice that they haven't signed. // If they aren't logged in, we can't be as sure, so don't show // anything. if ($viewer->isLoggedIn()) { $signed_status = id(new AphrontErrorView())->setSeverity(AphrontErrorView::SEVERITY_WARNING)->setErrors(array(pht('You have not signed this document yet.'))); } } else { $has_signed = true; $signature_data = $signature->getSignatureData(); // In this case, we know they've signed. $signed_at = $signature->getDateCreated(); if ($signature->getIsExemption()) { $exemption_phid = $signature->getExemptionPHID(); $handles = $this->loadViewerHandles(array($exemption_phid)); $exemption_handle = $handles[$exemption_phid]; $signed_text = pht('You do not need to sign this document. ' . '%s added a signature exemption for you on %s.', $exemption_handle->renderLink(), phabricator_datetime($signed_at, $viewer)); } else { $signed_text = pht('You signed this document on %s.', phabricator_datetime($signed_at, $viewer)); } $signed_status = id(new AphrontErrorView())->setSeverity(AphrontErrorView::SEVERITY_NOTICE)->setErrors(array($signed_text)); } $field_errors = array('name' => true, 'email' => true, 'agree' => true); } else { $signature = id(new LegalpadDocumentSignature())->setDocumentPHID($document->getPHID())->setDocumentVersion($document->getVersions()); if ($viewer->isLoggedIn()) { $has_signed = false; $signed_status = null; } else { // This just hides the form. $has_signed = true; $login_text = pht('This document requires a corporate signatory. You must log in to ' . 'accept this document on behalf of a company you represent.'); $signed_status = id(new AphrontErrorView())->setSeverity(AphrontErrorView::SEVERITY_WARNING)->setErrors(array($login_text)); } $field_errors = array('name' => true, 'address' => true, 'contact.name' => true, 'email' => true); } $signature->setSignatureData($signature_data); $errors = array(); if ($request->isFormOrHisecPost() && !$has_signed) { // Require two-factor auth to sign legal documents. if ($viewer->isLoggedIn()) { $engine = new PhabricatorAuthSessionEngine(); $engine->requireHighSecuritySession($viewer, $request, '/' . $document->getMonogram()); } list($form_data, $errors, $field_errors) = $this->readSignatureForm($document, $request); $signature_data = $form_data + $signature_data; $signature->setSignatureData($signature_data); $signature->setSignatureType($document->getSignatureType()); $signature->setSignerName((string) idx($signature_data, 'name')); $signature->setSignerEmail((string) idx($signature_data, 'email')); $agree = $request->getExists('agree'); if (!$agree) { $errors[] = pht('You must check "I agree to the terms laid forth above."'); $field_errors['agree'] = pht('Required'); } if ($viewer->isLoggedIn() && $is_individual) { $verified = LegalpadDocumentSignature::VERIFIED; } else { $verified = LegalpadDocumentSignature::UNVERIFIED; } $signature->setVerified($verified); if (!$errors) { $signature->save(); // If the viewer is logged in, send them to the document page, which // will show that they have signed the document. Otherwise, send them // to a completion page. if ($viewer->isLoggedIn() && $is_individual) { $next_uri = '/' . $document->getMonogram(); } else { $this->sendVerifySignatureEmail($document, $signature); $next_uri = $this->getApplicationURI('done/'); } return id(new AphrontRedirectResponse())->setURI($next_uri); } } $document_body = $document->getDocumentBody(); $engine = id(new PhabricatorMarkupEngine())->setViewer($viewer); $engine->addObject($document_body, LegalpadDocumentBody::MARKUP_FIELD_TEXT); $engine->process(); $document_markup = $engine->getOutput($document_body, LegalpadDocumentBody::MARKUP_FIELD_TEXT); $title = $document_body->getTitle(); $manage_uri = $this->getApplicationURI('view/' . $document->getID() . '/'); $can_edit = PhabricatorPolicyFilter::hasCapability($viewer, $document, PhabricatorPolicyCapability::CAN_EDIT); $header = id(new PHUIHeaderView())->setHeader($title)->addActionLink(id(new PHUIButtonView())->setTag('a')->setIcon(id(new PHUIIconView())->setIconFont('fa-pencil'))->setText(pht('Manage Document'))->setHref($manage_uri)->setDisabled(!$can_edit)->setWorkflow(!$can_edit)); $preamble = null; if (strlen($document->getPreamble())) { $preamble_text = PhabricatorMarkupEngine::renderOneObject(id(new PhabricatorMarkupOneOff())->setContent($document->getPreamble()), 'default', $viewer); $preamble = id(new PHUIPropertyListView())->addSectionHeader(pht('Preamble'))->addTextContent($preamble_text); } $content = id(new PHUIDocumentView())->addClass('legalpad')->setHeader($header)->setFontKit(PHUIDocumentView::FONT_SOURCE_SANS)->appendChild(array($signed_status, $preamble, $document_markup)); if (!$has_signed) { $error_view = null; if ($errors) { $error_view = id(new AphrontErrorView())->setErrors($errors); } $signature_form = $this->buildSignatureForm($document, $signature, $field_errors); $subheader = id(new PHUIHeaderView())->setHeader(pht('Agree and Sign Document'))->setBleedHeader(true); $content->appendChild(array($subheader, $error_view, $signature_form)); } $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($document->getMonogram()); return $this->buildApplicationPage(array($crumbs, $content), array('title' => $title, 'pageObjects' => array($document->getPHID()))); }