%PDF- %PDF-
Direktori : /www/varak.net/nextcloud.varak.net/apps_old/apps/passwords/lib/Services/ |
Current File : //www/varak.net/nextcloud.varak.net/apps_old/apps/passwords/lib/Services/EnvironmentService.php |
<?php /** * This file is part of the Passwords App * created by Marius David Wieschollek * and licensed under the AGPL. */ namespace OCA\Passwords\Services; use Exception; use OC\Authentication\Token\IProvider; use OC\Authentication\Token\IToken; use OC_User; use OCP\IConfig; use OCP\IRequest; use OCP\ISession; use OCP\IUser; use OCP\IUserManager; use OCP\IUserSession; use Throwable; /** * Class EnvironmentService * * @package OCA\Passwords\Services */ class EnvironmentService { const MODE_PUBLIC = 'public'; const MODE_USER = 'user'; const MODE_GLOBAL = 'global'; const TYPE_REQUEST = 'request'; const TYPE_CRON = 'cron'; const TYPE_CLI = 'cli'; const TYPE_MAINTENANCE = 'maintenance'; const LOGIN_NONE = 'none'; const LOGIN_TOKEN = 'token'; const LOGIN_SESSION = 'session'; const LOGIN_PASSWORD = 'password'; const LOGIN_EXTERNAL = 'external'; const CLIENT_MAINTENANCE = 'CLIENT::MAINTENANCE'; const CLIENT_UNKNOWN = 'CLIENT::UNKNOWN'; const CLIENT_SYSTEM = 'CLIENT::SYSTEM'; const CLIENT_PUBLIC = 'CLIENT::PUBLIC'; const CLIENT_CRON = 'CLIENT::CRON'; const CLIENT_CLI = 'CLIENT::CLI'; /** * @var array */ protected static array $protectedClients = [ self::CLIENT_MAINTENANCE, self::CLIENT_CLI, self::CLIENT_CRON, self::CLIENT_PUBLIC, self::CLIENT_SYSTEM, self::CLIENT_UNKNOWN, self::CLIENT_CRON, ]; /** * @var IConfig */ protected IConfig $config; /** * @var LoggingService */ protected LoggingService $logger; /** * @var ISession */ protected ISession $session; /** * @var IRequest */ protected IRequest $request; /** * @var IUserManager */ protected IUserManager $userManager; /** * @var IUserSession */ protected IUserSession $userSession; /** * @var IProvider */ protected IProvider $tokenProvider; /** * @var IUser */ protected IUser $user; /** * @var IUser|null */ protected ?IUser $realUser; /** * @var string */ protected string $password; /** * @var null|string */ protected ?string $userLogin; /** * @var null|IToken */ protected ?IToken $loginToken; /** * @var null|string */ protected ?string $client = self::CLIENT_UNKNOWN; /** * @var bool */ protected bool $impersonating = false; /** * @var string */ protected string $appMode = self::MODE_PUBLIC; /** * @var string */ protected string $runType = self::TYPE_REQUEST; /** * @var string */ protected string $loginType = self::LOGIN_NONE; /** * EnvironmentService constructor. * * @param null|string $userId * @param IConfig $config * @param IRequest $request * @param ISession $session * @param LoggingService $logger * @param IProvider $tokenProvider * @param IUserSession $userSession * @param IUserManager $userManager * * @throws Exception */ public function __construct( ?string $userId, IConfig $config, IRequest $request, ISession $session, LoggingService $logger, IProvider $tokenProvider, IUserSession $userSession, IUserManager $userManager ) { $this->config = $config; $this->logger = $logger; $this->session = $session; $this->request = $request; $this->userManager = $userManager; $this->userSession = $userSession; $this->tokenProvider = $tokenProvider; $this->determineRunType($request); $this->determineAppMode($userId, $request); } /** * @return string */ public function getAppMode(): string { return $this->appMode; } /** * @return string */ public function getRunType(): string { return $this->runType; } /** * @return null|IUser */ public function getUser(): ?IUser { return $this->user ?? null; } /** * @return null|string */ public function getUserId(): ?string { return isset($this->user) ? $this->user->getUID():null; } /** * @return null|string */ public function getUserLogin(): ?string { return $this->userLogin ?? null; } /** * @return null|string */ public function getUserPassword(): ?string { return $this->password ?? null; } /** * @return string */ public function getLoginType(): string { return $this->loginType; } /* * @return IToken|null */ public function getLoginToken(): ?IToken { return $this->loginToken ?? null; } /** * @return bool */ public function isImpersonating(): bool { return $this->impersonating; } /* * @return null|IUser */ public function getRealUser(): ?IUser { return $this->impersonating ? $this->realUser:$this->user; } /* * @return string */ public function getClient(): string { return $this->client; } /** * @return string */ public function getUserAgent(): string { $login = $this->getAppMode() === self::MODE_PUBLIC ? 'public access':$this->getUserLogin(); return $this->getClientFromRequest($this->request, $login); } /* * @return string[] */ public function getProtectedClients(): array { return self::$protectedClients; } /** * @param IRequest $request */ protected function determineRunType(IRequest $request): void { $this->runType = self::TYPE_REQUEST; if($this->isCronJob($request)) { $this->runType = self::TYPE_CRON; $this->client = self::CLIENT_CRON; } else if($this->config->getSystemValue('maintenance', false)) { $this->runType = self::TYPE_MAINTENANCE; $this->client = self::CLIENT_MAINTENANCE; } else if($this->isCliMode($request)) { $this->runType = self::TYPE_CLI; $this->client = self::CLIENT_CLI; } } /** * @param IRequest $request * * @return bool */ protected function isCronJob(IRequest $request): bool { $cronMode = $this->config->getAppValue('core', 'backgroundjobs_mode', 'ajax'); $cronScript = str_ends_with($request->getScriptName(), 'cron.php'); if($cronScript && (in_array($cronMode, ['ajax', 'webcron']) || (PHP_SAPI === 'cli' && $cronMode === 'cron'))) { return true; } try { $requestUri = $request->getPathInfo(); $webroot = $this->config->getSystemValue('overwritewebroot', ''); $webrootLength = strlen($webroot); if($webrootLength !== 0 && substr($requestUri, 0, $webrootLength) === $webroot) { $requestUri = substr($requestUri, $webrootLength); if($requestUri[0] !== '/') $requestUri = '/'.$requestUri; } return $requestUri === '/apps/passwords/cron/sharing'; } catch(Exception $e) { return false; } } /** * @param IRequest $request * * @return bool */ protected function isCliMode(IRequest $request): bool { try { return PHP_SAPI === 'cli' || ( $request->getMethod() === 'POST' && $request->getPathInfo() === '/apps/occweb/cmd' && $this->config->getAppValue('occweb', 'enabled', 'no') === 'yes' ); } catch(Exception $e) { $this->logger->logException($e); } return false; } /** * @param null|string $userId * @param IRequest $request * * @throws Exception */ protected function determineAppMode(?string $userId, IRequest $request): void { $this->appMode = self::MODE_PUBLIC; if($this->runType !== self::TYPE_REQUEST) { $this->appMode = self::MODE_GLOBAL; $this->logger->info('Passwords runs '.($request->getRequestUri() ? $request->getRequestUri():$request->getScriptName()).' in global mode'); } else if($this->isPublicPage($userId, $request)) { $this->appMode = self::MODE_USER; } else if($this->loadUserInformation($userId, $request)) { $this->appMode = self::MODE_USER; } } /** * @param null|string $userId * @param IRequest $request * * @return bool * @throws Exception */ protected function loadUserInformation(?string $userId, IRequest $request): bool { $authHeader = $request->getHeader('Authorization'); $userIdString = $userId ? :'invalid user id'; if($this->session->exists('login_credentials')) { if($this->loadUserFromSession($userId, $request)) return true; $this->logger->warning('Login attempt with invalid session for '.$userIdString); } else if($authHeader !== '') { [$type, $value] = explode(' ', $authHeader, 2); if($type === 'Basic' && $this->loadUserFromBasicAuth($userId, $request)) return true; if($type === 'Bearer' && $this->loadUserFromBearerAuth($userId, $value)) return true; $this->logger->warning('Login attempt with invalid authorization header for '.$userIdString); } else if(isset($_SERVER['PHP_AUTH_USER']) || isset($_SERVER['PHP_AUTH_PW'])) { if($this->loadUserFromBasicAuth($userId, $request)) return true; $this->logger->warning('Login attempt with invalid basic auth for '.$userIdString); } else if($userId !== null) { if($this->loadUserFromSessionToken($userId)) return true; $this->logger->warning('Login attempt with invalid session token for '.$userIdString); } else { $this->client = self::CLIENT_PUBLIC; return false; } $this->client = self::CLIENT_PUBLIC; if($userId !== null) throw new Exception('Unable to verify user '.$userIdString); return false; } /** * @param string|null $userId * @param IRequest $request * * @return bool */ protected function loadUserFromBasicAuth(?string $userId, IRequest $request): bool { if(empty($_SERVER['PHP_AUTH_USER'])) return false; if(empty($_SERVER['PHP_AUTH_PW'])) return false; $loginName = $_SERVER['PHP_AUTH_USER']; try { if($this->userSession->isTokenPassword($_SERVER['PHP_AUTH_PW'])) { return $this->getUserInfoFromToken($_SERVER['PHP_AUTH_PW'], $loginName, $userId); } else { return $this->getUserInfoFromPassword($userId, $request, $loginName, $_SERVER['PHP_AUTH_PW']); } } catch(Exception $e) { $this->logger->logException($e); } return false; } /** * @param string $userId * @param string $value * * @return bool */ protected function loadUserFromBearerAuth(string $userId, string $value): bool { if(empty($value)) return false; try { $token = $this->tokenProvider->getToken($value); $loginUser = $this->userManager->get($token->getUID()); if($loginUser !== null && $loginUser->getUID() === $userId) { $this->user = $loginUser; $this->userLogin = $token->getLoginName(); $this->client = $this->getClientFromToken($token); $this->loginToken = $token; $this->loginType = self::LOGIN_TOKEN; $this->password = $this->tokenProvider->getPassword($token, $token->getId()); return true; } } catch(Exception $e) { $this->logger->logException($e); } return false; } /** * @param null|string $userId * * @return bool */ protected function loadUserFromSessionToken(?string $userId): bool { try { $sessionToken = $this->tokenProvider->getToken($this->session->getId()); $uid = $sessionToken->getUID(); $user = $this->userManager->get($uid); if($user !== null) { if($uid === $userId) { $this->user = $user; $this->userLogin = $sessionToken->getLoginName(); $this->client = $this->getClientFromToken($sessionToken); $this->loginToken = $sessionToken; $this->loginType = self::LOGIN_SESSION; $this->password = $this->tokenProvider->getPassword($sessionToken, $sessionToken->getId()); return true; } else if($this->session->get('oldUserId') === $uid && OC_User::isAdminUser($uid)) { return $this->impersonateByUid($userId, $uid, self::LOGIN_SESSION); } } } catch(Throwable $e) { $this->logger->logException($e); } return false; } /** * @param string|null $userId * @param IRequest $request * * @return bool */ protected function loadUserFromSession(?string $userId, IRequest $request): bool { $loginCredentials = json_decode($this->session->get('login_credentials')); $loginName = $this->session->get('loginname'); $uid = $loginCredentials->uid; if($uid === $userId) { if(isset($loginCredentials->isTokenLogin) && $loginCredentials->isTokenLogin && $this->session->exists('app_password') && !empty($this->session->get('app_password'))) { $tokenId = $this->session->get('app_password'); return $this->getUserInfoFromToken($tokenId, $loginName, $userId, $loginCredentials->password ?? null); } else if(!empty($loginCredentials->password)) { return $this->getUserInfoFromPassword($userId, $request, $loginName, $loginCredentials->password); } else { return $this->getUserInfoFromUserId($userId, $request, $loginName); } } else if($this->session->get('oldUserId') === $uid && OC_User::isAdminUser($uid)) { return $this->impersonateByUid($userId, $uid, self::LOGIN_PASSWORD); } return false; } /** * @param string $tokenId * @param string $loginName * @param string|null $userId * @param string|null $password * * @return bool */ protected function getUserInfoFromToken(string $tokenId, string $loginName, ?string $userId, ?string $password = null): bool { try { $token = $this->tokenProvider->getToken($tokenId); $loginUser = $this->userManager->get($token->getUID()); if($loginUser !== null && $token->getLoginName() === $loginName && ($userId === null || $loginUser->getUID() === $userId)) { $this->user = $loginUser; $this->userLogin = $loginName; $this->password = $token->getPassword() ? $this->tokenProvider->getPassword($token, $tokenId):$password; $this->client = $this->getClientFromToken($token); $this->loginToken = $token; $this->loginType = self::LOGIN_TOKEN; return true; } } catch(Exception $e) { $this->logger->logException($e); } return false; } /** * @param string|null $userId * @param IRequest $request * @param string $loginName * @param string $password * * @return bool */ protected function getUserInfoFromPassword(?string $userId, IRequest $request, string $loginName, string $password): bool { /** @var false|IUser $loginUser */ $loginUser = $this->userManager->checkPasswordNoLogging($loginName, $password); if($loginUser !== false && ($userId === null || $loginUser->getUID() === $userId)) { $this->user = $loginUser; $this->userLogin = $loginName; $this->client = $this->getClientFromRequest($request, $loginName); $this->loginType = self::LOGIN_PASSWORD; $this->password = $password; return true; } return false; } /** * @param string|null $userId * @param IRequest $request * @param string $loginName * * @return bool */ protected function getUserInfoFromUserId(?string $userId, IRequest $request, string $loginName): bool { $loginUser = $this->userManager->get($loginName); if($loginUser !== null && ($userId === null || $loginUser->getUID() === $userId)) { $this->user = $loginUser; $this->userLogin = $loginName; $this->client = $this->getClientFromRequest($request, $loginName); $this->loginType = self::LOGIN_EXTERNAL; return true; } return false; } /** * @param string $uid * @param string $realUid * @param string $loginType * * @return bool */ protected function impersonateByUid(string $uid, string $realUid, string $loginType): bool { $user = $this->userManager->get($uid); if($user !== null) { $realUser = $this->userManager->get($realUid); $this->user = $user; $this->userLogin = $uid; $this->client = $realUser->getDisplayName().' via Impersonate'; $this->loginType = $loginType; $this->impersonating = true; $this->realUser = $realUser; $this->logger->warning(['Detected "%s" impersonating "%s"', $realUser->getDisplayName(), $user->getDisplayName()]); return true; } return false; } /** * @param IRequest $request * @param string $loginName * * @return string */ protected function getClientFromRequest(IRequest $request, string $loginName): string { $client = trim(htmlentities(strip_tags($request->getHeader('USER_AGENT')))); if(empty($client) || in_array($client, self::$protectedClients) || str_contains($client, 'Passwords Session')) { return $loginName.' via '.$request->getRemoteAddress(); } if(strlen($client) > 256) return substr($client, 0, 256); return $client; } /** * @param $token * * @return mixed */ protected function getClientFromToken(IToken $token): string { $client = trim($token->getName()); if(empty($client) || in_array($client, self::$protectedClients)) return $token->getUID().' via Token'; if(strlen($client) > 256) return substr($client, 0, 256); return $client; } protected function isPublicPage(?string $userId, IRequest $request): bool { try { return $request->getMethod() === 'POST' && $request->getPathInfo() === '/settings/personal/changepassword'; } catch(Exception $e) { $this->logger->logException($e); } return false; } }