user_repository = $user_repository; $this->principal_service = $principal_service; $this->user_service = $user_service; $this->cache_service = $cache_service; $this->auth_user_service = $auth_user_service; $this->otp_repository = $otp_repository; } /** * @return bool */ public function isUserLogged() { return Auth::check(); } /** * @return User|null */ public function getCurrentUser(): ?User { return Auth::user(); } /** * @param string $username * @param string $password * @param bool $remember_me * @return bool * @throws AuthenticationException */ public function login(string $username, string $password, bool $remember_me): bool { Log::debug("AuthService::login"); $this->last_login_error = ""; if (!Auth::attempt(['username' => $username, 'password' => $password], $remember_me)) { throw new AuthenticationException("We are sorry, your username or password does not match an existing record."); } Log::debug("AuthService::login: clearing principal"); $this->principal_service->clear(); $this->principal_service->register ( $this->getCurrentUser()->getId(), time() ); return true; } /** * @param OAuth2OTP $otpClaim * @param Client|null $client * @return OAuth2OTP|null * @throws AuthenticationException */ public function loginWithOTP(OAuth2OTP $otpClaim, ?Client $client = null): ?OAuth2OTP{ $otp = $this->tx_service->transaction(function() use($otpClaim, $client){ // find first db OTP by connection , by username (email/phone) number and client not redeemed $otp = $this->otp_repository->getByConnectionAndUserNameNotRedeemed ( $otpClaim->getConnection(), $otpClaim->getUserName(), $client ); if(is_null($otp)){ // otp no emitted throw new AuthenticationException("Non existent OTP."); } $otp->logRedeemAttempt(); return $otp; }); return $this->tx_service->transaction(function() use($otp, $otpClaim, $client){ if (!$otp->isAlive()) { throw new AuthenticationException("OTP is expired."); } if (!$otp->isValid()) { throw new AuthenticationException( "OTP is not valid."); } if ($otp->getValue() != $otpClaim->getValue()) { throw new AuthenticationException("OTP mismatch."); } if(!empty($otpClaim->getScope()) && !$otp->allowScope($otpClaim->getScope())) throw new InvalidOTPException("OTP Requested scopes escalates former scopes."); if (($otp->hasClient() && is_null($client)) || ($otp->hasClient() && !is_null($client) && $client->getClientId() != $otp->getClient()->getClientId())) { throw new AuthenticationException("OTP audience mismatch."); } $user = $this->getUserByUsername($otp->getUserName()); if (is_null($user)) { // we need to create a new one ( auto register) Log::debug(sprintf("AuthService::loginWithOTP user %s does not exists ...", $otp->getUserName())); $user = $this->auth_user_service->registerUser([ 'email' => $otp->getUserName(), 'email_verified' => true, ]); } $otp->setAuthTime(time()); $otp->setUserId($user->getId()); $otp->redeem(); Auth::login($user, false); return $otp; }); } public function logout() { $this->invalidateSession(); Auth::logout(); $this->principal_service->clear(); // put in past Cookie::queue ( IAuthService::LOGGED_RELAYING_PARTIES_COOKIE_NAME, null, $minutes = -2628000, $path = Config::get("session.path"), $domain = Config::get("session.domain"), $secure = true, $httpOnly = true, $raw = false, $sameSite = 'none' ); } /** * @return string */ public function getUserAuthorizationResponse() { if (Session::has("openid.authorization.response")) { $value = Session::get("openid.authorization.response"); return $value; } return IAuthService::AuthorizationResponse_None; } public function clearUserAuthorizationResponse() { if (Session::has("openid.authorization.response")) { Session::remove("openid.authorization.response"); Session::save(); } } public function setUserAuthorizationResponse($auth_response) { Session::put("openid.authorization.response", $auth_response); Session::save(); } /** * @param string $openid * @return User|null */ public function getUserByOpenId(string $openid): ?User { return $this->user_repository->getByIdentifier($openid); } /** * @param string $username * @return null|User */ public function getUserByUsername(string $username): ?User { return $this->user_repository->getByEmailOrName($username); } /** * @param int $id * @return null|User */ public function getUserById(int $id): ?User { return $this->user_repository->getById($id); } // Authentication public function getUserAuthenticationResponse() { if (Session::has("openstackid.authentication.response")) { $value = Session::get("openstackid.authentication.response"); return $value; } return IAuthService::AuthenticationResponse_None; } public function setUserAuthenticationResponse($auth_response) { Session::put("openstackid.authentication.response", $auth_response); Session::save(); } public function clearUserAuthenticationResponse() { if (Session::has("openstackid.authentication.response")) { Session::remove("openstackid.authentication.response"); Session::save(); } } /** * @param string $user_id * @return string */ public function unwrapUserId(string $user_id): string { $user = $this->getUserById(intval($user_id)); if (!is_null($user)) return $user_id; $unwrapped_name = $this->decrypt($user_id); $parts = explode(':', $unwrapped_name); return intval($parts[1]); } /** * @param int $user_id * @param IClient $client * @return string */ public function wrapUserId(int $user_id, IClient $client): string { if ($client->getSubjectType() === IClient::SubjectType_Public) return $user_id; $wrapped_name = sprintf('%s:%s', $client->getClientId(), $user_id); return $this->encrypt($wrapped_name); } /** * @param string $value * @return String */ private function encrypt(string $value): string { return base64_encode(Crypt::encrypt($value)); } /** * @param string $value * @return String */ private function decrypt(string $value): string { $value = base64_decode($value); return Crypt::decrypt($value); } /** * @return string */ public function getSessionId(): string { return Session::getId(); } /** * @param $client_id * @return void */ public function registerRPLogin(string $client_id): void { try { $rps = Cookie::get(IAuthService::LOGGED_RELAYING_PARTIES_COOKIE_NAME, ""); $zlib = CompressionAlgorithms_Registry::getInstance()->get(CompressionAlgorithmsNames::ZLib); if (!empty($rps)) { $rps = $this->decrypt($rps); $rps = $zlib->uncompress($rps); $rps .= '|'; } if (is_null($rps)) $rps = ""; if (!str_contains($rps, $client_id)) $rps .= $client_id; $rps = $zlib->compress($rps); $rps = $this->encrypt($rps); } catch (Exception $ex) { Log::warning($ex); $rps = ""; } Cookie::queue ( IAuthService::LOGGED_RELAYING_PARTIES_COOKIE_NAME, $rps, Config::get("session.lifetime", 120), $path = Config::get("session.path"), $domain = Config::get("session.domain"), $secure = true, $httpOnly = true, $raw = false, $sameSite = 'none' ); } /** * @return string[] */ public function getLoggedRPs(): array { $rps = Cookie::get(IAuthService::LOGGED_RELAYING_PARTIES_COOKIE_NAME); $zlib = CompressionAlgorithms_Registry::getInstance()->get(CompressionAlgorithmsNames::ZLib); if (!empty($rps)) { $rps = $this->decrypt($rps); $rps = $zlib->uncompress($rps); return explode('|', $rps); } return []; } /** * @param string $jti * @throws Exception */ public function reloadSession(string $jti): void { Log::debug(sprintf("AuthService::reloadSession jti %s", $jti)); $session_id = $this->cache_service->getSingleValue($jti); Log::debug(sprintf("AuthService::reloadSession session_id %s", $session_id)); if (empty($session_id)) throw new ReloadSessionException('session not found!'); if ($this->cache_service->exists($session_id . "invalid")) { // session was marked as void, check if we are authenticated if (!Auth::check()) throw new ReloadSessionException('user not found!'); } Session::setId(Crypt::decrypt($session_id)); Session::start(); if (!Auth::check()) { $user_id = $this->principal_service->get()->getUserId(); Log::debug(sprintf("AuthService::reloadSession user_id %s", $user_id)); $user = $this->getUserById($user_id); if (is_null($user)) throw new ReloadSessionException('user not found!'); Auth::login($user); } } /** * @param string $client_id * @param int $id_token_lifetime * @return string */ public function generateJTI(string $client_id, int $id_token_lifetime): string { $session_id = Crypt::encrypt(Session::getId()); $encoder = new Base64UrlRepresentation(); $jti = $encoder->encode(hash('sha512', $session_id . $client_id, true)); $this->cache_service->addSingleValue($jti, $session_id, $id_token_lifetime); return $jti; } public function invalidateSession(): void { $session_id = Crypt::encrypt(Session::getId()); $this->cache_service->addSingleValue($session_id . "invalid", $session_id); } }