urlGenerator->generate('app_security_login'); } public function authenticate(Request $request): Passport { $email = trim($request->request->getString('_username')); $passwordPlain = trim($request->request->getString('_password')); // Very lame hashing applied here as required by BPN $password = md5($passwordPlain); $csrfToken = $request->request->getString('_csrf_token'); return new SelfValidatingPassport( new UserBadge($email, function () use ($email, $password, $request) { try { $response = $this->apiClient->getProfile($email, $password); } catch (ApiClientException $e) { return null; } if (false === $response instanceof ProfileResponse) { return null; } // Final checks and local user loading/creation $user = $this->getOrCreateLocalUser($response, $email, $password); if (null === $user) { return null; } // Store BPN password in session for later use $request->getSession()->set('bpn_password', $password); return $user; }), [ new CsrfTokenBadge('authenticate', $csrfToken), new RememberMeBadge(), ] ); } public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response { /** @var User $user */ $user = $token->getUser(); $user->setLastLoginAt(new \DateTimeImmutable()); $this->logger->info('Login'); $this->entityManager->flush(); $route = $user->getDefaultRoute(); if ('app_teamer_index' === $route) { $check = $this->requiredTeamerCheckRegistry->getFirstUnresolvedCheck($user); if (null !== $check) { $route = $check->getRouteName(); } } $url = $this->urlGenerator->generate($route); return new RedirectResponse($url); } private function getOrCreateLocalUser( ProfileResponse $profileResponse, string $email, string $password, ): ?User { // Fetch CRM attributes, early return in case of an API error try { $crmAttributes = $this->apiClient->getCrmAttributes($email, $password); } catch (ApiClientException $e) { return null; } // BusPro answers with a notification record instead of the data on its own errors if (false === $crmAttributes instanceof CrmAttributesResponse) { return null; } // Flatten selected CRM attributes $crmSelections = $crmAttributes->toArray(); // Determine teamer status from CRM attributes $isTeamer = $crmAttributes->isTeamer(); // Everything the CRM grants this person here: pending markers plus ROLE_TEAMER $claimedRoles = $this ->userDataHandler ->collectRoles($crmAttributes) ; // Check if user is already present in local database $user = $this ->userDataHandler ->findLocalUser($profileResponse) ; // BusPro knows this person but grants them nothing in this application, so they // are no user of it: never create an account, block an existing one. Returning // the blocked user lets the UserChecker explain why the login was refused. if ([] === $claimedRoles) { // A response without any attribute group carries no roles either, so it looks // exactly like a revocation while it really means the CRM told us nothing: // an empty payload, a changed schema, a misconfigured attribute id. Blocking // on that would lock out every user logging in, so refuse this single login // instead and leave the account alone. if ([] === ($crmAttributes->getAttributeGroups() ?? [])) { $this->logger->warning('Skip demotion: CRM attributes response carries no attribute groups', [ 'user_id' => $user?->getId(), 'user_email' => $email, ]); return null; } if (null === $user) { return null; } $this->userDataHandler->disableForRevokedCrmRoles($user); return $user; } // Update existing user's teamer data and return it, leaving roles and hotel // codes alone: they are imported once on creation and managed manually after if (null !== $user) { $this ->userDataHandler ->updateLocalUser( $user, $profileResponse, $isTeamer, $crmSelections, $this->userDataHandler->collectPendingRoles($crmAttributes), ) ; return $user; } // Initial import of roles and hotel codes on user creation return $this ->userDataHandler ->createLocalUser( $profileResponse, $claimedRoles, $isTeamer, $crmSelections, $crmAttributes->getHotelCodes(), ) ; } }