Exemple #1
0
 public function execute()
 {
     if (!$this->getUser()->isLoggedIn()) {
         $this->dieUsage('Must be logged in to link accounts', 'notloggedin');
     }
     $params = $this->extractRequestParams();
     $this->requireAtLeastOneParameter($params, 'continue', 'returnurl');
     if ($params['returnurl'] !== null) {
         $bits = wfParseUrl($params['returnurl']);
         if (!$bits || $bits['scheme'] === '') {
             $encParamName = $this->encodeParamName('returnurl');
             $this->dieUsage("Invalid value '{$params['returnurl']}' for url parameter {$encParamName}", "badurl_{$encParamName}");
         }
     }
     $helper = new ApiAuthManagerHelper($this);
     $manager = AuthManager::singleton();
     // Check security-sensitive operation status
     $helper->securitySensitiveOperation('LinkAccounts');
     // Make sure it's possible to link accounts
     if (!$manager->canLinkAccounts()) {
         $this->getResult()->addValue(null, 'linkaccount', $helper->formatAuthenticationResponse(AuthenticationResponse::newFail($this->msg('userlogin-cannot-' . AuthManager::ACTION_LINK))));
         return;
     }
     // Perform the link step
     if ($params['continue']) {
         $reqs = $helper->loadAuthenticationRequests(AuthManager::ACTION_LINK_CONTINUE);
         $res = $manager->continueAccountLink($reqs);
     } else {
         $reqs = $helper->loadAuthenticationRequests(AuthManager::ACTION_LINK);
         $res = $manager->beginAccountLink($this->getUser(), $reqs, $params['returnurl']);
     }
     $this->getResult()->addValue(null, 'linkaccount', $helper->formatAuthenticationResponse($res));
 }
 public function execute()
 {
     $params = $this->extractRequestParams();
     $this->requireAtLeastOneParameter($params, 'continue', 'returnurl');
     if ($params['returnurl'] !== null) {
         $bits = wfParseUrl($params['returnurl']);
         if (!$bits || $bits['scheme'] === '') {
             $encParamName = $this->encodeParamName('returnurl');
             $this->dieUsage("Invalid value '{$params['returnurl']}' for url parameter {$encParamName}", "badurl_{$encParamName}");
         }
     }
     $helper = new ApiAuthManagerHelper($this);
     $manager = AuthManager::singleton();
     // Make sure it's possible to log in
     if (!$manager->canAuthenticateNow()) {
         $this->getResult()->addValue(null, 'clientlogin', $helper->formatAuthenticationResponse(AuthenticationResponse::newFail($this->msg('userlogin-cannot-' . AuthManager::ACTION_LOGIN))));
         return;
     }
     // Perform the login step
     if ($params['continue']) {
         $reqs = $helper->loadAuthenticationRequests(AuthManager::ACTION_LOGIN_CONTINUE);
         $res = $manager->continueAuthentication($reqs);
     } else {
         $reqs = $helper->loadAuthenticationRequests(AuthManager::ACTION_LOGIN);
         if ($params['preservestate']) {
             $req = $helper->getPreservedRequest();
             if ($req) {
                 $reqs[] = $req;
             }
         }
         $res = $manager->beginAuthentication($reqs, $params['returnurl']);
     }
     $this->getResult()->addValue(null, 'clientlogin', $helper->formatAuthenticationResponse($res));
 }
 /**
  * Return the appropriate response for failure
  * @param PasswordAuthenticationRequest $req
  * @return AuthenticationResponse
  */
 protected function failResponse(PasswordAuthenticationRequest $req)
 {
     if ($this->authoritative) {
         return AuthenticationResponse::newFail(wfMessage($req->password === '' ? 'wrongpasswordempty' : 'wrongpassword'));
     } else {
         return AuthenticationResponse::newAbstain();
     }
 }
 public function beginSecondaryAccountCreation($user, $creator, array $reqs)
 {
     if ($this->sendConfirmationEmail && $user->getEmail() && !$this->manager->getAuthenticationSessionData('no-email')) {
         // TODO show 'confirmemail_oncreate'/'confirmemail_sendfailed' message
         wfGetDB(DB_MASTER)->onTransactionIdle(function () use($user) {
             $user = $user->getInstanceForUpdate();
             $status = $user->sendConfirmationMail();
             $user->saveSettings();
             if (!$status->isGood()) {
                 $this->logger->warning('Could not send confirmation email: ' . $status->getWikiText(false, false, 'en'));
             }
         }, __METHOD__);
     }
     return AuthenticationResponse::newPass();
 }
Exemple #5
0
 public function execute()
 {
     $params = $this->extractRequestParams();
     $this->requireAtLeastOneParameter($params, 'continue', 'returnurl');
     if ($params['returnurl'] !== null) {
         $bits = wfParseUrl($params['returnurl']);
         if (!$bits || $bits['scheme'] === '') {
             $encParamName = $this->encodeParamName('returnurl');
             $this->dieUsage("Invalid value '{$params['returnurl']}' for url parameter {$encParamName}", "badurl_{$encParamName}");
         }
     }
     $helper = new ApiAuthManagerHelper($this);
     $manager = AuthManager::singleton();
     // Make sure it's possible to log in
     if (!$manager->canAuthenticateNow()) {
         $this->getResult()->addValue(null, 'clientlogin', $helper->formatAuthenticationResponse(AuthenticationResponse::newFail($this->msg('userlogin-cannot-' . AuthManager::ACTION_LOGIN))));
         $helper->logAuthenticationResult('login', 'userlogin-cannot-' . AuthManager::ACTION_LOGIN);
         return;
     }
     // Perform the login step
     if ($params['continue']) {
         $reqs = $helper->loadAuthenticationRequests(AuthManager::ACTION_LOGIN_CONTINUE);
         $res = $manager->continueAuthentication($reqs);
     } else {
         $reqs = $helper->loadAuthenticationRequests(AuthManager::ACTION_LOGIN);
         if ($params['preservestate']) {
             $req = $helper->getPreservedRequest();
             if ($req) {
                 $reqs[] = $req;
             }
         }
         $res = $manager->beginAuthentication($reqs, $params['returnurl']);
     }
     // Remove CreateFromLoginAuthenticationRequest from $res->neededRequests.
     // It's there so a RESTART treated as UI will work right, but showing
     // it to the API client is just confusing.
     $res->neededRequests = ApiAuthManagerHelper::blacklistAuthenticationRequests($res->neededRequests, [CreateFromLoginAuthenticationRequest::class]);
     $this->getResult()->addValue(null, 'clientlogin', $helper->formatAuthenticationResponse($res));
     $helper->logAuthenticationResult('login', $res);
 }
 /**
  * Continue the link attempt
  * @param User $user
  * @param string $key Session key to look in
  * @param AuthenticationRequest[] $reqs
  * @return AuthenticationResponse
  */
 protected function continueLinkAttempt($user, $key, array $reqs)
 {
     $req = ButtonAuthenticationRequest::getRequestByName($reqs, 'linkOk');
     if ($req) {
         return AuthenticationResponse::newPass();
     }
     $req = AuthenticationRequest::getRequestByClass($reqs, ConfirmLinkAuthenticationRequest::class);
     if (!$req) {
         // WTF? Retry.
         return $this->beginLinkAttempt($user, $key);
     }
     $session = $this->manager->getRequest()->getSession();
     $state = $session->getSecret($key);
     if (!is_array($state)) {
         return AuthenticationResponse::newAbstain();
     }
     $maybeLink = [];
     foreach ($state['maybeLink'] as $linkReq) {
         $maybeLink[$linkReq->getUniqueId()] = $linkReq;
     }
     if (!$maybeLink) {
         return AuthenticationResponse::newAbstain();
     }
     $state['maybeLink'] = [];
     $session->setSecret($key, $state);
     $statuses = [];
     $anyFailed = false;
     foreach ($req->confirmedLinkIDs as $id) {
         if (isset($maybeLink[$id])) {
             $req = $maybeLink[$id];
             $req->username = $user->getName();
             if (!$req->action) {
                 // Make sure the action is set, but don't override it if
                 // the provider filled it in.
                 $req->action = AuthManager::ACTION_CHANGE;
             }
             $status = $this->manager->allowsAuthenticationDataChange($req);
             $statuses[] = [$req, $status];
             if ($status->isGood()) {
                 $this->manager->changeAuthenticationData($req);
             } else {
                 $anyFailed = true;
             }
         }
     }
     if (!$anyFailed) {
         return AuthenticationResponse::newPass();
     }
     $combinedStatus = \Status::newGood();
     foreach ($statuses as $data) {
         list($req, $status) = $data;
         $descriptionInfo = $req->describeCredentials();
         $description = wfMessage('authprovider-confirmlink-option', $descriptionInfo['provider']->text(), $descriptionInfo['account']->text())->text();
         if ($status->isGood()) {
             $combinedStatus->error(wfMessage('authprovider-confirmlink-success-line', $description));
         } else {
             $combinedStatus->error(wfMessage('authprovider-confirmlink-failure-line', $description, $status->getMessage()->text()));
         }
     }
     return AuthenticationResponse::newUI([new ButtonAuthenticationRequest('linkOk', wfMessage('ok'), wfMessage('authprovider-confirmlink-ok-help'))], $combinedStatus->getMessage('authprovider-confirmlink-failed'));
 }
 public function beginSecondaryAccountCreation($user, $creator, array $reqs)
 {
     return AuthenticationResponse::newAbstain();
 }
 /**
  * @param string $action One of the AuthManager::ACTION_* constants
  * @param AuthenticationRequest[] $requests
  * @return AuthenticationResponse
  * @throws LogicException if $action is invalid
  */
 protected function performAuthenticationStep($action, array $requests)
 {
     if (!in_array($action, static::$allowedActions, true)) {
         throw new InvalidArgumentException('invalid action: ' . $action);
     }
     $authManager = AuthManager::singleton();
     $returnToUrl = $this->getPageTitle('return')->getFullURL($this->getPreservedParams(true), false, PROTO_HTTPS);
     switch ($action) {
         case AuthManager::ACTION_LOGIN:
             return $authManager->beginAuthentication($requests, $returnToUrl);
         case AuthManager::ACTION_LOGIN_CONTINUE:
             return $authManager->continueAuthentication($requests);
         case AuthManager::ACTION_CREATE:
             return $authManager->beginAccountCreation($this->getUser(), $requests, $returnToUrl);
         case AuthManager::ACTION_CREATE_CONTINUE:
             return $authManager->continueAccountCreation($requests);
         case AuthManager::ACTION_LINK:
             return $authManager->beginAccountLink($this->getUser(), $requests, $returnToUrl);
         case AuthManager::ACTION_LINK_CONTINUE:
             return $authManager->continueAccountLink($requests);
         case AuthManager::ACTION_CHANGE:
         case AuthManager::ACTION_REMOVE:
         case AuthManager::ACTION_UNLINK:
             if (count($requests) > 1) {
                 throw new InvalidArgumentException('only one auth request can be changed at a time');
             } elseif (!$requests) {
                 throw new InvalidArgumentException('no auth request');
             }
             $req = reset($requests);
             $status = $authManager->allowsAuthenticationDataChange($req);
             Hooks::run('ChangeAuthenticationDataAudit', [$req, $status]);
             if (!$status->isOK()) {
                 return AuthenticationResponse::newFail($status->getMessage());
             }
             $authManager->changeAuthenticationData($req);
             return AuthenticationResponse::newPass();
         default:
             // should never reach here but makes static code analyzers happy
             throw new InvalidArgumentException('invalid action: ' . $action);
     }
 }
 public function provideAccountLink()
 {
     $req = $this->getMockForAbstractClass(AuthenticationRequest::class);
     $good = StatusValue::newGood();
     return ['Pre-link test fail in pre' => [StatusValue::newFatal('fail-from-pre'), [], [AuthenticationResponse::newFail($this->message('fail-from-pre'))]], 'Failure in primary' => [$good, $tmp = [AuthenticationResponse::newFail($this->message('fail-from-primary'))], $tmp], 'All primary abstain' => [$good, [AuthenticationResponse::newAbstain()], [AuthenticationResponse::newFail($this->message('authmanager-link-no-primary'))]], 'Primary UI, then redirect, then fail' => [$good, $tmp = [AuthenticationResponse::newUI([$req], $this->message('...')), AuthenticationResponse::newRedirect([$req], '/foo.html', ['foo' => 'bar']), AuthenticationResponse::newFail($this->message('fail-in-primary-continue'))], $tmp], 'Primary redirect, then abstain' => [$good, [$tmp = AuthenticationResponse::newRedirect([$req], '/foo.html', ['foo' => 'bar']), AuthenticationResponse::newAbstain()], [$tmp, new \DomainException('MockPrimaryAuthenticationProvider::continuePrimaryAccountLink() returned ABSTAIN')]], 'Primary UI, then pass' => [$good, [$tmp1 = AuthenticationResponse::newUI([$req], $this->message('...')), AuthenticationResponse::newPass()], [$tmp1, AuthenticationResponse::newPass('')]], 'Primary pass' => [$good, [AuthenticationResponse::newPass('')], [AuthenticationResponse::newPass('')]]];
 }
 public function beginPrimaryAccountCreation($user, $creator, array $reqs)
 {
     /** @var TemporaryPasswordAuthenticationRequest $req */
     $req = AuthenticationRequest::getRequestByClass($reqs, TemporaryPasswordAuthenticationRequest::class);
     if ($req) {
         if ($req->username !== null && $req->password !== null) {
             // Nothing we can do yet, because the user isn't in the DB yet
             if ($req->username !== $user->getName()) {
                 $req = clone $req;
                 $req->username = $user->getName();
             }
             if ($req->mailpassword) {
                 // prevent EmailNotificationSecondaryAuthenticationProvider from sending another mail
                 $this->manager->setAuthenticationSessionData('no-email', true);
             }
             $ret = AuthenticationResponse::newPass($req->username);
             $ret->createRequest = $req;
             return $ret;
         }
     }
     return AuthenticationResponse::newAbstain();
 }
 public function beginPrimaryAccountCreation($user, $creator, array $reqs)
 {
     if ($this->accountCreationType() === self::TYPE_NONE) {
         throw new \BadMethodCallException('Shouldn\'t call this when accountCreationType() is NONE');
     }
     $req = AuthenticationRequest::getRequestByClass($reqs, PasswordAuthenticationRequest::class);
     if ($req) {
         if ($req->username !== null && $req->password !== null) {
             // Nothing we can do besides claim it, because the user isn't in
             // the DB yet
             if ($req->username !== $user->getName()) {
                 $req = clone $req;
                 $req->username = $user->getName();
             }
             $ret = AuthenticationResponse::newPass($req->username);
             $ret->createRequest = $req;
             return $ret;
         }
     }
     return AuthenticationResponse::newAbstain();
 }
 public function testBeginSecondaryAuthentication()
 {
     $provider = new EmailNotificationSecondaryAuthenticationProvider(['sendConfirmationEmail' => true]);
     $this->assertEquals(AuthenticationResponse::newAbstain(), $provider->beginSecondaryAuthentication(\User::newFromName('Foo'), []));
 }
Exemple #13
0
 /**
  * Continue an account linking flow
  * @param AuthenticationRequest[] $reqs
  * @return AuthenticationResponse
  */
 public function continueAccountLink(array $reqs)
 {
     $session = $this->request->getSession();
     try {
         if (!$this->canLinkAccounts()) {
             // Caller should have called canLinkAccounts()
             $session->remove('AuthManager::accountLinkState');
             throw new \LogicException('Account linking is not possible');
         }
         $state = $session->getSecret('AuthManager::accountLinkState');
         if (!is_array($state)) {
             return AuthenticationResponse::newFail(wfMessage('authmanager-link-not-in-progress'));
         }
         $state['continueRequests'] = [];
         // Step 0: Prepare and validate the input
         $user = User::newFromName($state['username'], 'usable');
         if (!is_object($user)) {
             $session->remove('AuthManager::accountLinkState');
             return AuthenticationResponse::newFail(wfMessage('noname'));
         }
         if ($user->getId() != $state['userid']) {
             throw new \UnexpectedValueException("User \"{$state['username']}\" is valid, but " . "ID {$user->getId()} != {$state['userid']}!");
         }
         foreach ($reqs as $req) {
             $req->username = $state['username'];
             $req->returnToUrl = $state['returnToUrl'];
         }
         // Step 1: Call the primary again until it succeeds
         $provider = $this->getAuthenticationProvider($state['primary']);
         if (!$provider instanceof PrimaryAuthenticationProvider) {
             // Configuration changed? Force them to start over.
             // @codeCoverageIgnoreStart
             $ret = AuthenticationResponse::newFail(wfMessage('authmanager-link-not-in-progress'));
             $this->callMethodOnProviders(3, 'postAccountLink', [$user, $ret]);
             $session->remove('AuthManager::accountLinkState');
             return $ret;
             // @codeCoverageIgnoreEnd
         }
         $id = $provider->getUniqueId();
         $res = $provider->continuePrimaryAccountLink($user, $reqs);
         switch ($res->status) {
             case AuthenticationResponse::PASS:
                 $this->logger->info("Account linked to {user} by {$id}", ['user' => $user->getName()]);
                 $this->callMethodOnProviders(3, 'postAccountLink', [$user, $res]);
                 $session->remove('AuthManager::accountLinkState');
                 return $res;
             case AuthenticationResponse::FAIL:
                 $this->logger->debug(__METHOD__ . ": Account linking failed by {$id}", ['user' => $user->getName()]);
                 $this->callMethodOnProviders(3, 'postAccountLink', [$user, $res]);
                 $session->remove('AuthManager::accountLinkState');
                 return $res;
             case AuthenticationResponse::REDIRECT:
             case AuthenticationResponse::UI:
                 $this->logger->debug(__METHOD__ . ": Account linking {$res->status} by {$id}", ['user' => $user->getName()]);
                 $state['continueRequests'] = $res->neededRequests;
                 $session->setSecret('AuthManager::accountLinkState', $state);
                 return $res;
             default:
                 throw new \DomainException(get_class($provider) . "::continuePrimaryAccountLink() returned {$res->status}");
         }
     } catch (\Exception $ex) {
         $session->remove('AuthManager::accountLinkState');
         throw $ex;
     }
 }
 public function testAccountCreationEmail()
 {
     $creator = \User::newFromName('Foo');
     $user = self::getMutableTestUser()->getUser();
     $user->setEmail(null);
     $req = TemporaryPasswordAuthenticationRequest::newRandom();
     $req->username = $user->getName();
     $req->mailpassword = true;
     $provider = $this->getProvider(['emailEnabled' => false]);
     $status = $provider->testForAccountCreation($user, $creator, [$req]);
     $this->assertEquals(\StatusValue::newFatal('emaildisabled'), $status);
     $req->hasBackchannel = true;
     $status = $provider->testForAccountCreation($user, $creator, [$req]);
     $this->assertFalse($status->hasMessage('emaildisabled'));
     $req->hasBackchannel = false;
     $provider = $this->getProvider(['emailEnabled' => true]);
     $status = $provider->testForAccountCreation($user, $creator, [$req]);
     $this->assertEquals(\StatusValue::newFatal('noemailcreate'), $status);
     $req->hasBackchannel = true;
     $status = $provider->testForAccountCreation($user, $creator, [$req]);
     $this->assertFalse($status->hasMessage('noemailcreate'));
     $req->hasBackchannel = false;
     $user->setEmail('*****@*****.**');
     $status = $provider->testForAccountCreation($user, $creator, [$req]);
     $this->assertEquals(\StatusValue::newGood(), $status);
     $mailed = false;
     $resetMailer = $this->hookMailer(function ($headers, $to, $from, $subject, $body) use(&$mailed, $req) {
         $mailed = true;
         $this->assertSame('*****@*****.**', $to[0]->address);
         $this->assertContains($req->password, $body);
         return false;
     });
     $expect = AuthenticationResponse::newPass($user->getName());
     $expect->createRequest = clone $req;
     $expect->createRequest->username = $user->getName();
     $res = $provider->beginPrimaryAccountCreation($user, $creator, [$req]);
     $this->assertEquals($expect, $res);
     $this->assertTrue($this->manager->getAuthenticationSessionData('no-email'));
     $this->assertFalse($mailed);
     $this->assertSame('byemail', $provider->finishAccountCreation($user, $creator, $res));
     $this->assertTrue($mailed);
     ScopedCallback::consume($resetMailer);
     $this->assertTrue($mailed);
 }
 public function beginPrimaryAccountCreation($user, $creator, array $reqs)
 {
     if ($this->accountCreationType() === self::TYPE_NONE) {
         throw new \BadMethodCallException('Shouldn\'t call this when accountCreationType() is NONE');
     }
     $req = AuthenticationRequest::getRequestByClass($reqs, $this->requestType);
     if (!$req || $req->username === null || $req->password === null || $this->hasDomain && $req->domain === null) {
         return AuthenticationResponse::newAbstain();
     }
     $username = User::getCanonicalName($req->username, 'usable');
     if ($username === false) {
         return AuthenticationResponse::newAbstain();
     }
     $this->setDomain($req);
     if ($this->auth->addUser($user, $req->password, $user->getEmail(), $user->getRealName())) {
         return AuthenticationResponse::newPass();
     } else {
         return AuthenticationResponse::newFail(new \Message('authmanager-authplugin-create-fail'));
     }
 }