public function provideAuthentication()
 {
     $user = \User::newFromName('UTSysop');
     $id = $user->getId();
     $name = $user->getName();
     $rememberReq = new RememberMeAuthenticationRequest();
     $rememberReq->action = AuthManager::ACTION_LOGIN;
     $req = $this->getMockForAbstractClass(AuthenticationRequest::class);
     $req->foobar = 'baz';
     $restartResponse = AuthenticationResponse::newRestart($this->message('authmanager-authn-no-local-user'));
     $restartResponse->neededRequests = [$rememberReq];
     $restartResponse2Pass = AuthenticationResponse::newPass(null);
     $restartResponse2Pass->linkRequest = $req;
     $restartResponse2 = AuthenticationResponse::newRestart($this->message('authmanager-authn-no-local-user-link'));
     $restartResponse2->createRequest = new CreateFromLoginAuthenticationRequest(null, [$req->getUniqueId() => $req]);
     $restartResponse2->neededRequests = [$rememberReq, $restartResponse2->createRequest];
     return ['Failure in pre-auth' => [StatusValue::newFatal('fail-from-pre'), [], [], [AuthenticationResponse::newFail($this->message('fail-from-pre')), AuthenticationResponse::newFail($this->message('authmanager-authn-not-in-progress'))]], 'Failure in primary' => [StatusValue::newGood(), $tmp = [AuthenticationResponse::newFail($this->message('fail-from-primary'))], [], $tmp], 'All primary abstain' => [StatusValue::newGood(), [AuthenticationResponse::newAbstain()], [], [AuthenticationResponse::newFail($this->message('authmanager-authn-no-primary'))]], 'Primary UI, then redirect, then fail' => [StatusValue::newGood(), $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' => [StatusValue::newGood(), [$tmp = AuthenticationResponse::newRedirect([$req], '/foo.html', ['foo' => 'bar']), AuthenticationResponse::newAbstain()], [], [$tmp, new \DomainException('MockPrimaryAuthenticationProvider::continuePrimaryAuthentication() returned ABSTAIN')]], 'Primary UI, then pass with no local user' => [StatusValue::newGood(), [$tmp = AuthenticationResponse::newUI([$req], $this->message('...')), AuthenticationResponse::newPass(null)], [], [$tmp, $restartResponse]], 'Primary UI, then pass with no local user (link type)' => [StatusValue::newGood(), [$tmp = AuthenticationResponse::newUI([$req], $this->message('...')), $restartResponse2Pass], [], [$tmp, $restartResponse2], true], 'Primary pass with invalid username' => [StatusValue::newGood(), [AuthenticationResponse::newPass('<>')], [], [new \DomainException('MockPrimaryAuthenticationProvider returned an invalid username: <>')]], 'Secondary fail' => [StatusValue::newGood(), [AuthenticationResponse::newPass($name)], $tmp = [AuthenticationResponse::newFail($this->message('fail-in-secondary'))], $tmp], 'Secondary UI, then abstain' => [StatusValue::newGood(), [AuthenticationResponse::newPass($name)], [$tmp = AuthenticationResponse::newUI([$req], $this->message('...')), AuthenticationResponse::newAbstain()], [$tmp, AuthenticationResponse::newPass($name)]], 'Secondary pass' => [StatusValue::newGood(), [AuthenticationResponse::newPass($name)], [AuthenticationResponse::newPass()], [AuthenticationResponse::newPass($name)]]];
 }
Beispiel #2
0
 /**
  * Continue an authentication flow
  *
  * Return values are interpreted as follows:
  * - status FAIL: Authentication failed. If $response->createRequest is
  *   set, that may be passed to self::beginAuthentication() or to
  *   self::beginAccountCreation() (after adding a username, if necessary)
  *   to preserve state.
  * - status REDIRECT: The client should be redirected to the contained URL,
  *   new AuthenticationRequests should be made (if any), then
  *   AuthManager::continueAuthentication() should be called.
  * - status UI: The client should be presented with a user interface for
  *   the fields in the specified AuthenticationRequests, then new
  *   AuthenticationRequests should be made, then
  *   AuthManager::continueAuthentication() should be called.
  * - status RESTART: The user logged in successfully with a third-party
  *   service, but the third-party credentials aren't attached to any local
  *   account. This could be treated as a UI or a FAIL.
  * - status PASS: Authentication was successful.
  *
  * @param AuthenticationRequest[] $reqs
  * @return AuthenticationResponse
  */
 public function continueAuthentication(array $reqs)
 {
     $session = $this->request->getSession();
     try {
         if (!$session->canSetUser()) {
             // Caller should have called canAuthenticateNow()
             // @codeCoverageIgnoreStart
             throw new \LogicException('Authentication is not possible now');
             // @codeCoverageIgnoreEnd
         }
         $state = $session->getSecret('AuthManager::authnState');
         if (!is_array($state)) {
             return AuthenticationResponse::newFail(wfMessage('authmanager-authn-not-in-progress'));
         }
         $state['continueRequests'] = [];
         $guessUserName = $state['guessUserName'];
         foreach ($reqs as $req) {
             $req->returnToUrl = $state['returnToUrl'];
         }
         // Step 1: Choose an primary authentication provider, and call it until it succeeds.
         if ($state['primary'] === null) {
             // We haven't picked a PrimaryAuthenticationProvider yet
             // @codeCoverageIgnoreStart
             $guessUserName = null;
             foreach ($reqs as $req) {
                 if ($req->username !== null && $req->username !== '') {
                     if ($guessUserName === null) {
                         $guessUserName = $req->username;
                     } elseif ($guessUserName !== $req->username) {
                         $guessUserName = null;
                         break;
                     }
                 }
             }
             $state['guessUserName'] = $guessUserName;
             // @codeCoverageIgnoreEnd
             $state['reqs'] = $reqs;
             foreach ($this->getPrimaryAuthenticationProviders() as $id => $provider) {
                 $res = $provider->beginPrimaryAuthentication($reqs);
                 switch ($res->status) {
                     case AuthenticationResponse::PASS:
                         $state['primary'] = $id;
                         $state['primaryResponse'] = $res;
                         $this->logger->debug("Primary login with {$id} succeeded");
                         break 2;
                     case AuthenticationResponse::FAIL:
                         $this->logger->debug("Login failed in primary authentication by {$id}");
                         if ($res->createRequest || $state['maybeLink']) {
                             $res->createRequest = new CreateFromLoginAuthenticationRequest($res->createRequest, $state['maybeLink']);
                         }
                         $this->callMethodOnProviders(7, 'postAuthentication', [User::newFromName($guessUserName) ?: null, $res]);
                         $session->remove('AuthManager::authnState');
                         \Hooks::run('AuthManagerLoginAuthenticateAudit', [$res, null, $guessUserName]);
                         return $res;
                     case AuthenticationResponse::ABSTAIN:
                         // Continue loop
                         break;
                     case AuthenticationResponse::REDIRECT:
                     case AuthenticationResponse::UI:
                         $this->logger->debug("Primary login with {$id} returned {$res->status}");
                         $state['primary'] = $id;
                         $state['continueRequests'] = $res->neededRequests;
                         $session->setSecret('AuthManager::authnState', $state);
                         return $res;
                         // @codeCoverageIgnoreStart
                     // @codeCoverageIgnoreStart
                     default:
                         throw new \DomainException(get_class($provider) . "::beginPrimaryAuthentication() returned {$res->status}");
                         // @codeCoverageIgnoreEnd
                 }
             }
             if ($state['primary'] === null) {
                 $this->logger->debug('Login failed in primary authentication because no provider accepted');
                 $ret = AuthenticationResponse::newFail(wfMessage('authmanager-authn-no-primary'));
                 $this->callMethodOnProviders(7, 'postAuthentication', [User::newFromName($guessUserName) ?: null, $ret]);
                 $session->remove('AuthManager::authnState');
                 return $ret;
             }
         } elseif ($state['primaryResponse'] === null) {
             $provider = $this->getAuthenticationProvider($state['primary']);
             if (!$provider instanceof PrimaryAuthenticationProvider) {
                 // Configuration changed? Force them to start over.
                 // @codeCoverageIgnoreStart
                 $ret = AuthenticationResponse::newFail(wfMessage('authmanager-authn-not-in-progress'));
                 $this->callMethodOnProviders(7, 'postAuthentication', [User::newFromName($guessUserName) ?: null, $ret]);
                 $session->remove('AuthManager::authnState');
                 return $ret;
                 // @codeCoverageIgnoreEnd
             }
             $id = $provider->getUniqueId();
             $res = $provider->continuePrimaryAuthentication($reqs);
             switch ($res->status) {
                 case AuthenticationResponse::PASS:
                     $state['primaryResponse'] = $res;
                     $this->logger->debug("Primary login with {$id} succeeded");
                     break;
                 case AuthenticationResponse::FAIL:
                     $this->logger->debug("Login failed in primary authentication by {$id}");
                     if ($res->createRequest || $state['maybeLink']) {
                         $res->createRequest = new CreateFromLoginAuthenticationRequest($res->createRequest, $state['maybeLink']);
                     }
                     $this->callMethodOnProviders(7, 'postAuthentication', [User::newFromName($guessUserName) ?: null, $res]);
                     $session->remove('AuthManager::authnState');
                     \Hooks::run('AuthManagerLoginAuthenticateAudit', [$res, null, $guessUserName]);
                     return $res;
                 case AuthenticationResponse::REDIRECT:
                 case AuthenticationResponse::UI:
                     $this->logger->debug("Primary login with {$id} returned {$res->status}");
                     $state['continueRequests'] = $res->neededRequests;
                     $session->setSecret('AuthManager::authnState', $state);
                     return $res;
                 default:
                     throw new \DomainException(get_class($provider) . "::continuePrimaryAuthentication() returned {$res->status}");
             }
         }
         $res = $state['primaryResponse'];
         if ($res->username === null) {
             $provider = $this->getAuthenticationProvider($state['primary']);
             if (!$provider instanceof PrimaryAuthenticationProvider) {
                 // Configuration changed? Force them to start over.
                 // @codeCoverageIgnoreStart
                 $ret = AuthenticationResponse::newFail(wfMessage('authmanager-authn-not-in-progress'));
                 $this->callMethodOnProviders(7, 'postAuthentication', [User::newFromName($guessUserName) ?: null, $ret]);
                 $session->remove('AuthManager::authnState');
                 return $ret;
                 // @codeCoverageIgnoreEnd
             }
             if ($provider->accountCreationType() === PrimaryAuthenticationProvider::TYPE_LINK && $res->linkRequest && $this->getAuthenticationProvider(ConfirmLinkSecondaryAuthenticationProvider::class)) {
                 $state['maybeLink'][$res->linkRequest->getUniqueId()] = $res->linkRequest;
                 $msg = 'authmanager-authn-no-local-user-link';
             } else {
                 $msg = 'authmanager-authn-no-local-user';
             }
             $this->logger->debug("Primary login with {$provider->getUniqueId()} succeeded, but returned no user");
             $ret = AuthenticationResponse::newRestart(wfMessage($msg));
             $ret->neededRequests = $this->getAuthenticationRequestsInternal(self::ACTION_LOGIN, [], $this->getPrimaryAuthenticationProviders() + $this->getSecondaryAuthenticationProviders());
             if ($res->createRequest || $state['maybeLink']) {
                 $ret->createRequest = new CreateFromLoginAuthenticationRequest($res->createRequest, $state['maybeLink']);
                 $ret->neededRequests[] = $ret->createRequest;
             }
             $session->setSecret('AuthManager::authnState', ['reqs' => [], 'primary' => null, 'primaryResponse' => null, 'secondary' => [], 'continueRequests' => $ret->neededRequests] + $state);
             return $ret;
         }
         // Step 2: Primary authentication succeeded, create the User object
         // (and add the user locally if necessary)
         $user = User::newFromName($res->username, 'usable');
         if (!$user) {
             throw new \DomainException(get_class($provider) . " returned an invalid username: {$res->username}");
         }
         if ($user->getId() === 0) {
             // User doesn't exist locally. Create it.
             $this->logger->info('Auto-creating {user} on login', ['user' => $user->getName()]);
             $status = $this->autoCreateUser($user, $state['primary'], false);
             if (!$status->isGood()) {
                 $ret = AuthenticationResponse::newFail(Status::wrap($status)->getMessage('authmanager-authn-autocreate-failed'));
                 $this->callMethodOnProviders(7, 'postAuthentication', [$user, $ret]);
                 $session->remove('AuthManager::authnState');
                 \Hooks::run('AuthManagerLoginAuthenticateAudit', [$ret, $user, $user->getName()]);
                 return $ret;
             }
         }
         // Step 3: Iterate over all the secondary authentication providers.
         $beginReqs = $state['reqs'];
         foreach ($this->getSecondaryAuthenticationProviders() as $id => $provider) {
             if (!isset($state['secondary'][$id])) {
                 // This provider isn't started yet, so we pass it the set
                 // of reqs from beginAuthentication instead of whatever
                 // might have been used by a previous provider in line.
                 $func = 'beginSecondaryAuthentication';
                 $res = $provider->beginSecondaryAuthentication($user, $beginReqs);
             } elseif (!$state['secondary'][$id]) {
                 $func = 'continueSecondaryAuthentication';
                 $res = $provider->continueSecondaryAuthentication($user, $reqs);
             } else {
                 continue;
             }
             switch ($res->status) {
                 case AuthenticationResponse::PASS:
                     $this->logger->debug("Secondary login with {$id} succeeded");
                     // fall through
                 // fall through
                 case AuthenticationResponse::ABSTAIN:
                     $state['secondary'][$id] = true;
                     break;
                 case AuthenticationResponse::FAIL:
                     $this->logger->debug("Login failed in secondary authentication by {$id}");
                     $this->callMethodOnProviders(7, 'postAuthentication', [$user, $res]);
                     $session->remove('AuthManager::authnState');
                     \Hooks::run('AuthManagerLoginAuthenticateAudit', [$res, $user, $user->getName()]);
                     return $res;
                 case AuthenticationResponse::REDIRECT:
                 case AuthenticationResponse::UI:
                     $this->logger->debug("Secondary login with {$id} returned " . $res->status);
                     $state['secondary'][$id] = false;
                     $state['continueRequests'] = $res->neededRequests;
                     $session->setSecret('AuthManager::authnState', $state);
                     return $res;
                     // @codeCoverageIgnoreStart
                 // @codeCoverageIgnoreStart
                 default:
                     throw new \DomainException(get_class($provider) . "::{$func}() returned {$res->status}");
                     // @codeCoverageIgnoreEnd
             }
         }
         // Step 4: Authentication complete! Set the user in the session and
         // clean up.
         $this->logger->info('Login for {user} succeeded', ['user' => $user->getName()]);
         $req = AuthenticationRequest::getRequestByClass($beginReqs, RememberMeAuthenticationRequest::class);
         $this->setSessionDataForUser($user, $req && $req->rememberMe);
         $ret = AuthenticationResponse::newPass($user->getName());
         $this->callMethodOnProviders(7, 'postAuthentication', [$user, $ret]);
         $session->remove('AuthManager::authnState');
         $this->removeAuthenticationSessionData(null);
         \Hooks::run('AuthManagerLoginAuthenticateAudit', [$ret, $user, $user->getName()]);
         return $ret;
     } catch (\Exception $ex) {
         $session->remove('AuthManager::authnState');
         throw $ex;
     }
 }