diff --git a/lam/HISTORY b/lam/HISTORY index ee01cf89..4dcbb324 100644 --- a/lam/HISTORY +++ b/lam/HISTORY @@ -1,5 +1,6 @@ March 2013 4.1 - updated EDU person module (RFE 3599128) + - Personal: allow management of user certificates (RFE 1753030) - fixed bugs: -> changed user and group size limits (3601649) diff --git a/lam/docs/manual-sources/howto.xml b/lam/docs/manual-sources/howto.xml index 120c1096..2aea7a6d 100644 --- a/lam/docs/manual-sources/howto.xml +++ b/lam/docs/manual-sources/howto.xml @@ -1605,7 +1605,16 @@ Have fun! - + User certificates can be uploaded and downloaded. LAM will + automatically convert PEM to DER format. + + + + + + + + LDAP attribute mappings @@ -1788,6 +1797,12 @@ Have fun! Job title + + userCertificate + + User certificates + + uid/userid diff --git a/lam/docs/manual-sources/images/mod_personal2.png b/lam/docs/manual-sources/images/mod_personal2.png new file mode 100644 index 00000000..e3aaf4b9 Binary files /dev/null and b/lam/docs/manual-sources/images/mod_personal2.png differ diff --git a/lam/lib/modules/inetOrgPerson.inc b/lam/lib/modules/inetOrgPerson.inc index 7ef4d977..d267d99e 100644 --- a/lam/lib/modules/inetOrgPerson.inc +++ b/lam/lib/modules/inetOrgPerson.inc @@ -78,7 +78,7 @@ class inetOrgPerson extends baseModule implements passwordService { $this->messages['uid'][1] = array('ERROR', _('Account %s:') . ' inetOrgPerson_userName', _('User name contains invalid characters. Valid characters are: a-z, A-Z, 0-9 and .-_ !')); $this->messages['uid'][3] = array('ERROR', _('Account %s:') . ' inetOrgPerson_userName', _('User name already exists!')); $this->messages['manager'][0] = array('ERROR', _('Account %s:') . ' inetOrgPerson_manager', _('This is not a valid DN!')); - $this->messages['photo'][0] = array('ERROR', _('No file selected.')); + $this->messages['file'][0] = array('ERROR', _('No file selected.')); $this->messages['businessCategory'][0] = array('ERROR', _('Business category'), _('Please enter a valid business category!')); $this->messages['businessCategory'][1] = array('ERROR', _('Account %s:') . ' inetOrgPerson_businessCategory', _('Please enter a valid business category!')); $this->messages['userPassword'][0] = array('ERROR', _('Account %s:') . ' posixAccount_password', _('Password contains invalid characters. Valid characters are:') . ' a-z, A-Z, 0-9 and #*,.;:_-+!%&/|?{[()]}=@$ §°!'); @@ -117,7 +117,7 @@ class inetOrgPerson extends baseModule implements passwordService { $return['attributes'] = array('uid', 'cn', 'employeeType', 'givenName', 'jpegPhoto', 'mail', 'manager', 'mobile', 'title', 'telephoneNumber', 'facsimileTelephoneNumber', 'street', 'postOfficeBox', 'postalCode', 'postalAddress', 'sn', 'userPassword', 'description', 'homePhone', 'roomNumber', 'businessCategory', 'l', 'st', 'physicalDeliveryOfficeName', - 'carLicense', 'departmentNumber', 'o', 'employeeNumber', 'initials', 'registeredAddress', 'labeledURI', 'ou'); + 'carLicense', 'departmentNumber', 'o', 'employeeNumber', 'initials', 'registeredAddress', 'labeledURI', 'ou', 'userCertificate;binary'); // self service search attributes $return['selfServiceSearchAttributes'] = array('uid', 'mail', 'cn', 'surname', 'givenName', 'employeeNumber'); // self service field settings @@ -127,11 +127,12 @@ class inetOrgPerson extends baseModule implements passwordService { 'postalCode' => _('Postal code'), 'postOfficeBox' => _('Post office box'), 'jpegPhoto' => _('Photo'), 'homePhone' => _('Home telephone number'), 'roomNumber' => _('Room number'), 'carLicense' => _('Car license'), 'location' => _('Location'), 'state' => _('State'), 'officeName' => _('Office name'), 'businessCategory' => _('Business category'), - 'departmentNumber' => _('Department'), 'initials' => _('Initials'), 'title' => _('Job title'), 'labeledURI' => _('Web site')); + 'departmentNumber' => _('Department'), 'initials' => _('Initials'), 'title' => _('Job title'), 'labeledURI' => _('Web site'), + 'userCertificate' => _('User certificates')); // possible self service read-only fields $return['selfServiceReadOnlyFields'] = array('firstName', 'lastName', 'mail', 'telephoneNumber', 'mobile', 'faxNumber', 'street', 'postalAddress', 'registeredAddress', 'postalCode', 'postOfficeBox', 'jpegPhoto', 'homePhone', 'roomNumber', 'carLicense', - 'location', 'state', 'officeName', 'businessCategory', 'departmentNumber', 'initials', 'title', 'labeledURI'); + 'location', 'state', 'officeName', 'businessCategory', 'departmentNumber', 'initials', 'title', 'labeledURI', 'userCertificate'); // profile elements $profileElements = array(); if (!$this->isBooleanConfigOptionSet('inetOrgPerson_hideInitials')) { @@ -313,6 +314,8 @@ class inetOrgPerson extends baseModule implements passwordService { $configContainerOptions->addElement(new htmlTableExtendedInputCheckbox('inetOrgPerson_hideInitials', false, _('Initials'), null, false)); $configContainerOptions->addNewLine(); $configContainerOptions->addElement(new htmlTableExtendedInputCheckbox('inetOrgPerson_hideLabeledURI', false, _('Web site'), null, false)); + $configContainerOptions->addElement(new htmlOutputText(' ')); + $configContainerOptions->addElement(new htmlTableExtendedInputCheckbox('inetOrgPerson_hideuserCertificate', false, _('User certificates'), null, false)); $configContainer->addElement($configContainerOptions, true); if (isset($_SESSION['conf_config'])) { // add password hash type if posixAccount is inactive @@ -842,6 +845,10 @@ class inetOrgPerson extends baseModule implements passwordService { "Headline" => _("Password"), "Text" => _("Please enter the password which you want to set for this account.") ), + 'userCertificate' => array( + "Headline" => _('User certificates'), + "Text" => _('These are the user\'s certificates.') + ), ); return $return; } @@ -1743,7 +1750,8 @@ class inetOrgPerson extends baseModule implements passwordService { if (!$this->isBooleanConfigOptionSet('inetOrgPerson_hideJobTitle') || !$this->isBooleanConfigOptionSet('inetOrgPerson_hideCarLicense') || !$this->isBooleanConfigOptionSet('inetOrgPerson_hideEmployeeType') || !$this->isBooleanConfigOptionSet('inetOrgPerson_hideBusinessCategory') - || !$this->isBooleanConfigOptionSet('inetOrgPerson_hideDepartments') || !$this->isBooleanConfigOptionSet('inetOrgPerson_hideManager')) { + || !$this->isBooleanConfigOptionSet('inetOrgPerson_hideDepartments') || !$this->isBooleanConfigOptionSet('inetOrgPerson_hideManager') + || !$this->isBooleanConfigOptionSet('inetOrgPerson_hideuserCertificate')) { $fieldContainer->addElement(new htmlSubTitle(_('Work details')), true); } @@ -1852,6 +1860,20 @@ class inetOrgPerson extends baseModule implements passwordService { $oHelp->alignment = htmlElement::ALIGN_TOP; $fieldContainer->addElement($oHelp, true); } + // user certificates + if (!$this->isBooleanConfigOptionSet('inetOrgPerson_hideuserCertificate')) { + $fieldContainer->addElement(new htmlOutputText(_('User certificates'))); + $userCertificateGroup = new htmlGroup(); + $userCertificateCount = 0; + if (isset($this->attributes['userCertificate;binary'])) { + $userCertificateCount = sizeof($this->attributes['userCertificate;binary']); + } + $userCertificateGroup->addElement(new htmlOutputText($userCertificateCount)); + $userCertificateGroup->addElement(new htmlSpacer('10px', null)); + $userCertificateGroup->addElement(new htmlAccountPageButton(get_class($this), 'userCertificate', 'manage', _('Manage'))); + $fieldContainer->addElement($userCertificateGroup); + $fieldContainer->addElement(new htmlHelpLink('userCertificate'), true); + } // manager if (!$this->isBooleanConfigOptionSet('inetOrgPerson_hideManager')) { $fieldContainer->addElement(new htmlOutputText(_('Manager'))); @@ -1922,7 +1944,7 @@ class inetOrgPerson extends baseModule implements passwordService { $this->attributes['jpegPhoto'][0] = $data; } else { - $messages[] = $this->messages['photo'][0]; + $messages[] = $this->messages['file'][0]; } return $messages; } @@ -2044,6 +2066,103 @@ class inetOrgPerson extends baseModule implements passwordService { return $return; } + /** + * Displays the certificate upload page. + * + * @return array meta HTML code + */ + function display_html_userCertificate() { + $container = new htmlTable(); + if (isset($this->attributes['userCertificate;binary'])) { + $table = new htmlTable(); + $table->colspan = 10; + for ($i = 0; $i < sizeof($this->attributes['userCertificate;binary']); $i++) { + $filename = 'userCertificate' . $_SESSION['ldap']->new_rand() . '.der'; + $out = @fopen(dirname(__FILE__) . '/../../tmp/' . $filename, "wb"); + fwrite($out, $this->attributes['userCertificate;binary'][$i]); + fclose ($out); + $path = '../../tmp/' . $filename; + $link = new htmlLink('', $path, '../../graphics/save.png'); + $link->setTargetWindow('_blank'); + $table->addElement($link); + $deleteButton = new htmlAccountPageButton(get_class($this), 'userCertificate', 'delete_' . $i, 'delete.png', true); + $deleteButton->setIconClass('deleteButton'); + $table->addElement($deleteButton); + if (function_exists('openssl_x509_parse')) { + $pem = @chunk_split(@base64_encode($this->attributes['userCertificate;binary'][$i]), 64, "\n"); + if (!empty($pem)) { + $pem = "-----BEGIN CERTIFICATE-----\n" . $pem . "-----END CERTIFICATE-----\n"; + $pemData = @openssl_x509_parse($pem); + $data = array(); + if (isset($pemData['serialNumber'])) { + $data[] = $pemData['serialNumber']; + } + if (isset($pemData['name'])) { + $data[] = $pemData['name']; + } + if (sizeof($data) > 0) { + $table->addElement(new htmlOutputText(implode(': ', $data))); + } + } + } + $table->addNewLine(); + } + $container->addElement($table, true); + $container->addElement(new htmlSpacer(null, '20px'), true); + } + $newGroup = new htmlGroup(); + $newGroup->addElement(new htmlOutputText(_('New user certificate'))); + $newGroup->addElement(new htmlSpacer('1px', null)); + $newGroup->addElement(new htmlInputFileUpload('userCertificateUpload')); + $newGroup->addElement(new htmlSpacer('1px', null)); + $uploadButton = new htmlAccountPageButton(get_class($this), 'userCertificate', 'submit', _('Upload')); + $uploadButton->setIconClass('upButton'); + $newGroup->addElement($uploadButton); + $container->addElement($newGroup, true); + $container->addElement(new htmlSpacer(null, '10px'), true); + $container->addElement(new htmlAccountPageButton(get_class($this), 'attributes', 'back', _('Back'))); + return $container; + } + + /** + * Sets a new certificate or deletes old ones. + */ + function process_userCertificate() { + $messages = array(); + if (isset($_POST['form_subpage_' . get_class($this) . '_userCertificate_submit'])) { + if ($_FILES['userCertificateUpload'] && ($_FILES['userCertificateUpload']['size'] > 0)) { + $handle = fopen($_FILES['userCertificateUpload']['tmp_name'], "r"); + $data = fread($handle, 10000000); + fclose($handle); + if (strpos($data, '-----BEGIN CERTIFICATE-----') === 0) { + $pemData = str_replace("\r", '', $data); + $pemData = explode("\n", $pemData); + array_shift($pemData); + $last = array_pop($pemData); + while (($last != '-----END CERTIFICATE-----') && sizeof($pemData) > 2) { + $last = array_pop($pemData); + } + $pemData = implode('', $pemData); + $data = base64_decode($pemData); + } + $this->attributes['userCertificate;binary'][] = $data; + } + else { + $messages[] = $this->messages['file'][0]; + } + } + elseif (isset($this->attributes['userCertificate;binary'])) { + for ($i = 0; $i < sizeof($this->attributes['userCertificate;binary']); $i++) { + if (isset($_POST['form_subpage_' . get_class($this) . '_userCertificate_delete_' . $i])) { + unset($this->attributes['userCertificate;binary'][$i]); + $this->attributes['userCertificate;binary'] = array_values($this->attributes['userCertificate;binary']); + break; + } + } + } + return $messages; + } + /** * Returns the PDF entries for this module. *