diff --git a/api/src/Accounts/Univention.php b/api/src/Accounts/Univention.php index d349f5edcf..9da4c3c16e 100644 --- a/api/src/Accounts/Univention.php +++ b/api/src/Accounts/Univention.php @@ -1,6 +1,6 @@ @@ -15,33 +15,25 @@ namespace EGroupware\Api\Accounts; use EGroupware\Api; /** - * Univention LDAP Backend for accounts + * Accounts backend for Univention * * This backend is mostly identical to LDAP backend and need to be configured in the same way. * - * Only difference is that new users get created via univention-directory-manager CLI program, - * to generate necesary Kerberos stuff. + * Only difference is that some actions are currently done directly via Univention UDM webservice: + * - create new users: to generate necesary Kerberos stuff and all password hashes + * - password change: to generate als Samba hashes + * - create groups with given gidNumber/sambaRID + * - rename / -position users or groups, as this is a remove and re-create + * (removing and adding entry under new dn via LDAP fails: "Type or value exists") * - * New groups are generated via same CLI, if we have an ID/RID to set. - * - * Existing users and groups need to be renamed via same CLI, as removing and - * adding entry under new dn via LDAP fails (Type or value exists). + * Once UDM webservice is out of beta, we could think about replacing LDAP accounts stuff completly. + * Possible problems to look out for: + * - search with sorting + * - caching done on LDAP level + * - mail account and addressbook is also affected */ class Univention extends Ldap { - /** - * Attribute with mail address - */ - const MAIL_ATTR = 'mailprimaryaddress'; - - /** - * Name of binary to call - * - * It is a symlink to /usr/share/univention-directory-manager-tools/directory-manager-cli. - * Both directories must be included in open_basedir! - */ - const DIRECTORY_MANAGER_BIN = '/usr/sbin/univention-directory-manager'; - /** * Saves / adds the data of one account * @@ -49,148 +41,67 @@ class Univention extends Ldap * * @param array $data array with account-data * @return int|boolean the account_id or false on error + * @throws Univention\UdmException on error */ function save(&$data) { // UCS lowercases email when storing $data['account_email'] = strtolower($data['account_email']); - if (self::available()) + $config = $this->frontend->config && $this->frontend->config['ldap_context'] ? + $this->frontend->config : $GLOBALS['egw_info']['server']; + + $udm = new Univention\Udm($config); + + if ($data['account_type'] !== 'g' && (empty($data['account_id']) || !$this->id2name($data['account_id']))) { - $ssh = null;//'/usr/bin/ssh -o "UserKnownHostsFile=/dev/null" -o "StrictHostKeyChecking=no" -i /var/lib/egroupware/id_rsa root@10.44.22.194'; - $config = $this->frontend->config && $this->frontend->config['ldap_context'] ? - $this->frontend->config : $GLOBALS['egw_info']['server']; + // empty names give an error: The property lastname is required is not valid + if (empty($data['account_lastname'])) $data['account_lastname'] = 'n/a'; - if ($data['account_type'] !== 'g' && (empty($data['account_id']) || !$this->id2name($data['account_id']))) + // we can't create a new user without a password, setting a randowm one for now + $matches = null; + if (empty($data['account_passwd']) || preg_match('/^{([a-z0-9_]+)}/i', $data['account_passwd'], $matches)) { - // empty names give an error: The property Last/First name is required is not valid - if (empty($data['account_firstname'])) $data['account_firstname'] = 'n/a'; - if (empty($data['account_lastname'])) $data['account_lastname'] = 'n/a'; - - $params = array( - 'users/user','create', - '--binddn', $config['ldap_root_dn'], - '--bindpwd', 5=>$config['ldap_root_pw'], - '--position', $config['ldap_context'], - '--set', 'username='.$data['account_lid'], - '--set', 'firstname='.$data['account_firstname'], - '--set', 'lastname='.$data['account_lastname'], - ); - - // we can't create a new user without a password, setting a randowm one for now - $matches = null; - if (empty($data['account_passwd']) || preg_match('/^{([a-z0-9_]+)}/i', $data['account_passwd'], $matches)) + if ($matches && strtolower($matches[1]) === 'plain') { - if ($matches && strtolower($matches[1]) === 'plain') - { - $data['account_passwd'] = substr($data['account_passwd'], 7); - } - else - { - $data['account_passwd'] = Api\Auth::randomstring(12); - //file_put_contents('/tmp/passwords', "$data[account_lid]\t$data[account_passwd]\n", FILE_APPEND); - } + $data['account_passwd'] = substr($data['account_passwd'], 7); } - $params[] = '--set'; $params[] = 'password='.$data['account_passwd']; - - // if account_id is given and bigger then 1000, set it to facilitate migration - if (!empty($data['account_id']) && $data['account_id'] >= Ads::MIN_ACCOUNT_ID) + else { - $params[] = '--set'; $params[] = 'uidNumber='.(int)$data['account_id']; - $params[] = '--set'; $params[] = 'sambaRID='.(int)$data['account_id']; + $data['account_passwd'] = Api\Auth::randomstring(12); + //file_put_contents('/tmp/passwords', "$data[account_lid]\t$data[account_passwd]\n", FILE_APPEND); } - - if (!empty($data['account_email'])) - { - $params[] = '--set'; $params[] = 'mailPrimaryAddress='.$data['account_email']; - - // we need to set mailHomeServer, so mailbox gets created for Dovecot - // get_default() does not work for Adminstrator, try acc_id=1 instead - // if everything fails try hostname ... - try { - if (!($account = Api\Mail\Account::get_default(false, false, false))) - { - $account = Api\Mail\Account::read(1); - } - $hostname = $account->acc_imap_host; - } - catch(\Exception $e) { - unset($e); - } - //$hostname='master.test-org.intranet'; - if (empty($hostname)) $hostname = trim(exec('hostname -f')); - $params[] = '--set'; $params[] = 'mailHomeServer='.$hostname; - } - $cmd = self::DIRECTORY_MANAGER_BIN.' '.implode(' ', array_map('escapeshellarg', $params)); - if (isset($ssh)) $cmd = $ssh.' bash -c "\\"'.$cmd.'\\""'; - $output_arr = $ret = $matches = null; - exec($cmd, $output_arr, $ret); - $output = implode("\n", $output_arr); - if ($ret || !preg_match('/^Object created: (uid=.*)$/mui', $output, $matches)) - { - $params[5] = '********'; // mask out password! - $cmd = self::DIRECTORY_MANAGER_BIN.' '.implode(' ', array_map('escapeshellarg', $params)); - throw new Api\Exception\WrongUserinput($cmd."\nreturned\n".$output); - } - $data['account_dn'] = $matches[1]; - $data['account_id'] = $this->name2id($data['account_lid'], 'account_lid', 'u'); } - // create new groups with given account_id via directory-manager too, to be able to set the RID - elseif($data['account_type'] === 'g' && !empty($data['account_id']) && - $data['account_id'] >= Ads::MIN_ACCOUNT_ID && !$this->id2name($data['account_id'])) - { - $params = array( - 'groups/group', 'create', - '--binddn', $config['ldap_root_dn'], - '--bindpwd', 5=>$config['ldap_root_pw'], - '--position', empty($config['ldap_group_context']) ? $config['ldap_context'] : $config['ldap_group_context'], - '--set', 'name='.$data['account_lid'], - '--set', 'gidNumber='.(int)$data['account_id'], - '--set', 'sambaRID='.(int)$data['account_id'], - ); - $cmd = self::DIRECTORY_MANAGER_BIN.' '.implode(' ', array_map('escapeshellarg', $params)); - if (isset($ssh)) $cmd = $ssh.' bash -c "\\"'.$cmd.'\\""'; - $output_arr = $ret = $matches = null; - exec($cmd, $output_arr, $ret); - $output = implode("\n", $output_arr); - if ($ret || !preg_match('/^Object created: (cn=.*)$/mui', $output, $matches)) - { - $params[5] = '********'; // mask out password! - $cmd = self::DIRECTORY_MANAGER_BIN.' '.implode(' ', array_map('escapeshellarg', $params)); - throw new Api\Exception\WrongUserinput($cmd."\nreturned\n".$output); - } - $data['account_dn'] = $matches[1]; - $data['account_id'] = $this->name2id($data['account_lid'], 'account_lid', 'g'); - } - elseif($data['account_id'] && ($data['old_loginid'] || ($data['old_loginid'] = $this->id2name($data['account_id']))) && - $data['account_lid'] != $data['old_loginid'] && - ($data['account_dn'] = $this->id2name($data['account_id'], 'account_dn'))) + // if account_id is given and bigger then 1000, set it to facilitate migration + if (empty($data['account_id']) || $data['account_id'] < Ads::MIN_ACCOUNT_ID) { - $params = array( - $data['account_type'] !== 'g' ? 'users/user' : 'groups/group', 'modify', - '--binddn', $config['ldap_root_dn'], - '--bindpwd', 5=>$config['ldap_root_pw'], - '--dn', $data['account_dn'], - '--set', ($data['account_type'] !== 'g' ? 'username' : 'name').'='.$data['account_lid'], - ); - $cmd = self::DIRECTORY_MANAGER_BIN.' '.implode(' ', array_map('escapeshellarg', $params)); - if (isset($ssh)) $cmd = $ssh.' bash -c "\\"'.$cmd.'\\""'; - $output_arr = $ret = $matches = null; - exec($cmd, $output_arr, $ret); - $output = implode("\n", $output_arr); - if ($ret || !preg_match('/^Object modified: ((uid|cn)=.*)$/mui', $output, $matches)) - { - $params[5] = '********'; // mask out password! - $cmd = self::DIRECTORY_MANAGER_BIN.' '.implode(' ', array_map('escapeshellarg', $params)); - throw new Api\Exception\WrongUserinput($cmd."\nreturned\n".$output); - } - $data['account_dn'] = $data['account_type'] !== 'g' ? $matches[1] : - // duno why but directory-manager returns old dn for groups ... - preg_replace('/^cn=[^,]+,/', 'cn='.$data['account_lid'].',', $data['account_dn']); + unset($data['account_id']); + } + + $data['account_dn'] = $udm->createUser($data); + $data['account_id'] = $this->name2id($data['account_lid'], 'account_lid', 'u'); + } + // create new groups with given account_id via directory-manager too, to be able to set the RID + elseif($data['account_type'] === 'g' && !empty($data['account_id']) && + $data['account_id'] >= Ads::MIN_ACCOUNT_ID && !$this->id2name($data['account_id'])) + { + $data['account_dn'] = $udm->createGroup($data); + $data['account_id'] = $this->name2id($data['account_lid'], 'account_lid', 'g'); + } + elseif($data['account_id'] && ($data['old_loginid'] || ($data['old_loginid'] = $this->id2name($data['account_id']))) && + $data['account_lid'] != $data['old_loginid'] && + ($data['account_dn'] = $this->id2name($data['account_id'], 'account_dn'))) + { + if ($data['account_type'] !== 'g') + { + $data['account_dn'] = $udm->updateUser($data['account_dn'], $data); + } + else + { + $data['account_dn'] = $udm->updateGroup($data['account_dn'], $data); } } - //else error_log(__METHOD__."() ".self::DIRECTORY_MANAGER_BIN." is NOT available!"); return parent::save($data); } @@ -246,96 +157,50 @@ class Univention extends Ldap } /** - * Check if our function depending on an external binary is available - * - * @return boolean - */ - public static function available() - { - //return true; - return file_exists(self::DIRECTORY_MANAGER_BIN) && is_executable(self::DIRECTORY_MANAGER_BIN); - } - - /** - * changes password in LDAP - * - * If $old_passwd is given, the password change is done binded as user and NOT with the - * "root" dn given in the configurations. + * Change password via UDM to update all hashes supported by Univention * * @param string $old_passwd must be cleartext or empty to not to be checked * @param string $new_passwd must be cleartext * @param int $account_id account id of user whose passwd should be changed * @param boolean $update_lastchange =true * @return boolean true if password successful changed, false otherwise + * @throws Univention\UdmException on error */ function change_password($old_passwd, $new_passwd, $account_id=0, $update_lastchange=true) { - if (!self::available()) - { - return false; - } - if (!$account_id) - { - $username = $GLOBALS['egw_info']['user']['account_lid']; - } - else - { - $username = Api\Translation::convert($GLOBALS['egw']->accounts->id2name($account_id), - Api\Translation::charset(),'utf-8'); - } - if ($this->debug) error_log(__METHOD__."('$old_passwd','$new_passwd',$account_id, $update_lastchange) username='$username'"); - - $filter = str_replace(array('%user','%domain'),array($username,$GLOBALS['egw_info']['user']['domain']), - $GLOBALS['egw_info']['server']['ldap_search_filter'] ? $GLOBALS['egw_info']['server']['ldap_search_filter'] : '(uid=%user)'); - - $ds = $ds_admin = Api\Ldap::factory(); - $sri = ldap_search($ds, $GLOBALS['egw_info']['server']['ldap_context'], $filter); - $allValues = ldap_get_entries($ds, $sri); - - if ($update_lastchange) - { - // ToDo: $entry['shadowlastchange'] = round((time()-date('Z')) / (24*3600)); - } - - $dn = $allValues[0]['dn']; + $dn = $this->id2name($account_id ? $account_id : $GLOBALS['egw_info']['user']['account_id'], 'account_dn'); + if ($this->debug) error_log(__METHOD__."('$old_passwd','$new_passwd',$account_id, $update_lastchange) db='$dn'"); if($old_passwd) // if old password given (not called by admin) --> bind as that user to change the pw { try { - $ds = Api\Ldap::factory(true, '', $dn, $old_passwd); + Api\Ldap::factory(true, '', $dn, $old_passwd); } catch (Api\Exception\NoPermission $e) { unset($e); return false; // wrong old user password } } - $ssh = null;//'/usr/bin/ssh -o "UserKnownHostsFile=/dev/null" -o "StrictHostKeyChecking=no" -i /var/lib/egroupware/id_rsa root@10.44.22.194'; + $config = $this->frontend->config && $this->frontend->config['ldap_context'] ? $this->frontend->config : $GLOBALS['egw_info']['server']; - $params = array( - 'users/user','modify', - '--binddn', $config['ldap_root_dn'], - '--bindpwd', 5=>$config['ldap_root_pw'], - '--dn', $dn, - '--set', 'password='.$new_passwd, - ); + $udm = new Univention\Udm($config); + + $data = [ + 'account_passwd' => $new_passwd + ]; if ($old_passwd) { - $params[] = '--set'; - $params[] = 'pwdChangeNextLogin=0'; + $data['pwdChangeNextLogin'] = false; } - $cmd = self::DIRECTORY_MANAGER_BIN.' '.implode(' ', array_map('escapeshellarg', $params)); - if (isset($ssh)) $cmd = $ssh.' bash -c "\\"'.$cmd.'\\""'; - $output_arr = $ret = $matches = null; - exec($cmd, $output_arr, $ret); - $output = implode("\n", $output_arr); - if ($ret || !preg_match('/^Object modified: ((uid|cn)=.*)$/mui', $output, $matches)) + if ($update_lastchange) { - $params[5] = '********'; // mask out password! - $cmd = self::DIRECTORY_MANAGER_BIN.' '.implode(' ', array_map('escapeshellarg', $params)); - throw new Api\Exception\WrongUserinput($cmd."\nreturned\n".$output); + // ToDo: $entry['shadowlastchange'] = round((time()-date('Z')) / (24*3600)); } + + $udm->updateUser($dn, $data); + if($old_passwd) // if old password given (not called by admin) update the password in the session { // using time() is sufficient to represent the current time, we do not need the timestamp written to the storage diff --git a/api/src/Accounts/Univention/Udm.php b/api/src/Accounts/Univention/Udm.php new file mode 100644 index 0000000000..04fd719e5a --- /dev/null +++ b/api/src/Accounts/Univention/Udm.php @@ -0,0 +1,348 @@ + + * + * @link https://www.univention.com/blog-en/2019/07/udm-rest-api-beta-version-released/ + * + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package api + * @subpackage accounts + */ + +namespace EGroupware\Api\Accounts\Univention; + +use EGroupware\Api; + +/** + * Univention UDM REST Api + * + * @todo Use just UDM instead of still calling ldap/parent + */ +class Udm +{ + /** + * Config to use + * + * @var array $config + */ + protected $config; + + /** + * Hostname of master, derived from ldap_host + * + * @var string + */ + protected $host; + + /** + * Username, derived from ldap_root_dn + * + * @var string + */ + protected $user; + + /** + * Udm url prefix, prepend to relative path like 'users/user' + */ + const PREFIX = '/univention/udm/'; + + /** + * Log webservice-calls to error_log + */ + const DEBUG = true; + + /** + * Constructor + * + * @param array $config =null config to use, default $GLOBALS['egw_info']['server'] + * @throws Api\Exception\WrongParameter for missing LDAP config + */ + public function __construct(array $config=null) + { + $this->config = isset($config) ? $config : $GLOBALS['egw_info']['server']; + + $this->host = parse_url($this->config['ldap_host'], PHP_URL_HOST); + if (empty($this->host)) + { + throw new Api\Exception\WrongParameter ("Univention needs 'ldap_host' configured!"); + } + $matches = null; + if (!preg_match('/^(cn|uid)=([^,]+),/i', $this->config['ldap_root_dn'], $matches)) + { + throw new Api\Exception\WrongParameter ("Univention needs 'ldap_rood_dn' configured!"); + } + $this->user = $matches[2]; + } + + /** + * Call UDM REST Api + * + * @param string $_path path to call, if relative PREFIX is prepended eg. 'users/user' + * @param string $_method ='GET' + * @param array $_payload =[] payload to send + * @param array& $headers =[] on return response headers + * @param string $if_match =null etag for If-Match header + * @param boolean $return_dn =false return DN of Location header + * @throws Exception on error + * @return array|string decoded JSON or DN for $return_DN === true + * @throws UdmCantConnect for connection errors or JSON decoding errors + * @throws UdmError for returned JSON error object + * @throws UdmMissingLocation for missing Location header with DN ($return_dn === true) + */ + protected function call($_path, $_method='GET', array $_payload=[], &$headers=[], $if_match=null, $return_dn=false) + { + $curl = curl_init(); + + // fix error: Request argument "policies" is not a "dict" (PHP encodes empty arrays as array, not object) + /*if (array_key_exists('policies', $_payload) && empty($_payload['policies'])) + { + $_payload['policies'] = new \stdClass(); // force "policies": {} + }*/ + + $headers = []; + $curlOpts = [ + CURLOPT_URL => 'https://'.$this->host.($_path[0] !== '/' ? self::PREFIX : '').$_path, + CURLOPT_USERPWD => $this->user.':'.$this->config['ldap_root_pw'], + //CURLOPT_SSL_VERIFYHOST => 2, // 0: to disable certificate check + CURLOPT_HTTPHEADER => [ + 'Accept: application/json', + ], + CURLOPT_CUSTOMREQUEST => $_method, + CURLOPT_RETURNTRANSFER => 1, + //CURLOPT_FOLLOWLOCATION => 1, + CURLOPT_TIMEOUT => 1, + CURLOPT_VERBOSE => 1, + CURLOPT_HEADERFUNCTION => + function($curl, $header) use (&$headers) + { + $len = strlen($header); + $header = explode(':', $header, 2); + if (count($header) < 2) + { + $headers[] = $header[0]; // http status + return $len; + } + $name = strtolower(trim($header[0])); + if (!array_key_exists($name, $headers)) + { + $headers[$name] = trim($header[1]); + } + else + { + $headers[$name] = [$headers[$name]]; + $headers[$name][] = trim($header[1]); + } + unset($curl); // not used, but required by function signature + return $len; + }, + ]; + if (isset($if_match)) + { + $curlOpts[CURLOPT_HTTPHEADER][] = 'If-Match: '.$if_match; + } + switch($_method) + { + case 'PUT': + case 'POST': + $curlOpts[CURLOPT_HTTPHEADER][] = 'Content-Type: application/json'; + $curlOpts[CURLOPT_POSTFIELDS] = json_encode($_payload, JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE); + break; + + case 'GET': + default: + if ($_payload) + { + $curlOpts[CURLOPT_URL] .= '?'. http_build_query($_payload); + } + break; + } + curl_setopt_array($curl, $curlOpts); + $response = curl_exec($curl); + + if (!$response || !($json = json_decode($response, true)) && json_last_error()) + { + $info = curl_getinfo($curl); + curl_close($curl); + error_log(__METHOD__."($_path, $_method, ".json_encode($_payload, JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE).") returned $response, headers=".json_encode($headers, JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE).", curl_getinfo()=".json_encode($info, JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE)); + throw new UdmCantConnect("Error contacting Univention UDM REST Api ($_path)".($response ? ': '.json_last_error() : '')); + } + curl_close($curl); + if (!empty($json['error'])) + { + error_log(__METHOD__."($_path, $_method, ".json_encode($_payload, JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE).") returned $response, headers=".json_encode($headers, JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE)); + throw new UdmError("UDM REST Api ($_path): ".(empty($json['error']['message']) ? $response : $json['error']['message']), $json['error']['code']); + } + if (self::DEBUG) error_log(__METHOD__."($_path, $_method, ".json_encode($_payload, JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE).") returned $response, headers=".json_encode($headers, JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE)); + + if ($return_dn) + { + $matches = null; + if (!isset($headers['location']) || !preg_match('|/([^/]+)$|', $headers['location'], $matches)) + { + throw new UdmMissingLocation("UDM REST Api ($_path) did not return Location header!"); + } + return urldecode($matches[1]); + } + return $json; + } + + /** + * Create a user + * + * @param array $data + * @throws Exception on error-message + * @return string with DN of new user + */ + public function createUser(array $data) + { + // set default values + $payload = $this->user2udm($data, $this->call('users/user/add')['entry']); + + $payload['superordinate'] = null; + $payload['position'] = $this->config['ldap_context']; + + $headers = []; + return $this->call('users/user/', 'POST', $payload, $headers, null, true); + } + + /** + * Update a user + * + * @param string $dn dn of user to update + * @param array $data + * @return string with dn + * @throws Exception on error-message + */ + public function updateUser($dn, array $data) + { + // set existing values + $get_headers = []; + $payload = $this->user2udm($data, $this->call('users/user/'.urlencode($dn), 'GET', [], $get_headers)['entry']); + + $headers = []; + return $this->call('users/user/', 'PUT', $payload, $headers, $get_headers['etag'], true); + } + + /** + * Copy EGroupware user-values to UDM ones + * + * @param array $data + * @param array $payload + * @return array with updated payload + */ + protected function user2udm(array $data, array $payload) + { + foreach([ + 'account_lid' => 'username', + 'account_passwd' => 'password', + 'account_lastname' => 'lastname', + 'account_firstname' => 'firstname', + 'account_id' => ['uidNumber', 'sambaRID'], + 'account_email' => 'mailPrimaryAddress', + ] as $egw => $names) + { + if (!empty($data[$egw])) + { + foreach((array)$names as $name) + { + if (!array_key_exists($name, $payload['properties'])) + { + throw new \Exception ("No '$name' in properties: ".json_encode($payload['properties'])); + } + $payload['properties'][$name] = $data[$egw]; + } + } + } + + if (!empty($data['account_email'])) + { + // we need to set mailHomeServer, so mailbox gets created for Dovecot + // get_default() does not work for Adminstrator, try acc_id=1 instead + // if everything fails try ldap host / master ... + try { + if (!($account = Api\Mail\Account::get_default(false, false, false))) + { + $account = Api\Mail\Account::read(1); + } + $hostname = $account->acc_imap_host; + } + catch(\Exception $e) { + unset($e); + } + if (empty($hostname)) $hostname = $this->host; + $payload['properties']['mailHomeServer'] = $hostname; + } + + return $payload; + } + + /** + * Create a group + * + * @param array $data + * @throws Exception on error-message + * @return string with DN of new user + */ + public function createGroup(array $data) + { + // set default values + $payload = $this->group2udm($data, $this->call('groups/group/add')['entry']); + + $payload['superordinate'] = null; + $payload['position'] = empty($this->config['ldap_group_context']) ? $this->config['ldap_context'] : $this->config['ldap_group_context']; + + $headers = []; + return $this->call('groups/group/', 'POST', $payload, $headers, null, true); + } + + /** + * Update a group + * + * @param string $dn dn of group to update + * @param array $data + * @throws Exception on error-message + * @return string with DN of new user + */ + public function updateGroup($dn, array $data) + { + // set existing values + $get_headers = []; + $payload = $this->user2udm($data, $this->call('groups/group/'.urlencode($dn), 'GET', [], $get_headers)['entry']); + + $headers = []; + return $this->call('groups/group/', 'PUT', $payload, $headers, $get_headers['etag'], true); + } + + /** + * Copy EGroupware group values to UDM ones + * + * @param array $data + * @param array $payload + * @return array with updated payload + */ + protected function group2udm(array $data, array $payload) + { + foreach([ + 'account_lid' => 'name', + 'account_id' => ['gidNumber', 'sambaRID'], + ] as $egw => $names) + { + if (!empty($data[$egw])) + { + foreach((array)$names as $name) + { + if (!array_key_exists($name, $payload['properties'])) + { + throw new \Exception ("No '$name' in properties: ".json_encode($payload['properties'])); + } + $payload['properties'][$name] = $data[$egw]; + } + } + } + + return $payload; + } +} \ No newline at end of file diff --git a/api/src/Accounts/Univention/UdmCantConnect b/api/src/Accounts/Univention/UdmCantConnect new file mode 100644 index 0000000000..a907994bd9 --- /dev/null +++ b/api/src/Accounts/Univention/UdmCantConnect @@ -0,0 +1,26 @@ + + * + * @link https://www.univention.com/blog-en/2019/07/udm-rest-api-beta-version-released/ + * + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package api + * @subpackage accounts + */ + +namespace EGroupware\Api\Accounts\Univention; + +/** + * Cant connect to UDM Rest API or no JSON returned + */ +class UdmCantConnect extends UdmException +{ + public function __construct($msg = null, $code = 100, \Exception $previous = null) + { + parent::__construct($msg, $code, $previous); + } +} \ No newline at end of file diff --git a/api/src/Accounts/Univention/UdmError.php b/api/src/Accounts/Univention/UdmError.php new file mode 100644 index 0000000000..4de9d50626 --- /dev/null +++ b/api/src/Accounts/Univention/UdmError.php @@ -0,0 +1,26 @@ + + * + * @link https://www.univention.com/blog-en/2019/07/udm-rest-api-beta-version-released/ + * + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package api + * @subpackage accounts + */ + +namespace EGroupware\Api\Accounts\Univention; + +/** + * UDM Rest API returned a JSON error object + */ +class UdmError extends UdmException +{ + public function __construct($msg = null, $code = 100, \Exception $previous = null) + { + parent::__construct($msg, $code, $previous); + } +} \ No newline at end of file diff --git a/api/src/Accounts/Univention/UdmException.php b/api/src/Accounts/Univention/UdmException.php new file mode 100644 index 0000000000..49d9d41178 --- /dev/null +++ b/api/src/Accounts/Univention/UdmException.php @@ -0,0 +1,28 @@ + + * + * @link https://www.univention.com/blog-en/2019/07/udm-rest-api-beta-version-released/ + * + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package api + * @subpackage accounts + */ + +namespace EGroupware\Api\Accounts\Univention; + +use EGroupware\Api; + +/** + * UDM Rest API base for all exceptions thrown from Udm (exception WrongParameter from constructor for missing LDAP config) + */ +class UdmException extends Api\Exception +{ + public function __construct($msg = null, $code = 100, \Exception $previous = null) + { + parent::__construct($msg, $code, $previous); + } +} \ No newline at end of file diff --git a/api/src/Accounts/Univention/UdmMissingLocation.php b/api/src/Accounts/Univention/UdmMissingLocation.php new file mode 100644 index 0000000000..9e1f35c54e --- /dev/null +++ b/api/src/Accounts/Univention/UdmMissingLocation.php @@ -0,0 +1,26 @@ + + * + * @link https://www.univention.com/blog-en/2019/07/udm-rest-api-beta-version-released/ + * + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package api + * @subpackage accounts + */ + +namespace EGroupware\Api\Accounts\Univention; + +/** + * UDM Rest API returned no Location header + */ +class UdmMissingLocation extends UdmException +{ + public function __construct($msg = null, $code = 100, \Exception $previous = null) + { + parent::__construct($msg, $code, $previous); + } +} \ No newline at end of file