init ninedocker

This commit is contained in:
2024-07-04 12:42:13 +02:00
commit 0a7777d6e8
194 changed files with 29944 additions and 0 deletions

View File

@ -0,0 +1,1025 @@
<?php
/**
* ownCloud - user_cas
*
* @author Felix Rupp <kontakt@felixrupp.com>
* @copyright Felix Rupp <kontakt@felixrupp.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\UserCAS\Service;
use OC\Authentication\Token\IToken;
use OCA\UserCAS\Exception\PhpCas\PhpUserCasLibraryNotFoundException;
use OCP\App\IAppManager;
use \OCP\IConfig;
use \OCP\IUserSession;
use \OCP\IUserManager;
use \OCP\IURLGenerator;
/**
* Class UserService
*
* @package OCA\UserCAS\Service
*
* @author Felix Rupp <kontakt@felixrupp.com>
* @copyright Felix Rupp <kontakt@felixrupp.com>
*
* @since 1.4.0
*/
class AppService
{
/**
* @var string $appName
*/
private $appName;
/**
* @var \OCP\IConfig $appConfig
*/
private $config;
/**
* @var \OCA\UserCAS\Service\LoggingService
*/
private $loggingService;
/**
* @var \OCP\IUserManager $userManager
*/
private $userManager;
/**
* @var \OCP\IUserSession $userSession
*/
private $userSession;
/**
* @var \OCP\IURLGenerator $urlGenerator
*/
private $urlGenerator;
/**
* @var IAppManager $appManager
*/
private $appManager;
/**
* @var string
*/
private $casVersion;
/**
* @var string
*/
private $casHostname;
/**
* @var int
*/
private $casPort;
/**
* @var string
*/
private $casPath;
/**
* @var string
*/
private $casDebugFile;
/**
* @var string
*/
private $casCertPath;
/**
* @var string
*/
private $casPhpFile;
/**
* @var string
*/
private $casServiceUrl;
/**
* @var boolean
*/
private $casDisableLogout;
/**
* @var boolean
*/
private $casDisableSinglesignout;
/**
* @var array
*/
private $casHandleLogoutServers;
/**
* @var boolean
*/
private $casKeepTicketIds;
/**
* @var string
*/
private $cas_ecas_accepted_strengths;
/**
* @var string
*/
private $cas_ecas_retrieve_groups;
/**
* @var string
*/
private $cas_ecas_assurance_level;
/**
* @var boolean
*/
private $cas_ecas_request_full_userdetails;
/**
* @var string
*/
private $cas_ecas_internal_ip_range;
/**
* @var boolean
*/
private $casInitialized;
/**
* @var boolean
*/
private $ecasAttributeParserEnabled;
/**
* @var boolean
*/
private $casUseProxy;
/**
* UserService constructor.
* @param $appName
* @param \OCP\IConfig $config
* @param \OCA\UserCAS\Service\LoggingService $loggingService
* @param \OCP\IUserManager $userManager
* @param \OCP\IUserSession $userSession
* @param \OCP\IURLGenerator $urlGenerator
* @param IAppManager $appManager
*/
public function __construct($appName, IConfig $config, LoggingService $loggingService, IUserManager $userManager, IUserSession $userSession, IURLGenerator $urlGenerator, IAppManager $appManager)
{
$this->appName = $appName;
$this->config = $config;
$this->loggingService = $loggingService;
$this->userManager = $userManager;
$this->userSession = $userSession;
$this->urlGenerator = $urlGenerator;
$this->appManager = $appManager;
$this->casInitialized = FALSE;
}
/**
* init method.
* @throws PhpUserCasLibraryNotFoundException
*/
public function init()
{
$serverHostName = (isset($_SERVER['SERVER_NAME'])) ? $_SERVER['SERVER_NAME'] : '';
// Gather all app config values
$this->casVersion = $this->config->getAppValue($this->appName, 'cas_server_version', '3.0');
$this->casHostname = $this->config->getAppValue($this->appName, 'cas_server_hostname', $serverHostName);
$this->casPort = intval($this->config->getAppValue($this->appName, 'cas_server_port', 443));
$this->casPath = $this->config->getAppValue($this->appName, 'cas_server_path', '/cas');
$this->casServiceUrl = $this->config->getAppValue($this->appName, 'cas_service_url', '');
$this->casCertPath = $this->config->getAppValue($this->appName, 'cas_cert_path', '');
// Correctly handle cas server path for document root
if ($this->casPath === '/') {
$this->casPath = '';
}
$this->casUseProxy = boolval($this->config->getAppValue($this->appName, 'cas_use_proxy', false));
$this->casDisableLogout = boolval($this->config->getAppValue($this->appName, 'cas_disable_logout', false));
$this->casDisableSinglesignout = boolval($this->config->getAppValue($this->appName, 'cas_disable_singlesignout', false));
$logoutServersArray = explode(",", $this->config->getAppValue($this->appName, 'cas_handlelogout_servers', ''));
$this->casHandleLogoutServers = array();
$this->casKeepTicketIds = boolval($this->config->getAppValue($this->appName, 'cas_keep_ticket_ids', false));
# ECAS
$this->ecasAttributeParserEnabled = boolval($this->config->getAppValue($this->appName, 'cas_ecas_attributeparserenabled', false));
$this->cas_ecas_request_full_userdetails = boolval($this->config->getAppValue($this->appName, 'cas_ecas_request_full_userdetails', false));
$this->cas_ecas_accepted_strengths = $this->config->getAppValue($this->appName, 'cas_ecas_accepted_strengths');
$this->cas_ecas_retrieve_groups = $this->config->getAppValue($this->appName, 'cas_ecas_retrieve_groups');
$this->cas_ecas_assurance_level = $this->config->getAppValue($this->appName, 'cas_ecas_assurance_level');
$this->cas_ecas_internal_ip_range = $this->config->getAppValue($this->appName, 'cas_ecas_internal_ip_range');
foreach ($logoutServersArray as $casHandleLogoutServer) {
$casHandleLogoutServer = ltrim(trim($casHandleLogoutServer));
if (strlen($casHandleLogoutServer) > 4) {
$this->casHandleLogoutServers[] = $casHandleLogoutServer;
}
}
$this->casDebugFile = $this->config->getAppValue($this->appName, 'cas_debug_file', '');
$this->casPhpFile = $this->config->getAppValue($this->appName, 'cas_php_cas_path', '');
if (is_string($this->casPhpFile) && strlen($this->casPhpFile) > 0) {
$this->loggingService->write(\OCA\UserCas\Service\LoggingService::DEBUG, 'Use custom phpCAS file:: ' . $this->casPhpFile);
#\OCP\Util::writeLog('cas', 'Use custom phpCAS file:: ' . $this->casPhpFile, \OCA\UserCas\Service\LoggingService::DEBUG);
if (is_file($this->casPhpFile)) {
require_once("$this->casPhpFile");
} else {
throw new PhpUserCasLibraryNotFoundException('Your custom phpCAS library could not be loaded. The class was not found. Please disable the app with ./occ command or in Database and adjust the path to your library (or remove it to use the shipped library).', 500);
}
} else {
if (is_file(__DIR__ . '/../../vendor/jasig/phpcas/CAS.php')) {
require_once(__DIR__ . '/../../vendor/jasig/phpcas/CAS.php');
} else {
throw new PhpUserCasLibraryNotFoundException('phpCAS library could not be loaded. The class was not found.', 500);
}
}
if (!class_exists('\\phpCAS')) {
$this->loggingService->write(\OCA\UserCas\Service\LoggingService::ERROR, 'phpCAS library could not be loaded. The class was not found.');
throw new PhpUserCasLibraryNotFoundException('phpCAS library could not be loaded. The class was not found.', 500);
}
if (!\phpCAS::isInitialized()) {
try {
\phpCAS::setVerbose(FALSE);
if (!empty($this->casDebugFile)) {
\phpCAS::setDebug($this->casDebugFile);
\phpCAS::setVerbose(TRUE);
}
$serviceBasedUrl = $this->getServiceBasedUrl();
# Initialize client
if ($this->casUseProxy) {
\phpCAS::proxy($this->casVersion, $this->casHostname, (int)($this->casPort), $this->casPath, $serviceBasedUrl);
} else {
\phpCAS::client($this->casVersion, $this->casHostname, (int)($this->casPort), $this->casPath, $serviceBasedUrl);
}
# Handle SingleSignout requests
if (!$this->casDisableSinglesignout) {
\phpCAS::setSingleSignoutCallback([$this, 'casSingleSignOut']);
\phpCAS::handleLogoutRequests(true, $this->casHandleLogoutServers);
}
# Handle fixed service URL
if (!empty($this->casServiceUrl)) {
\phpCAS::setFixedServiceURL($this->casServiceUrl);
}
# Handle certificate
if (!empty($this->casCertPath)) {
\phpCAS::setCasServerCACert($this->casCertPath);
} else {
\phpCAS::setNoCasServerValidation();
}
# Handle keeping of cas-ticket-ids
if ($this->casKeepTicketIds) {
\phpCAS::setNoClearTicketsFromUrl();
}
# Handle ECAS Attributes if enabled
if ($this->ecasAttributeParserEnabled) {
if (is_file(__DIR__ . '/../../vendor/ec-europa/ecas-phpcas-parser/src/EcasPhpCASParser.php')) {
require_once(__DIR__ . '/../../vendor/ec-europa/ecas-phpcas-parser/src/EcasPhpCASParser.php');
} else {
$this->loggingService->write(\OCA\UserCas\Service\LoggingService::ERROR, 'phpCAS EcasPhpCASParser library could not be loaded. The class was not found.');
throw new PhpUserCasLibraryNotFoundException('phpCAS EcasPhpCASParser could not be loaded. The class was not found.', 500);
}
# Register the parser
\phpCAS::setCasAttributeParserCallback(array(new \EcasPhpCASParser\EcasPhpCASParser(), 'parse'));
$this->loggingService->write(\OCA\UserCas\Service\LoggingService::DEBUG, "phpCAS EcasPhpCASParser has been successfully set.");
}
#### Register the new ticket validation url
if ((is_string($this->cas_ecas_retrieve_groups) && strlen($this->cas_ecas_retrieve_groups) > 0)
|| ($this->cas_ecas_request_full_userdetails)
|| (is_string($this->cas_ecas_assurance_level) && strlen($this->cas_ecas_assurance_level) > 0)
|| (is_string($this->cas_ecas_accepted_strengths) && strlen($this->cas_ecas_accepted_strengths) > 0)) {
## Check for external IP Ranges to en-/disable the Two-Factor-Authentication (AcceptedStrength at least MEDIUM)
if ($this->isIpInLocalRange($this->cas_ecas_internal_ip_range) && $this->cas_ecas_accepted_strengths !== '') {
$this->cas_ecas_accepted_strengths = 'BASIC';
$this->loggingService->write(\OCA\UserCas\Service\LoggingService::DEBUG, "phpCAS ECAS AcceptedStrength Level is forced to BASIC, because the user is in the internal network. Test Address: " . $endIp . " | Users Remote Address: " . $remoteAddress);
}
# Add acceptedStrength Querystring Parameters
if (is_string($this->cas_ecas_accepted_strengths) && strlen($this->cas_ecas_accepted_strengths) > 0) {
# Register the new login url
$serverLoginUrl = \phpCAS::getServerLoginURL();
$serverLoginUrl = $this->buildQueryUrl($serverLoginUrl, 'acceptStrengths=' . urlencode($this->cas_ecas_accepted_strengths));
\phpCAS::setServerLoginURL($serverLoginUrl);
$this->loggingService->write(\OCA\UserCas\Service\LoggingService::DEBUG, "phpCAS ECAS strength attribute has been successfully set. New service login URL: " . $serverLoginUrl);
}
## Change validation URL based on ECAS assuranceLevel
$newProtocol = 'http://';
$newUrl = '';
$newSamlUrl = '';
if ($this->getCasPort() === 443) {
$newProtocol = 'https://';
}
if ($this->getCasVersion() === "1.0") {
$newUrl = $newProtocol . $this->getCasHostname() . $this->getCasPath() . '/validate';
} else if ($this->getCasVersion() === "2.0") {
$newUrl = $newProtocol . $this->getCasHostname() . $this->getCasPath() . '/serviceValidate';
} else if ($this->getCasVersion() === "3.0") {
$newUrl = $newProtocol . $this->getCasHostname() . $this->getCasPath() . '/p3/serviceValidate';
} else if ($this->getCasVersion() === "S1") {
$newSamlUrl = $newProtocol . $this->getCasHostname() . $this->getCasPath() . '/samlValidate';
}
if (is_string($this->cas_ecas_assurance_level) && $this->cas_ecas_assurance_level === 'LOW') {
$newUrl = $newProtocol . $this->getCasHostname() . $this->getCasPath() . '/laxValidate';
} else if (is_string($this->cas_ecas_assurance_level) && $this->cas_ecas_assurance_level === 'MEDIUM') {
$newUrl = $newProtocol . $this->getCasHostname() . $this->getCasPath() . '/sponsorValidate';
} else if (is_string($this->cas_ecas_assurance_level) && $this->cas_ecas_assurance_level === 'HIGH') {
$newUrl = $newProtocol . $this->getCasHostname() . $this->getCasPath() . '/interinstitutionalValidate';
} else if (is_string($this->cas_ecas_assurance_level) && $this->cas_ecas_assurance_level === 'TOP') {
$newUrl = $newProtocol . $this->getCasHostname() . $this->getCasPath() . '/strictValidate';
}
if (!empty($this->casServiceUrl)) {
$newUrl = $this->buildQueryUrl($newUrl, 'service=' . urlencode($this->casServiceUrl));
$newSamlUrl = $this->buildQueryUrl($newSamlUrl, 'TARGET=' . urlencode($this->casServiceUrl));
} else {
$newUrl = $this->buildQueryUrl($newUrl, 'service=' . urlencode(\phpCAS::getServiceURL()));
$newSamlUrl = $this->buildQueryUrl($newSamlUrl, 'TARGET=' . urlencode(\phpCAS::getServiceURL()));
}
# Add the groups to retrieve
if (is_string($this->cas_ecas_retrieve_groups) && strlen($this->cas_ecas_retrieve_groups) > 0) {
$newUrl = $this->buildQueryUrl($newUrl, 'groups=' . urlencode($this->cas_ecas_retrieve_groups));
$newSamlUrl = $this->buildQueryUrl($newSamlUrl, 'groups=' . urlencode($this->cas_ecas_retrieve_groups));
}
# Add the requestFullUserDetails flag
if ($this->cas_ecas_request_full_userdetails) {
$newUrl = $this->buildQueryUrl($newUrl, 'userDetails=' . urlencode('true'));
$newSamlUrl = $this->buildQueryUrl($newSamlUrl, 'userDetails=' . urlencode('true'));
}
# Set the user agent to mimic an ecas client
$userAgent = sprintf("ECAS PHP Client (%s, %s)",
'2.1.3',
$_SERVER['SERVER_SOFTWARE']);
\phpCAS::setExtraCurlOption(CURLOPT_USERAGENT, $userAgent);
# Set the new URLs
if ($this->getCasVersion() !== "S1" && !empty($newUrl)) {
\phpCAS::setServerServiceValidateURL($newUrl);
$this->loggingService->write(\OCA\UserCas\Service\LoggingService::DEBUG, "phpCAS ECAS additional attributes have been successfully set. New CAS " . $this->getCasVersion() . " service validate URL: " . $newUrl);
} elseif ($this->getCasVersion() === "S1" && !empty($newSamlUrl)) {
\phpCAS::setServerSamlValidateURL($newSamlUrl);
$this->loggingService->write(\OCA\UserCas\Service\LoggingService::DEBUG, "phpCAS ECAS additional attributes have been successfully set. New SAML 1.0 service validate URL: " . $newSamlUrl);
}
}
$this->casInitialized = TRUE;
$this->loggingService->write(\OCA\UserCas\Service\LoggingService::DEBUG, "phpCAS has been successfully initialized.");
} catch (\CAS_Exception $e) {
\phpCAS::setVerbose(TRUE);
$this->casInitialized = FALSE;
$this->loggingService->write(\OCA\UserCas\Service\LoggingService::ERROR, "phpCAS has thrown an exception with code: " . $e->getCode() . " and message: " . $e->getMessage() . ".");
}
} else {
$this->casInitialized = TRUE;
$this->loggingService->write(\OCA\UserCas\Service\LoggingService::DEBUG, "phpCAS has already been initialized.");
}
}
/**
* Test if the instance is not a Nextcloud instance
*
* @return bool
*/
public function isNotNextcloud()
{
require __DIR__ . '/../../../../version.php';
/**
* @var string $vendor The vendor of this instance
*/
#$this->loggingService->write(\OCA\UserCas\Service\LoggingService::DEBUG, "phpCAS vendor: ".$vendor);
if (strpos(strtolower($vendor), 'next') === FALSE) {
return TRUE;
}
return FALSE;
}
/**
* Check if login should be enforced using user_cas.
*
* @param $remoteAddress
* @param string $requestUri
* @return bool TRUE|FALSE
*/
public function isEnforceAuthentication($remoteAddress, $requestUri)
{
$isEnforced = TRUE;
$forceLoginExceptions = $this->config->getAppValue($this->appName, 'cas_force_login_exceptions', '');
$forceLoginExceptionsArray = explode(',', $forceLoginExceptions);
# Enforce off
if ($this->config->getAppValue($this->appName, 'cas_force_login') !== '1') {
$isEnforced = FALSE;
} else {
# Check enforce IP ranges
foreach ($forceLoginExceptionsArray as $forceLoginException) {
$forceLoginExceptionRanges = explode('-', $forceLoginException);
if (isset($forceLoginExceptionRanges[0])) {
if (isset($forceLoginExceptionRanges[1])) {
$baseIpComponents = explode('.', $forceLoginExceptionRanges[0]);
$baseIp = $baseIpComponents[0] . '.' . $baseIpComponents[1] . '.';
$additionalIpComponents = explode('.', $forceLoginExceptionRanges[1]);
if (isset($additionalIpComponents[1]) && $additionalIpComponents[0]) {
# We have a two part range here (eg. 127.0.0.1-1.19) which means, we have to cover 127.0.0.1-127.0.0.254 and 127.0.1.1-127.0.1.19
for ($ipThirdPart = intval($baseIpComponents[2]); $ipThirdPart <= intval($additionalIpComponents[0]); $ipThirdPart++) {
if ($ipThirdPart !== intval($additionalIpComponents[0])) {
$ipFourthPartMax = 254;
} else {
$ipFourthPartMax = intval($additionalIpComponents[1]);
}
for ($ipFourthPart = intval($baseIpComponents[3]); $ipFourthPart <= $ipFourthPartMax; $ipFourthPart++) {
$endIp = $baseIp . $ipThirdPart . '.' . $ipFourthPart;
#$this->loggingService->write(\OCA\UserCas\Service\LoggingService::DEBUG, "phpCAS Enforce Login IP checked: " . $endIp);
if ($remoteAddress === $endIp) {
$isEnforced = FALSE;
$this->loggingService->write(\OCA\UserCas\Service\LoggingService::DEBUG, "phpCAS Enforce Login NOT triggered. Test Address: " . $endIp . " | Users Remote Address: " . $remoteAddress);
}
}
}
} elseif ($additionalIpComponents[0]) {
# We have a one part range here (eg. 127.0.0.1-19)
$newIp = $baseIp . $baseIpComponents[2] . '.';
for ($ipFourthPart = intval($baseIpComponents[3]); $ipFourthPart <= intval($additionalIpComponents[0]); $ipFourthPart++) {
$endIp = $newIp . $ipFourthPart;
if ($remoteAddress === $endIp) {
$isEnforced = FALSE;
$this->loggingService->write(\OCA\UserCas\Service\LoggingService::DEBUG, "phpCAS Enforce Login NOT triggered. Test Address: " . $endIp . " | Users Remote Address: " . $remoteAddress);
}
}
}
} else {
# Single IP-Adress given
if ($remoteAddress === $forceLoginExceptionRanges[0]) {
$isEnforced = FALSE;
$this->loggingService->write(\OCA\UserCas\Service\LoggingService::DEBUG, "phpCAS Enforce Login NOT triggered. Test Address: " . $forceLoginExceptionRanges[0] . " | Users Remote Address: " . $remoteAddress);
}
}
}
}
}
# User already logged in
if ($this->userSession->isLoggedIn()) {
$isEnforced = FALSE;
}
# Disable on Nextcloud login-flow use
if (!$this->isNotNextcloud() && strpos($requestUri, "/login/flow") !== FALSE) {
$isEnforced = FALSE;
}
return $isEnforced;
}
/**
* Check if public shares should be protected
*
* @return bool TRUE|FALSE
*/
public function arePublicSharesProtected()
{
$protected = (bool)$this->config->getAppValue($this->appName, 'cas_shares_protected', FALSE);
$loggedIn = $this->userSession->isLoggedIn();
if($loggedIn && $protected) {
$protected = FALSE;
}
return $protected;
}
/**
* Register Login
*
*/
public function registerLogIn()
{
$loginButtonLabel = $this->config->getAppValue($this->appName, 'cas_login_button_label', 'CAS Login');
// Login Button handling
if (strlen($loginButtonLabel) <= 0) {
$loginButtonLabel = 'CAS Login';
}
$this->unregisterLogin();
if ($this->isNotNextcloud()) {
/** @var array $loginAlternatives */
/*$loginAlternatives = $this->config->getSystemValue('login.alternatives', []);
$loginAlreadyRegistered = FALSE;
foreach ($loginAlternatives as $key => $loginAlternative) {
if (isset($loginAlternative['name']) && $loginAlternative['name'] === $loginButtonLabel) {
$loginAlternatives[$key]['href'] = $this->linkToRoute($this->appName . '.authentication.casLogin');
$this->config->setSystemValue('login.alternatives', $loginAlternatives);
$loginAlreadyRegistered = TRUE;
}
}
if (!$loginAlreadyRegistered) {*/
$loginAlternatives[] = ['href' => $this->linkToRoute($this->appName . '.authentication.casLogin'), 'name' => $loginButtonLabel, 'img' => $this->appManager->getAppWebPath($this->appName).'/img/cas-logo.png'];
$this->config->setSystemValue('login.alternatives', $loginAlternatives);
#}
} else {
#$this->loggingService->write(\OCA\UserCas\Service\LoggingService::DEBUG, "phpCAS Nextcloud " . $version[0] . "." . $version[1] . "." . $version[2] . "." . " detected.");
\OC_App::registerLogIn(array('href' => $this->linkToRoute($this->appName . '.authentication.casLogin'), 'name' => $loginButtonLabel));
}
}
/**
* UnregisterLogin
*/
public function unregisterLogin()
{
$loginButtonLabel = $this->config->getAppValue($this->appName, 'cas_login_button_label', 'CAS Login');
// Login Button handling
if (strlen($loginButtonLabel) <= 0) {
$loginButtonLabel = 'CAS Login';
}
if ($this->isNotNextcloud()) {
$loginAlternatives = $this->config->getSystemValue('login.alternatives', []);
foreach ($loginAlternatives as $key => $loginAlternative) {
if (isset($loginAlternative['name']) && ($loginAlternative['name'] === $loginButtonLabel || $loginAlternative['name'] === 'CAS Login')) {
unset($loginAlternatives[$key]);
}
}
$this->config->setSystemValue('login.alternatives', $loginAlternatives);
}
}
/**
* @return bool
*/
public function isSetupValid()
{
$casHostname = $this->config->getAppValue($this->appName, 'cas_server_hostname');
$casPort = intval($this->config->getAppValue($this->appName, 'cas_server_port'));
$casPath = $this->config->getAppValue($this->appName, 'cas_server_path');
if (is_string($casHostname) && strlen($casHostname) > 1 && is_int($casPort) && $casPort > 1 && is_string($casPath) && strpos($casPath, "/") === 0) {
return TRUE;
}
return FALSE;
}
/**
* Create a link to $route with URLGenerator.
*
* @param string $route
* @param array $arguments
* @return string
*/
public function linkToRoute($route, $arguments = array())
{
return $this->urlGenerator->linkToRoute($route, $arguments);
}
/**
* Create an absolute link to $route with URLGenerator.
*
* @param string $route
* @param array $arguments
* @return string
*/
public function linkToRouteAbsolute($route, $arguments = array())
{
return $this->urlGenerator->linkToRouteAbsolute($route, $arguments);
}
/**
* Create an url relative to owncloud host
*
* @param string $url
* @return mixed
*/
public function getAbsoluteURL($url)
{
return $this->urlGenerator->getAbsoluteURL($url);
}
/**
* @return boolean
*/
public function isCasInitialized()
{
return $this->casInitialized;
}
/**
* This method is used to append query parameters to an url. Since the url
* might already contain parameter it has to be detected and to build a proper
* URL
*
* @param string $url base url to add the query params to
* @param string $query params in query form with & separated
*
* @return string url with query params
*/
private function buildQueryUrl($url, $query)
{
$url .= (strstr($url, '?') === false) ? '?' : '&';
$url .= $query;
return $url;
}
/**
* Test if the clients IP adress is in our local range
*
* @param string|array $internalIps
* @return bool TRUE|FALSE
*/
private function isIpInLocalRange($internalIps)
{
if (is_string($internalIps)) {
$internalIps = explode(',', $internalIps);
}
$remoteAddress = $_SERVER['REMOTE_ADDR'];
$proxyHeader = "HTTP_X_FORWARDED_FOR";
# Header can contain multiple IP-s of proxies that are passed through.
# Only the IP added by the last proxy (last IP in the list) can be trusted.
if (array_key_exists($proxyHeader, $_SERVER)) {
$explodedProxyHeader = explode(",", $_SERVER[$proxyHeader]);
$proxyIp = trim(end($explodedProxyHeader));
if (filter_var($proxyIp, FILTER_VALIDATE_IP)) {
$remoteAddress = $proxyIp;
}
}
# Check enforce IP ranges for acceptedStrength attribute
foreach ($internalIps as $internalIp) {
$internalIpRanges = explode('-', $internalIp);
if (isset($internalIpRanges[0])) {
if (isset($internalIpRanges[1])) {
$baseIpComponents = explode('.', $internalIpRanges[0]);
$baseIp = $baseIpComponents[0] . '.' . $baseIpComponents[1] . '.';
$additionalIpComponents = explode('.', $internalIpRanges[1]);
if (isset($additionalIpComponents[1]) && $additionalIpComponents[0]) {
# We have a two part range here (eg. 127.0.0.1-1.19) which means, we have to cover 127.0.0.1-127.0.0.254 and 127.0.1.1-127.0.1.19
for ($ipThirdPart = intval($baseIpComponents[2]); $ipThirdPart <= intval($additionalIpComponents[0]); $ipThirdPart++) {
if ($ipThirdPart !== intval($additionalIpComponents[0])) {
$ipFourthPartMax = 254;
} else {
$ipFourthPartMax = intval($additionalIpComponents[1]);
}
for ($ipFourthPart = intval($baseIpComponents[3]); $ipFourthPart <= $ipFourthPartMax; $ipFourthPart++) {
$endIp = $baseIp . $ipThirdPart . '.' . $ipFourthPart;
#$this->loggingService->write(\OCA\UserCas\Service\LoggingService::DEBUG, "phpCAS Enforce Login IP checked: " . $endIp);
if ($remoteAddress === $endIp) {
return TRUE;
/*if ($this->cas_ecas_accepted_strengths !== '') {
$this->cas_ecas_accepted_strengths = 'BASIC';
$this->loggingService->write(\OCA\UserCas\Service\LoggingService::DEBUG, "phpCAS ECAS AcceptedStrength Level is forced to BASIC, because the user is in the internal network. Test Address: " . $endIp . " | Users Remote Address: " . $remoteAddress);
}*/
}
}
}
} elseif ($additionalIpComponents[0]) {
# We have a one part range here (eg. 127.0.0.1-19)
$newIp = $baseIp . $baseIpComponents[2] . '.';
for ($ipFourthPart = intval($baseIpComponents[3]); $ipFourthPart <= intval($additionalIpComponents[0]); $ipFourthPart++) {
$endIp = $newIp . $ipFourthPart;
if ($remoteAddress === $endIp) {
return TRUE;
/*if ($this->cas_ecas_accepted_strengths !== '') {
$this->cas_ecas_accepted_strengths = 'BASIC';
$this->loggingService->write(\OCA\UserCas\Service\LoggingService::DEBUG, "phpCAS ECAS AcceptedStrength is forced to BASIC, because the user is in the internal network. Test Address: " . $endIp . " | Users Remote Address: " . $remoteAddress);
}*/
}
}
}
} else {
# Single IP-Adress given
if ($remoteAddress === $internalIpRanges[0]) {
return TRUE;
/*if ($this->cas_ecas_accepted_strengths !== '') {
$this->cas_ecas_accepted_strengths = 'BASIC';
$this->loggingService->write(\OCA\UserCas\Service\LoggingService::DEBUG, "phpCAS ECAS AcceptedStrength is forced to BASIC, because the user is in the internal network. Test Address: " . $internalIpRanges[0] . " | Users Remote Address: " . $remoteAddress);
}*/
}
}
}
}
return FALSE;
}
/**
* Callback function for CAS singleSignOut call
*
* @author Vincent <https://github.com/pingou2712>
*
* @param string $ticket Ticket Object
*/
public function casSingleSignOut($ticket)
{
// Extract the userID from the SAML Request
$decodedLogoutRequest = urldecode($_POST['logoutRequest']);
preg_match(
"|<saml:NameID[^>]*>(.*)</saml:NameID>|",
$decodedLogoutRequest, $tick, PREG_OFFSET_CAPTURE, 3
);
$wrappedSamlNameId = preg_replace(
'|<saml:NameID[^>]*>|', '', $tick[0][0]
);
$nameId = preg_replace(
'|</saml:NameID>|', '', $wrappedSamlNameId
);
//Kill Session Of UserID:
$this->killSessionUserName($nameId);
}
/**
* Kill the username's session.
*
* @author Vincent <https://github.com/pingou2712>
* @author Felix Rupp <kontakt@felixrupp.com>
*
* @param string $username The username of the user.
* @return NULL
*/
private function killSessionUserName($username)
{
if ($this->userManager->userExists($username)) {
$tokenType = IToken::TEMPORARY_TOKEN;
$sql = "DELETE FROM oc_authtoken WHERE uid = ? AND type = ? AND password IS NULL;";
$stmt = \OC::$server->getDatabaseConnection()->prepare($sql);
$stmt->bindParam(1, $username, \PDO::PARAM_STR);
$stmt->bindParam(2, $tokenType, \PDO::PARAM_INT);
$stmt->execute();
}
return NULL;
}
## Setters/Getters
/**
* @return string
*/
public function getAppName()
{
return $this->appName;
}
/**
* @return string
*/
public function getCasVersion()
{
return $this->casVersion;
}
/**
* @return string
*/
public function getCasHostname()
{
return $this->casHostname;
}
/**
* @return int
*/
public function getCasPort()
{
return $this->casPort;
}
/**
* @return string
*/
public function getCasPath()
{
return $this->casPath;
}
private function getServiceBasedUrl(): string {
$overwrite = \OC::$server->getConfig()->getSystemValue('overwrite.cli.url');
if($overwrite) return $overwrite;
$httpProtocol = \OC::$server->getConfig()->getSystemValue('protocol');
$currentUrl = $_SERVER['SERVER_NAME'];
return $httpProtocol . $currentUrl;
}
}

View File

@ -0,0 +1,477 @@
<?php
namespace OCA\UserCAS\Service\Import;
use OCA\UserCAS\Service\Merge\AdUserMerger;
use OCA\UserCAS\Service\Merge\MergerInterface;
use OCP\IConfig;
use Psr\Log\LoggerInterface;
/**
* Class AdImporter
* @package OCA\UserCAS\Service\Import
*
* @author Felix Rupp <kontakt@felixrupp.com>
* @copyright Felix Rupp
*
* @since 1.0.0
*/
class AdImporter implements ImporterInterface
{
/**
* @var boolean|resource
*/
private $ldapConnection;
/**
* @var MergerInterface $merger
*/
private $merger;
/**
* @var LoggerInterface $logger
*/
private $logger;
/**
* @var IConfig
*/
private $config;
/**
* @var string $appName
*/
private $appName = 'user_cas';
/**
* AdImporter constructor.
* @param IConfig $config
*/
public function __construct(IConfig $config)
{
$this->config = $config;
}
/**
* @param LoggerInterface $logger
*
* @throws \Exception
*/
public function init(LoggerInterface $logger)
{
$this->merger = new AdUserMerger($logger);
$this->logger = $logger;
$this->ldapConnect();
$this->ldapBind();
$this->logger->info("Init complete.");
}
/**
* @throws \Exception
*/
public function close()
{
$this->ldapClose();
}
/**
* Get User data
*
* @return array User data
*/
public function getUsers()
{
$uidAttribute = $this->config->getAppValue($this->appName, 'cas_import_map_uid');
$displayNameAttribute1 = $this->config->getAppValue($this->appName, 'cas_import_map_displayname');
$displayNameAttribute2 = '';
if (strpos($displayNameAttribute1, "+") !== FALSE) {
$displayNameAttributes = explode("+", $displayNameAttribute1);
$displayNameAttribute1 = $displayNameAttributes[0];
$displayNameAttribute2 = $displayNameAttributes[1];
}
$emailAttribute = $this->config->getAppValue($this->appName, 'cas_import_map_email');
$groupsAttribute = $this->config->getAppValue($this->appName, 'cas_import_map_groups');
$quotaAttribute = $this->config->getAppValue($this->appName, 'cas_import_map_quota');
$enableAttribute = $this->config->getAppValue($this->appName, 'cas_import_map_enabled');
$dnAttribute = $this->config->getAppValue($this->appName, 'cas_import_map_dn');
$mergeAttribute = boolval($this->config->getAppValue($this->appName, 'cas_import_merge'));
$primaryAccountDnStartswWith = $this->config->getAppValue($this->appName, 'cas_import_map_dn_filter');
$preferEnabledAccountsOverDisabled = boolval($this->config->getAppValue($this->appName, 'cas_import_merge_enabled'));
$andEnableAttributeBitwise = $this->config->getAppValue($this->appName, 'cas_import_map_enabled_and_bitwise');
$keep = [$uidAttribute, $displayNameAttribute1, $displayNameAttribute2, $emailAttribute, $groupsAttribute, $quotaAttribute, $enableAttribute, $dnAttribute];
$groupAttrField = $this->config->getAppValue($this->appName, 'cas_import_map_groups_description');
$groupsKeep = [$groupAttrField];
$pageSize = $this->config->getAppValue($this->appName, 'cas_import_ad_sync_pagesize');
$users = [];
$this->logger->info("Getting all users from the AD …");
# Get all members of the sync group
$memberPages = $this->getLdapList($this->config->getAppValue($this->appName, 'cas_import_ad_base_dn'), $this->config->getAppValue($this->appName, 'cas_import_ad_sync_filter'), $keep, $pageSize);
foreach ($memberPages as $memberPage) {
#var_dump($memberPage["count"]);
for ($key = 0; $key < $memberPage["count"]; $key++) {
$m = $memberPage[$key];
# Each attribute is returned as an array, the first key is [count], [0]+ will contain the actual value(s)
$employeeID = isset($m[$uidAttribute][0]) ? $m[$uidAttribute][0] : "";
$mail = isset($m[$emailAttribute][0]) ? $m[$emailAttribute][0] : "";
$dn = isset($m[$dnAttribute]) ? $m[$dnAttribute] : "";
$displayName = $employeeID;
if (isset($m[$displayNameAttribute1][0])) {
$displayName = $m[$displayNameAttribute1][0];
if (strlen($displayNameAttribute2) > 0 && isset($m[$displayNameAttribute2][0])) {
$displayName .= " " . $m[$displayNameAttribute2][0];
}
} else {
if (strlen($displayNameAttribute2) > 0 && isset($m[$displayNameAttribute2][0])) {
$displayName = $m[$displayNameAttribute2][0];
}
}
$quota = isset($m[$quotaAttribute][0]) ? intval($m[$quotaAttribute][0]) : 0;
$enable = 1;
# Shift enable attribute bytewise?
if (isset($m[$enableAttribute][0])) {
if (strlen($andEnableAttributeBitwise) > 0) {
if (is_numeric($andEnableAttributeBitwise)) {
$andEnableAttributeBitwise = intval($andEnableAttributeBitwise);
}
$enable = intval((intval($m[$enableAttribute][0]) & $andEnableAttributeBitwise) == 0);
} else {
$enable = intval($m[$enableAttribute][0]);
}
}
$groupsArray = [];
$addUser = FALSE;
if (isset($m[$groupsAttribute][0])) {
# Cycle all groups of the user
for ($j = 0; $j < $m[$groupsAttribute]["count"]; $j++) {
# Check if user has MAP_GROUPS attribute
if (isset($m[$groupsAttribute][$j])) {
$addUser = TRUE; # Only add user if the group has a MAP_GROUPS attribute
$groupCn = $m[$groupsAttribute][$j];
# Retrieve the MAP_GROUPS_FIELD attribute of the group
$groupAttr = $this->getLdapAttributes($groupCn, $groupsKeep);
$groupName = '';
if (isset($groupAttr[$groupAttrField][0])) {
$groupName = $groupAttr[$groupAttrField][0];
/*# Replace umlauts
if (boolval($this->config->getAppValue($this->appName, 'cas_import_map_groups_letter_umlauts'))) {
$groupName = str_replace("Ä", "Ae", $groupName);
$groupName = str_replace("Ö", "Oe", $groupName);
$groupName = str_replace("Ü", "Ue", $groupName);
$groupName = str_replace("ä", "ae", $groupName);
$groupName = str_replace("ö", "oe", $groupName);
$groupName = str_replace("ü", "ue", $groupName);
$groupName = str_replace("ß", "ss", $groupName);
}
# Filter unwanted characters
$nameFilter = $this->config->getAppValue($this->appName, 'cas_import_map_groups_letter_filter');
if (strlen($nameFilter) > 0) {
$groupName = preg_replace("/[^" . $nameFilter . "]+/", "", $groupName);
}
# Filter length to max 64 chars
$groupName = substr($groupName, 0, 64);*/
}
else {
$groupCnArray = explode(",", $groupCn);
$groupName = substr($groupCnArray[0], 3, strlen($groupCnArray[0]));
}
if (strlen($groupName) > 0) {
$groupsArray[] = $groupName;
}
}
}
}
# Fill the users array only if we have an employeeId and addUser is true
if (isset($employeeID) && $addUser) {
$this->merger->mergeUsers($users, ['uid' => $employeeID, 'displayName' => $displayName, 'email' => $mail, 'quota' => $quota, 'groups' => $groupsArray, 'enable' => $enable, 'dn' => $dn], $mergeAttribute, $preferEnabledAccountsOverDisabled, $primaryAccountDnStartswWith);
}
}
}
$this->logger->info("Users have been retrieved.");
return $users;
}
/**
* List ldap entries in the base dn
*
* @param string $object_dn
* @param $filter
* @param array $keepAtributes
* @param $pageSize
* @return array
*/
protected function getLdapList($object_dn, $filter, $keepAtributes, $pageSize)
{
$cookie = '';
$members = [];
do {
// Query Group members
ldap_control_paged_result($this->ldapConnection, $pageSize, false, $cookie);
$results = ldap_search($this->ldapConnection, $object_dn, $filter, $keepAtributes/*, array("member;range=$range_start-$range_end")*/) or die('Error searching LDAP: ' . ldap_error($this->ldapConnection));
$members[] = ldap_get_entries($this->ldapConnection, $results);
ldap_control_paged_result_response($this->ldapConnection, $results, $cookie);
} while ($cookie !== null && $cookie != '');
// Return sorted member list
sort($members);
return $members;
}
/**
* @param string $user_dn
* @param bool $keep
* @return array Attribute list
*/
protected function getLdapAttributes($user_dn, $keep = false)
{
if (!isset($this->ldapConnection)) die('Error, no LDAP connection established');
if (empty($user_dn)) die('Error, no LDAP user specified');
// Disable pagination setting, not needed for individual attribute queries
ldap_control_paged_result($this->ldapConnection, 1);
// Query user attributes
$results = (($keep) ? ldap_search($this->ldapConnection, $user_dn, 'cn=*', $keep) : ldap_search($this->ldapConnection, $user_dn, 'cn=*'))
or die('Error searching LDAP: ' . ldap_error($this->ldapConnection));
$attributes = ldap_get_entries($this->ldapConnection, $results);
$this->logger->debug("AD attributes successfully retrieved.");
// Return attributes list
if (isset($attributes[0])) return $attributes[0];
else return array();
}
/**
* Connect ldap
*
* @return bool|resource
* @throws \Exception
*/
protected function ldapConnect()
{
try {
$host = $this->config->getAppValue($this->appName, 'cas_import_ad_host');
$this->ldapConnection = ldap_connect($this->config->getAppValue($this->appName, 'cas_import_ad_protocol') . $host . ":" . $this->config->getAppValue($this->appName, 'cas_import_ad_port')) or die("Could not connect to " . $host);
ldap_set_option($this->ldapConnection, LDAP_OPT_PROTOCOL_VERSION, 3);
ldap_set_option($this->ldapConnection, LDAP_OPT_REFERRALS, 0);
ldap_set_option($this->ldapConnection, LDAP_OPT_NETWORK_TIMEOUT, 10);
$this->logger->info("AD connected successfully.");
return $this->ldapConnection;
} catch (\Exception $e) {
throw $e;
}
}
/**
* Bind ldap
*
* @throws \Exception
*/
protected function ldapBind()
{
try {
if ($this->ldapConnection) {
$ldapIsBound = ldap_bind($this->ldapConnection, $this->config->getAppValue($this->appName, 'cas_import_ad_user') . "@" . $this->config->getAppValue($this->appName, 'cas_import_ad_domain'), $this->config->getAppValue($this->appName, 'cas_import_ad_password'));
if (!$ldapIsBound) {
throw new \Exception("LDAP bind failed. Error: " . ldap_error($this->ldapConnection));
} else {
$this->logger->info("AD bound successfully.");
}
}
} catch (\Exception $e) {
throw $e;
}
}
/**
* Unbind ldap
*
* @throws \Exception
*/
protected function ldapUnbind()
{
try {
ldap_unbind($this->ldapConnection);
$this->logger->info("AD unbound successfully.");
} catch (\Exception $e) {
throw $e;
}
}
/**
* Close ldap connection
*
* @throws \Exception
*/
protected function ldapClose()
{
try {
ldap_close($this->ldapConnection);
$this->logger->info("AD connection closed successfully.");
} catch (\Exception $e) {
throw $e;
}
}
/**
* @param array $exportData
*/
public function exportAsCsv(array $exportData)
{
$this->logger->info("Exporting users to .csv …");
$fp = fopen('accounts.csv', 'wa+');
fputcsv($fp, ["UID", "displayName", "email", "quota", "groups", "enabled"]);
foreach ($exportData as $fields) {
for ($i = 0; $i < count($fields); $i++) {
if (is_array($fields[$i])) {
$fields[$i] = $this->multiImplode($fields[$i], " ");
}
}
fputcsv($fp, $fields);
}
fclose($fp);
$this->logger->info("CSV export finished.");
}
/**
* @param array $exportData
*/
public function exportAsText(array $exportData)
{
$this->logger->info("Exporting users to .txt …");
file_put_contents('accounts.txt', serialize($exportData));
$this->logger->info("TXT export finished.");
}
/**
* @param array $array
* @param string $glue
* @return bool|string
*/
private function multiImplode($array, $glue)
{
$ret = '';
foreach ($array as $item) {
if (is_array($item)) {
$ret .= $this->multiImplode($item, $glue) . $glue;
} else {
$ret .= $item . $glue;
}
}
$ret = substr($ret, 0, 0 - strlen($glue));
return $ret;
}
}

View File

@ -0,0 +1,34 @@
<?php
namespace OCA\UserCAS\Service\Import;
use Psr\Log\LoggerInterface;
/**
* Interface ImporterInterface
* @package OCA\UserCAS\Service\Import
*
* @author Felix Rupp <kontakt@felixrupp.com>
* @copyright Felix Rupp
*
* @since 1.0.0
*/
interface ImporterInterface
{
/**
* @param LoggerInterface $logger
*/
public function init(LoggerInterface $logger);
public function close();
public function getUsers();
/**
* @param array $userData
*/
public function exportAsCsv(array $userData);
}

View File

@ -0,0 +1,101 @@
<?php
/**
* ownCloud - user_cas
*
* @author Felix Rupp <kontakt@felixrupp.com>
* @copyright Felix Rupp <kontakt@felixrupp.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\UserCAS\Service;
use \OCP\IConfig;
use \OCP\ILogger;
/**
* Class LoggingService
*
* @package OCA\UserCAS\Service
*
* @author Felix Rupp <kontakt@felixrupp.com>
* @copyright Felix Rupp <kontakt@felixrupp.com>
*
* @since 1.5.0
*/
class LoggingService
{
/**
* @since 1.6.1
*/
const DEBUG = 0;
/**
* @since 1.6.1
*/
const INFO = 1;
/**
* @since 1.6.1
*/
const WARN = 2;
/**
* @since 1.6.1
*/
const ERROR = 3;
/**
* @since 1.6.1
*/
const FATAL = 4;
/**
* @var string $appName
*/
private $appName;
/**
* @var \OCP\IConfig $appConfig
*/
private $config;
/**
* @var \OCP\ILogger $logger
*/
private $logger;
/**
* LoggingService constructor.
* @param string $appName
* @param \OCP\IConfig $config
* @param \OCP\ILogger $logger
*/
public function __construct($appName, IConfig $config, ILogger $logger)
{
$this->appName = $appName;
$this->config = $config;
$this->logger = $logger;
}
/**
* @param mixed $level
* @param string $message
*/
public function write($level, $message)
{
$this->logger->log($level, $message, ['app' => $this->appName]);
}
}

View File

@ -0,0 +1,89 @@
<?php
namespace OCA\UserCAS\Service\Merge;
use Psr\Log\LoggerInterface;
/**
* Class AdUserMerger
* @package OCA\UserCAS\Service\Merge
*
* @author Felix Rupp <kontakt@felixrupp.com>
* @copyright Felix Rupp
*
* @since 1.0.0
*/
class AdUserMerger implements MergerInterface
{
/**
* @var LoggerInterface
*/
protected $logger;
/**
* AdUserMerger constructor.
* @param LoggerInterface $logger
*/
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
/**
* Merge users method
*
* @param array $userStack
* @param array $userToMerge
* @param bool $merge
* @param bool $preferEnabledAccountsOverDisabled
* @param string $primaryAccountDnStartswWith
*/
public function mergeUsers(array &$userStack, array $userToMerge, $merge, $preferEnabledAccountsOverDisabled, $primaryAccountDnStartswWith)
{
# User already in stack
if ($merge && isset($userStack[$userToMerge["uid"]])) {
$this->logger->debug("User " . $userToMerge["uid"] . " has to be merged …");
// Check if accounts are enabled or disabled
// if both disabled, first account stays
// if one is enabled, use this account
// if both enabled, use information of $primaryAccountDnStartswWith
if ($preferEnabledAccountsOverDisabled && $userStack[$userToMerge["uid"]]['enable'] == 0 && $userToMerge['enable'] == 1) { # First disabled, second enabled and $preferEnabledAccountsOverDisabled is true
$this->logger->info("User " . $userToMerge["uid"] . " is merged because first account was disabled.");
$userStack[$userToMerge["uid"]] = $userToMerge;
}
elseif(!$preferEnabledAccountsOverDisabled && $userStack[$userToMerge["uid"]]['enable'] == 0 && $userToMerge['enable'] == 1) { # First disabled, second enabled and $preferEnabledAccountsOverDisabled is false
$this->logger->info("User " . $userToMerge["uid"] . " has not been merged, second enabled account was not preferred, because of preferEnabledAccountsOverDisabled option.");
}
elseif ($userStack[$userToMerge["uid"]]['enable'] == 1 && $userToMerge['enable'] == 1) { # Both enabled
if (strpos(strtolower($userToMerge['dn']), strtolower($primaryAccountDnStartswWith) !== FALSE)) {
$this->logger->info("User " . $userToMerge["uid"] . " is merged because second account is primary, based on merge filter.");
$userStack[$userToMerge["uid"]] = $userToMerge;
}
else {
$this->logger->info("User " . $userToMerge["uid"] . " has not been merged, second account was not primary, based on merge filter.");
}
} else {
$this->logger->info("User " . $userToMerge["uid"] . " has not been merged, second account was disabled, first account was enabled.");
}
} else { # User not in stack
$userStack[$userToMerge["uid"]] = $userToMerge;
}
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace OCA\UserCAS\Service\Merge;
/**
* Interface MergerInterface
* @package OCA\UserCAS\Service\Merge
*
* @author Felix Rupp <kontakt@felixrupp.com>
* @copyright Felix Rupp
*
* @since 1.0.0
*/
interface MergerInterface
{
public function mergeUsers(array &$userStack, array $userToMerge, $merge, $preferEnabledAccountsOverDisabled, $primaryAccountDnStartswWith);
}

View File

@ -0,0 +1,680 @@
<?php
/**
* ownCloud - user_cas
*
* @author Felix Rupp <kontakt@felixrupp.com>
* @copyright Felix Rupp <kontakt@felixrupp.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\UserCAS\Service;
use OCA\UserCAS\Exception\PhpCas\PhpUserCasLibraryNotFoundException;
use OCA\UserCas\Service\LoggingService;
use OCA\UserCAS\User\UserCasBackendInterface;
use \OCP\IConfig;
use \OCP\IUserManager;
use \OCP\IGroupManager;
use \OCP\IUserSession;
use OCA\UserCAS\User\Backend;
/**
* Class UserService
*
* @package OCA\UserCAS\Service
*
* @author Felix Rupp <kontakt@felixrupp.com>
* @copyright Felix Rupp <kontakt@felixrupp.com>
*
* @since 1.4
*/
class UserService
{
/**
* @var string $appName
*/
private $appName;
/**
* @var \OCP\IConfig $appConfig
*/
private $config;
/**
* @var \OCP\IUserSession $userSession
*/
private $userSession;
/**
* @var \OCP\IUserManager $userManager
*/
private $userManager;
/**
* @var \OCP\IGroupManager
*/
private $groupManager;
/**
* @var AppService $appService
*/
private $appService;
/**
* @var LoggingService $loggingService
*/
private $loggingService;
/**
* UserService constructor.
*
* @param $appName
* @param IConfig $config
* @param IUserManager $userManager
* @param IUserSession $userSession
* @param IGroupManager $groupManager
* @param AppService $appService
* @param LoggingService $loggingService
*/
public function __construct($appName, IConfig $config, IUserManager $userManager, IUserSession $userSession, IGroupManager $groupManager, AppService $appService, LoggingService $loggingService)
{
$this->appName = $appName;
$this->config = $config;
$this->userManager = $userManager;
$this->userSession = $userSession;
$this->groupManager = $groupManager;
$this->appService = $appService;
$this->loggingService = $loggingService;
}
/**
* Login hook method.
*
* @param $request
* @param string $uid
* @param string $password
* @return bool
*/
public function login($request, $uid, $password = '')
{
$this->loggingService->write(LoggingService::DEBUG, 'phpCAS login function step 1.');
#\OCP\Util::writeLog('cas', 'phpCAS login function step 1.', \OCA\UserCas\Service\LoggingService::DEBUG);
try {
if (!boolval($this->config->getAppValue($this->appName, 'cas_autocreate')) && !$this->userExists($uid)) {
$this->loggingService->write(LoggingService::DEBUG, 'phpCas autocreate disabled, and OC User does not exist, phpCas based login not possible. Bye.');
return FALSE;
}
# Check if user may be authorized based on groups or not
$cas_access_allow_groups = $this->config->getAppValue($this->appName, 'cas_access_allow_groups');
if (is_string($cas_access_allow_groups) && strlen($cas_access_allow_groups) > 0) {
$cas_access_allow_groups = explode(',', $cas_access_allow_groups);
$casAttributes = \phpCAS::getAttributes();
$casGroups = array();
$isAuthorized = FALSE;
$groupMapping = $this->config->getAppValue($this->appName, 'cas_group_mapping');
# Test if an attribute parser added a new dimension to our attributes array
if (array_key_exists('attributes', $casAttributes)) {
$newAttributes = $casAttributes['attributes'];
unset($casAttributes['attributes']);
$casAttributes = array_merge($casAttributes, $newAttributes);
}
# Test for mapped attribute from settings
if (array_key_exists($groupMapping, $casAttributes)) {
$casGroups = (array)$casAttributes[$groupMapping];
} # Test for standard 'groups' attribute
else if (array_key_exists('groups', $casAttributes)) {
if($this->config->getAppValue($this->appName, 'cas_groups_json_decode')) {
$casGroups = json_decode($casAttributes['groups']);
}
else {
$casGroups = (array)$casAttributes['groups'];
}
}
foreach ($casGroups as $casGroup) {
if (in_array($casGroup, $cas_access_allow_groups)) {
$this->loggingService->write(LoggingService::DEBUG, 'phpCas CAS users login has been authorized with group: ' . $casGroup);
$isAuthorized = TRUE;
} else {
$this->loggingService->write(LoggingService::DEBUG, 'phpCas CAS users login has not been authorized with group: ' . $casGroup . ', because the group was not in allowedGroups: ' . implode(", ", $cas_access_allow_groups));
}
}
if ($this->groupManager->isInGroup($uid, 'admin')) {
$this->loggingService->write(LoggingService::DEBUG, 'phpCas CAS users login has been authorized with group: admin');
$isAuthorized = TRUE;
}
if (!$isAuthorized) {
$this->loggingService->write(LoggingService::DEBUG, 'phpCas CAS user is not authorized to log into ownCloud. Bye.');
return FALSE;
}
}
$loginSuccessful = $this->userSession->login($uid, $password);
$this->loggingService->write(LoggingService::DEBUG, 'phpCAS login function result: ' . $loginSuccessful);
#\OCP\Util::writeLog('cas', 'phpCAS login function result: ' . $loginSuccessful, \OCA\UserCas\Service\LoggingService::DEBUG);
if ($loginSuccessful) {
return $this->userSession->createSessionToken($request, $this->userSession->getUser()->getUID(), $uid, NULL);
}
$this->loggingService->write(LoggingService::DEBUG, 'phpCAS login function not successful.');
#\OCP\Util::writeLog('cas', 'phpCAS login function not successful.', \OCA\UserCas\Service\LoggingService::DEBUG);
return FALSE;
} catch (\OC\User\LoginException $e) {
$this->loggingService->write(LoggingService::ERROR, 'Owncloud could not log in the user with UID: ' . $uid . '. Exception thrown with code: ' . $e->getCode() . ' and message: ' . $e->getMessage() . '.');
#\OCP\Util::writeLog('cas', 'Owncloud could not log in the user with UID: ' . $uid . '. Exception thrown with code: ' . $e->getCode() . ' and message: ' . $e->getMessage() . '.', \OCA\UserCas\Service\LoggingService::ERROR);
return FALSE;
}
}
/**
* Logout function
*
* @return bool|void
*/
public function logout()
{
return $this->userSession->logout();
}
/**
* IsLoggedIn method.
*
* @return boolean
*/
public function isLoggedIn()
{
return $this->userSession->isLoggedIn();
}
/**
* @param string $userId
* @param UserCasBackendInterface $backend
* @return boolean|\OCP\IUser the created user or false
*/
public function create($userId, UserCasBackendInterface $backend)
{
$randomPassword = $this->getNewPassword();
if ($backend->implementsActions(\OC\User\Backend::CREATE_USER)) {
return $this->userManager->createUserFromBackend($userId, $randomPassword, $backend);
}
return FALSE;
}
/**
* @param string $userId
* @return mixed
*/
public function userExists($userId)
{
return $this->userManager->userExists($userId);
}
/**
* @return string
*/
public function getUserId()
{
$uid = \phpCAS::getUser();
$casAttributes = \phpCAS::getAttributes();
if($this->config->getAppValue($this->appName, 'cas_userid_mapping') && strlen($this->config->getAppValue($this->appName, 'cas_userid_mapping')) > 0) {
$userIdAttribute = $this->config->getAppValue($this->appName, 'cas_userid_mapping');
if(isset($casAttributes[$userIdAttribute])) {
$uid = $casAttributes[$userIdAttribute];
}
}
return $uid;
}
/**
* Update the user
*
* @param \OCP\IUser $user
* @param array $attributes
*/
public function updateUser($user, $attributes)
{
$userId = $user->getUID();
/*$attributesString = '';
foreach ($attributes as $key => $attribute) {
$attributesString .= $key . ': ' . $attribute . '; ';
}*/
$newGroupQuota = NULL;
$this->loggingService->write(LoggingService::DEBUG, 'Updating data of the user: ' . $userId);
#\OCP\Util::writeLog('cas', 'Updating data of the user: ' . $userId, \OCA\UserCas\Service\LoggingService::DEBUG);
#\OCP\Util::writeLog('cas', 'Attributes: ' . $attributesString, \OCA\UserCas\Service\LoggingService::DEBUG);
if (isset($attributes['cas_email']) && is_object($user)) {
$this->updateMail($user, $attributes['cas_email']);
}
if (isset($attributes['cas_name']) && is_object($user)) {
$this->updateName($user, $attributes['cas_name']);
}
if (isset($attributes['cas_groups']) && is_object($user)) {
$this->updateGroups($user, $attributes['cas_groups'], $this->config->getAppValue($this->appName, 'cas_protected_groups'));
}
if (isset($attributes['cas_group_quota']) && is_object($user)) {
$newGroupQuota = $this->updateGroupQuota($user, $attributes['cas_group_quota']);
}
if (isset($attributes['cas_quota']) && is_object($user)) {
$this->updateQuota($user, $newGroupQuota, $attributes['cas_quota']);
}
$this->loggingService->write(LoggingService::DEBUG, 'Updating data finished.');
#\OCP\Util::writeLog('cas', 'Updating data finished.', \OCA\UserCas\Service\LoggingService::DEBUG);
}
/**
* Update the eMail address
*
* @param \OCP\IUser $user
* @param string|array $email
*/
private function updateMail($user, $email)
{
if (is_array($email)) {
$email = $email[0];
}
if ($email !== $user->getEMailAddress()) {
$user->setEMailAddress($email);
$this->loggingService->write(LoggingService::DEBUG, 'Set email "' . $email . '" for the user: ' . $user->getUID());
#\OCP\Util::writeLog('cas', 'Set email "' . $email . '" for the user: ' . $user->getUID(), \OCA\UserCas\Service\LoggingService::DEBUG);
}
}
/**
* Update the display name
*
* @param \OCP\IUser $user
* @param string| $name
*/
private function updateName($user, $name)
{
if (is_array($name)) {
$name = $name[0];
}
if ($name !== $user->getDisplayName() && strlen($name) > 0) {
$user->setDisplayName($name);
$this->loggingService->write(LoggingService::DEBUG, 'Set Name: ' . $name . ' for the user: ' . $user->getUID());
#\OCP\Util::writeLog('cas', 'Set Name: ' . $name . ' for the user: ' . $user->getUID(), \OCA\UserCas\Service\LoggingService::DEBUG);
}
}
/**
* Gets an array of groups and will try to add the group to OC and then add the user to the groups.
*
* @param \OCP\IUser $user
* @param string|array $groups
* @param string|array $protectedGroups
* @param bool $justCreated
*/
public function updateGroups($user, $groups, $protectedGroups = '', $justCreated = false)
{
if (is_string($groups)) $groups = explode(",", $groups);
if (is_string($protectedGroups)) $protectedGroups = explode(",", $protectedGroups);
$uid = $user->getUID();
# Add default user group to groups and protectedGroups
if($this->config->getAppValue($this->appName, 'cas_groups_create_default_for_user')) {
$userGroupPrefix = $this->config->getAppValue($this->appName, 'cas_groups_create_default_for_user_prefix', '');
if(strpos($userGroupPrefix, '/') !== strlen($userGroupPrefix)) {
$userGroupPrefix .= '/';
}
$userGroupName = $userGroupPrefix.$uid;
$groups[] = $userGroupName;
$protectedGroups[] = $userGroupName;
}
if (!$justCreated) {
$oldGroups = $this->groupManager->getUserGroups($user);
foreach ($oldGroups as $group) {
if ($group instanceof \OCP\IGroup) {
$groupId = $group->getGID();
if (!in_array($groupId, $protectedGroups) && !in_array($groupId, $groups)) {
$group->removeUser($user);
$this->loggingService->write(LoggingService::DEBUG, "Removed '" . $uid . "' from the group '" . $groupId . "'");
#\OCP\Util::writeLog('cas', 'Removed "' . $uid . '" from the group "' . $groupId . '"', \OCA\UserCas\Service\LoggingService::DEBUG);
}
}
}
}
foreach ($groups as $group) {
$groupObject = NULL;
# Replace umlauts
if (boolval($this->config->getAppValue($this->appName, 'cas_groups_letter_umlauts'))) {
$group = str_replace("Ä", "Ae", $group);
$group = str_replace("Ö", "Oe", $group);
$group = str_replace("Ü", "Ue", $group);
$group = str_replace("ä", "ae", $group);
$group = str_replace("ö", "oe", $group);
$group = str_replace("ü", "ue", $group);
$group = str_replace("ß", "ss", $group);
}
# Filter unwanted characters
$nameFilter = $this->config->getAppValue($this->appName, 'cas_groups_letter_filter');
if (strlen($nameFilter) > 0) {
$group = preg_replace("/[^" . $nameFilter . "]+/", "", $group);
} else { # Use default filter
$group = preg_replace("/[^a-zA-Z0-9\.\-_ @\/]+/", "", $group);
}
# Filter length to max 64 chars
if (strlen($group) > 64) {
$group = substr($group, 0, 63) . "";
}
if (!$this->groupManager->isInGroup($uid, $group)) {
if (!$this->groupManager->groupExists($group)) {
$groupObject = $this->groupManager->createGroup($group);
$this->loggingService->write(LoggingService::DEBUG, 'New group created: ' . $group);
#\OCP\Util::writeLog('cas', 'New group created: ' . $group, \OCA\UserCas\Service\LoggingService::DEBUG);
} else {
$groupObject = $this->groupManager->get($group);
}
$groupObject->addUser($user);
$this->loggingService->write(LoggingService::DEBUG, "Added '" . $uid . "' to the group '" . $group . "'");
#\OCP\Util::writeLog('cas', 'Added "' . $uid . '" to the group "' . $group . '"', \OCA\UserCas\Service\LoggingService::DEBUG);
}
}
}
/**
* @param \OCP\IUser $user
* @param int|boolean $newGroupQuota
* @param string $quota
*/
public function updateQuota($user, $newGroupQuota, $quota = 'default')
{
$this->loggingService->write(\OCA\UserCas\Service\LoggingService::DEBUG, 'phpCas new UserQuota contents: ' . $quota . ' | New GroupQuota was: ' . $newGroupQuota);
$defaultQuota = $this->config->getAppValue('files', 'default_quota');
if ($defaultQuota === '') {
$defaultQuota = 'none';
}
$uid = $user->getUID();
if ($quota === 'none') {
$newQuota = PHP_INT_MAX;
} elseif ($quota === 'default') {
$newQuota = \OCP\Util::computerFileSize($defaultQuota);
} else {
$newQuota = \OCP\Util::computerFileSize($quota);
}
$usersOldQuota = $user->getQuota();
if ($usersOldQuota === 'none') {
$usersOldQuota = PHP_INT_MAX;
} elseif ($usersOldQuota === 'default') {
$usersOldQuota = \OCP\Util::computerFileSize($defaultQuota);
} else {
$usersOldQuota = \OCP\Util::computerFileSize($usersOldQuota);
}
$this->loggingService->write(LoggingService::DEBUG, "Default System Quota is: '" . $defaultQuota . "'");
$this->loggingService->write(LoggingService::DEBUG, "User '" . $uid . "' old computerized Quota is: '" . $usersOldQuota . "'");
$this->loggingService->write(LoggingService::DEBUG, "User '" . $uid . "' new computerized User Quota would be: '" . $newQuota . "'");
if ($usersOldQuota < $newQuota || ($usersOldQuota > $newQuota && $newGroupQuota != NULL)) {
$user->setQuota($newQuota);
$this->loggingService->write(LoggingService::DEBUG, "User '" . $uid . "' has new Quota: '" . $newQuota . "'");
}
}
/**
* @param \OCP\IUser $user
* @param array $groupQuotas
* @return int New Quota
*/
private function updateGroupQuota($user, $groupQuotas)
{
$defaultQuota = $this->config->getAppValue('files', 'default_quota');
if ($defaultQuota === '') {
$defaultQuota = 'none';
}
$uid = $user->getUID();
$collectedQuotas = array();
foreach ($groupQuotas as $groupName => $groupQuota) {
if ($this->groupManager->isInGroup($uid, $groupName)) {
if ($groupQuota === 'none') {
$collectedQuotas[PHP_INT_MAX] = $groupQuota;
} elseif ($groupQuota === 'default') {
$defaultQuotaFilesize = \OCP\Util::computerFileSize($defaultQuota);
$collectedQuotas[$defaultQuotaFilesize] = $groupQuota;
} else {
$groupQuotaComputerFilesize = \OCP\Util::computerFileSize($groupQuota);
$collectedQuotas[$groupQuotaComputerFilesize] = $groupQuota;
}
}
}
# Sort descending by key
krsort($collectedQuotas);
$newQuota = \OCP\Util::computerFileSize(array_shift($collectedQuotas));
$usersOldQuota = $user->getQuota();
if ($usersOldQuota === 'none') {
$usersOldQuota = PHP_INT_MAX;
} elseif ($usersOldQuota === 'default') {
$usersOldQuota = \OCP\Util::computerFileSize($defaultQuota);
} else {
$usersOldQuota = \OCP\Util::computerFileSize($usersOldQuota);
}
$this->loggingService->write(LoggingService::DEBUG, "Default System Quota is: '" . $defaultQuota . "'");
$this->loggingService->write(LoggingService::DEBUG, "User '" . $uid . "' old computerized Quota is: '" . $usersOldQuota . "'");
$this->loggingService->write(LoggingService::DEBUG, "User '" . $uid . "' new computerized Group Quota would be: '" . $newQuota . "'");
if ($usersOldQuota < $newQuota) {
$user->setQuota($newQuota);
$this->loggingService->write(LoggingService::DEBUG, "User '" . $uid . "' has new Quota: '" . $newQuota . "'");
return $newQuota;
}
return $usersOldQuota;
}
/**
* Register User Backend.
*
* @param UserCasBackendInterface $backend
*/
public function registerBackend(UserCasBackendInterface $backend)
{
$this->userManager->registerBackend($backend);
}
/**
* Update the backend of the user on ownCloud
*
* @param \OCP\IUser $user
* @return bool|int|\OC_DB_StatementWrapper
*
* @deprecated
**/
public function updateBackend(\OCP\IUser $user)
{
try {
$uid = $user->getUID();
$result = false;
if ($this->appService->isNotNextcloud()) {
if (!is_null($user) && ($user->getBackendClassName() === 'OC\User\Database' || $user->getBackendClassName() === "Database")) {
$query = \OC_DB::prepare('UPDATE `*PREFIX*accounts` SET `backend` = ? WHERE LOWER(`user_id`) = LOWER(?)');
$result = $query->execute([get_class($this->getBackend()), $uid]);
$this->loggingService->write(\OCA\UserCas\Service\LoggingService::DEBUG, 'phpCAS user existing in database backend, move to CAS-Backend with result: ' . $result);
}
}
return $result;
} catch (\Exception $e) {
return false;
}
}
/**
* Generate a random PW with special char symbol characters
*
* @return string New Password
*/
protected function getNewPassword()
{
$newPasswordCharsLower = \OC::$server->getSecureRandom()->generate(8, \OCP\Security\ISecureRandom::CHAR_LOWER);
$newPasswordCharsUpper = \OC::$server->getSecureRandom()->generate(4, \OCP\Security\ISecureRandom::CHAR_UPPER);
$newPasswordNumbers = \OC::$server->getSecureRandom()->generate(4, \OCP\Security\ISecureRandom::CHAR_DIGITS);
$newPasswordSymbols = \OC::$server->getSecureRandom()->generate(4, \OCP\Security\ISecureRandom::CHAR_SYMBOLS);
return str_shuffle($newPasswordCharsLower . $newPasswordCharsUpper . $newPasswordNumbers . $newPasswordSymbols);
}
}