Cicalese has submitted this change and it was merged. ( https://gerrit.wikimedia.org/r/366780 )
Change subject: Adds request/approve account special pages and hooks. ...................................................................... Adds request/approve account special pages and hooks. T173378 Change-Id: I97c2c995eb0eeea760824468d3b4453f6e7ccb09 --- M extension.json M i18n/en.json M i18n/qqq.json A includes/EchoEAPresentationModel.php R includes/EmailAuthorization.alias.php A includes/EmailAuthorizationApprove.php M includes/EmailAuthorizationConfig.php M includes/EmailAuthorizationHooks.php A includes/EmailAuthorizationRequest.php R sql/EmailAuth.sql A sql/EmailRequest.sql 11 files changed, 768 insertions(+), 17 deletions(-) Approvals: Cicalese: Verified; Looks good to me, approved jenkins-bot: Checked diff --git a/extension.json b/extension.json index 0f991aa..4c72bc2 100644 --- a/extension.json +++ b/extension.json @@ -1,6 +1,6 @@ { "name": "Email Authorization", - "version": "1.2", + "version": "1.3", "author": [ "[https://www.mediawiki.org/wiki/User:Cindy.cicalese Cindy Cicalese]" ], @@ -9,7 +9,9 @@ "license-name": "MIT", "type": "other", "SpecialPages": { - "EmailAuthorizationConfig": "EmailAuthorizationConfig" + "EmailAuthorizationConfig": "EmailAuthorizationConfig", + "EmailAuthorizationRequest": "EmailAuthorizationRequest", + "EmailAuthorizationApprove": "EmailAuthorizationApprove" }, "MessagesDirs": { "EmailAuthorization": [ @@ -17,7 +19,7 @@ ] }, "ExtensionMessagesFiles": { - "EmailAuthorizationAlias": "includes/EmailAuthorizationConfig.alias.php" + "EmailAuthorizationAlias": "includes/EmailAuthorization.alias.php" }, "ResourceModules": { "ext.EmailAuthorization": { @@ -37,13 +39,22 @@ "AutoloadClasses": { "EmailAuthorization": "includes/EmailAuthorization.php", "EmailAuthorizationHooks": "includes/EmailAuthorizationHooks.php", - "EmailAuthorizationConfig": "includes/EmailAuthorizationConfig.php" + "EmailAuthorizationConfig": "includes/EmailAuthorizationConfig.php", + "EmailAuthorizationRequest": "includes/EmailAuthorizationRequest.php", + "EmailAuthorizationApprove": "includes/EmailAuthorizationApprove.php", + "EchoEAPresentationModel": "includes/EchoEAPresentationModel.php" }, "Hooks": { "PluggableAuthUserAuthorization": [ "EmailAuthorizationHooks::authorize" ], "LoadExtensionSchemaUpdates": [ "EmailAuthorizationHooks::loadExtensionSchemaUpdates" - ] + ], + "BeforeCreateEchoEvent": "EmailAuthorizationHooks::onBeforeCreateEchoEvent" + }, + "callback": "EmailAuthorizationHooks::onRegistration", + "config": { + "EmailAuthorization_EnableRequests": false, + "EmailAuthorization_RequestFields": [] }, "manifest_version": 1 } diff --git a/i18n/en.json b/i18n/en.json index 3535d27..1e58e35 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -5,7 +5,7 @@ ] }, "emailauthorization-desc": "Authorize users by email address", - "emailauthorizationconfig": "Configure Email Authorization", + "emailauthorizationconfig": "Email Authorization Dashboard", "emailauthorization-config-instructions": "To add or revoke all email addresses in a domain use ''@domain'', where ''domain'' is the email domain. This will not affect other email addresses explicitly added in that domain.", "emailauthorization-config-authorized": "''$1'' is authorized.", "emailauthorization-config-notauthorized": "''$1'' is not authorized.", @@ -15,7 +15,7 @@ "emailauthorization-config-noauthfound": "No authorized email addresses or domains found.", "emailauthorization-config-nousersfound": "No users found.", "emailauthorization-config-invalidemail": "''$1'' is not a valid email address or domain.", - "emailauthorization-config-label-email": "Email", + "emailauthorization-config-label-email": "Email Address", "emailauthorization-config-label-username": "Username", "emailauthorization-config-label-realname": "Real Name", "emailauthorization-config-label-userpage": "User Page", @@ -34,6 +34,29 @@ "emailauthorization-config-button-showauth": "Show authorized email addresses and domains", "emailauthorization-config-button-showall": "Show all wiki users", "right-emailauthorizationconfig": "Configure user authorization by email address", - "action-emailauthorizationconfig": "configure email authorization" - + "action-emailauthorizationconfig": "configure email authorization", + "emailauthorizationrequest": "Request an Account", + "emailauthorization-request-instructions": "Fill in fields and submit to request an account. Fields marked with * are mandatory.", + "emailauthorization-request-label-email": "Email Address", + "emailauthorization-request-button-submit": "Submit", + "emailauthorization-request-requested": "An account has been requested for email address $1.", + "emailauthorization-request-missingmandatory": "Missing mandatory field value(s).", + "emailauthorization-request-invalidemail": "Invalid email address: $1", + "emailauthorization-request-error": "An error has occurred requesting an account for $1.", + "echo-category-title-emailauthorization-notification-category": "New account requests", + "notification-header-emailauthorization-account-request": "A request has been submitted for a new account.", + "notification-subject-emailauthorization-account-request": "New account request.", + "notification-body-emailauthorization-account-request": "$1 has requested an account.", + "notification-link-label-emailauthorization-account-request": "Visit account approval page", + "emailauthorizationapprove": "Approve Account Requests", + "emailauthorization-approve-label-email": "Email Address", + "emailauthorization-approve-label-extra": "Extra Fields", + "emailauthorization-approve-label-action": "Actions", + "emailauthorization-approve-button-approve": "Approve", + "emailauthorization-approve-button-reject": "Reject", + "emailauthorization-approve-button-next": "Next", + "emailauthorization-approve-button-previous": "Previous", + "emailauthorization-approve-norequestsfound": "No account requests found.", + "emailauthorization-approve-approved": "Approved $1.", + "emailauthorization-approve-rejected": "Rejected $1." } diff --git a/i18n/qqq.json b/i18n/qqq.json index 2ea7d30..7c18561 100644 --- a/i18n/qqq.json +++ b/i18n/qqq.json @@ -8,6 +8,7 @@ }, "emailauthorization-desc": "{{desc|name=Email Authorization|url=https://www.mediawiki.org/wiki/Extension:Email_Authorization}}", "emailauthorizationconfig": "Special page name", + "emailauthorizationapprove": "Special page name", "emailauthorization-config-instructions": "Instructions", "emailauthorization-config-authorized": "Status message", "emailauthorization-config-notauthorized": "Status message", @@ -36,5 +37,28 @@ "emailauthorization-config-button-showauth": "Button text", "emailauthorization-config-button-showall": "Button text", "right-emailauthorizationconfig": "{{doc-right|emailauthorizationconfig}}", - "action-emailauthorizationconfig": "{{doc-action|emailauthorizationconfig}}" + "action-emailauthorizationconfig": "{{doc-action|emailauthorizationconfig}}", + "emailauthorizationrequest": "Special page name", + "emailauthorization-request-instructions": "Instructions", + "emailauthorization-request-label-email": "Field label", + "emailauthorization-request-button-submit": "Button text", + "emailauthorization-request-requested": "Status message", + "emailauthorization-request-missingmandatory": "Error message", + "emailauthorization-request-invalidemail": "Error message", + "emailauthorization-request-error": "Error message", + "echo-category-title-emailauthorization-notification-category": "Name of category on Prefences/Notifications page for EmailAuthorization notifications", + "notification-header-emailauthorization-account-requested": "Flyout-specific format for displaying notification header of a new account request.\n\nParameters:\n* $1 - email address of requested account.", + "notification-subject-emailauthorization-account-requested": "Echo email subject.", + "notification-body-emailauthorization-reply-on-watched-page": "Echo email body.\n\nParameters:\n*$1 - email address of requested account.", + "notification-link-label-emailauthorization-account-requested": "Label", + "emailauthorization-approve-label-email": "Table column label\n{{Identical|E-mail}}", + "emailauthorization-approve-label-extra": "Table Column Label", + "emailauthorization-approve-label-action": "Table Column Label", + "emailauthorization-approve-button-approve": "Button text\n{{Identical|Approve}}", + "emailauthorization-approve-button-reject": "Button text\n{{Identical|Reject}}", + "emailauthorization-approve-button-next": "Button text\n{{Identical|Next}}", + "emailauthorization-approve-button-previous": "Button text\n{{Identical|Previous}}", + "emailauthorization-approve-norequestsfound": "Status message", + "emailauthorization-approve-approved": "Status message", + "emailauthorization-approve-rejected": "Status message" } diff --git a/includes/EchoEAPresentationModel.php b/includes/EchoEAPresentationModel.php new file mode 100644 index 0000000..436b6ce --- /dev/null +++ b/includes/EchoEAPresentationModel.php @@ -0,0 +1,76 @@ +<?php +/* + * Copyright (c) 2017 The MITRE Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +class EchoEAPresentationModel extends EchoEventPresentationModel { + + /** + * @return string The symbolic icon name as defined in $wgEchoNotificationIcons + */ + public function getIconType() { + return 'user-rights'; + } + + /** + * Array of primary link details, with possibly-relative URL & label. + * + * @return array|bool Array of link data, or false for no link: + * ['url' => (string) url, 'label' => (string) link text (non-escaped)] + */ + public function getPrimaryLink() { + return [ + 'url' => Title::newFromText( 'Special:EmailAuthorizationApprove' )->getFullURL(), + 'label' => $this->msg( "notification-link-label-{$this->type}" ) + ]; + } + + /** + * Get a message object and add the performer's name as + * a parameter. It is expected that subclasses will override + * this. + * + * @return Message + */ + public function getHeaderMessage() { + $msg = wfMessage( "notification-header-{$this->type}" ); + $msg->params( $this->event->getExtraParam( + 'email' ) ); + return $msg; + } + + public function getBodyMessage() { + $msg = wfMessage( "notification-body-{$this->type}" ); + $msg->params( $this->event->getExtraParam( + 'email' ) ); + return $msg; + } + + /** + * If this function returns false, no other methods will be called + * on the object. + * + * @return bool + */ + public function canRender() { + return !is_null( $this->event->getTitle() ); + } +} diff --git a/includes/EmailAuthorizationConfig.alias.php b/includes/EmailAuthorization.alias.php similarity index 100% rename from includes/EmailAuthorizationConfig.alias.php rename to includes/EmailAuthorization.alias.php diff --git a/includes/EmailAuthorizationApprove.php b/includes/EmailAuthorizationApprove.php new file mode 100644 index 0000000..25630e9 --- /dev/null +++ b/includes/EmailAuthorizationApprove.php @@ -0,0 +1,296 @@ +<?php + +/* + * Copyright (c) 2017 The MITRE Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + t copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +class EmailAuthorizationApprove extends SpecialPage { + + function __construct() { + parent::__construct( 'EmailAuthorizationApprove', + 'emailauthorizationconfig' ); + } + + function execute( $par ) { + if ( !$this->userCanExecute( $this->getUser() ) ) { + $this->displayRestrictionError(); + return; + } + + $request = $this->getRequest(); + $this->setHeaders(); + $this->getOutput()->addModuleStyles( 'ext.EmailAuthorization' ); + + $title = Title::newFromText( 'Special:' . __CLASS__ ); + $url = $title->getFullURL(); + + $approve_email = $request->getText( 'approve-email' ); + if ( !is_null( $approve_email ) && strlen( $approve_email ) ) { + $fields = self::getRequestFields( $approve_email ); + self::insertEmail( $approve_email ); + self::deleteRequest( $approve_email ); + $this->displayMessage( + wfMessage( 'emailauthorization-approve-approved', $approve_email ) + ); + wfRunHooks( 'EmailAuthorizationApprove', [ $approve_email, $fields, $this->getUser() ] ); + } + + $reject_email = $request->getText( 'reject-email' ); + if ( !is_null( $reject_email ) && strlen( $reject_email ) ) { + $fields = self::getRequestFields( $reject_email ); + self::deleteRequest( $reject_email ); + $this->displayMessage( + wfMessage( 'emailauthorization-approve-rejected', $reject_email ) + ); + wfRunHooks( 'EmailAuthorizationReject', [ $reject_email, $fields, $this->getUser() ] ); + } + + $offset = $request->getText( 'offset' ); + + if ( is_null( $offset ) || strlen( $offset ) === 0 || + !is_numeric( $offset ) || $offset < 0 ) { + $offset = 0; + } + + $limit = 20; + + $requests = self::getRequests( $limit + 1, $offset ); + $next = false; + + if ( !$requests->valid() ) { + $offset = 0; + $requests = self::getRequests( $limit + 1, $offset ); + if ( !$requests->valid() ) { + $this->displayMessage( + wfMessage( 'emailauthorization-approve-norequestsfound' ) + ); + return; + } + } + + $html = Html::openElement( 'table', [ + 'class' => 'wikitable emailauth-wikitable' + ] ) + . Html::openElement( 'tr' ) + . Html::openElement( 'th' ) + . wfMessage( 'emailauthorization-approve-label-email' ) + . Html::closeElement( 'th' ) + . Html::openElement( 'th' ) + . wfMessage( 'emailauthorization-approve-label-extra' ) + . Html::closeElement( 'th' ) + . Html::openElement( 'th' ) + . wfMessage( 'emailauthorization-approve-label-action' ) + . Html::closeElement( 'th' ) + . Html::closeElement( 'tr' ); + + $index = 0; + $more = false; + foreach ( $requests as $request ) { + if ( $index < $limit ) { + $email = htmlspecialchars( $request->email, ENT_QUOTES ); + $html .= Html::openElement( 'tr' ) + . Html::openElement( 'td' ) + . $email + . Html::closeElement( 'td' ) + . Html::openElement( 'td' ); + $json = $request->request; + $data = json_decode( $json ); + foreach ( $data as $field => $value ) { + $html .= + Html::openElement( 'b' ) . + htmlspecialchars( $field ) . + Html::closeElement( 'b' ) . + ': ' . + htmlspecialchars( $value ) . + Html::element( 'br' ); + } + $html .= + Html::closeElement( 'td' ) + . Html::openElement( 'td', [ + 'style' => 'text-align: center;' + ] ) + . $this->createApproveButton( $url, $email ) + . $this->createRejectButton( $url, $email ) + . Html::closeElement( 'td' ) + . Html::closeElement( 'tr' ); + $index ++; + } else { + $more = true; + } + } + + $html .= Html::closeElement( 'table' ); + $this->getOutput()->addHtml( $html ); + + if ( $offset > 0 || $more ) { + $this->addTableNavigation( $offset, $more, $limit, 'offset' ); + } + } + + private function createApproveButton( $url, $email ) { + $html = Html::openElement( 'form', [ + 'method' => 'post', + 'action' => $url, + 'style' => 'display: inline-block;' + ] ) + . Html::hidden( 'approve-email', $email ) + . Xml::submitButton( + wfMessage( 'emailauthorization-approve-button-approve' ), + [ 'class' => 'emailauth-button' ] ) + . Html::closeElement( 'form' ); + return $html; + } + + private function createRejectButton( $url, $email ) { + $html = Html::openElement( 'form', [ + 'method' => 'post', + 'action' => $url, + 'style' => 'display: inline-block;' + ] ) + . Html::hidden( 'reject-email', $email ) + . Xml::submitButton( + wfMessage( 'emailauthorization-approve-button-reject' ), + [ 'class' => 'emailauth-button' ] ) + . Html::closeElement( 'form' ); + return $html; + } + + private function addTableNavigation( $offset, $more, $limit, $paramname ) { + + $title = Title::newFromText( 'Special:EmailAuthorizationApprove' ); + $url = $title->getFullURL(); + + $html = Html::openElement( 'table', [ + 'class' => 'emailauth-navigationtable' + ] ) + . Html::openElement( 'tr' ) + . Html::openElement( 'td' ); + + if ( $offset > 0 ) { + $prevurl = $url . '?' . $paramname . '=' . ( $offset - $limit ); + $html .= Html::openElement( 'a', [ + 'href' => $prevurl, + 'class' => 'emailauth-button' + ] ) + . wfMessage( 'emailauthorization-approve-button-previous' ) + . Html::closeElement( 'a' ); + } + + $html .= Html::closeElement( 'td' ) + . Html::openElement( 'td', [ + 'style' => 'text-align:right;' + ] ); + + if ( $more ) { + $nexturl = $url . '?' . $paramname . '=' . ( $offset + $limit ); + $html .= Html::openElement( 'a', [ + 'href' => $nexturl, + 'class' => 'emailauth-button' + ] ) + . wfMessage( 'emailauthorization-approve-button-next' ) + . Html::closeElement( 'a' ); + } + + $html .= Html::closeElement( 'td' ) + . Html::closeElement( 'tr' ) + . Html::closeElement( 'table' ); + $this->getOutput()->addHtml( $html ); + } + + private function displayMessage( $message ) { + $html = Html::openElement( 'p', [ + 'class' => 'emailauth-message' + ] ) + . $message + . Html::closeElement( 'p' ); + $this->getOutput()->addHtml( $html ); + } + + private static function getRequests( $limit, $offset ) { + $dbr = wfGetDB( DB_SLAVE ); + $requests = $dbr->select( + 'emailrequest', + [ + 'email', + 'request', + ], + [], + __METHOD__, + [ + 'ORDER BY' => 'email', + 'LIMIT' => $limit, + 'OFFSET' => $offset + ] + ); + return $requests; + } + + private static function getRequestFields( $email ) { + $dbr = wfGetDB( DB_SLAVE ); + $request = $dbr->selectRow( + 'emailrequest', + [ + 'request', + ], + [ + 'email' => $email + ], + __METHOD__ + ); + if ( $request === false ) { + return ''; + } + return json_decode( $request->request ); + } + + private static function insertEmail( $email ) { + $dbw = wfGetDB( DB_MASTER ); + $dbw->upsert( + 'emailauth', + [ + 'email' => $email + ], + [ + 'email' => $email + ], + [ + 'email' => $email + ], + __METHOD__ + ); + if ( $dbw->affectedRows() === 1 ) { + return true; + } else { + return false; + } + } + + private static function deleteRequest( $email ) { + $dbw = wfGetDB( DB_MASTER ); + $dbw->delete( + 'emailrequest', + [ + 'email' => $email + ], + __METHOD__ + ); + } +} diff --git a/includes/EmailAuthorizationConfig.php b/includes/EmailAuthorizationConfig.php index 23e0b00..02971fb 100644 --- a/includes/EmailAuthorizationConfig.php +++ b/includes/EmailAuthorizationConfig.php @@ -404,8 +404,8 @@ . Html::element( 'legend', null, wfMessage( 'emailauthorization-config-legend-search' ) . ':' ); list( $label, $input ) = - Xml::inputLabelSep( 'Email address:', 'searchemail', 'searchemail', - 50 ); + Xml::inputLabelSep( wfMessage( 'emailauthorization-config-label-email' ) . ':', + 'searchemail', 'searchemail', 50 ); $html .= $label . ' ' . $input . ' ' . Xml::submitButton( wfMessage( 'emailauthorization-config-button-search' ), @@ -425,8 +425,8 @@ . Html::element( 'legend', null, wfMessage( 'emailauthorization-config-legend-add' ) . ':' ); list( $label, $input ) = - Xml::inputLabelSep( 'Email address:', 'addemail', 'addemail', 50, - $default ); + Xml::inputLabelSep( wfMessage( 'emailauthorization-config-label-email' ) . ':', + 'addemail', 'addemail', 50, $default ); $html .= $label . ' ' . $input . ' ' . Xml::submitButton( wfMessage( 'emailauthorization-config-button-add' ), @@ -446,8 +446,8 @@ . Html::element( 'legend', null, wfMessage( 'emailauthorization-config-legend-revoke' ) . ':' ); list( $label, $input ) = - Xml::inputLabelSep( 'Email address:', 'revokeemail', 'revokeemail', - 50, $default ); + Xml::inputLabelSep( wfMessage( 'emailauthorization-config-label-email' ) . ':', + 'revokeemail', 'revokeemail', 50, $default ); $html .= $label . ' ' . $input . ' ' . Xml::submitButton( wfMessage( 'emailauthorization-config-button-revoke' ), diff --git a/includes/EmailAuthorizationHooks.php b/includes/EmailAuthorizationHooks.php index 2b70624..f5bc345 100644 --- a/includes/EmailAuthorizationHooks.php +++ b/includes/EmailAuthorizationHooks.php @@ -28,7 +28,9 @@ $dir = $GLOBALS['wgExtensionDirectory'] . DIRECTORY_SEPARATOR . 'EmailAuthorization' . DIRECTORY_SEPARATOR . 'sql' . DIRECTORY_SEPARATOR; $updater->addExtensionTable( 'emailauth', - $dir . 'EmailAuthorization.sql', true ); + $dir . 'EmailAuth.sql', true ); + $updater->addExtensionTable( 'emailrequest', + $dir . 'EmailRequest.sql', true ); return true; } @@ -36,4 +38,50 @@ $authorized = EmailAuthorization::isEmailAuthorized( $user->mEmail ); return $authorized; } + + public static function onRegistration() { + $GLOBALS['wgHooks']['SpecialPage_initList'][] = function ( &$list ) { + if ( !$GLOBALS['wgEmailAuthorization_EnableRequests'] ) { + unset( $list['EmailAuthorizationRequest'] ); + } + }; + } + + public static function onBeforeCreateEchoEvent( &$notifications, + &$notificationCategories, &$icons ) { + + $notificationCategories['emailauthorization-notification-category'] = [ + 'priority' => 3 + ]; + + $notifications['emailauthorization-account-request'] = [ + 'category' => 'emailauthorization-notification-category', + 'group' => 'positive', + 'section' => 'alert', + 'presentation-model' => EchoEAPresentationModel::class, + 'user-locators' => [ 'EmailAuthorizationHooks::locateBureaucrats' ] + ]; + } + + public static function locateBureaucrats( $event ) { + $db = wfGetDB( DB_REPLICA ); + $res = $db->select( + [ 'user_groups', 'user' ], + [ 'ug_user', 'ug_expiry' ], + [ 'ug_group' => 'bureaucrat' ], + __METHOD__, + [], + [ 'user' => [ 'INNER JOIN', [ 'ug_user = user_id' ] ] ] + ); + $users = []; + foreach ( $res as $row ) { + $id = $row->ug_user; + $user = User::newFromId( $id ); + $expiry = $row->ug_expiry; + if ( !$expiry || wfTimestampNow() < $expiry ) { + $users[$id] = $user; + } + } + return $users; + } } diff --git a/includes/EmailAuthorizationRequest.php b/includes/EmailAuthorizationRequest.php new file mode 100644 index 0000000..a520835 --- /dev/null +++ b/includes/EmailAuthorizationRequest.php @@ -0,0 +1,268 @@ +<?php + +/* + * Copyright (c) 2017 The MITRE Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +class EmailAuthorizationRequest extends SpecialPage { + + function __construct() { + parent::__construct( 'EmailAuthorizationRequest' ); + } + + function execute( $par ) { + $request = $this->getRequest(); + $this->setHeaders(); + $this->getOutput()->addModuleStyles( 'ext.EmailAuthorization' ); + + $emailLabel = + wfMessage( 'emailauthorization-request-label-email' )->text(); + $emailfield = [ + 'label' => $emailLabel, + 'mandatory' => true + ]; + $fields = array_merge( [ $emailfield ], + $GLOBALS['wgEmailAuthorization_RequestFields'] ); + + $showform = true; + + $submitted = $request->getBool( + 'emailauthorization-request-field-submitted' ); + + if ( $submitted ) { + + $showform = self::processRequest( $request, $fields ); + + } + + if ( $showform ) { + + $html = Html::openElement( 'p' ) + . Html::openElement( 'b' ) + . wfMessage( 'emailauthorization-request-instructions' )->parse() + . Html::closeElement( 'b' ) + . Html::closeElement( 'p' ) + . Html::element( 'br' ); + $this->getOutput()->addHtml( $html ); + + $title = Title::newFromText( 'Special:' . __CLASS__ ); + $url = $title->getFullURL(); + + $html = Html::openElement( 'form', [ + 'method' => 'post', + 'action' => $url, + 'id' => 'RequestEmail' + ] ); + $id = 'emailauthorization-request-field-submitted'; + $html .= Html::hidden( $id, true ); + $i = 0; + foreach ( $fields as $field ) { + $id = 'emailauthorization-request-field-' . $i; + $i++; + if ( isset( $field['label'] ) ) { + $html .= Xml::label( $field['label'] . ': ', $id ); + $mandatory = false; + if ( isset( $field['mandatory'] ) && $field['mandatory'] ) { + $html .= '* '; + $mandatory = true; + } + $attribs = [ 'id' => $id ]; + if ( $submitted && $mandatory && $request->getText( $id ) === '' ) { + $attribs['style'] = "border-color:red;"; + } + if ( isset( $field['values'] ) ) { + $attribs['name'] = $id; + $html .= Xml::openElement( 'select', $attribs ); + foreach ( $field['values'] as $value ) { + $html .= Xml::option( $value, $value ); + } + $html .= Xml::closeElement( 'select' ); + $html .= Html::element( 'br' ); + } elseif ( isset( $field['rows'] ) ) { + $rows = $field['rows']; + $columns = 50; + if ( isset( $field['columns'] ) ) { + $columns = $field['columns']; + } + $value = ''; + if ( $submitted ) { + $value = $request->getText( $id ); + } + $input = Xml::textarea( $id, $value, $columns, $rows, $attribs ); + $html .= $input; + } else { + $columns = 50; + if ( isset( $field['columns'] ) ) { + $columns = $field['columns']; + } + $value = ''; + if ( $submitted ) { + $value = $request->getText( $id ); + } + $input = Xml::input( $id, $columns, $value, $attribs ); + $html .= $input; + $html .= Html::element( 'br' ); + } + $html .= Html::element( 'br' ); + } + } + $html .= Xml::submitButton( + wfMessage( 'emailauthorization-request-button-submit' ), + [ 'class' => 'emailauth-button' ] ) + . Html::closeElement( 'form' ); + $this->getOutput()->addHtml( $html ); + } + } + + private function displayMessage( $message ) { + $html = Html::openElement( 'p', [ + 'class' => 'emailauth-message' + ] ) + . $message + . Html::closeElement( 'p' ); + $this->getOutput()->addHtml( $html ); + } + + private function validateEmail( $email ) { + if ( is_null( $email ) || strlen( $email ) < 1 ) { + return false; + } + $email = mb_strtolower( htmlspecialchars( trim( $email ), ENT_QUOTES ) ); + if ( filter_var( $email, FILTER_VALIDATE_EMAIL ) ) { + return $email; + } + return false; + } + + private function processRequest( $request, $fields ) { + $i = 0; + foreach ( $fields as $field ) { + $id = 'emailauthorization-request-field-' . $i; + $i++; + if ( isset( $field['label'] ) ) { + if ( isset( $field['mandatory'] ) && $field['mandatory'] && + $request->getText( $id ) === '' ) { + $this->displayMessage( + wfMessage( 'emailauthorization-request-missingmandatory' ) ); + return true; + } + } + } + $email = $request->getText( 'emailauthorization-request-field-0' ); + $validatedemail = $this->validateEmail( $email ); + if ( $validatedemail === false ) { + $this->displayMessage( + wfMessage( 'emailauthorization-request-invalidemail', $email ) + ); + return true; + } + $email = $validatedemail; + if ( self::checkEmail( $email ) ) { + if ( self::insertRequest( $email, $request ) ) { + if ( class_exists( 'EchoEvent' ) ) { + $extra = [ + 'email' => $validatedemail, + 'notifyAgent' => true + ]; + EchoEvent::create( [ + 'type' => 'emailauthorization-account-request', + 'extra' => $extra + ] ); + } + } else { + $this->displayMessage( + wfMessage( 'emailauthorization-request-error', $validatedemail ) + ); + return true; + } + } + $this->displayMessage( + wfMessage( 'emailauthorization-request-requested', $validatedemail ) + ); + return false; + } + + private static function checkEmail( $email ) { + $dbr = wfGetDB( DB_SLAVE ); + $users = $dbr->select( + 'user', + [ + 'user_email' + ], + [ + 'user_email' => $email + ], + __METHOD__ + ); + if ( $users->valid() ) { + $users = $dbr->select( + 'emailauth', + [ + 'email' + ], + [ + 'email' => $email + ], + __METHOD__ + ); + if ( $users->valid() ) { + return false; + } + return true; + } + return true; + return true; + } + private static function insertRequest( $email, $request ) { + $i = 1; + $data = []; + foreach ( $GLOBALS['wgEmailAuthorization_RequestFields'] as $field ) { + $id = 'emailauthorization-request-field-' . $i; + $i++; + $value = $request->getText( $id ) ; + if ( $value ) { + $data[$field['label']] = $value; + } + } + $json = json_encode($data); + $dbw = wfGetDB( DB_MASTER ); + $res = $dbw->upsert( + 'emailrequest', + [ + 'email' => $email, + 'request' => $json, + ], + [ + 'email' => $email + ], + [ + 'request' => $json, + ], + __METHOD__ + ); + if ( $res ) { + wfRunHooks( 'EmailAuthorizationRequest', [ $email, $data ] ); + return true; + } else { + return false; + } + } +} diff --git a/sql/EmailAuthorization.sql b/sql/EmailAuth.sql similarity index 100% rename from sql/EmailAuthorization.sql rename to sql/EmailAuth.sql diff --git a/sql/EmailRequest.sql b/sql/EmailRequest.sql new file mode 100644 index 0000000..b2d8062 --- /dev/null +++ b/sql/EmailRequest.sql @@ -0,0 +1,5 @@ +CREATE TABLE `emailrequest` ( + `email` tinyblob NOT NULL, + `request` blob NOT NULL, + PRIMARY KEY (`email`(50)) +) ENGINE=InnoDB DEFAULT CHARSET=binary; -- To view, visit https://gerrit.wikimedia.org/r/366780 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: merged Gerrit-Change-Id: I97c2c995eb0eeea760824468d3b4453f6e7ccb09 Gerrit-PatchSet: 18 Gerrit-Project: mediawiki/extensions/EmailAuthorization Gerrit-Branch: master Gerrit-Owner: Cicalese <cin...@gmail.com> Gerrit-Reviewer: Cicalese <cin...@gmail.com> Gerrit-Reviewer: Matthew-a-thompson <mathomp...@mitre.org> Gerrit-Reviewer: Siebrand <siebr...@kitano.nl> Gerrit-Reviewer: jenkins-bot <> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits