/** * Move old stale requests to rejected list. Delete old rejected requests. */ public static function runAutoMaintenance() { global $wgRejectedAccountMaxAge, $wgConfirmAccountRejectAge, $wgConfirmAccountFSRepos; $dbw = wfGetDB(DB_MASTER); $repo = new FSRepo($wgConfirmAccountFSRepos['accountreqs']); # Select all items older than time $encCutoff $encCutoff = $dbw->addQuotes($dbw->timestamp(time() - $wgRejectedAccountMaxAge)); $res = $dbw->select('account_requests', array('acr_id', 'acr_storage_key'), array("acr_rejected < {$encCutoff}"), __METHOD__); # Clear out any associated attachments and delete those rows foreach ($res as $row) { $key = $row->acr_storage_key; if ($key) { $path = $repo->getZonePath('public') . '/' . UserAccountRequest::relPathFromKey($key); if ($path && file_exists($path)) { unlink($path); } } $dbw->delete('account_requests', array('acr_id' => $row->acr_id), __METHOD__); } # Select all items older than time $encCutoff $encCutoff = $dbw->addQuotes($dbw->timestamp(time() - $wgConfirmAccountRejectAge)); # Old stale accounts will count as rejected. If the request was held, give it more time. $dbw->update('account_requests', array('acr_rejected' => $dbw->timestamp(), 'acr_user' => 0, 'acr_comment' => wfMsgForContent('confirmaccount-autorej'), 'acr_deleted' => 1), array("acr_rejected IS NULL", "acr_registration < {$encCutoff}", "acr_held < {$encCutoff} OR acr_held IS NULL"), __METHOD__); # Clear cache for notice of how many account requests there are self::clearAccountRequestCountCache(); }
/** * @param $user User * @param $abortError * @return bool */ public static function checkIfAccountNameIsPending(User $user, &$abortError) { # If an account is made with name X, and one is pending with name X # we will have problems if the pending one is later confirmed if (!UserAccountRequest::acquireUsername($user->getName())) { $abortError = wfMsgHtml('requestaccount-inuse'); return false; } return true; }
/** * Get requested account request row and load some fields * @param $id int * @param $wasPosted bool * @return void */ protected function loadAccountRequest($id, $wasPosted) { $from = $wasPosted ? 'dbmaster' : 'dbslave'; $this->accountReq = UserAccountRequest::newFromId($id, $from); # Check if parameters are to be overridden if ($this->accountReq) { $this->reqUsername = $this->reqUsername != '' ? $this->reqUsername : $this->accountReq->getName(); $this->reqBio = $this->reqBio != '' ? $this->reqBio : $this->accountReq->getBio(); $this->reqType = !is_null($this->reqType) ? $this->reqType : $this->accountReq->getType(); $origAreas = $this->accountReq->getAreas(); foreach ($this->reqAreas as $area => $within) { # If admin didn't set any of these checks, go back to how the user set them. # On GET requests, the admin probably didn't set anything. if ($within == -1) { if (in_array($area, $origAreas)) { $this->reqAreas[$area] = 1; } else { $this->reqAreas[$area] = 0; } } } } }
protected function acceptRequest(IContextSource $context) { global $wgAuth, $wgAccountRequestTypes, $wgConfirmAccountSaveInfo; global $wgAllowAccountRequestFiles, $wgConfirmAccountFSRepos; $accReq = $this->accountReq; // convenience # Now create user and check if the name is valid $user = User::newFromName($this->userName, 'creatable'); if (!$user) { return array('accountconf_invalid_name', wfMsgHtml('noname')); } # Check if account name is already in use if (0 != $user->idForName() || $wgAuth->userExists($user->getName())) { return array('accountconf_user_exists', wfMsgHtml('userexists')); } $dbw = wfGetDB(DB_MASTER); $dbw->begin(); # Make a random password $p = User::randomPassword(); # Insert the new user into the DB... $tokenExpires = $accReq->getEmailTokenExpires(); $authenticated = $accReq->getEmailAuthTimestamp(); $params = array('real_name' => $accReq->getRealName(), 'newpassword' => User::crypt($p), 'email' => $accReq->getEmail(), 'email_authenticated' => $dbw->timestampOrNull($authenticated), 'email_token_expires' => $dbw->timestamp($tokenExpires), 'email_token' => $accReq->getEmailToken()); $user = User::createNew($user->getName(), $params); # Grant any necessary rights (exclude blank or dummy groups) $group = self::getGroupFromType($this->type); if ($group != '' && $group != 'user' && $group != '*') { $user->addGroup($group); } $acd_id = null; // used for rollback cleanup # Save account request data to credentials system if ($wgConfirmAccountSaveInfo) { $key = $accReq->getFileStorageKey(); # Copy any attached files to new storage group if ($wgAllowAccountRequestFiles && $key) { $repoOld = new FSRepo($wgConfirmAccountFSRepos['accountreqs']); $repoNew = new FSRepo($wgConfirmAccountFSRepos['accountcreds']); $pathRel = UserAccountRequest::relPathFromKey($key); $oldPath = $repoOld->getZonePath('public') . '/' . $pathRel; $triplet = array($oldPath, 'public', $pathRel); $status = $repoNew->storeBatch(array($triplet)); // copy! if (!$status->isOK()) { $dbw->rollback(); # DELETE new rows in case there was a COMMIT somewhere $this->acceptRequest_rollback($dbw, $user->getId(), $acd_id); return array('accountconf_copyfailed', $context->getOutput()->parse($status->getWikiText())); } } $acd_id = $dbw->nextSequenceValue('account_credentials_acd_id_seq'); # Move request data into a separate table $dbw->insert('account_credentials', array('acd_user_id' => $user->getID(), 'acd_real_name' => $accReq->getRealName(), 'acd_email' => $accReq->getEmail(), 'acd_email_authenticated' => $dbw->timestampOrNull($authenticated), 'acd_bio' => $accReq->getBio(), 'acd_notes' => $accReq->getNotes(), 'acd_urls' => $accReq->getUrls(), 'acd_ip' => $accReq->getIP(), 'acd_filename' => $accReq->getFileName(), 'acd_storage_key' => $accReq->getFileStorageKey(), 'acd_areas' => $accReq->getAreas('flat'), 'acd_registration' => $dbw->timestamp($accReq->getRegistration()), 'acd_accepted' => $dbw->timestamp(), 'acd_user' => $this->admin->getID(), 'acd_comment' => $this->reason, 'acd_id' => $acd_id), __METHOD__); if (is_null($acd_id)) { $acd_id = $dbw->insertId(); // set $acd_id to ID inserted } } # Add to global user login system (if there is one) if (!$wgAuth->addUser($user, $p, $accReq->getEmail(), $accReq->getRealName())) { $dbw->rollback(); # DELETE new rows in case there was a COMMIT somewhere $this->acceptRequest_rollback($dbw, $user->getId(), $acd_id); return array('accountconf_externaldberror', wfMsgHtml('externaldberror')); } # OK, now remove the request from the queue $accReq->remove(); # Commit this if we make past the CentralAuth system # and the groups are added. Next step is sending out an # email, which we cannot take back... $dbw->commit(); # Prepare a temporary password email... if ($this->reason != '') { $msg = "confirmaccount-email-body2-pos{$this->type}"; # If the user is in a group and there is a welcome for that group, use it if ($group && !wfEmptyMsg($msg)) { $ebody = wfMsgExt($msg, array('parsemag', 'content'), $user->getName(), $p, $this->reason); # Use standard if none found... } else { $ebody = wfMsgExt('confirmaccount-email-body2', array('parsemag', 'content'), $user->getName(), $p, $this->reason); } } else { $msg = "confirmaccount-email-body-pos{$this->type}"; # If the user is in a group and there is a welcome for that group, use it if ($group && !wfEmptyMsg($msg)) { $ebody = wfMsgExt($msg, array('parsemag', 'content'), $user->getName(), $p, $this->reason); # Use standard if none found... } else { $ebody = wfMsgExt('confirmaccount-email-body', array('parsemag', 'content'), $user->getName(), $p, $this->reason); } } # Actually send out the email (@TODO: rollback on failure including $wgAuth) $result = $user->sendMail(wfMsgForContent('confirmaccount-email-subj'), $ebody); /* if ( !$result->isOk() ) { # DELETE new rows in case there was a COMMIT somewhere $this->acceptRequest_rollback( $dbw, $user->getId(), $acd_id ); return array( 'accountconf_mailerror', wfMsg( 'mailerror', $context->getOutput()->parse( $result->getWikiText() ) ) ); } */ # Update user count $ssUpdate = new SiteStatsUpdate(0, 0, 0, 0, 1); $ssUpdate->doUpdate(); # Safe to hook/log now... wfRunHooks('AddNewAccount', array($user, false)); $user->addNewUserLogEntry(); # Clear cache for notice of how many account requests there are ConfirmAccount::clearAccountRequestCountCache(); # Delete any attached file and don't stop the whole process if this fails if ($wgAllowAccountRequestFiles) { $key = $accReq->getFileStorageKey(); if ($key) { $repoOld = new FSRepo($wgConfirmAccountFSRepos['accountreqs']); $pathRel = UserAccountRequest::relPathFromKey($key); $oldPath = $repoOld->getZonePath('public') . '/' . $pathRel; if (file_exists($oldPath)) { unlink($oldPath); // delete! } } } # Start up the user's userpages if set to do so. # Will not append, so previous content will be blanked. $this->createUserPage($user); # Greet the new user if set to do so. $this->createUserTalkPage($user); return array(true, null); }
/** * Attempt to validate and submit this data to the DB * @param $context IContextSource * @return array( true or error key string, html error msg or null ) */ public function submit(IContextSource $context) { global $wgAuth, $wgAccountRequestThrottle, $wgMemc, $wgContLang; global $wgConfirmAccountRequestFormItems; $formConfig = $wgConfirmAccountRequestFormItems; // convience $reqUser = $this->requester; # Make sure that basic permissions are checked $block = ConfirmAccount::getAccountRequestBlock($reqUser); if ($block) { return array('accountreq_permission_denied', $context->msg('badaccess-group0')->escaped()); } elseif (wfReadOnly()) { return array('accountreq_readonly', $context->msg('badaccess-group0')->escaped()); } # Now create a dummy user ($u) and check if it is valid if ($this->userName === '') { return array('accountreq_no_name', $context->msg('noname')->escaped()); } $u = User::newFromName($this->userName, 'creatable'); if (!$u) { return array('accountreq_invalid_name', $context->msg('noname')->escaped()); } # No request spamming... if ($wgAccountRequestThrottle && $reqUser->isPingLimitable()) { $key = wfMemcKey('acctrequest', 'ip', $this->ip); $value = (int) $wgMemc->get($key); if ($value > $wgAccountRequestThrottle) { return array('accountreq_throttled', $context->msg('acct_request_throttle_hit', $wgAccountRequestThrottle)->text()); } } # Make sure user agrees to policy here if ($formConfig['TermsOfService']['enabled'] && !$this->tosAccepted) { return array('acct_request_skipped_tos', $context->msg('requestaccount-agree')->escaped()); } # Validate email address if (!Sanitizer::validateEmail($this->email)) { return array('acct_request_invalid_email', $context->msg('invalidemailaddress')->escaped()); } # Check if biography is long enough if ($formConfig['Biography']['enabled'] && str_word_count($this->bio) < $formConfig['Biography']['minWords']) { $minWords = $formConfig['Biography']['minWords']; return array('acct_request_short_bio', $context->msg('requestaccount-tooshort')->numParams($minWords)->text()); } # Per security reasons, file dir cannot be pulled from client, # so ask them to resubmit it then... # If the extra fields are off, then uploads are off $allowFiles = $formConfig['CV']['enabled']; if ($allowFiles && $this->attachmentPrevName && !$this->attachmentSrcName) { # If the user is submitting forgotAttachment as true with no file, # then they saw the notice and choose not to re-select the file. # Assume that they don't want to send one anymore. if (!$this->attachmentDidNotForget) { $this->attachmentPrevName = ''; $this->attachmentDidNotForget = 0; return array(false, $context->msg('requestaccount-resub')->escaped()); } } # Check if already in use if (0 != $u->idForName() || $wgAuth->userExists($u->getName())) { return array('accountreq_username_exists', $context->msg('userexists')->escaped()); } # Set email and real name $u->setEmail($this->email); $u->setRealName($this->realName); $dbw = wfGetDB(DB_MASTER); $dbw->begin(); // ready to acquire locks # Check pending accounts for name use if (!UserAccountRequest::acquireUsername($u->getName())) { $dbw->rollback(); return array('accountreq_username_pending', $context->msg('requestaccount-inuse')->escaped()); } # Check if someone else has an account request with the same email if (!UserAccountRequest::acquireEmail($u->getEmail())) { $dbw->rollback(); return array('acct_request_email_exists', $context->msg('requestaccount-emaildup')->escaped()); } # Process upload... if ($allowFiles && $this->attachmentSrcName) { global $wgAccountRequestExts, $wgConfirmAccountFSRepos; $ext = explode('.', $this->attachmentSrcName); $finalExt = $ext[count($ext) - 1]; # File must have size. if (trim($this->attachmentSrcName) == '' || empty($this->attachmentSize)) { $this->attachmentPrevName = ''; $dbw->rollback(); return array('acct_request_empty_file', $context->msg('emptyfile')->escaped()); } # Look at the contents of the file; if we can recognize the # type but it's corrupt or data of the wrong type, we should # probably not accept it. if (!in_array($finalExt, $wgAccountRequestExts)) { $this->attachmentPrevName = ''; $dbw->rollback(); return array('acct_request_bad_file_ext', $context->msg('requestaccount-exts')->escaped()); } $veri = ConfirmAccount::verifyAttachment($this->attachmentTempPath, $finalExt); if (!$veri->isGood()) { $this->attachmentPrevName = ''; $dbw->rollback(); return array('acct_request_corrupt_file', $context->msg('verification-error')->escaped()); } # Start a transaction, move file from temp to account request directory. $repo = new FSRepo($wgConfirmAccountFSRepos['accountreqs']); $key = sha1_file($this->attachmentTempPath) . '.' . $finalExt; $pathRel = UserAccountRequest::relPathFromKey($key); $triplet = array($this->attachmentTempPath, 'public', $pathRel); $status = $repo->storeBatch(array($triplet), FSRepo::OVERWRITE_SAME); // save! if (!$status->isOk()) { $dbw->rollback(); return array('acct_request_file_store_error', $context->msg('filecopyerror', $this->attachmentTempPath, $pathRel)->escaped()); } } $expires = null; // passed by reference $token = ConfirmAccount::getConfirmationToken($u, $expires); # Insert into pending requests... $req = UserAccountRequest::newFromArray(array('name' => $u->getName(), 'email' => $u->getEmail(), 'real_name' => $u->getRealName(), 'registration' => $this->registration, 'bio' => $this->bio, 'notes' => $this->notes, 'urls' => $this->urls, 'filename' => isset($this->attachmentSrcName) ? $this->attachmentSrcName : null, 'type' => $this->type, 'areas' => $this->areas, 'storage_key' => isset($key) ? $key : null, 'comment' => '', 'email_token' => md5($token), 'email_token_expires' => $expires, 'ip' => $this->ip, 'xff' => $this->xff, 'agent' => $this->agent)); $req->insertOn(); # Send confirmation, required! $result = ConfirmAccount::sendConfirmationMail($u, $this->ip, $token, $expires); if (!$result->isOK()) { $dbw->rollback(); // nevermind if (isset($repo) && isset($pathRel)) { // remove attachment $repo->cleanupBatch(array(array('public', $pathRel))); } $param = $context->getOutput()->parse($result->getWikiText()); return array('acct_request_mail_failed', $context->msg('mailerror')->rawParams($param)->escaped()); } $dbw->commit(); # Clear cache for notice of how many account requests there are ConfirmAccount::clearAccountRequestCountCache(); # No request spamming... if ($wgAccountRequestThrottle && $reqUser->isPingLimitable()) { $ip = $context->getRequest()->getIP(); $key = wfMemcKey('acctrequest', 'ip', $ip); $value = $wgMemc->incr($key); if (!$value) { $wgMemc->set($key, 1, 86400); } } # Done! return array(true, null); }
/** * Show a private file requested by the visitor. * @param $key string * @return void */ function showFile($key) { global $wgConfirmAccountFSRepos; $out = $this->getOutput(); $request = $this->getRequest(); $out->disable(); # We mustn't allow the output to be Squid cached, otherwise # if an admin previews a private image, and it's cached, then # a user without appropriate permissions can toddle off and # nab the image, and Squid will serve it $request->response()->header('Expires: ' . gmdate('D, d M Y H:i:s', 0) . ' GMT'); $request->response()->header('Cache-Control: no-cache, no-store, max-age=0, must-revalidate'); $request->response()->header('Pragma: no-cache'); $repo = new FSRepo($wgConfirmAccountFSRepos['accountcreds']); $path = $repo->getZonePath('public') . '/' . UserAccountRequest::relPathFromKey($key); $repo->streamFile($path); }