Commit f8018de9 authored by Sebastian Kummer's avatar Sebastian Kummer

ZP-1329 Refactored impersonation feature to a global level.

- changed Logger->SetUser() to not include brackets anymore
- changed Logger to include impersonation username in log filename when
doing per-user logging
- IPC & loopdetection are done on user#impersonation level
- changed IBackend->Logon() to include $impersontedUsername
- changed all backends and Autodiscover to use new Logon()
- changed IMPERSONATE_DELIM to '#' and moved it to Request
- chamged MAPIProvider and others to use Request::GetUser() to reflect
the user transmitting data (auth or impersonated)
- changed Request::GetAuthUser() to always return the authenticated user
- added Request::GetImpersontedUser() to always return the impersonted
user (if available)
- added Request::GetUser() to return the user operating on data
(impersonted if set - else auth).

Released under the Affero GNU General Public License (AGPL) version 3.
parent 8fedea77
......@@ -226,7 +226,7 @@ class ZPushAutodiscover {
// charset was sent by the client and convert it to UTF-8. See https://jira.z-hub.io/browse/ZP-864.
$username = Utils::ConvertAuthorizationToUTF8($username);
$password = Utils::ConvertAuthorizationToUTF8($_SERVER['PHP_AUTH_PW']);
if ($backend->Logon($username, "", $password) == false) {
if ($backend->Logon($username, false, "", $password) == false) {
throw new AuthenticationRequiredException("Access denied. Username or password incorrect.");
}
......@@ -305,7 +305,7 @@ class ZPushAutodiscover {
$username = Utils::GetLocalPartFromEmail($username);
}
$backend = ZPush::GetBackend();
if ($backend->Logon($username, "", $_SERVER['PHP_AUTH_PW']) == false) {
if ($backend->Logon($username, false, "", $_SERVER['PHP_AUTH_PW']) == false) {
ZLog::Write(LOGLEVEL_ERROR, sprintf("ZPushAutodiscover->getLogin(): Login failed for user '%s' from IP %s.", $username, $_SERVER["REMOTE_ADDR"]));
throw new AuthenticationRequiredException("Access denied. Username or password incorrect.");
}
......
......@@ -57,7 +57,7 @@ class BackendCalDAV extends BackendDiff {
* Login to the CalDAV backend
* @see IBackend::Logon()
*/
public function Logon($username, $domain, $password) {
public function Logon($username, $impersonatedUsername, $domain, $password) {
$this->_caldav_path = str_replace('%u', $username, CALDAV_PATH);
$url = sprintf("%s://%s:%d%s", CALDAV_PROTOCOL, CALDAV_SERVER, CALDAV_PORT, $this->_caldav_path);
$this->_caldav = new CalDAVClient($url, $username, $password);
......
......@@ -76,7 +76,7 @@ class BackendCardDAV extends BackendDiff implements ISearchProvider {
* @access public
* @return boolean
*/
public function Logon($username, $domain, $password) {
public function Logon($username, $impersonatedUsername, $domain, $password) {
$this->url = CARDDAV_PROTOCOL . '://' . CARDDAV_SERVER . ':' . CARDDAV_PORT . str_replace("%d", $domain, str_replace("%u", $username, CARDDAV_PATH));
$this->default_url = CARDDAV_PROTOCOL . '://' . CARDDAV_SERVER . ':' . CARDDAV_PORT . str_replace("%d", $domain, str_replace("%u", $username, CARDDAV_DEFAULT_PATH));
if (defined('CARDDAV_GAL_PATH')) {
......
......@@ -63,13 +63,14 @@ class BackendCombined extends Backend implements ISearchProvider {
* Authenticates the user on each backend
*
* @param string $username
* @param string $impersonatedUsername
* @param string $domain
* @param string $password
*
* @access public
* @return boolean
*/
public function Logon($username, $domain, $password) {
public function Logon($username, $impersonatedUsername, $domain, $password) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("Combined->Logon('%s', '%s',***))", $username, $domain));
if(!is_array($this->backends)){
return false;
......@@ -90,7 +91,8 @@ class BackendCombined extends Backend implements ISearchProvider {
if(isset($this->config['backends'][$i]['users'][$username]['domain']))
$d = $this->config['backends'][$i]['users'][$username]['domain'];
}
if($this->backends[$i]->Logon($u, $d, $p) == false){
// TODO: impersonation is not supported by Combined
if($this->backends[$i]->Logon($u, false, $d, $p) == false){
ZLog::Write(LOGLEVEL_DEBUG, sprintf("Combined->Logon() failed on %s ", $this->config['backends'][$i]['name']));
return false;
}
......
......@@ -78,6 +78,7 @@ class BackendIMAP extends BackendDiff implements ISearchProvider {
* Authenticates the user
*
* @param string $username
* @param string $impersonatedUsername
* @param string $domain
* @param string $password
*
......@@ -85,7 +86,7 @@ class BackendIMAP extends BackendDiff implements ISearchProvider {
* @return boolean
* @throws FatalException if php-imap module can not be found
*/
public function Logon($username, $domain, $password) {
public function Logon($username, $impersonatedUsername, $domain, $password) {
$this->wasteID = false;
$this->sentID = false;
$this->server = "{" . IMAP_SERVER . ":" . IMAP_PORT . "/imap" . IMAP_OPTIONS . "}";
......
......@@ -28,7 +28,7 @@ function create_calendar_dav($data) {
if (defined('IMAP_MEETING_USE_CALDAV') && IMAP_MEETING_USE_CALDAV) {
$caldav = new BackendCalDAV();
if ($caldav->Logon(Request::GetAuthUser(), Request::GetAuthDomain(), Request::GetAuthPassword())) {
if ($caldav->Logon(Request::GetAuthUser(), Request::GetImpersonatedUser(), Request::GetAuthDomain(), Request::GetAuthPassword())) {
$etag = $caldav->CreateUpdateCalendar($data);
ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->create_calendar_dav(): Calendar created with etag '%s' and data <%s>", $etag, $data));
$caldav->Logoff();
......@@ -48,7 +48,7 @@ function delete_calendar_dav($uid) {
else {
if (defined('IMAP_MEETING_USE_CALDAV') && IMAP_MEETING_USE_CALDAV) {
$caldav = new BackendCalDAV();
if ($caldav->Logon(Request::GetAuthUser(), Request::GetAuthDomain(), Request::GetAuthPassword())) {
if ($caldav->Logon(Request::GetAuthUser(), Request::GetImpersonatedUser(), Request::GetAuthDomain(), Request::GetAuthPassword())) {
$events = $caldav->FindCalendar($uid);
if (count($events) == 1) {
$href = $events[0]["href"];
......@@ -85,7 +85,7 @@ function update_calendar_attendee($uid, $mailto, $status) {
else {
if (defined('IMAP_MEETING_USE_CALDAV') && IMAP_MEETING_USE_CALDAV) {
$caldav = new BackendCalDAV();
if ($caldav->Logon(Request::GetAuthUser(), Request::GetAuthDomain(), Request::GetAuthPassword())) {
if ($caldav->Logon(Request::GetAuthUser(), Request::GetImpersonatedUser(), Request::GetAuthDomain(), Request::GetAuthPassword())) {
$events = $caldav->FindCalendar($uid);
if (count($events) == 1) {
$href = $events[0]["href"];
......
......@@ -71,7 +71,6 @@ class BackendKopano implements IBackend, ISearchProvider {
const FREEBUSYENUMBLOCKS = 50;
const MAXFREEBUSYSLOTS = 32767; // max length of 32k for the MergedFreeBusy element is allowed
const HALFHOURSECONDS = 1800;
const IMPERSONATE_DELIM = '+share+';
/**
* Constructor of the Kopano Backend
......@@ -133,6 +132,7 @@ class BackendKopano implements IBackend, ISearchProvider {
* Authenticates the user with the configured Kopano server
*
* @param string $username
* @param string $impersonatedUsername
* @param string $domain
* @param string $password
*
......@@ -140,19 +140,19 @@ class BackendKopano implements IBackend, ISearchProvider {
* @return boolean
* @throws AuthenticationRequiredException
*/
public function Logon($user, $domain, $pass) {
public function Logon($user, $impersonatedUsername, $domain, $pass) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("KopanoBackend->Logon(): Trying to authenticate user '%s'..", $user));
$this->mainUser = strtolower($user);
$this->impersonateUser = $impersonatedUsername;
// check if we are impersonating someone
// $defaultUser will be used for $this->defaultStore
if (defined('KOE_CAPABILITY_IMPERSONATE') && KOE_CAPABILITY_IMPERSONATE && stripos($user, self::IMPERSONATE_DELIM) !== false) {
list($this->mainUser, $this->impersonateUser) = explode(self::IMPERSONATE_DELIM, strtolower($user));
if ($impersonatedUsername !== false) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("KopanoBackend->Logon(): Impersonation active - authenticating: '%s' - impersonating '%s'", $this->mainUser, $this->impersonateUser));
$defaultUser = $this->impersonateUser;
}
else {
$this->mainUser = strtolower($user);
$this->impersonateUser = false;
$defaultUser = $this->mainUser;
}
......
......@@ -297,7 +297,7 @@ class MAPIProvider {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("MAPIProvider->getAppointment: adding ourself as an attendee for iOS6 workaround"));
$attendee = new SyncAttendee();
$meinfo = mapi_zarafa_getuser_by_name($this->store, Request::GetAuthUser());
$meinfo = mapi_zarafa_getuser_by_name($this->store, Request::GetUser());
if (is_array($meinfo)) {
$attendee->email = w2u($meinfo["emailaddress"]);
......@@ -314,7 +314,7 @@ class MAPIProvider {
// the user is the owner or it will not work properly with android devices
// @see https://jira.z-hub.io/browse/ZP-1020
if(isset($messageprops[$appointmentprops["meetingstatus"]]) && $messageprops[$appointmentprops["meetingstatus"]] == olNonMeeting && empty($message->attendees)) {
$meinfo = mapi_zarafa_getuser_by_name($this->store, Request::GetAuthUser());
$meinfo = mapi_zarafa_getuser_by_name($this->store, Request::GetUser());
if (is_array($meinfo)) {
$message->organizeremail = w2u($meinfo["emailaddress"]);
......@@ -1434,8 +1434,8 @@ class MAPIProvider {
$props[$appointmentprops["representingentryid"]] = $storeProps[PR_MAILBOX_OWNER_ENTRYID];
$displayname = $this->getFullnameFromEntryID($storeProps[PR_MAILBOX_OWNER_ENTRYID]);
$props[$appointmentprops["representingname"]] = ($displayname !== false) ? $displayname : Request::GetAuthUser();
$props[$appointmentprops["sentrepresentingemail"]] = Request::GetAuthUser();
$props[$appointmentprops["representingname"]] = ($displayname !== false) ? $displayname : Request::GetUser();
$props[$appointmentprops["sentrepresentingemail"]] = Request::GetUser();
$props[$appointmentprops["sentrepresentingaddt"]] = "ZARAFA";
$props[$appointmentprops["sentrepresentinsrchk"]] = $props[$appointmentprops["sentrepresentingaddt"]].":".$props[$appointmentprops["sentrepresentingemail"]];
......@@ -1709,7 +1709,7 @@ class MAPIProvider {
$p = array( $taskprops["owner"]);
$owner = $this->getProps($mapimessage, $p);
if (!isset($owner[$taskprops["owner"]])) {
$userinfo = mapi_zarafa_getuser_by_name($this->store, Request::GetAuthUser());
$userinfo = mapi_zarafa_getuser_by_name($this->store, Request::GetUser());
if(mapi_last_hresult() == NOERROR && isset($userinfo["fullname"])) {
$props[$taskprops["owner"]] = $userinfo["fullname"];
}
......
......@@ -498,7 +498,7 @@ class ReplyBackImExporter implements IImportChanges, IExportChanges {
*/
private function sendNotificationEmail($message, $oldmessage) {
// get email address and full name of the user
$userinfo = ZPush::GetBackend()->GetUserDetails(Request::GetAuthUser());
$userinfo = ZPush::GetBackend()->GetUserDetails(Request::GetUser());
// get the name of the folder
$foldername = "unknown";
......
......@@ -35,7 +35,7 @@ class BackendLDAP extends BackendDiff {
private $ldap_link;
private $user;
public function Logon($username, $domain, $password) {
public function Logon($username, $impersonatedUsername, $domain, $password) {
$this->user = $username;
$user_dn = str_replace('%u', $username, LDAP_USER_DN);
$this->ldap_link = ldap_connect(LDAP_SERVER, LDAP_SERVER_PORT);
......
......@@ -56,7 +56,7 @@ class BackendMaildir extends BackendDiff {
* @access public
* @return boolean
*/
public function Logon($username, $domain, $password) {
public function Logon($username, $impersonatedUsername, $domain, $password) {
return true;
}
......
......@@ -44,7 +44,7 @@ class BackendVCardDir extends BackendDiff {
* @access public
* @return boolean
*/
public function Logon($username, $domain, $password) {
public function Logon($username, $impersonatedUsername, $domain, $password) {
return true;
}
......
......@@ -96,7 +96,7 @@ abstract class InterProcessData {
if (!isset(self::$devid)) {
self::$devid = Request::GetDeviceID();
self::$pid = @getmypid();
self::$user = Request::GetAuthUser();
self::$user = Request::GetAuthUserString(); // we want to see everything here
self::$start = time();
}
return true;
......
......@@ -145,8 +145,13 @@ class ZLog {
throw new \Exception($errmsg);
}
list($user) = Utils::SplitDomainUser(strtolower(Request::GetGETUser()));
$user = '['.$user.']';
// if there is an impersonated user it's used instead of the GET user
if (Request::GetImpersonatedUser()) {
$user = Request::GetImpersonatedUser();
}
else {
list($user) = Utils::SplitDomainUser(strtolower(Request::GetGETUser()));
}
self::$logger = new $logger();
self::$logger->SetUser($user);
......
......@@ -86,7 +86,7 @@ abstract class Backend implements IBackend {
/*********************************************************************
* Methods to be implemented
*
* public function Logon($username, $domain, $password);
* public function Logon($username, $impersonatedUsername, $domain, $password);
* public function Setup($store, $checkACLonly = false, $folderid = false, $readonly = false);
* public function Logoff();
* public function GetHierarchy();
......@@ -182,16 +182,16 @@ abstract class Backend implements IBackend {
if (Request::GetProtocolVersion() >= 14.1) {
$account = new SyncAccount();
$emailaddresses = new SyncEmailAddresses();
$emailaddresses->smtpaddress[] = ZPush::GetBackend()->GetUserDetails(Request::GetAuthUser())['emailaddress'];
$emailaddresses->primarysmtpaddress = ZPush::GetBackend()->GetUserDetails(Request::GetAuthUser())['emailaddress'];
$emailaddresses->smtpaddress[] = ZPush::GetBackend()->GetUserDetails(Request::GetUser())['emailaddress'];
$emailaddresses->primarysmtpaddress = ZPush::GetBackend()->GetUserDetails(Request::GetUser())['emailaddress'];
$account->emailaddresses = $emailaddresses;
$userinformation->accounts[] = $account;
}
else {
$userinformation->emailaddresses = array(ZPush::GetBackend()->GetUserDetails(Request::GetAuthUser())['emailaddress']);
$userinformation->emailaddresses = array(ZPush::GetBackend()->GetUserDetails(Request::GetUser())['emailaddress']);
}
$settings->emailaddresses = array(ZPush::GetBackend()->GetUserDetails(Request::GetAuthUser())['emailaddress']);
$settings->emailaddresses = array(ZPush::GetBackend()->GetUserDetails(Request::GetUser())['emailaddress']);
}
if ($settings instanceof SyncRightsManagementTemplates) {
......@@ -233,7 +233,7 @@ abstract class Backend implements IBackend {
* @return Array
*/
public function GetCurrentUsername() {
return $this->GetUserDetails(Request::GetAuthUser());
return $this->GetUserDetails(Request::GetUser());
}
/**
......
......@@ -60,6 +60,7 @@ interface IBackend {
* Authenticates the user
*
* @param string $username
* @param string $impersonatedUsername
* @param string $domain
* @param string $password
*
......@@ -67,7 +68,7 @@ interface IBackend {
* @return boolean
* @throws FatalException e.g. some required libraries are unavailable
*/
public function Logon($username, $domain, $password);
public function Logon($username, $impersonatedUsername, $domain, $password);
/**
* Setup the backend to work on a specific store or checks ACLs there.
......
......@@ -50,6 +50,7 @@ class FileLog extends Log {
else {
$this->setLogToUserFile(
preg_replace('/[^a-z0-9]/', '_', strtolower($this->GetAuthUser())) .'-'.
(($this->GetAuthUser() != $this->GetUser()) ? preg_replace('/[^a-z0-9]/', '_', strtolower($this->GetUser())) .'-' : '') .
preg_replace('/[^a-z0-9]/', '_', strtolower($this->GetDevid())) .
'.log'
);
......@@ -84,7 +85,13 @@ class FileLog extends Log {
$log = Utils::GetFormattedTime() .' ['. str_pad($this->GetPid(),5," ",STR_PAD_LEFT) .'] '. $this->GetLogLevelString($loglevel, $loglevel >= LOGLEVEL_INFO);
if ($includeUserDevice) {
$log .= ' '. $this->GetUser();
// when the users differ, we need to log both
if ($this->GetUser() != $this->GetAuthUser()) {
$log .= ' ['. $this->GetAuthUser() . Request::IMPERSONATE_DELIM . $this->GetUser() .']';
}
else {
$log .= ' ['. $this->GetUser() .']';
}
}
if ($includeUserDevice && (LOGLEVEL >= LOGLEVEL_DEVICEID || (LOGUSERLEVEL >= LOGLEVEL_DEVICEID && $this->IsAuthUserInSpecialLogUsers()))) {
$log .= ' ['. $this->GetDevid() .']';
......
......@@ -166,7 +166,7 @@ class Syslog extends Log {
*/
public function BuildLogString($loglevel, $message, $includeUserDevice = true) {
$log = $this->GetLogLevelString($loglevel); // Never pad syslog log because syslog log are usually read with a software.
$log .= $this->GetUser();
$log .= ' ['. $this->GetUser() .']';
if ($loglevel >= LOGLEVEL_DEVICEID) {
$log .= '['. $this->GetDevid() .']';
}
......
......@@ -198,7 +198,7 @@ class FolderSync extends RequestProcessor {
// say that we are done with partial synching
self::$deviceManager->SetFolderSyncComplete(true);
// reset the loop data to prevent any loop detection to kick in now
self::$deviceManager->ClearLoopDetectionData(Request::GetAuthUser(), Request::GetDeviceID());
self::$deviceManager->ClearLoopDetectionData(Request::GetAuthUserString(), Request::GetDeviceID());
ZLog::Write(LOGLEVEL_INFO, "Request->HandleFolderSync(): Chunked exporting of folders completed successfully");
}
......
......@@ -27,6 +27,7 @@
class Request {
const MAXMEMORYUSAGE = 0.9; // use max. 90% of allowed memory when synching
const UNKNOWN = "unknown";
const IMPERSONATE_DELIM = '#';
/**
* self::filterEvilInput() options
......@@ -65,9 +66,11 @@ class Request {
static private $getUser;
static private $devid;
static private $devtype;
static private $authUserString;
static private $authUser;
static private $authDomain;
static private $authPassword;
static private $impersontedUser;
static private $asProtocolVersion;
static private $policykey;
static private $useragent;
......@@ -179,9 +182,17 @@ class Request {
// authUser & authPassword are unfiltered!
// split username & domain if received as one
if (isset($_SERVER['PHP_AUTH_USER'])) {
list(self::$authUser, self::$authDomain) = Utils::SplitDomainUser($_SERVER['PHP_AUTH_USER']);
list(self::$authUserString, self::$authDomain) = Utils::SplitDomainUser($_SERVER['PHP_AUTH_USER']);
self::$authPassword = (isset($_SERVER['PHP_AUTH_PW']))?$_SERVER['PHP_AUTH_PW'] : "";
}
// process impersonation
self::$authUser = self::$authUserString; // auth will fail when impersonating & KOE_CAPABILITY_IMPERSONATE is disabled
if (defined('KOE_CAPABILITY_IMPERSONATE') && KOE_CAPABILITY_IMPERSONATE && stripos(self::$authUserString, self::IMPERSONATE_DELIM) !== false) {
list(self::$authUser, self::$impersontedUser) = explode(self::IMPERSONATE_DELIM, strtolower(self::$authUserString));
}
if(defined('USE_FULLEMAIL_FOR_LOGIN') && ! USE_FULLEMAIL_FOR_LOGIN) {
self::$authUser = Utils::GetLocalPartFromEmail(self::$authUser);
}
......@@ -391,16 +402,64 @@ class Request {
}
/**
* Returns the authenticated user
* Returns user that is synchronizing data.
* If impersonation is active it returns the impersonated user,
* else the auth user.
*
* @access public
* @return string/boolean false if not available
*/
static public function GetUser() {
if (self::GetImpersonatedUser()) {
return self::GetImpersonatedUser();
}
else {
return self::GetAuthUser();
}
}
/**
* Returns the AuthUser string send by the client.
*
* @access public
* @return string/boolean false if not available
*/
static public function GetAuthUserString() {
if (isset(self::$authUserString)) {
return self::$authUserString;
}
else {
return false;
}
}
/**
* Returns the impersonated user. If not availabe, returns false.
*
* @access public
* @return string/boolean false if not available
*/
static public function GetImpersonatedUser() {
if (isset(self::$impersontedUser)) {
return self::$impersontedUser;
}
else {
return false;
}
}
/**
* Returns the authenticated user.
*
* @access public
* @return string/boolean false if not available
*/
static public function GetAuthUser() {
if (isset(self::$authUser))
if (isset(self::$authUser)) {
return self::$authUser;
else
}
else {
return false;
}
}
/**
......
......@@ -60,8 +60,12 @@ abstract class RequestProcessor {
if(defined("CERTIFICATE_OWNER_PARAMETER") && isset($_SERVER[CERTIFICATE_OWNER_PARAMETER]) && strtolower($_SERVER[CERTIFICATE_OWNER_PARAMETER]) != strtolower(Request::GetAuthUser()))
throw new AuthenticationRequiredException(sprintf("Access denied. Access is allowed only for the certificate owner '%s'", $_SERVER[CERTIFICATE_OWNER_PARAMETER]));
if (Request::GetImpersonatedUser() && Request::GetAuthUser() != Request::GetImpersonatedUser()) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("RequestProcessor->Authenticate(): Impersonation active - authenticating: '%s' - impersonating '%s'", Request::GetAuthUser(), Request::GetImpersonatedUser()));
}
$backend = ZPush::GetBackend();
if($backend->Logon(Request::GetAuthUser(), Request::GetAuthDomain(), Request::GetAuthPassword()) == false)
if($backend->Logon(Request::GetAuthUser(), Request::GetImpersonatedUser(), Request::GetAuthDomain(), Request::GetAuthPassword()) == false)
throw new AuthenticationRequiredException("Access denied. Username or password incorrect");
// mark this request as "authenticated"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment