Compare commits

...

31 Commits

Author SHA1 Message Date
dependabot[bot]
00a53a8339 Bump ini from 1.3.5 to 1.3.8
Bumps [ini](https://github.com/isaacs/ini) from 1.3.5 to 1.3.8.
- [Release notes](https://github.com/isaacs/ini/releases)
- [Commits](https://github.com/isaacs/ini/compare/v1.3.5...v1.3.8)

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-05 16:55:00 +00:00
Alan Cell
253b298b0d Merge branch 'release/v29.0.0.OS' 2021-04-05 18:53:54 +02:00
Alan Cell
df554680c4 Sync changes v29.0.0 from IceHrmPro (https://icehrm.com/purchase-icehrmpro) 2021-04-05 18:52:23 +02:00
Gamonoid
92032cf1eb Update readme.md 2021-01-12 19:55:00 +01:00
Gamonoid
22cd81611d Add applity 2020-12-20 20:32:30 +01:00
Alan Cell
1a3e468458 Fix displaying wring generated profile image for in staff directory 2020-11-14 19:37:18 +01:00
Alan Cell
88962d4380 Merge branch 'release/v28.2.0.OS' 2020-11-13 02:46:51 +01:00
Alan Cell
3b1285aeaf Merge tag 'v28.2.0.OS' into develop
v28.2.0.OS
2020-11-13 02:46:51 +01:00
Alan Cell
b73e244865 Production build, release notes and version update for v28.2.0 2020-11-13 02:45:48 +01:00
Alan Cell
5f050282f0 Update readme and change delete label for employees 2020-11-13 02:43:40 +01:00
Alan Cell
f1dcc6b6a0 Merge branch 'release/v28.1.1.OS' 2020-11-07 12:52:02 +01:00
Alan Cell
e5eccf32a7 Merge tag 'v28.1.1.OS' into develop
v28.1.1.OS
2020-11-07 12:52:02 +01:00
Alan Cell
b6c0256b49 Update production build 2020-11-07 12:37:11 +01:00
Alan Cell
e31bf5a4b9 Fix api issues and production build 2020-11-07 12:25:12 +01:00
Alan Cell
06a3172a38 Merge branch 'release/v28.1.1.OS' 2020-11-07 11:46:23 +01:00
Alan Cell
e74ca00902 Merge tag 'v28.1.1.OS' into develop
v28.1.1.OS
2020-11-07 11:46:23 +01:00
Alan Cell
28aa16f35c Update version 2020-11-07 11:45:05 +01:00
Alan Cell
fb3b5b562e Merge branch 'release/v28.1.1.OS' 2020-11-07 11:41:24 +01:00
Alan Cell
8663a7aff1 Merge tag 'v28.1.1.OS' into develop
v28.1.1.OS
2020-11-07 11:41:24 +01:00
Alan Cell
e9baf45d7c Production build + fixing code style issues 2020-11-06 20:08:07 +01:00
Alan Cell
2abe52963f Imitate the REST api using url parameter based implementation
Reason for this implementation is some clients having trouble configuring the rest api either due to not having proper access to the webserver in a shared hosting environment or security restrictions. But still from icehrm frontend we need to consume the backend rest api.
2020-11-06 20:00:53 +01:00
Alan Cell
da55c7a2d2 Ability to delete files from S3 2020-11-06 19:41:56 +01:00
Alan Cell
5cd7963f6f Implement password change for employee profile 2020-11-06 19:38:48 +01:00
Alan Cell
d986a2b5bb Fix issue: employee not be selected when filtering employee documents 2020-11-06 19:32:52 +01:00
Alan Cell
1ee4fb4ba1 Update vagrant file 2020-11-06 19:23:05 +01:00
Alan Cell
b06780c466 Fix issue with time-sheets module nit being able to load projects 2020-11-06 18:51:46 +01:00
Alan Cell
5ec497e11d Upgrade vagrant config 2020-11-06 18:41:39 +01:00
Alan Cell
df3b6e968a Update vagrant 2020-11-06 09:46:20 +01:00
Thilina Pituwala
3ceb427479 Fix ci build 2020-11-01 10:17:04 +01:00
Thilina Pituwala
d3b4748cba Remove in lage in readme 2020-11-01 02:37:33 +01:00
Thilina Pituwala
2a9e65d8a8 Remove php5.6 from travis 2020-11-01 02:31:57 +01:00
137 changed files with 9629 additions and 31835 deletions

View File

@@ -14,7 +14,6 @@ install:
script: ant build-ci
language: php
php:
- '5.6'
- '7.0'
- '7.1'
- '7.2'

16
Vagrantfile vendored
View File

@@ -3,8 +3,6 @@ Vagrant.configure("2") do |config|
config.vm.box_version = "1.0.0"
config.vm.network "private_network", ip: "192.168.10.12"
config.vm.synced_folder ".", "/vagrant", type: "nfs"
config.vm.synced_folder "./deployment/vagrant/sites-available", "/etc/nginx/sites-enabled", type: "nfs"
config.vm.synced_folder "./deployment/vagrant/ssl", "/etc/nginx/ssl", type: "nfs"
config.vm.provider "virtualbox" do |vb|
vb.memory = "1024"
@@ -13,9 +11,17 @@ Vagrant.configure("2") do |config|
end
config.vm.provision "shell", inline: <<-SHELL
sed -i 's/PasswordAuthentication no/PasswordAuthentication yes/g' /etc/ssh/sshd_config
systemctl restart sshd.service
sudo service nginx restart
sudo rm /etc/nginx/ssl/icehrm.*
sudo ln -s /vagrant/deployment/vagrant/ssl/icehrm.crt /etc/nginx/ssl/icehrm.crt
sudo ln -s /vagrant/deployment/vagrant/ssl/icehrm.key /etc/nginx/ssl/icehrm.key
sudo rm /etc/nginx/sites-enabled/default
sudo ln -s /vagrant/deployment/vagrant/sites-available/default /etc/nginx/sites-enabled/default
sed -i 's/PasswordAuthentication no/PasswordAuthentication yes/g' /etc/ssh/sshd_config
sudo service nginx restart
sudo chmod 755 -R /var/log
SHELL
config.vm.hostname = "icehrm.os"

View File

@@ -17,6 +17,13 @@ if($group == 'admin' || $group == 'modules'){
$name = str_replace("..","",$name);
$name = str_replace("/","",$name);
include APP_BASE_PATH.'/'.$group.'/'.$name.'/index.php';
}else if ($group == 'extension'){
$name = str_replace("..","",$name);
$name = str_replace("/","",$name);
$moduleName = $name;
$moduleGroup = 'extensions';
$extensionIndex = APP_BASE_PATH.'/../extensions/'.$name.'/web/index.php';
include $extensionIndex;
}else{
exit();
}
}

View File

@@ -1,3 +1,3 @@
<?php
include ('config.php');
include (APP_BASE_PATH.'rest.php');
include (APP_BASE_PATH.'rest.php');

1
bin/export-plural-rules Symbolic link
View File

@@ -0,0 +1 @@
../core/lib/composer/vendor/gettext/languages/bin/export-plural-rules

1
bin/markdown Symbolic link
View File

@@ -0,0 +1 @@
../core/lib/composer/vendor/cebe/markdown/bin/markdown

1
bin/pdepend Symbolic link
View File

@@ -0,0 +1 @@
../core/lib/composer/vendor/pdepend/pdepend/src/bin/pdepend

1
bin/phpcb Symbolic link
View File

@@ -0,0 +1 @@
../core/lib/composer/vendor/mayflower/php-codebrowser/bin/phpcb

1
bin/phpcbf Symbolic link
View File

@@ -0,0 +1 @@
../core/lib/composer/vendor/squizlabs/php_codesniffer/scripts/phpcbf

1
bin/phpcpd Symbolic link
View File

@@ -0,0 +1 @@
../core/lib/composer/vendor/sebastian/phpcpd/composer/bin/phpcpd

1
bin/phpcs Symbolic link
View File

@@ -0,0 +1 @@
../core/lib/composer/vendor/squizlabs/php_codesniffer/scripts/phpcs

1
bin/phploc Symbolic link
View File

@@ -0,0 +1 @@
../core/lib/composer/vendor/phploc/phploc/phploc

1
bin/phpmd Symbolic link
View File

@@ -0,0 +1 @@
../core/lib/composer/vendor/phpmd/phpmd/src/bin/phpmd

1
bin/phpunit Symbolic link
View File

@@ -0,0 +1 @@
../core/lib/composer/vendor/phpunit/phpunit/phpunit

1
bin/release Symbolic link
View File

@@ -0,0 +1 @@
../core/lib/composer/vendor/consolidation/self-update/scripts/release

1
bin/robo Symbolic link
View File

@@ -0,0 +1 @@
../core/lib/composer/vendor/consolidation/robo/robo

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project name="icehrm" default="build">
<!-- By default, we assume all tools to be on the $PATH -->
<property name="toolsdir" value="${basedir}/tools/"/>
<property name="toolsdir" value="${basedir}/bin/"/>
<property name="destination" value="${basedir}/build/app"/>
<property name="testdir" value="${basedir}/build/test"/>
<property name="origin" value="${basedir}"/>

View File

@@ -4,48 +4,48 @@
Developer: Thilina Hasantha (http://lk.linkedin.com/in/thilinah | https://github.com/thilinah)
*/
use Classes\PermissionManager;
use Clients\Common\Model\Client;
$moduleName = 'clients';
$moduleGroup = 'admin';
define('MODULE_PATH',dirname(__FILE__));
include APP_BASE_PATH.'header.php';
include APP_BASE_PATH.'modulejslibs.inc.php';
define('MODULE_PATH', dirname(__FILE__));
include APP_BASE_PATH . 'header.php';
include APP_BASE_PATH . 'modulejslibs.inc.php';
?><div class="span9">
<ul class="nav nav-tabs" id="modTab" style="margin-bottom:0px;margin-left:5px;border-bottom: none;">
<li class="active"><a id="tabClient" href="#tabPageClient"><?=t('Clients')?></a></li>
</ul>
<ul class="nav nav-tabs" id="modTab" style="margin-bottom:0px;margin-left:5px;border-bottom: none;">
<li class="active"><a id="tabClient" href="#tabPageClient"><?= t('Clients') ?></a></li>
</ul>
<div class="tab-content">
<div class="tab-pane active" id="tabPageClient">
<div id="Client" class="reviewBlock" data-content="List" style="padding-left:5px;">
</div>
<div id="ClientForm" class="reviewBlock" data-content="Form" style="padding-left:5px;display:none;">
</div>
</div>
</div>
<div class="tab-content">
<div class="tab-pane active" id="tabPageClient">
<div id="ClientTable" class="reviewBlock" data-content="List" style="padding-left:5px;"></div>
<div id="ClientForm"></div>
<div id="ClientFilterForm"></div>
</div>
</div>
</div>
<script>
var modJsList = [];
<?php
$moduleData = [
'user_level' => $user->user_level,
'permissions' => [
'Client' => PermissionManager::checkGeneralAccess(new Client()),
]
];
?>
<script>
var data = <?= json_encode($moduleData) ?>;
var modJsList = [];
modJsList['tabClient'] = new ClientAdapter('Client','Client');
modJsList['tabClient'] = new ClientAdapter('Client');
<?php if(isset($modulePermissions['perm']['Add Clients']) && $modulePermissions['perm']['Add Clients'] == "No"){?>
modJsList['tabClient'].setShowAddNew(false);
<?php }?>
modJsList.tabClient.setObjectTypeName('Client');
modJsList.tabClient.setAccess(data.permissions.Client);
modJsList.tabClient.setDataPipe(new IceDataPipe(modJsList.tabClient));
modJsList.tabClient.setRemoteTable(true);
<?php if(isset($modulePermissions['perm']['Delete Clients']) && $modulePermissions['perm']['Delete Clients'] == "No"){?>
modJsList['tabClient'].setShowDelete(false);
<?php }?>
<?php if(isset($modulePermissions['perm']['Edit Clients']) && $modulePermissions['perm']['Edit Clients'] == "No"){?>
modJsList['tabClient'].setShowSave(false);
<?php }?>
var modJs = modJsList['tabClient'];
</script>
<?php include APP_BASE_PATH.'footer.php';?>
var modJs = modJsList['tabClient'];
</script>
<?php include APP_BASE_PATH . 'footer.php'; ?>

View File

@@ -4,6 +4,7 @@
Developer: Thilina Hasantha (http://lk.linkedin.com/in/thilinah | https://github.com/thilinah)
*/
use Classes\BaseService;
use Classes\PermissionManager;
use Company\Common\Model\CompanyStructure;
@@ -60,6 +61,7 @@ path.link {
<?php
$moduleData = [
'user_level' => $user->user_level,
'customFields' => BaseService::getInstance()->getCustomFields("CompanyStructure"),
'permissions' => [
'CompanyStructure' => PermissionManager::checkGeneralAccess(new CompanyStructure()),
]

View File

@@ -0,0 +1,42 @@
<?php
use Classes\BaseService;
use Classes\PermissionManager;
use FieldNames\Common\Model\CustomField;
$moduleName = 'assets';
$moduleGroup = 'admin';
define('MODULE_PATH',dirname(__FILE__));
include APP_BASE_PATH.'header.php';
include APP_BASE_PATH.'modulejslibs.inc.php';
$modelClasses = BaseService::getInstance()->getCustomFieldClassMap();
?><div class="span9">
<ul class="nav nav-tabs" id="modTab" style="margin-bottom:0px;margin-left:5px;border-bottom: none;">
<li class="active"><a id="tabCustomField" href="#tabPageCustomField"><?=t('Custom Fields')?></a></li>
</ul>
<div class="tab-content">
<div class="tab-pane active" id="tabPageCustomField">
<div id="CustomFieldTable" class="reviewBlock" data-content="List"></div>
<div id="CustomFieldForm"></div>
<div id="CustomFieldFilterForm"></div>
</div>
</div>
</div>
<div id="dataGroup"></div>
<?php
$moduleData = [
'user_level' => $user->user_level,
'types' => $modelClasses,
'permissions' => [
'CustomField' => PermissionManager::checkGeneralAccess(new CustomField()),
]
];
?>
<script>
initAdminCustomFields(<?=json_encode($moduleData)?>);
</script>
<?php include APP_BASE_PATH.'footer.php';?>

View File

@@ -0,0 +1,12 @@
{
"label": "Custom Fields",
"menu": "Admin",
"order": "892",
"icon": "fa-code",
"user_levels": [
"Admin"
],
"permissions": [],
"model_namespace": "\\FieldNames\\Common\\Model",
"manager": "\\CustomField\\Admin\\Api\\CustomFieldAdminManager"
}

View File

@@ -13,41 +13,25 @@ include APP_BASE_PATH.'modulejslibs.inc.php';
<ul class="nav nav-tabs" id="modTab" style="margin-bottom:0px;margin-left:5px;border-bottom: none;">
<li class="active"><a id="tabEmployeeFieldName" href="#tabPageEmployeeFieldName"><?=t('Employee Field Names')?></a></li>
<li><a id="tabEmployeeCustomField" href="#tabPageEmployeeCustomField"><?=t('Employee Custom Fields')?></a></li>
</ul>
</ul>
<div class="tab-content">
<div class="tab-pane active" id="tabPageEmployeeFieldName">
<div id="EmployeeFieldName" class="reviewBlock" data-content="List" style="padding-left:5px;">
</div>
<div id="EmployeeFieldNameForm" class="reviewBlock" data-content="Form" style="padding-left:5px;display:none;">
</div>
</div>
<div class="tab-pane" id="tabPageEmployeeCustomField">
<div id="EmployeeCustomField" class="reviewBlock" data-content="List" style="padding-left:5px;">
</div>
<div id="EmployeeCustomFieldForm" class="reviewBlock" data-content="Form" style="padding-left:5px;display:none;">
</div>
</div>
</div>
</div>
<script>
var modJsList = new Array();
var modJsList = [];
modJsList['tabEmployeeFieldName'] = new FieldNameAdapter('FieldNameMapping','EmployeeFieldName',{"type":"Employee"});
modJsList['tabEmployeeFieldName'].setRemoteTable(true);
modJsList['tabEmployeeFieldName'].setShowDelete(false);
modJsList['tabEmployeeFieldName'].setShowAddNew(false);
modJsList['tabEmployeeCustomField'] = new CustomFieldAdapter('CustomField','EmployeeCustomField',{"type":"Employee"},"display_order desc");
modJsList['tabEmployeeCustomField'].setRemoteTable(true);
modJsList['tabEmployeeCustomField'].setTableType("Employee");
var modJs = modJsList['tabEmployeeFieldName'];

View File

@@ -4,71 +4,65 @@
Developer: Thilina Hasantha (http://lk.linkedin.com/in/thilinah | https://github.com/thilinah)
*/
use Classes\PermissionManager;
use Projects\Common\Model\Project;
use Projects\Common\Model\EmployeeProject;
$moduleName = 'projects';
$moduleGroup = 'admin';
define('MODULE_PATH',dirname(__FILE__));
include APP_BASE_PATH.'header.php';
include APP_BASE_PATH.'modulejslibs.inc.php';
define('MODULE_PATH', dirname(__FILE__));
include APP_BASE_PATH . 'header.php';
include APP_BASE_PATH . 'modulejslibs.inc.php';
?><div class="span9">
<ul class="nav nav-tabs" id="modTab" style="margin-bottom:0px;margin-left:5px;border-bottom: none;">
<li class="active"><a id="tabProject" href="#tabPageProject"><?=t('Projects')?></a></li>
<li><a id="tabEmployeeProject" href="#tabPageEmployeeProject"><?=t('Employee Projects')?></a></li>
</ul>
<div class="tab-content">
<div class="tab-pane active" id="tabPageProject">
<div id="Project" class="reviewBlock" data-content="List" style="padding-left:5px;">
<ul class="nav nav-tabs" id="modTab" style="margin-bottom:0px;margin-left:5px;border-bottom: none;">
<li class="active"><a id="tabProject" href="#tabPageProject"><?= t('Projects') ?></a></li>
<li><a id="tabEmployeeProject" href="#tabPageEmployeeProject"><?= t('Employee Projects') ?></a></li>
</ul>
</div>
<div id="ProjectForm" class="reviewBlock" data-content="Form" style="padding-left:5px;display:none;">
</div>
</div>
<div class="tab-pane" id="tabPageEmployeeProject">
<div id="EmployeeProject" class="reviewBlock" data-content="List" style="padding-left:5px;">
</div>
<div id="EmployeeProjectForm" class="reviewBlock" data-content="Form" style="padding-left:5px;display:none;">
</div>
</div>
</div>
<div class="tab-content">
<div class="tab-pane active" id="tabPageProject">
<div id="ProjectTable" class="reviewBlock" data-content="List" style="padding-left:5px;"></div>
<div id="ProjectForm"></div>
<div id="ProjectFilterForm"></div>
</div>
<div class="tab-pane" id="tabPageEmployeeProject">
<div id="EmployeeProjectTable" class="reviewBlock" data-content="List" style="padding-left:5px;"></div>
<div id="EmployeeProjectForm"></div>
<div id="EmployeeProjectFilterForm"></div>
</div>
</div>
</div>
<?php
$moduleData = [
'user_level' => $user->user_level,
'permissions' => [
'Project' => PermissionManager::checkGeneralAccess(new Project()),
'EmployeeProject' => PermissionManager::checkGeneralAccess(new EmployeeProject()),
]
];
?>
<script>
var modJsList = [];
var modJsList = [];
var data = <?= json_encode($moduleData) ?>;
modJsList['tabProject'] = new ProjectAdapter('Project', 'Project');
modJsList.tabProject.setObjectTypeName('Project');
modJsList.tabProject.setAccess(data.permissions.Project);
modJsList.tabProject.setDataPipe(new IceDataPipe(modJsList.tabProject));
modJsList.tabProject.setRemoteTable(true);
modJsList['tabProject'] = new ProjectAdapter('Project','Project');
<?php if(isset($modulePermissions['perm']['Add Projects']) && $modulePermissions['perm']['Add Projects'] == "No"){?>
modJsList['tabProject'].setShowAddNew(false);
<?php }?>
<?php if(isset($modulePermissions['perm']['Delete Projects']) && $modulePermissions['perm']['Delete Projects'] == "No"){?>
modJsList['tabProject'].setShowDelete(false);
<?php }?>
<?php if(isset($modulePermissions['perm']['Edit Projects']) && $modulePermissions['perm']['Edit Projects'] == "No"){?>
modJsList['tabProject'].setShowSave(false);
<?php }?>
modJsList['tabEmployeeProject'] = new EmployeeProjectAdapter('EmployeeProject', 'EmployeeProject');
modJsList['tabEmployeeProject'] = new EmployeeProjectAdapter('EmployeeProject','EmployeeProject');
modJsList['tabEmployeeProject'].setRemoteTable(true);
modJsList.tabEmployeeProject.setObjectTypeName('Employee Project');
modJsList.tabEmployeeProject.setAccess(data.permissions.EmployeeProject);
modJsList.tabEmployeeProject.setDataPipe(new IceDataPipe(modJsList.tabEmployeeProject));
modJsList.tabEmployeeProject.setRemoteTable(true);
<?php if(isset($modulePermissions['perm']['Add Projects']) && $modulePermissions['perm']['Add Projects'] == "No"){?>
modJsList['tabEmployeeProject'].setShowAddNew(false);
<?php }?>
<?php if(isset($modulePermissions['perm']['Delete Projects']) && $modulePermissions['perm']['Delete Projects'] == "No"){?>
modJsList['tabEmployeeProject'].setShowDelete(false);
<?php }?>
<?php if(isset($modulePermissions['perm']['Edit Projects']) && $modulePermissions['perm']['Edit Projects'] == "No"){?>
modJsList['tabEmployeeProject'].setShowEdit(false);
<?php }?>
var modJs = modJsList['tabProject'];
var modJs = modJsList['tabProject'];
</script>
<?php include APP_BASE_PATH.'footer.php';?>
<?php include APP_BASE_PATH . 'footer.php'; ?>

View File

@@ -19,28 +19,42 @@ $options1 = array();
$options1['setShowAddNew'] = 'false';
$options1['setRemoteTable'] = 'true';
$notCloud = !defined('IS_CLOUD') || IS_CLOUD == false;
$moduleBuilder->addModuleOrGroup(new ModuleTab(
'CompanySetting','Setting','Company','SettingAdapter','{"category":"Company"}','name',true,$options1
));
$moduleBuilder->addModuleOrGroup(new ModuleTab(
'SystemSetting','Setting','System','SettingAdapter','{"category":"System"}','name',false,$options1
));
if (!defined('CLOUD_INSTALLATION')) {
if ( $notCloud ) {
$moduleBuilder->addModuleOrGroup(new ModuleTab(
'EmailSetting', 'Setting', 'Email', 'SettingAdapter', '{"category":"Email"}', 'name', false, $options1
));
}
if(!defined('LEAVE_ENABLED') || LDAP_ENABLED == true) {
$moduleBuilder->addModuleOrGroup(new ModuleTab(
'LeaveSetting', 'Setting', 'Leave', 'SettingAdapter', '{"category":"Leave"}', 'name', false, $options1
));
}
$moduleBuilder->addModuleOrGroup(new ModuleTab(
'LeaveSetting','Setting','Leave / PTO','SettingAdapter','{"category":"Leave"}','name',false,$options1
'AttendanceSetting','Setting','Attendance','SettingAdapter','{"category":"Attendance"}','name',false,$options1
));
if(!defined('LDAP_ENABLED') || LDAP_ENABLED == true){
$moduleBuilder->addModuleOrGroup(new ModuleTab(
'LDAPSetting','Setting','LDAP','SettingAdapter','{"category":"LDAP"}','name',false,$options1
));
}
$moduleBuilder->addModuleOrGroup(new ModuleTab(
'AttendanceSetting','Setting','Attendance','SettingAdapter','{"category":"Attendance"}','name',false,$options1
));
if(!defined('SAML_ENABLED') || SAML_ENABLED == true){
$moduleBuilder->addModuleOrGroup(new ModuleTab(
'SAMLSetting','Setting','SAML','SettingAdapter','{"category":"SAML"}','name',false,$options1
));
}
$moduleBuilder->addModuleOrGroup(new ModuleTab(
'OtherSetting','Setting','Other','SettingAdapter','{"category":["Projects","Recruitment","Notifications","Expense","Travel","Api","Overtime"]}','name',false,$options1
));

View File

@@ -31,22 +31,6 @@ $moduleBuilder->addModuleOrGroup(new \Classes\ModuleBuilder\ModuleTab(
$travelRequestOptions
));
if ($user->user_level === 'Admin') {
$travelCustomFieldOptions = [];
$travelCustomFieldOptions['setRemoteTable'] = 'true';
$travelCustomFieldOptions['setTableType'] = '\'EmployeeTravelRecord\'';
$moduleBuilder->addModuleOrGroup(new \Classes\ModuleBuilder\ModuleTab(
'TravelCustomField',
'CustomField',
'Custom Fields',
'CustomFieldAdapter',
'{"type":"EmployeeTravelRecord"}',
'',
false,
$travelCustomFieldOptions
));
}
echo \Classes\UIManager::getInstance()->renderModule($moduleBuilder);

47
core/api-rest.php Normal file
View File

@@ -0,0 +1,47 @@
<?php
define('CLIENT_PATH',dirname(__FILE__));
include ("config.base.php");
include ("include.common.php");
include("server.includes.inc.php");
if(\Classes\SettingsManager::getInstance()->getSetting('Api: REST Api Enabled') == '1') {
if (defined('SYM_CLIENT')) {
define('REST_API_PATH', '/'.SYM_CLIENT.'/');
} else if (!defined('REST_API_PATH')){
define('REST_API_PATH', '/');
}
\Utils\LogManager::getInstance()->info("Request: " . $_REQUEST);
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(200);
exit();
}
$echoRoute = \Classes\Macaw::get(REST_API_PATH . 'echo', function () {
echo "Echo " . rand();
});
\Utils\LogManager::getInstance()->debug('Api registered URI: '.$echoRoute);
$moduleManagers = \Classes\BaseService::getInstance()->getModuleManagers();
foreach ($moduleManagers as $moduleManagerObj) {
$moduleManagerObj->setupRestEndPoints();
}
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$method = $_SERVER['REQUEST_METHOD'];
\Utils\LogManager::getInstance()->debug('Api dispatch URI: '.$uri);
\Utils\LogManager::getInstance()->debug('Api dispatch method: '.$uri);
if (!defined('SYM_CLIENT')) {
//For hosted installations, dispatch will be done in app/index
\Classes\Macaw::dispatch();
}
}else{
echo "REST Api is not enabled. Please set 'Api: REST Api Enabled' setting to true";
}

41
core/api-url-based.php Normal file
View File

@@ -0,0 +1,41 @@
<?php
define('CLIENT_PATH',dirname(__FILE__));
include ("config.base.php");
include ("include.common.php");
include("server.includes.inc.php");
if(\Classes\SettingsManager::getInstance()->getSetting('Api: REST Api Enabled') == '1') {
\Utils\LogManager::getInstance()->info("Request: " . $_REQUEST);
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(200);
exit();
}
define('REST_API_PATH', '/');
$echoRoute = \Classes\Macaw::get(REST_API_PATH . 'echo', function () {
echo "Echo " . rand();
});
\Utils\LogManager::getInstance()->debug('Api registered URI: '.$echoRoute);
$moduleManagers = \Classes\BaseService::getInstance()->getModuleManagers();
foreach ($moduleManagers as $moduleManagerObj) {
$moduleManagerObj->setupRestEndPoints();
}
$method = $_SERVER['REQUEST_METHOD'];
if (strtoupper($method) === 'GET') {
\Classes\IceRoute::dispatch($_GET['url'], $method);
} else {
$method = strtoupper($_REQUEST['method']);
\Classes\IceRoute::dispatch($_REQUEST['url'], $method);
}
}else{
echo "REST Api is not enabled. Please set 'Api: REST Api Enabled' setting to true";
}

View File

@@ -13,14 +13,13 @@ if(!defined('HOME_LINK_OTHERS')){
}
//Version
define('VERSION', '28.1.0.OS');
define('CACHE_VALUE', '28.1.0.OS.2020-10311445');
define('VERSION_NUMBER', '280100');
define('VERSION_DATE', '31/10/2020');
define('VERSION', '29.0.0.OS');
define('CACHE_VALUE', '29.0.0.OS.2020-04021509');
define('VERSION_NUMBER', '290000');
define('VERSION_DATE', '02/04/2021');
if(!defined('CONTACT_EMAIL')){define('CONTACT_EMAIL','icehrm@gamonoid.com');}
if(!defined('KEY_PREFIX')){define('KEY_PREFIX','IceHrm');}
if(!defined('APP_SEC')){define('APP_SEC','dbcs234d2saaqw');}
define('UI_SHOW_SWITCH_PROFILE', true);
define('CRON_LOG', ini_get('error_log'));
@@ -33,6 +32,13 @@ if(!defined('WK_HTML_PATH')){
}
define('ALL_CLIENT_BASE_PATH', '/var/www/icehrm.app/icehrmapp/');
define('IS_CLOUD', false);
define('LDAP_ENABLED', true);
define('SAML_ENABLED', true);
define('LEAVE_ENABLED', true);
define('RECRUITMENT_ENABLED', false);
define('APP_WEB_URL', 'https://icehrm.com');
if (!defined('EXTENSIONS_URL')) {
define('EXTENSIONS_URL', str_replace('/web/', '/extensions/', BASE_URL));
}

View File

@@ -215,6 +215,26 @@ if (defined('SYM_CLIENT')) {
<?php }?>
<?php foreach($extensions as $menu){?>
<?php if(count($menu['menu']) == 0){continue;}?>
<li class="treeview" ref="<?="extension_".str_replace(" ", "_", $menu['name'])?>">
<a href="#">
<i class="fa <?=!isset($mainIcons[$menu['name']])?"fa-th":$mainIcons[$menu['name']];?>"></i></i> <span><?=\Classes\LanguageManager::tran($menu['name'])?></span>
<i class="fa fa-angle-left pull-right"></i>
</a>
<ul class="treeview-menu" id="<?="extension_".str_replace(" ", "_", $menu['name'])?>">
<?php foreach ($menu['menu'] as $item){?>
<li>
<a data-turbolinks="true" href="<?=CLIENT_BASE_URL?>?g=extension&n=<?=$item['name']?>&m=<?="extension_".str_replace(" ", "_", $menu['name'])?>">
<i class="fa <?=!isset($item['icon'])?"fa-angle-double-right":$item['icon']?>"></i> <?=\Classes\LanguageManager::tran($item['label'])?>
</a>
</li>
<?php }?>
</ul>
</li>
<?php }?>
<?php
if(file_exists(CLIENT_PATH.'/third_party_meta.json')){
$tpModules = json_decode(file_get_contents(CLIENT_PATH.'/third_party_meta.json'),true);

1354
core/lib/saml2/Assertion.php Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,185 @@
<?php
include_once 'Utilities.php';
class IDPMetadataReader{
private $identityProviders;
private $serviceProviders;
public function __construct(DOMNode $xml = NULL){
$this->identityProviders = array();
$this->serviceProviders = array();
$entitiesDescriptor = Utilities::xpQuery($xml, './saml_metadata:EntitiesDescriptor');
if(!empty($entitiesDescriptor))
$entityDescriptors = Utilities::xpQuery($entitiesDescriptor[0], './saml_metadata:EntityDescriptor');
else
$entityDescriptors = Utilities::xpQuery($xml, './saml_metadata:EntityDescriptor');
foreach ($entityDescriptors as $entityDescriptor) {
$idpSSODescriptor = Utilities::xpQuery($entityDescriptor, './saml_metadata:IDPSSODescriptor');
if(isset($idpSSODescriptor) && !empty($idpSSODescriptor)){
array_push($this->identityProviders,new IdentityProviders($entityDescriptor));
}
//TODO: add sp descriptor
}
}
public function getIdentityProviders(){
return $this->identityProviders;
}
public function getServiceProviders(){
return $this->serviceProviders;
}
}
class IdentityProviders{
private $idpName;
private $entityID;
private $loginDetails;
private $logoutDetails;
private $signingCertificate;
private $encryptionCertificate;
private $signedRequest;
public function __construct(DOMElement $xml = NULL){
$this->idpName = '';
$this->loginDetails = array();
$this->logoutDetails = array();
$this->signingCertificate = array();
$this->encryptionCertificate = array();
if ($xml->hasAttribute('entityID')) {
$this->entityID = $xml->getAttribute('entityID');
}
if($xml->hasAttribute('WantAuthnRequestsSigned')){
$this->signedRequest = $xml->getAttribute('WantAuthnRequestsSigned');
}
$idpSSODescriptor = Utilities::xpQuery($xml, './saml_metadata:IDPSSODescriptor');
if (count($idpSSODescriptor) > 1) {
throw new Exception('More than one <IDPSSODescriptor> in <EntityDescriptor>.');
} elseif (empty($idpSSODescriptor)) {
throw new Exception('Missing required <IDPSSODescriptor> in <EntityDescriptor>.');
}
$idpSSODescriptorEL = $idpSSODescriptor[0];
$info = Utilities::xpQuery($xml, './saml_metadata:Extensions');
if($info)
$this->parseInfo($idpSSODescriptorEL);
$this->parseSSOService($idpSSODescriptorEL);
$this->parseSLOService($idpSSODescriptorEL);
$this->parsex509Certificate($idpSSODescriptorEL);
}
private function parseInfo($xml){
$displayNames = Utilities::xpQuery($xml, './mdui:UIInfo/mdui:DisplayName');
foreach ($displayNames as $name) {
if($name->hasAttribute('xml:lang') && $name->getAttribute('xml:lang')=="en"){
$this->idpName = $name->textContent;
}
}
}
private function parseSSOService($xml){
$ssoServices = Utilities::xpQuery($xml, './saml_metadata:SingleSignOnService');
foreach ($ssoServices as $ssoService) {
$binding = str_replace("urn:oasis:names:tc:SAML:2.0:bindings:","",$ssoService->getAttribute('Binding'));
$this->loginDetails = array_merge(
$this->loginDetails,
array($binding => $ssoService->getAttribute('Location'))
);
}
}
private function parseSLOService($xml){
$sloServices = Utilities::xpQuery($xml, './saml_metadata:SingleLogoutService');
foreach ($sloServices as $sloService) {
$binding = str_replace("urn:oasis:names:tc:SAML:2.0:bindings:","",$sloService->getAttribute('Binding'));
$this->logoutDetails = array_merge(
$this->logoutDetails,
array($binding => $sloService->getAttribute('Location'))
);
}
}
private function parsex509Certificate($xml){
foreach ( Utilities::xpQuery($xml, './saml_metadata:KeyDescriptor') as $KeyDescriptorNode ) {
if($KeyDescriptorNode->hasAttribute('use')){
if($KeyDescriptorNode->getAttribute('use')=='encryption'){
$this->parseEncryptionCertificate($KeyDescriptorNode);
}else{
$this->parseSigningCertificate($KeyDescriptorNode);
}
}else{
$this->parseSigningCertificate($KeyDescriptorNode);
}
}
}
private function parseSigningCertificate($xml){
$certNode = Utilities::xpQuery($xml, './ds:KeyInfo/ds:X509Data/ds:X509Certificate');
$certData = trim($certNode[0]->textContent);
$certData = str_replace(array ( "\r", "\n", "\t", ' '), '', $certData);
if(!empty($certNode))
array_push($this->signingCertificate, Utilities::sanitize_certificate( $certData ));
}
private function parseEncryptionCertificate($xml){
$certNode = Utilities::xpQuery($xml, './ds:KeyInfo/ds:X509Data/ds:X509Certificate');
$certData = trim($certNode[0]->textContent);
$certData = str_replace(array ( "\r", "\n", "\t", ' '), '', $certData);
if(!empty($certNode))
array_push($this->encryptionCertificate, $certData);
}
public function getIdpName(){
return "";
}
public function getEntityID(){
return $this->entityID;
}
public function getLoginURL($binding){
return $this->loginDetails[$binding];
}
public function getLogoutURL($binding){
return $this->logoutDetails[$binding];
}
public function getLoginDetails(){
return $this->loginDetails;
}
public function getLogoutDetails(){
return $this->logoutDetails;
}
public function getSigningCertificate(){
return $this->signingCertificate;
}
public function getEncryptionCertificate(){
return $this->encryptionCertificate[0];
}
public function isRequestSigned(){
return $this->signedRequest;
}
}

View File

@@ -0,0 +1,33 @@
<?php
abstract class MoSAMLBasicEnum {
private static $constCacheArray = NULL;
public static function getConstants() {
if (self::$constCacheArray == NULL) {
self::$constCacheArray = array();
}
$calledClass = get_called_class();
if (!array_key_exists($calledClass, self::$constCacheArray)) {
$reflect = new ReflectionClass($calledClass);
self::$constCacheArray[$calledClass] = $reflect->getConstants();
}
return self::$constCacheArray[$calledClass];
}
public static function isValidName($name, $strict = false) {
$constants = self::getConstants();
if ($strict) {
return array_key_exists($name, $constants);
}
$keys = array_map('strtolower', array_keys($constants));
return in_array(strtolower($name), $keys);
}
public static function isValidValue($value, $strict = true) {
$values = array_values(self::getConstants());
return in_array($value, $values, $strict);
}
}

View File

@@ -0,0 +1,109 @@
<?php
/**
* Created by PhpStorm.
* User: HP
* Date: 9/11/2018
* Time: 9:48 AM
*/
class MoSAMLPointer
{
private $content,$anchor_id,$edge,$align,$active,$pointer_name;
function __construct($header,$body,$anchor_id,$edge,$align,$active,$prefix){
$this->content = '<h3>'.$header.'</h3>';
$this->content .= '<p id="'.$prefix.'" style="font-size: initial;">' .$body . '</p>';
$this-> anchor_id = $anchor_id;
$this->edge = $edge;
$this->align = $align;
$this->active = $active;
$this->pointer_name = 'miniorange_admin_pointer_'.$prefix;
}
function return_array(){
return array(
// The content needs to point to what we created above in the $new_pointer_content variable
'content' => $this->content,
// In order for the custom pointer to appear in the right location we need to specify the ID
// of the element we want it to appear next to
'anchor_id' => $this->anchor_id,
// On what edge do we want the pointer to appear. Options are 'top', 'left', 'right', 'bottom'
'edge' => $this->edge,
// How do we want out custom pointer to align to the element it is attached to. Options are
// 'left', 'right', 'center'
'align' => $this->align,
// This is how we tell the pointer to be dismissed or not. Make sure that the 'new_items'
// string matches the string at the beginning of the array item
'active' => $this->active
);
}
/**
* @return mixed
*/
public function getContent()
{
return $this->content;
}
/**
* @param mixed $content
*/
public function setContent($content)
{
$this->content = $content;
}
/**
* @return mixed
*/
public function getAnchorId()
{
return $this->anchor_id;
}
/**
* @return mixed
*/
public function getEdge()
{
return $this->edge;
}
/**
* @return mixed
*/
public function getActive()
{
return $this->active;
}
/**
* @param mixed $active
*/
public function setActive($active)
{
$this->active = $active;
}
/**
* @return mixed
*/
public function getPointerName()
{
return $this->pointer_name;
}
}

View File

@@ -0,0 +1,51 @@
<?php
class MoSAMLPointersManager {
private $pfile;
private $version;
private $prefix;
private $pointers = array();
public function __construct( $file, $version, $prefix ) {
$this->pfile = file_exists( $file ) ? $file : FALSE;
$this->version = str_replace( '.', '_', $version );
$this->prefix = $prefix;
}
public function parse() {
if ( empty( $this->pfile ) ) return;
$pointers = (array) require_once $this->pfile;
if ( empty($pointers) ) return;
foreach ( $pointers as $i => $pointer ) {
if(is_array($pointer)){
$pointer['id'] = "{$this->prefix}{$this->version}_{$i}";
$this->pointers[$pointer['id']] = (object) $pointer;
}
}
}
public function filter( $page ) {
if ( empty( $this->pointers ) ) return array();
$uid = get_current_user_id();
$no = explode( ',', (string) get_user_meta( $uid, 'dismissed_wp_pointers', TRUE ) );
$active_ids = array_diff( array_keys( $this->pointers ), $no );
$good = array();
foreach( $this->pointers as $i => $pointer ) {
if (
in_array( $i, $active_ids, TRUE ) // is active
&& isset( $pointer->where ) // has where
&& in_array( $page, (array) $pointer->where, TRUE ) // current page is in where
) {
$good[] = $pointer;
}
}
$count = count( $good );
if ( $good === 0 ) return array();
foreach( array_values( $good ) as $i => $pointer ) {
$good[$i]->next = $i+1 < $count ? $good[$i+1]->id : '';
}
return $good;
}
}

114
core/lib/saml2/Response.php Normal file
View File

@@ -0,0 +1,114 @@
<?php
/**
* This file is part of miniOrange SAML plugin.
*
* miniOrange SAML plugin is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* miniOrange SAML plugin 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with miniOrange SAML plugin. If not, see <http://www.gnu.org/licenses/>.
*/
include 'Assertion.php';
/**
* Class for SAML2 Response messages.
*
*/
class SAML2_Response
{
/**
* The assertions in this response.
*/
private $assertions;
/**
* The destination URL in this response.
*/
private $destination;
private $certificates;
private $signatureData;
/**
* Constructor for SAML 2 response messages.
*
* @param DOMElement|NULL $xml The input message.
*/
public function __construct(DOMElement $xml = NULL)
{
//parent::__construct('Response', $xml);
$this->assertions = array();
$this->certificates = array();
if ($xml === NULL) {
return;
}
$sig = Utilities::validateElement($xml);
if ($sig !== FALSE) {
$this->certificates = $sig['Certificates'];
$this->signatureData = $sig;
}
/* set the destination from saml response */
if ($xml->hasAttribute('Destination')) {
$this->destination = $xml->getAttribute('Destination');
}
for ($node = $xml->firstChild; $node !== NULL; $node = $node->nextSibling) {
if ($node->namespaceURI !== 'urn:oasis:names:tc:SAML:2.0:assertion') {
continue;
}
if ($node->localName === 'Assertion' || $node->localName === 'EncryptedAssertion') {
$this->assertions[] = new SAML2_Assertion($node);
}
}
}
/**
* Retrieve the assertions in this response.
*
* @return SAML2_Assertion[]|SAML2_EncryptedAssertion[]
*/
public function getAssertions()
{
return $this->assertions;
}
/**
* Set the assertions that should be included in this response.
*
* @param SAML2_Assertion[]|SAML2_EncryptedAssertion[] The assertions.
*/
public function setAssertions(array $assertions)
{
$this->assertions = $assertions;
}
public function getDestination()
{
return $this->destination;
}
public function getCertificates()
{
return $this->certificates;
}
public function getSignatureData()
{
return $this->signatureData;
}
}

View File

@@ -0,0 +1,511 @@
<?php
namespace RobRichards\XMLSecLibs;
use DOMDocument;
use DOMElement;
use DOMNode;
use DOMXPath;
use Exception;
use RobRichards\XMLSecLibs\Utils\MoXPath as XPath;
/**
* xmlseclibs.php
*
* Copyright (c) 2007-2020, Robert Richards <rrichards@cdatazone.org>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* * Neither the name of Robert Richards nor the names of his
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* @author Robert Richards <rrichards@cdatazone.org>
* @copyright 2007-2020 Robert Richards <rrichards@cdatazone.org>
* @license http://www.opensource.org/licenses/bsd-license.php BSD License
*/
class MoXMLSecEnc
{
const template = "<xenc:EncryptedData xmlns:xenc='http://www.w3.org/2001/04/xmlenc#'>
<xenc:CipherData>
<xenc:CipherValue></xenc:CipherValue>
</xenc:CipherData>
</xenc:EncryptedData>";
const Element = 'http://www.w3.org/2001/04/xmlenc#Element';
const Content = 'http://www.w3.org/2001/04/xmlenc#Content';
const URI = 3;
const XMLENCNS = 'http://www.w3.org/2001/04/xmlenc#';
/** @var null|DOMDocument */
private $encdoc = null;
/** @var null|DOMNode */
private $rawNode = null;
/** @var null|string */
public $type = null;
/** @var null|DOMElement */
public $encKey = null;
/** @var array */
private $references = array();
public function __construct()
{
$this->_resetTemplate();
}
private function _resetTemplate()
{
$this->encdoc = new DOMDocument();
$this->encdoc->loadXML(self::template);
}
/**
* @param string $name
* @param DOMNode $node
* @param string $type
* @throws Exception
*/
public function addReference($name, $node, $type)
{
if (! $node instanceOf DOMNode) {
throw new Exception('$node is not of type DOMNode');
}
$curencdoc = $this->encdoc;
$this->_resetTemplate();
$encdoc = $this->encdoc;
$this->encdoc = $curencdoc;
$refuri = MoXMLSecurityDSig::generateGUID();
$element = $encdoc->documentElement;
$element->setAttribute("Id", $refuri);
$this->references[$name] = array("node" => $node, "type" => $type, "encnode" => $encdoc, "refuri" => $refuri);
}
/**
* @param DOMNode $node
*/
public function setNode($node)
{
$this->rawNode = $node;
}
/**
* Encrypt the selected node with the given key.
*
* @param MoXMLSecurityKey $objKey The encryption key and algorithm.
* @param bool $replace Whether the encrypted node should be replaced in the original tree. Default is true.
* @return DOMElement The <xenc:EncryptedData>-element.
*@throws Exception
*
*/
public function encryptNode($objKey, $replace = true)
{
$data = '';
if (empty($this->rawNode)) {
throw new Exception('Node to encrypt has not been set');
}
if (! $objKey instanceof MoXMLSecurityKey) {
throw new Exception('Invalid Key');
}
$doc = $this->rawNode->ownerDocument;
$xPath = new DOMXPath($this->encdoc);
$objList = $xPath->query('/xenc:EncryptedData/xenc:CipherData/xenc:CipherValue');
$cipherValue = $objList->item(0);
if ($cipherValue == null) {
throw new Exception('Error locating CipherValue element within template');
}
switch ($this->type) {
case (self::Element):
$data = $doc->saveXML($this->rawNode);
$this->encdoc->documentElement->setAttribute('Type', self::Element);
break;
case (self::Content):
$children = $this->rawNode->childNodes;
foreach ($children AS $child) {
$data .= $doc->saveXML($child);
}
$this->encdoc->documentElement->setAttribute('Type', self::Content);
break;
default:
throw new Exception('Type is currently not supported');
}
$encMethod = $this->encdoc->documentElement->appendChild($this->encdoc->createElementNS(self::XMLENCNS, 'xenc:EncryptionMethod'));
$encMethod->setAttribute('Algorithm', $objKey->getAlgorithm());
$cipherValue->parentNode->parentNode->insertBefore($encMethod, $cipherValue->parentNode->parentNode->firstChild);
$strEncrypt = base64_encode($objKey->encryptData($data));
$value = $this->encdoc->createTextNode($strEncrypt);
$cipherValue->appendChild($value);
if ($replace) {
switch ($this->type) {
case (self::Element):
if ($this->rawNode->nodeType == XML_DOCUMENT_NODE) {
return $this->encdoc;
}
$importEnc = $this->rawNode->ownerDocument->importNode($this->encdoc->documentElement, true);
$this->rawNode->parentNode->replaceChild($importEnc, $this->rawNode);
return $importEnc;
case (self::Content):
$importEnc = $this->rawNode->ownerDocument->importNode($this->encdoc->documentElement, true);
while ($this->rawNode->firstChild) {
$this->rawNode->removeChild($this->rawNode->firstChild);
}
$this->rawNode->appendChild($importEnc);
return $importEnc;
}
} else {
return $this->encdoc->documentElement;
}
}
/**
* @param MoXMLSecurityKey $objKey
* @throws Exception
*/
public function encryptReferences($objKey)
{
$curRawNode = $this->rawNode;
$curType = $this->type;
foreach ($this->references AS $name => $reference) {
$this->encdoc = $reference["encnode"];
$this->rawNode = $reference["node"];
$this->type = $reference["type"];
try {
$encNode = $this->encryptNode($objKey);
$this->references[$name]["encnode"] = $encNode;
} catch (Exception $e) {
$this->rawNode = $curRawNode;
$this->type = $curType;
throw $e;
}
}
$this->rawNode = $curRawNode;
$this->type = $curType;
}
/**
* Retrieve the CipherValue text from this encrypted node.
*
* @throws Exception
* @return string|null The Ciphervalue text, or null if no CipherValue is found.
*/
public function getCipherValue()
{
if (empty($this->rawNode)) {
throw new Exception('Node to decrypt has not been set');
}
$doc = $this->rawNode->ownerDocument;
$xPath = new DOMXPath($doc);
$xPath->registerNamespace('xmlencr', self::XMLENCNS);
/* Only handles embedded content right now and not a reference */
$query = "./xmlencr:CipherData/xmlencr:CipherValue";
$nodeset = $xPath->query($query, $this->rawNode);
$node = $nodeset->item(0);
if (!$node) {
return null;
}
return base64_decode($node->nodeValue);
}
/**
* Decrypt this encrypted node.
*
* The behaviour of this function depends on the value of $replace.
* If $replace is false, we will return the decrypted data as a string.
* If $replace is true, we will insert the decrypted element(s) into the
* document, and return the decrypted element(s).
*
* @param MoXMLSecurityKey $objKey The decryption key that should be used when decrypting the node.
* @param boolean $replace Whether we should replace the encrypted node in the XML document with the decrypted data. The default is true.
*
* @return string|DOMElement The decrypted data.
*/
public function decryptNode($objKey, $replace=true)
{
if (! $objKey instanceof MoXMLSecurityKey) {
throw new Exception('Invalid Key');
}
$encryptedData = $this->getCipherValue();
if ($encryptedData) {
$decrypted = $objKey->decryptData($encryptedData);
if ($replace) {
switch ($this->type) {
case (self::Element):
$newdoc = new DOMDocument();
$newdoc->loadXML($decrypted);
if ($this->rawNode->nodeType == XML_DOCUMENT_NODE) {
return $newdoc;
}
$importEnc = $this->rawNode->ownerDocument->importNode($newdoc->documentElement, true);
$this->rawNode->parentNode->replaceChild($importEnc, $this->rawNode);
return $importEnc;
case (self::Content):
if ($this->rawNode->nodeType == XML_DOCUMENT_NODE) {
$doc = $this->rawNode;
} else {
$doc = $this->rawNode->ownerDocument;
}
$newFrag = $doc->createDocumentFragment();
$newFrag->appendXML($decrypted);
$parent = $this->rawNode->parentNode;
$parent->replaceChild($newFrag, $this->rawNode);
return $parent;
default:
return $decrypted;
}
} else {
return $decrypted;
}
} else {
throw new Exception("Cannot locate encrypted data");
}
}
/**
* Encrypt the XMLSecurityKey
*
* @param MoXMLSecurityKey $srcKey
* @param MoXMLSecurityKey $rawKey
* @param bool $append
* @throws Exception
*/
public function encryptKey($srcKey, $rawKey, $append=true)
{
if ((! $srcKey instanceof MoXMLSecurityKey) || (! $rawKey instanceof MoXMLSecurityKey)) {
throw new Exception('Invalid Key');
}
$strEncKey = base64_encode($srcKey->encryptData($rawKey->key));
$root = $this->encdoc->documentElement;
$encKey = $this->encdoc->createElementNS(self::XMLENCNS, 'xenc:EncryptedKey');
if ($append) {
$keyInfo = $root->insertBefore($this->encdoc->createElementNS('http://www.w3.org/2000/09/xmldsig#', 'dsig:KeyInfo'), $root->firstChild);
$keyInfo->appendChild($encKey);
} else {
$this->encKey = $encKey;
}
$encMethod = $encKey->appendChild($this->encdoc->createElementNS(self::XMLENCNS, 'xenc:EncryptionMethod'));
$encMethod->setAttribute('Algorithm', $srcKey->getAlgorith());
if (! empty($srcKey->name)) {
$keyInfo = $encKey->appendChild($this->encdoc->createElementNS('http://www.w3.org/2000/09/xmldsig#', 'dsig:KeyInfo'));
$keyInfo->appendChild($this->encdoc->createElementNS('http://www.w3.org/2000/09/xmldsig#', 'dsig:KeyName', $srcKey->name));
}
$cipherData = $encKey->appendChild($this->encdoc->createElementNS(self::XMLENCNS, 'xenc:CipherData'));
$cipherData->appendChild($this->encdoc->createElementNS(self::XMLENCNS, 'xenc:CipherValue', $strEncKey));
if (is_array($this->references) && count($this->references) > 0) {
$refList = $encKey->appendChild($this->encdoc->createElementNS(self::XMLENCNS, 'xenc:ReferenceList'));
foreach ($this->references AS $name => $reference) {
$refuri = $reference["refuri"];
$dataRef = $refList->appendChild($this->encdoc->createElementNS(self::XMLENCNS, 'xenc:DataReference'));
$dataRef->setAttribute("URI", '#' . $refuri);
}
}
return;
}
/**
* @param MoXMLSecurityKey $encKey
* @return DOMElement|string
* @throws Exception
*/
public function decryptKey($encKey)
{
if (! $encKey->isEncrypted) {
throw new Exception("Key is not Encrypted");
}
if (empty($encKey->key)) {
throw new Exception("Key is missing data to perform the decryption");
}
return $this->decryptNode($encKey, false);
}
/**
* @param DOMDocument $element
* @return DOMNode|null
*/
public function locateEncryptedData($element)
{
if ($element instanceof DOMDocument) {
$doc = $element;
} else {
$doc = $element->ownerDocument;
}
if ($doc) {
$xpath = new DOMXPath($doc);
$query = "//*[local-name()='EncryptedData' and namespace-uri()='".self::XMLENCNS."']";
$nodeset = $xpath->query($query);
return $nodeset->item(0);
}
return null;
}
/**
* Returns the key from the DOM
* @param null|DOMNode $node
* @return null|MoXMLSecurityKey
*/
public function locateKey($node=null)
{
if (empty($node)) {
$node = $this->rawNode;
}
if (! $node instanceof DOMNode) {
return null;
}
if ($doc = $node->ownerDocument) {
$xpath = new DOMXPath($doc);
$xpath->registerNamespace('xmlsecenc', self::XMLENCNS);
$query = ".//xmlsecenc:EncryptionMethod";
$nodeset = $xpath->query($query, $node);
if ($encmeth = $nodeset->item(0)) {
$attrAlgorithm = $encmeth->getAttribute("Algorithm");
try {
$objKey = new MoXMLSecurityKey($attrAlgorithm, array('type' => 'private'));
} catch (Exception $e) {
return null;
}
return $objKey;
}
}
return null;
}
/**
* @param null|MoXMLSecurityKey $objBaseKey
* @param null|DOMNode $node
* @return null|MoXMLSecurityKey
* @throws Exception
*/
public static function staticLocateKeyInfo($objBaseKey=null, $node=null)
{
if (empty($node) || (! $node instanceof DOMNode)) {
return null;
}
$doc = $node->ownerDocument;
if (!$doc) {
return null;
}
$xpath = new DOMXPath($doc);
$xpath->registerNamespace('xmlsecenc', self::XMLENCNS);
$xpath->registerNamespace('xmlsecdsig', MoXMLSecurityDSig::XMLDSIGNS);
$query = "./xmlsecdsig:KeyInfo";
$nodeset = $xpath->query($query, $node);
$encmeth = $nodeset->item(0);
if (!$encmeth) {
/* No KeyInfo in EncryptedData / EncryptedKey. */
return $objBaseKey;
}
foreach ($encmeth->childNodes AS $child) {
switch ($child->localName) {
case 'KeyName':
if (! empty($objBaseKey)) {
$objBaseKey->name = $child->nodeValue;
}
break;
case 'KeyValue':
foreach ($child->childNodes AS $keyval) {
switch ($keyval->localName) {
case 'DSAKeyValue':
throw new Exception("DSAKeyValue currently not supported");
case 'RSAKeyValue':
$modulus = null;
$exponent = null;
if ($modulusNode = $keyval->getElementsByTagName('Modulus')->item(0)) {
$modulus = base64_decode($modulusNode->nodeValue);
}
if ($exponentNode = $keyval->getElementsByTagName('Exponent')->item(0)) {
$exponent = base64_decode($exponentNode->nodeValue);
}
if (empty($modulus) || empty($exponent)) {
throw new Exception("Missing Modulus or Exponent");
}
$publicKey = MoXMLSecurityKey::convertRSA($modulus, $exponent);
$objBaseKey->loadKey($publicKey);
break;
}
}
break;
case 'RetrievalMethod':
$type = $child->getAttribute('Type');
if ($type !== 'http://www.w3.org/2001/04/xmlenc#EncryptedKey') {
/* Unsupported key type. */
break;
}
$uri = $child->getAttribute('URI');
if ($uri[0] !== '#') {
/* URI not a reference - unsupported. */
break;
}
$id = substr($uri, 1);
$query = '//xmlsecenc:EncryptedKey[@Id="'.MoXPath::filterAttrValue($id, MoXPath::DOUBLE_QUOTE).'"]';
$keyElement = $xpath->query($query)->item(0);
if (!$keyElement) {
throw new Exception("Unable to locate EncryptedKey with @Id='$id'.");
}
return MoXMLSecurityKey::fromEncryptedKeyElement($keyElement);
case 'EncryptedKey':
return MoXMLSecurityKey::fromEncryptedKeyElement($child);
case 'X509Data':
if ($x509certNodes = $child->getElementsByTagName('X509Certificate')) {
if ($x509certNodes->length > 0) {
$x509cert = $x509certNodes->item(0)->textContent;
$x509cert = str_replace(array("\r", "\n", " "), "", $x509cert);
$x509cert = "-----BEGIN CERTIFICATE-----\n".chunk_split($x509cert, 64, "\n")."-----END CERTIFICATE-----\n";
$objBaseKey->loadKey($x509cert, false, true);
}
}
break;
}
}
return $objBaseKey;
}
/**
* @param null|MoXMLSecurityKey $objBaseKey
* @param null|DOMNode $node
* @return null|MoXMLSecurityKey
*/
public function locateKeyInfo($objBaseKey=null, $node=null)
{
if (empty($node)) {
$node = $this->rawNode;
}
return self::staticLocateKeyInfo($objBaseKey, $node);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,800 @@
<?php
namespace RobRichards\XMLSecLibs;
use DOMElement;
use Exception;
/**
* xmlseclibs.php
*
* Copyright (c) 2007-2020, Robert Richards <rrichards@cdatazone.org>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* * Neither the name of Robert Richards nor the names of his
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* @author Robert Richards <rrichards@cdatazone.org>
* @copyright 2007-2020 Robert Richards <rrichards@cdatazone.org>
* @license http://www.opensource.org/licenses/bsd-license.php BSD License
*/
class MoXMLSecurityKey
{
const TRIPLEDES_CBC = 'http://www.w3.org/2001/04/xmlenc#tripledes-cbc';
const AES128_CBC = 'http://www.w3.org/2001/04/xmlenc#aes128-cbc';
const AES192_CBC = 'http://www.w3.org/2001/04/xmlenc#aes192-cbc';
const AES256_CBC = 'http://www.w3.org/2001/04/xmlenc#aes256-cbc';
const AES128_GCM = 'http://www.w3.org/2009/xmlenc11#aes128-gcm';
const AES192_GCM = 'http://www.w3.org/2009/xmlenc11#aes192-gcm';
const AES256_GCM = 'http://www.w3.org/2009/xmlenc11#aes256-gcm';
const RSA_1_5 = 'http://www.w3.org/2001/04/xmlenc#rsa-1_5';
const RSA_OAEP_MGF1P = 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p';
const DSA_SHA1 = 'http://www.w3.org/2000/09/xmldsig#dsa-sha1';
const RSA_SHA1 = 'http://www.w3.org/2000/09/xmldsig#rsa-sha1';
const RSA_SHA256 = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256';
const RSA_SHA384 = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha384';
const RSA_SHA512 = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512';
const HMAC_SHA1 = 'http://www.w3.org/2000/09/xmldsig#hmac-sha1';
const AUTHTAG_LENGTH = 16;
/** @var array */
private $cryptParams = array();
/** @var int|string */
public $type = 0;
/** @var mixed|null */
public $key = null;
/** @var string */
public $passphrase = "";
/** @var string|null */
public $iv = null;
/** @var string|null */
public $name = null;
/** @var mixed|null */
public $keyChain = null;
/** @var bool */
public $isEncrypted = false;
/** @var MoXMLSecEnc|null */
public $encryptedCtx = null;
/** @var mixed|null */
public $guid = null;
/**
* This variable contains the certificate as a string if this key represents an X509-certificate.
* If this key doesn't represent a certificate, this will be null.
* @var string|null
*/
private $x509Certificate = null;
/**
* This variable contains the certificate thumbprint if we have loaded an X509-certificate.
* @var string|null
*/
private $X509Thumbprint = null;
/**
* @param string $type
* @param null|array $params
* @throws Exception
*/
public function __construct($type, $params=null)
{
switch ($type) {
case (self::TRIPLEDES_CBC):
$this->cryptParams['library'] = 'openssl';
$this->cryptParams['cipher'] = 'des-ede3-cbc';
$this->cryptParams['type'] = 'symmetric';
$this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#tripledes-cbc';
$this->cryptParams['keysize'] = 24;
$this->cryptParams['blocksize'] = 8;
break;
case (self::AES128_CBC):
$this->cryptParams['library'] = 'openssl';
$this->cryptParams['cipher'] = 'aes-128-cbc';
$this->cryptParams['type'] = 'symmetric';
$this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#aes128-cbc';
$this->cryptParams['keysize'] = 16;
$this->cryptParams['blocksize'] = 16;
break;
case (self::AES192_CBC):
$this->cryptParams['library'] = 'openssl';
$this->cryptParams['cipher'] = 'aes-192-cbc';
$this->cryptParams['type'] = 'symmetric';
$this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#aes192-cbc';
$this->cryptParams['keysize'] = 24;
$this->cryptParams['blocksize'] = 16;
break;
case (self::AES256_CBC):
$this->cryptParams['library'] = 'openssl';
$this->cryptParams['cipher'] = 'aes-256-cbc';
$this->cryptParams['type'] = 'symmetric';
$this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#aes256-cbc';
$this->cryptParams['keysize'] = 32;
$this->cryptParams['blocksize'] = 16;
break;
case (self::AES128_GCM):
$this->cryptParams['library'] = 'openssl';
$this->cryptParams['cipher'] = 'aes-128-gcm';
$this->cryptParams['type'] = 'symmetric';
$this->cryptParams['method'] = 'http://www.w3.org/2009/xmlenc11#aes128-gcm';
$this->cryptParams['keysize'] = 32;
$this->cryptParams['blocksize'] = 16;
break;
case (self::AES192_GCM):
$this->cryptParams['library'] = 'openssl';
$this->cryptParams['cipher'] = 'aes-192-gcm';
$this->cryptParams['type'] = 'symmetric';
$this->cryptParams['method'] = 'http://www.w3.org/2009/xmlenc11#aes192-gcm';
$this->cryptParams['keysize'] = 32;
$this->cryptParams['blocksize'] = 16;
break;
case (self::AES256_GCM):
$this->cryptParams['library'] = 'openssl';
$this->cryptParams['cipher'] = 'aes-256-gcm';
$this->cryptParams['type'] = 'symmetric';
$this->cryptParams['method'] = 'http://www.w3.org/2009/xmlenc11#aes256-gcm';
$this->cryptParams['keysize'] = 32;
$this->cryptParams['blocksize'] = 16;
break;
case (self::RSA_1_5):
$this->cryptParams['library'] = 'openssl';
$this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING;
$this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#rsa-1_5';
if (is_array($params) && ! empty($params['type'])) {
if ($params['type'] == 'public' || $params['type'] == 'private') {
$this->cryptParams['type'] = $params['type'];
break;
}
}
throw new Exception('Certificate "type" (private/public) must be passed via parameters');
case (self::RSA_OAEP_MGF1P):
$this->cryptParams['library'] = 'openssl';
$this->cryptParams['padding'] = OPENSSL_PKCS1_OAEP_PADDING;
$this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p';
$this->cryptParams['hash'] = null;
if (is_array($params) && ! empty($params['type'])) {
if ($params['type'] == 'public' || $params['type'] == 'private') {
$this->cryptParams['type'] = $params['type'];
break;
}
}
throw new Exception('Certificate "type" (private/public) must be passed via parameters');
case (self::RSA_SHA1):
$this->cryptParams['library'] = 'openssl';
$this->cryptParams['method'] = 'http://www.w3.org/2000/09/xmldsig#rsa-sha1';
$this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING;
if (is_array($params) && ! empty($params['type'])) {
if ($params['type'] == 'public' || $params['type'] == 'private') {
$this->cryptParams['type'] = $params['type'];
break;
}
}
throw new Exception('Certificate "type" (private/public) must be passed via parameters');
case (self::RSA_SHA256):
$this->cryptParams['library'] = 'openssl';
$this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256';
$this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING;
$this->cryptParams['digest'] = 'SHA256';
if (is_array($params) && ! empty($params['type'])) {
if ($params['type'] == 'public' || $params['type'] == 'private') {
$this->cryptParams['type'] = $params['type'];
break;
}
}
throw new Exception('Certificate "type" (private/public) must be passed via parameters');
case (self::RSA_SHA384):
$this->cryptParams['library'] = 'openssl';
$this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha384';
$this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING;
$this->cryptParams['digest'] = 'SHA384';
if (is_array($params) && ! empty($params['type'])) {
if ($params['type'] == 'public' || $params['type'] == 'private') {
$this->cryptParams['type'] = $params['type'];
break;
}
}
throw new Exception('Certificate "type" (private/public) must be passed via parameters');
case (self::RSA_SHA512):
$this->cryptParams['library'] = 'openssl';
$this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512';
$this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING;
$this->cryptParams['digest'] = 'SHA512';
if (is_array($params) && ! empty($params['type'])) {
if ($params['type'] == 'public' || $params['type'] == 'private') {
$this->cryptParams['type'] = $params['type'];
break;
}
}
throw new Exception('Certificate "type" (private/public) must be passed via parameters');
case (self::HMAC_SHA1):
$this->cryptParams['library'] = $type;
$this->cryptParams['method'] = 'http://www.w3.org/2000/09/xmldsig#hmac-sha1';
break;
default:
throw new Exception('Invalid Key Type');
}
$this->type = $type;
}
/**
* Retrieve the key size for the symmetric encryption algorithm..
*
* If the key size is unknown, or this isn't a symmetric encryption algorithm,
* null is returned.
*
* @return int|null The number of bytes in the key.
*/
public function getSymmetricKeySize()
{
if (! isset($this->cryptParams['keysize'])) {
return null;
}
return $this->cryptParams['keysize'];
}
/**
* Generates a session key using the openssl-extension.
* In case of using DES3-CBC the key is checked for a proper parity bits set.
* @return string
* @throws Exception
*/
public function generateSessionKey()
{
if (!isset($this->cryptParams['keysize'])) {
throw new Exception('Unknown key size for type "' . $this->type . '".');
}
$keysize = $this->cryptParams['keysize'];
$key = openssl_random_pseudo_bytes($keysize);
if ($this->type === self::TRIPLEDES_CBC) {
/* Make sure that the generated key has the proper parity bits set.
* Mcrypt doesn't care about the parity bits, but others may care.
*/
for ($i = 0; $i < strlen($key); $i++) {
$byte = ord($key[$i]) & 0xfe;
$parity = 1;
for ($j = 1; $j < 8; $j++) {
$parity ^= ($byte >> $j) & 1;
}
$byte |= $parity;
$key[$i] = chr($byte);
}
}
$this->key = $key;
return $key;
}
/**
* Get the raw thumbprint of a certificate
*
* @param string $cert
* @return null|string
*/
public static function getRawThumbprint($cert)
{
$arCert = explode("\n", $cert);
$data = '';
$inData = false;
foreach ($arCert AS $curData) {
if (! $inData) {
if (strncmp($curData, '-----BEGIN CERTIFICATE', 22) == 0) {
$inData = true;
}
} else {
if (strncmp($curData, '-----END CERTIFICATE', 20) == 0) {
break;
}
$data .= trim($curData);
}
}
if (! empty($data)) {
return strtolower(sha1(base64_decode($data)));
}
return null;
}
/**
* Loads the given key, or - with isFile set true - the key from the keyfile.
*
* @param string $key
* @param bool $isFile
* @param bool $isCert
* @throws Exception
*/
public function loadKey($key, $isFile=false, $isCert = false)
{
if ($isFile) {
$this->key = file_get_contents($key);
} else {
$this->key = $key;
}
if ($isCert) {
$this->key = openssl_x509_read($this->key);
openssl_x509_export($this->key, $str_cert);
$this->x509Certificate = $str_cert;
$this->key = $str_cert;
} else {
$this->x509Certificate = null;
}
if ($this->cryptParams['library'] == 'openssl') {
switch ($this->cryptParams['type']) {
case 'public':
if ($isCert) {
/* Load the thumbprint if this is an X509 certificate. */
$this->X509Thumbprint = self::getRawThumbprint($this->key);
}
$this->key = openssl_get_publickey($this->key);
if (! $this->key) {
throw new Exception('Unable to extract public key');
}
break;
case 'private':
$this->key = openssl_get_privatekey($this->key, $this->passphrase);
break;
case'symmetric':
if (strlen($this->key) < $this->cryptParams['keysize']) {
throw new Exception('Key must contain at least 25 characters for this cipher');
}
break;
default:
throw new Exception('Unknown type');
}
}
}
/**
* ISO 10126 Padding
*
* @param string $data
* @param integer $blockSize
* @throws Exception
* @return string
*/
private function padISO10126($data, $blockSize)
{
if ($blockSize > 256) {
throw new Exception('Block size higher than 256 not allowed');
}
$padChr = $blockSize - (strlen($data) % $blockSize);
$pattern = chr($padChr);
return $data . str_repeat($pattern, $padChr);
}
/**
* Remove ISO 10126 Padding
*
* @param string $data
* @return string
*/
private function unpadISO10126($data)
{
$padChr = substr($data, -1);
$padLen = ord($padChr);
return substr($data, 0, -$padLen);
}
/**
* Encrypts the given data (string) using the openssl-extension
*
* @param string $data
* @return string
*/
private function encryptSymmetric($data)
{
$this->iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length($this->cryptParams['cipher']));
$authTag = null;
if(in_array($this->cryptParams['cipher'], ['aes-128-gcm', 'aes-192-gcm', 'aes-256-gcm'])) {
if (version_compare(PHP_VERSION, '7.1.0') < 0) {
throw new Exception('PHP 7.1.0 is required to use AES GCM algorithms');
}
$authTag = openssl_random_pseudo_bytes(self::AUTHTAG_LENGTH);
$encrypted = openssl_encrypt($data, $this->cryptParams['cipher'], $this->key, OPENSSL_RAW_DATA, $this->iv, $authTag);
} else {
$data = $this->padISO10126($data, $this->cryptParams['blocksize']);
$encrypted = openssl_encrypt($data, $this->cryptParams['cipher'], $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $this->iv);
}
if (false === $encrypted) {
throw new Exception('Failure encrypting Data (openssl symmetric) - ' . openssl_error_string());
}
return $this->iv . $encrypted . $authTag;
}
/**
* Decrypts the given data (string) using the openssl-extension
*
* @param string $data
* @return string
*/
private function decryptSymmetric($data)
{
$iv_length = openssl_cipher_iv_length($this->cryptParams['cipher']);
$this->iv = substr($data, 0, $iv_length);
$data = substr($data, $iv_length);
$authTag = null;
if(in_array($this->cryptParams['cipher'], ['aes-128-gcm', 'aes-192-gcm', 'aes-256-gcm'])) {
if (version_compare(PHP_VERSION, '7.1.0') < 0) {
throw new Exception('PHP 7.1.0 is required to use AES GCM algorithms');
}
// obtain and remove the authentication tag
$offset = 0 - self::AUTHTAG_LENGTH;
$authTag = substr($data, $offset);
$data = substr($data, 0, $offset);
$decrypted = openssl_decrypt($data, $this->cryptParams['cipher'], $this->key, OPENSSL_RAW_DATA, $this->iv, $authTag);
} else {
$decrypted = openssl_decrypt($data, $this->cryptParams['cipher'], $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $this->iv);
}
if (false === $decrypted) {
throw new Exception('Failure decrypting Data (openssl symmetric) - ' . openssl_error_string());
}
return null !== $authTag ? $decrypted : $this->unpadISO10126($decrypted);
}
/**
* Encrypts the given public data (string) using the openssl-extension
*
* @param string $data
* @return string
* @throws Exception
*/
private function encryptPublic($data)
{
if (! openssl_public_encrypt($data, $encrypted, $this->key, $this->cryptParams['padding'])) {
throw new Exception('Failure encrypting Data (openssl public) - ' . openssl_error_string());
}
return $encrypted;
}
/**
* Decrypts the given public data (string) using the openssl-extension
*
* @param string $data
* @return string
* @throws Exception
*/
private function decryptPublic($data)
{
if (! openssl_public_decrypt($data, $decrypted, $this->key, $this->cryptParams['padding'])) {
throw new Exception('Failure decrypting Data (openssl public) - ' . openssl_error_string());
}
return $decrypted;
}
/**
* Encrypts the given private data (string) using the openssl-extension
*
* @param string $data
* @return string
* @throws Exception
*/
private function encryptPrivate($data)
{
if (! openssl_private_encrypt($data, $encrypted, $this->key, $this->cryptParams['padding'])) {
throw new Exception('Failure encrypting Data (openssl private) - ' . openssl_error_string());
}
return $encrypted;
}
/**
* Decrypts the given private data (string) using the openssl-extension
*
* @param string $data
* @return string
* @throws Exception
*/
private function decryptPrivate($data)
{
if (! openssl_private_decrypt($data, $decrypted, $this->key, $this->cryptParams['padding'])) {
throw new Exception('Failure decrypting Data (openssl private) - ' . openssl_error_string());
}
return $decrypted;
}
/**
* Signs the given data (string) using the openssl-extension
*
* @param string $data
* @return string
* @throws Exception
*/
private function signOpenSSL($data)
{
$algo = OPENSSL_ALGO_SHA1;
if (! empty($this->cryptParams['digest'])) {
$algo = $this->cryptParams['digest'];
}
if (! openssl_sign($data, $signature, $this->key, $algo)) {
throw new Exception('Failure Signing Data: ' . openssl_error_string() . ' - ' . $algo);
}
return $signature;
}
/**
* Verifies the given data (string) belonging to the given signature using the openssl-extension
*
* Returns:
* 1 on succesful signature verification,
* 0 when signature verification failed,
* -1 if an error occurred during processing.
*
* NOTE: be very careful when checking the return value, because in PHP,
* -1 will be cast to True when in boolean context. So always check the
* return value in a strictly typed way, e.g. "$obj->verify(...) === 1".
*
* @param string $data
* @param string $signature
* @return int
*/
private function verifyOpenSSL($data, $signature)
{
$algo = OPENSSL_ALGO_SHA1;
if (! empty($this->cryptParams['digest'])) {
$algo = $this->cryptParams['digest'];
}
return openssl_verify($data, $signature, $this->key, $algo);
}
/**
* Encrypts the given data (string) using the regarding php-extension, depending on the library assigned to algorithm in the contructor.
*
* @param string $data
* @return mixed|string
*/
public function encryptData($data)
{
if ($this->cryptParams['library'] === 'openssl') {
switch ($this->cryptParams['type']) {
case 'symmetric':
return $this->encryptSymmetric($data);
case 'public':
return $this->encryptPublic($data);
case 'private':
return $this->encryptPrivate($data);
}
}
}
/**
* Decrypts the given data (string) using the regarding php-extension, depending on the library assigned to algorithm in the contructor.
*
* @param string $data
* @return mixed|string
*/
public function decryptData($data)
{
if ($this->cryptParams['library'] === 'openssl') {
switch ($this->cryptParams['type']) {
case 'symmetric':
return $this->decryptSymmetric($data);
case 'public':
return $this->decryptPublic($data);
case 'private':
return $this->decryptPrivate($data);
}
}
}
/**
* Signs the data (string) using the extension assigned to the type in the constructor.
*
* @param string $data
* @return mixed|string
*/
public function signData($data)
{
switch ($this->cryptParams['library']) {
case 'openssl':
return $this->signOpenSSL($data);
case (self::HMAC_SHA1):
return hash_hmac("sha1", $data, $this->key, true);
}
}
/**
* Verifies the data (string) against the given signature using the extension assigned to the type in the constructor.
*
* Returns in case of openSSL:
* 1 on succesful signature verification,
* 0 when signature verification failed,
* -1 if an error occurred during processing.
*
* NOTE: be very careful when checking the return value, because in PHP,
* -1 will be cast to True when in boolean context. So always check the
* return value in a strictly typed way, e.g. "$obj->verify(...) === 1".
*
* @param string $data
* @param string $signature
* @return bool|int
*/
public function verifySignature($data, $signature)
{
switch ($this->cryptParams['library']) {
case 'openssl':
return $this->verifyOpenSSL($data, $signature);
case (self::HMAC_SHA1):
$expectedSignature = hash_hmac("sha1", $data, $this->key, true);
return strcmp($signature, $expectedSignature) == 0;
}
}
/**
* @deprecated
* @see getAlgorithm()
* @return mixed
*/
public function getAlgorith()
{
return $this->getAlgorithm();
}
/**
* @return mixed
*/
public function getAlgorithm()
{
return $this->cryptParams['method'];
}
/**
*
* @param int $type
* @param string $string
* @return null|string
*/
public static function makeAsnSegment($type, $string)
{
switch ($type) {
case 0x02:
if (ord($string) > 0x7f)
$string = chr(0).$string;
break;
case 0x03:
$string = chr(0).$string;
break;
}
$length = strlen($string);
if ($length < 128) {
$output = sprintf("%c%c%s", $type, $length, $string);
} else if ($length < 0x0100) {
$output = sprintf("%c%c%c%s", $type, 0x81, $length, $string);
} else if ($length < 0x010000) {
$output = sprintf("%c%c%c%c%s", $type, 0x82, $length / 0x0100, $length % 0x0100, $string);
} else {
$output = null;
}
return $output;
}
/**
*
* Hint: Modulus and Exponent must already be base64 decoded
* @param string $modulus
* @param string $exponent
* @return string
*/
public static function convertRSA($modulus, $exponent)
{
/* make an ASN publicKeyInfo */
$exponentEncoding = self::makeAsnSegment(0x02, $exponent);
$modulusEncoding = self::makeAsnSegment(0x02, $modulus);
$sequenceEncoding = self::makeAsnSegment(0x30, $modulusEncoding.$exponentEncoding);
$bitstringEncoding = self::makeAsnSegment(0x03, $sequenceEncoding);
$rsaAlgorithmIdentifier = pack("H*", "300D06092A864886F70D0101010500");
$publicKeyInfo = self::makeAsnSegment(0x30, $rsaAlgorithmIdentifier.$bitstringEncoding);
/* encode the publicKeyInfo in base64 and add PEM brackets */
$publicKeyInfoBase64 = base64_encode($publicKeyInfo);
$encoding = "-----BEGIN PUBLIC KEY-----\n";
$offset = 0;
while ($segment = substr($publicKeyInfoBase64, $offset, 64)) {
$encoding = $encoding.$segment."\n";
$offset += 64;
}
return $encoding."-----END PUBLIC KEY-----\n";
}
/**
* @param mixed $parent
*/
public function serializeKey($parent)
{
}
/**
* Retrieve the X509 certificate this key represents.
*
* Will return the X509 certificate in PEM-format if this key represents
* an X509 certificate.
*
* @return string The X509 certificate or null if this key doesn't represent an X509-certificate.
*/
public function getX509Certificate()
{
return $this->x509Certificate;
}
/**
* Get the thumbprint of this X509 certificate.
*
* Returns:
* The thumbprint as a lowercase 40-character hexadecimal number, or null
* if this isn't a X509 certificate.
*
* @return string Lowercase 40-character hexadecimal number of thumbprint
*/
public function getX509Thumbprint()
{
return $this->X509Thumbprint;
}
/**
* Create key from an EncryptedKey-element.
*
* @param DOMElement $element The EncryptedKey-element.
* @return MoXMLSecurityKey The new key.
* @throws Exception
*
*/
public static function fromEncryptedKeyElement(DOMElement $element)
{
$objenc = new MoXMLSecEnc();
$objenc->setNode($element);
if (! $objKey = $objenc->locateKey()) {
throw new Exception("Unable to locate algorithm for this Encrypted Key");
}
$objKey->isEncrypted = true;
$objKey->encryptedCtx = $objenc;
MoXMLSecEnc::staticLocateKeyInfo($objKey, $element);
return $objKey;
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace RobRichards\XMLSecLibs\Utils;
class MoXPath
{
const ALPHANUMERIC = '\w\d';
const NUMERIC = '\d';
const LETTERS = '\w';
const EXTENDED_ALPHANUMERIC = '\w\d\s\-_:\.';
const SINGLE_QUOTE = '\'';
const DOUBLE_QUOTE = '"';
const ALL_QUOTES = '[\'"]';
/**
* Filter an attribute value for save inclusion in an XPath query.
*
* @param string $value The value to filter.
* @param string $quotes The quotes used to delimit the value in the XPath query.
*
* @return string The filtered attribute value.
*/
public static function filterAttrValue($value, $quotes = self::ALL_QUOTES)
{
return preg_replace('#'.$quotes.'#', '', $value);
}
/**
* Filter an attribute name for save inclusion in an XPath query.
*
* @param string $name The attribute name to filter.
* @param mixed $allow The set of characters to allow. Can be one of the constants provided by this class, or a
* custom regex excluding the '#' character (used as delimiter).
*
* @return string The filtered attribute name.
*/
public static function filterAttrName($name, $allow = self::EXTENDED_ALPHANUMERIC)
{
return preg_replace('#[^'.$allow.']#', '', $name);
}
}

View File

@@ -0,0 +1,830 @@
<?php
/**
* This file is part of miniOrange SAML plugin.
*
* miniOrange SAML plugin is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* miniOrange SAML plugin 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with miniOrange SAML plugin. If not, see <http://www.gnu.org/licenses/>.
*/
include_once 'xmlseclibs.php';
use \RobRichards\XMLSecLibs\MoXMLSecurityKey;
use \RobRichards\XMLSecLibs\MoXMLSecurityDSig;
use \RobRichards\XMLSecLibs\MoXMLSecEnc;
class Utilities {
public static function generateID() {
return '_' . self::stringToHex(self::generateRandomBytes(21));
}
public static function stringToHex($bytes) {
$ret = '';
for($i = 0; $i < strlen($bytes); $i++) {
$ret .= sprintf('%02x', ord($bytes[$i]));
}
return $ret;
}
public static function generateRandomBytes($length, $fallback = TRUE) {
return openssl_random_pseudo_bytes($length);
}
public static function createAuthnRequest($acsUrl, $issuer, $force_authn = 'false') {
$requestXmlStr = '<?xml version="1.0" encoding="UTF-8"?>' .
'<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="' . self::generateID() .
'" Version="2.0" IssueInstant="' . self::generateTimestamp() . '"';
if( $force_authn == 'true') {
$requestXmlStr .= ' ForceAuthn="true"';
}
$requestXmlStr .= ' ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" AssertionConsumerServiceURL="' . $acsUrl .
'" ><saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">' . $issuer . '</saml:Issuer></samlp:AuthnRequest>';
$deflatedStr = gzdeflate($requestXmlStr);
$base64EncodedStr = base64_encode($deflatedStr);
$urlEncoded = urlencode($base64EncodedStr);
update_option('MO_SAML_REQUEST',$base64EncodedStr);
return $urlEncoded;
}
public static function createSAMLRequest($acsUrl, $issuer, $destination, $force_authn = 'false') {
$requestXmlStr = '<?xml version="1.0" encoding="UTF-8"?>' .
'<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns="urn:oasis:names:tc:SAML:2.0:assertion" ID="' . self::generateID() .
'" Version="2.0" IssueInstant="' . self::generateTimestamp() . '"';
if( $force_authn == 'true') {
$requestXmlStr .= ' ForceAuthn="true"';
}
$requestXmlStr .= ' ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" AssertionConsumerServiceURL="' . $acsUrl .
'" Destination="' . htmlspecialchars($destination) . '"><saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">' . $issuer . '</saml:Issuer><samlp:NameIDPolicy AllowCreate="true" Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
/></samlp:AuthnRequest>';
$samlRequest = base64_encode($requestXmlStr);
update_option('MO_SAML_REQUEST',$samlRequest);
return $requestXmlStr;
}
public static function generateTimestamp($instant = NULL) {
if($instant === NULL) {
$instant = time();
}
return gmdate('Y-m-d\TH:i:s\Z', $instant);
}
public static function xpQuery(DOMNode $node, $query)
{
static $xpCache = NULL;
if ($node instanceof DOMDocument) {
$doc = $node;
} else {
$doc = $node->ownerDocument;
}
if ($xpCache === NULL || !$xpCache->document->isSameNode($doc)) {
$xpCache = new DOMXPath($doc);
$xpCache->registerNamespace('soap-env', 'http://schemas.xmlsoap.org/soap/envelope/');
$xpCache->registerNamespace('saml_protocol', 'urn:oasis:names:tc:SAML:2.0:protocol');
$xpCache->registerNamespace('saml_assertion', 'urn:oasis:names:tc:SAML:2.0:assertion');
$xpCache->registerNamespace('saml_metadata', 'urn:oasis:names:tc:SAML:2.0:metadata');
$xpCache->registerNamespace('ds', 'http://www.w3.org/2000/09/xmldsig#');
$xpCache->registerNamespace('xenc', 'http://www.w3.org/2001/04/xmlenc#');
}
$results = $xpCache->query($query, $node);
$ret = array();
for ($i = 0; $i < $results->length; $i++) {
$ret[$i] = $results->item($i);
}
return $ret;
}
public static function parseNameId(DOMElement $xml)
{
$ret = array('Value' => trim($xml->textContent));
foreach (array('NameQualifier', 'SPNameQualifier', 'Format') as $attr) {
if ($xml->hasAttribute($attr)) {
$ret[$attr] = $xml->getAttribute($attr);
}
}
return $ret;
}
public static function xsDateTimeToTimestamp($time)
{
$matches = array();
// We use a very strict regex to parse the timestamp.
$regex = '/^(\\d\\d\\d\\d)-(\\d\\d)-(\\d\\d)T(\\d\\d):(\\d\\d):(\\d\\d)(?:\\.\\d+)?Z$/D';
if (preg_match($regex, $time, $matches) == 0) {
echo sprintf("Invalid SAML2 timestamp passed to xsDateTimeToTimestamp: ". htmlspecialchars($time));
exit;
}
// Extract the different components of the time from the matches in the regex.
// intval will ignore leading zeroes in the string.
$year = intval($matches[1]);
$month = intval($matches[2]);
$day = intval($matches[3]);
$hour = intval($matches[4]);
$minute = intval($matches[5]);
$second = intval($matches[6]);
// We use gmmktime because the timestamp will always be given
//in UTC.
$ts = gmmktime($hour, $minute, $second, $month, $day, $year);
return $ts;
}
public static function extractStrings(DOMElement $parent, $namespaceURI, $localName)
{
$ret = array();
for ($node = $parent->firstChild; $node !== NULL; $node = $node->nextSibling) {
if ($node->namespaceURI !== $namespaceURI || $node->localName !== $localName) {
continue;
}
$ret[] = trim($node->textContent);
}
return $ret;
}
public static function validateElement(DOMElement $root)
{
//$data = $root->ownerDocument->saveXML($root);
//echo htmlspecialchars($data);
/* Create an XML security object. */
$objXMLSecDSig = new MoXMLSecurityDSig();
/* Both SAML messages and SAML assertions use the 'ID' attribute. */
$objXMLSecDSig->idKeys[] = 'ID';
/* Locate the XMLDSig Signature element to be used. */
$signatureElement = self::xpQuery($root, './ds:Signature');
//print_r($signatureElement);
if (count($signatureElement) === 0) {
/* We don't have a signature element to validate. */
return FALSE;
} elseif (count($signatureElement) > 1) {
echo sprintf("XMLSec: more than one signature element in root.");
exit;
}/* elseif ((in_array('Response', $signatureElement) && $ocurrence['Response'] > 1) ||
(in_array('Assertion', $signatureElement) && $ocurrence['Assertion'] > 1) ||
!in_array('Response', $signatureElement) && !in_array('Assertion', $signatureElement)
) {
return false;
} */
$signatureElement = $signatureElement[0];
$objXMLSecDSig->sigNode = $signatureElement;
/* Canonicalize the XMLDSig SignedInfo element in the message. */
$objXMLSecDSig->canonicalizeSignedInfo();
/* Validate referenced xml nodes. */
if (!$objXMLSecDSig->validateReference()) {
echo sprintf("XMLSec: digest validation failed");
exit;
}
/* Check that $root is one of the signed nodes. */
$rootSigned = FALSE;
/** @var DOMNode $signedNode */
foreach ($objXMLSecDSig->getValidatedNodes() as $signedNode) {
if ($signedNode->isSameNode($root)) {
$rootSigned = TRUE;
break;
} elseif ($root->parentNode instanceof DOMDocument && $signedNode->isSameNode($root->ownerDocument)) {
/* $root is the root element of a signed document. */
$rootSigned = TRUE;
break;
}
}
if (!$rootSigned) {
echo sprintf("XMLSec: The root element is not signed.");
exit;
}
/* Now we extract all available X509 certificates in the signature element. */
$certificates = array();
foreach (self::xpQuery($signatureElement, './ds:KeyInfo/ds:X509Data/ds:X509Certificate') as $certNode) {
$certData = trim($certNode->textContent);
$certData = str_replace(array("\r", "\n", "\t", ' '), '', $certData);
$certificates[] = $certData;
//echo "CertDate: " . $certData . "<br />";
}
$ret = array(
'Signature' => $objXMLSecDSig,
'Certificates' => $certificates,
);
//echo "Signature validated";
return $ret;
}
public static function validateSignature(array $info, MoXMLSecurityKey $key)
{
/** @var MoXMLSecurityDSig $objXMLSecDSig */
$objXMLSecDSig = $info['Signature'];
$sigMethod = self::xpQuery($objXMLSecDSig->sigNode, './ds:SignedInfo/ds:SignatureMethod');
if (empty($sigMethod)) {
echo sprintf('Missing SignatureMethod element');
exit();
}
$sigMethod = $sigMethod[0];
if (!$sigMethod->hasAttribute('Algorithm')) {
echo sprintf('Missing Algorithm-attribute on SignatureMethod element.');
exit;
}
$algo = $sigMethod->getAttribute('Algorithm');
if ($key->type === MoXMLSecurityKey::RSA_SHA1 && $algo !== $key->type) {
$key = self::castKey($key, $algo);
}
/* Check the signature. */
if (! $objXMLSecDSig->verify($key)) {
echo sprintf('Unable to validate Signature');
exit;
}
}
public static function castKey(MoXMLSecurityKey $key, $algorithm, $type = 'public')
{
// do nothing if algorithm is already the type of the key
if ($key->type === $algorithm) {
return $key;
}
$keyInfo = openssl_pkey_get_details($key->key);
if ($keyInfo === FALSE) {
echo sprintf('Unable to get key details from XMLSecurityKey.');
exit;
}
if (!isset($keyInfo['key'])) {
echo sprintf('Missing key in public key details.');
exit;
}
$newKey = new MoXMLSecurityKey($algorithm, array('type'=>$type));
$newKey->loadKey($keyInfo['key']);
return $newKey;
}
public static function processResponse($currentURL, $certFingerprint, $signatureData,
SAML2_Response $response, $certNumber,$relayState) {
$assertion = current($response->getAssertions());
$notBefore = $assertion->getNotBefore();
if ($notBefore !== NULL && $notBefore > time() + 60) {
error_log('Received an assertion that is valid in the future. Check clock synchronization on IdP and SP.');
return false;
}
$notOnOrAfter = $assertion->getNotOnOrAfter();
if ($notOnOrAfter !== NULL && $notOnOrAfter <= time() - 60) {
error_log('Received an assertion that has expired. Check clock synchronization on IdP and SP.');
return false;
}
$sessionNotOnOrAfter = $assertion->getSessionNotOnOrAfter();
if ($sessionNotOnOrAfter !== NULL && $sessionNotOnOrAfter <= time() - 60) {
error_log('Received an assertion with a session that has expired. Check clock synchronization on IdP and SP.');
return false;
}
/* Validate Response-element destination. */
$msgDestination = $response->getDestination();
if(substr($msgDestination, -1) == '/') {
$msgDestination = substr($msgDestination, 0, -1);
}
if(substr($currentURL, -1) == '/') {
$currentURL = substr($currentURL, 0, -1);
}
if ($msgDestination !== NULL && $msgDestination !== $currentURL) {
echo sprintf('Destination in response doesn\'t match the current URL. Destination is "' .
htmlspecialchars($msgDestination) . '", current URL is "' . htmlspecialchars($currentURL) . '".');
exit;
}
$responseSigned = self::checkSign($certFingerprint, $signatureData, $certNumber,$relayState);
if (!$responseSigned) {
error_log('SAML: Responses is not signed');
}
/* Returning boolean $responseSigned */
return $responseSigned;
}
public static function checkSign($certFingerprint, $signatureData, $certNumber, $relayState) {
$certificates = $signatureData['Certificates'];
error_log('SAML: certificate count = '.count($certificates));
if (count($certificates) === 0) {
$storedCerts = maybe_unserialize(get_option('saml_x509_certificate'));
$pemCert = $storedCerts[$certNumber];
}else{
$fpArray = array();
$fpArray[] = $certFingerprint;
$pemCert = self::findCertificate($fpArray, $certificates, $relayState);
if($pemCert==false)
return false;
}
$lastException = NULL;
$key = new MoXMLSecurityKey(MoXMLSecurityKey::RSA_SHA1, array('type'=>'public'));
$key->loadKey($pemCert);
try {
/*
* Make sure that we have a valid signature
*/
self::validateSignature($signatureData, $key);
return TRUE;
} catch (Exception $e) {
$lastException = $e;
}
/* We were unable to validate the signature with any of our keys. */
if ($lastException !== NULL) {
throw $lastException;
} else {
return FALSE;
}
}
public static function validateIssuerAndAudience($samlResponse, $spEntityId, $issuerToValidateAgainst, $relayState) {
$issuer = current($samlResponse->getAssertions())->getIssuer();
$assertion = current($samlResponse->getAssertions());
$audiences = $assertion->getValidAudiences();
if(strcmp($issuerToValidateAgainst, $issuer) === 0) {
if(!empty($audiences)) {
if(in_array($spEntityId, $audiences, TRUE)) {
return TRUE;
} else {
if($relayState=='testValidate'){
$Error_message=mo_saml_options_error_constants::Error_invalid_audience;
$Cause_message = mo_saml_options_error_constants::Cause_invalid_audience;
echo '<div style="font-family:Calibri;padding:0 3%;">';
echo '<div style="color: #a94442;background-color: #f2dede;padding: 15px;margin-bottom: 20px;text-align:center;border:1px solid #E6B3B2;font-size:18pt;"> ' . __('ERROR','miniorange-saml-20-single-sign-on') . '</div>
<div style="color: #a94442;font-size:14pt; margin-bottom:20px;"><p><strong>' . __('Error','miniorange-saml-20-single-sign-on') . ': </strong>'.$Error_message.'</p>
<p><strong>' . __('Possible Cause','miniorange-saml-20-single-sign-on'). ': </strong>'.$Cause_message.'</p>
<p>' . __('Expected one of the Audiences to be','miniorange-saml-20-single-sign-on'). ': '.$spEntityId.'<p>
</div>';
mo_saml_download_logs($Error_message,$Cause_message);
exit;
}
else
{
wp_die(__("We could not sign you in. Please contact your administrator",'miniorange-saml-20-single-sign-on'),"Error: Invalid Audience URI");
}
}
}
} else {
if($relayState=='testValidate'){
$Error_message=mo_saml_options_error_constants::Error_issuer_not_verfied;
$Cause_message = mo_saml_options_error_constants::Cause_issuer_not_verfied;
update_option('mo_saml_required_issuer',$issuer);
echo '<div style="font-family:Calibri;padding:0 3%;">';
echo '<div style="color: #a94442;background-color: #f2dede;padding: 15px;margin-bottom: 20px;text-align:center;border:1px solid #E6B3B2;font-size:18pt;">' . __('ERROR','miniorange-saml-20-single-sign-on') . '</div>
<div style="color: #a94442;font-size:14pt; margin-bottom:20px;text-align: justify"><p><strong>' . __('Error','miniorange-saml-20-single-sign-on'). ':'.$Error_message.' </strong></p>
<p><strong>' . __('Possible Cause','miniorange-saml-20-single-sign-on') . ':'.$Cause_message.' </strong></p>
<div>
<ol style="text-align: center">
<form method="post" action="" name="mo_fix_entityid" id="mo_fix_certificate">';
wp_nonce_field('mo_fix_entity_id');
echo '<input type="hidden" name="option" value="mo_fix_entity_id" />
<input type="submit" class="miniorange-button" style="width: 55%" value="' . __('Fix Issue','miniorange-saml-20-single-sign-on' ) .'">
</form>
</ol>
</div>
</div>
</div>';
mo_saml_download_logs($Error_message,$Cause_message);
exit;
}
else
{
wp_die(__("We could not sign you in. Please contact your administrator",'miniorange-saml-20-single-sign-on'),"Error: Issuer cannot be verified");
}
}
}
private static function findCertificate(array $certFingerprints, array $certificates, $relayState) {
$candidates = array();
//foreach ($certificates as $cert) {
$fp = strtolower(sha1(base64_decode($certificates[0])));
if (!in_array($fp, $certFingerprints, TRUE)) {
$candidates[] = $fp;
return false;
//continue;
}
/* We have found a matching fingerprint. */
$pem = "-----BEGIN CERTIFICATE-----\n" .
chunk_split($certificates[0], 64) .
"-----END CERTIFICATE-----\n";
return $pem;
// }
// if($relayState=='testValidate'){
// $pem = "-----BEGIN CERTIFICATE-----<br>" .
// chunk_split($cert, 64) .
// "<br>-----END CERTIFICATE-----";
// echo '<div style="font-family:Calibri;padding:0 3%;">';
// echo '<div style="color: #a94442;background-color: #f2dede;padding: 15px;margin-bottom: 20px;text-align:center;border:1px solid #E6B3B2;font-size:18pt;"> ERROR</div>
// <div style="color: #a94442;font-size:14pt; margin-bottom:20px;"><p><strong>Error: </strong>Unable to find a certificate matching the configured fingerprint.</p>
// <p>Please contact your administrator and report the following error:</p>
// <p><strong>Possible Cause: </strong>Content of \'X.509 Certificate\' field in Service Provider Settings is incorrect. Please replace it with certificate given below.</p>
// <p><strong>Certificate found in SAML Response: </strong><br><br>'.$pem.'</p>
// </div>
// <div style="margin:3%;display:block;text-align:center;">
// <form action="index.php">
// <div style="margin:3%;display:block;text-align:center;"><input style="padding:1%;width:100px;background: #0091CD none repeat scroll 0% 0%;cursor: pointer;font-size:15px;border-width: 1px;border-style: solid;border-radius: 3px;white-space: nowrap;box-sizing: border-box;border-color: #0073AA;box-shadow: 0px 1px 0px rgba(120, 200, 230, 0.6) inset;color: #FFF;"type="button" value="Done" onClick="self.close();"></div>';
// exit;
// }
// else{
// wp_die("We could not sign you in. Please contact your administrator","Error: Invalid Certificate");
// }
}
/**
* Decrypt an encrypted element.
*
* This is an internal helper function.
*
* @param DOMElement $encryptedData The encrypted data.
* @param MoXMLSecurityKey $inputKey The decryption key.
* @param array &$blacklist Blacklisted decryption algorithms.
* @return DOMElement The decrypted element.
* @throws Exception
*/
private static function doDecryptElement(DOMElement $encryptedData, MoXMLSecurityKey $inputKey, array &$blacklist)
{
$enc = new MoXMLSecEnc();
$enc->setNode($encryptedData);
$enc->type = $encryptedData->getAttribute("Type");
$symmetricKey = $enc->locateKey($encryptedData);
if (!$symmetricKey) {
echo sprintf(__('Could not locate key algorithm in encrypted data.','miniorange-saml-20-single-sign-on'));
exit;
}
$symmetricKeyInfo = $enc->locateKeyInfo($symmetricKey);
if (!$symmetricKeyInfo) {
echo sprintf(__('Could not locate <dsig:KeyInfo> for the encrypted key.','miniorange-saml-20-single-sign-on'));
exit;
}
$inputKeyAlgo = $inputKey->getAlgorith();
if ($symmetricKeyInfo->isEncrypted) {
$symKeyInfoAlgo = $symmetricKeyInfo->getAlgorith();
if (in_array($symKeyInfoAlgo, $blacklist, TRUE)) {
echo sprintf('Algorithm disabled: ' . var_export($symKeyInfoAlgo, TRUE));
exit;
}
if ($symKeyInfoAlgo === MoXMLSecurityKey::RSA_OAEP_MGF1P && $inputKeyAlgo === MoXMLSecurityKey::RSA_1_5) {
/*
* The RSA key formats are equal, so loading an RSA_1_5 key
* into an RSA_OAEP_MGF1P key can be done without problems.
* We therefore pretend that the input key is an
* RSA_OAEP_MGF1P key.
*/
$inputKeyAlgo = MoXMLSecurityKey::RSA_OAEP_MGF1P;
}
/* Make sure that the input key format is the same as the one used to encrypt the key. */
if ($inputKeyAlgo !== $symKeyInfoAlgo) {
echo sprintf( 'Algorithm mismatch between input key and key used to encrypt ' .
' the symmetric key for the message. Key was: ' .
var_export($inputKeyAlgo, TRUE) . '; message was: ' .
var_export($symKeyInfoAlgo, TRUE));
exit;
}
/** @var MoXMLSecEnc $encKey */
$encKey = $symmetricKeyInfo->encryptedCtx;
$symmetricKeyInfo->key = $inputKey->key;
$keySize = $symmetricKey->getSymmetricKeySize();
if ($keySize === NULL) {
/* To protect against "key oracle" attacks, we need to be able to create a
* symmetric key, and for that we need to know the key size.
*/
echo sprintf('Unknown key size for encryption algorithm: ' . var_export($symmetricKey->type, TRUE));
exit;
}
try {
$key = $encKey->decryptKey($symmetricKeyInfo);
if (strlen($key) != $keySize) {
echo sprintf('Unexpected key size (' . strlen($key) * 8 . 'bits) for encryption algorithm: ' .
var_export($symmetricKey->type, TRUE));
exit;
}
} catch (Exception $e) {
/* We failed to decrypt this key. Log it, and substitute a "random" key. */
/* Create a replacement key, so that it looks like we fail in the same way as if the key was correctly padded. */
/* We base the symmetric key on the encrypted key and private key, so that we always behave the
* same way for a given input key.
*/
$encryptedKey = $encKey->getCipherValue();
$pkey = openssl_pkey_get_details($symmetricKeyInfo->key);
$pkey = sha1(serialize($pkey), TRUE);
$key = sha1($encryptedKey . $pkey, TRUE);
/* Make sure that the key has the correct length. */
if (strlen($key) > $keySize) {
$key = substr($key, 0, $keySize);
} elseif (strlen($key) < $keySize) {
$key = str_pad($key, $keySize);
}
}
$symmetricKey->loadkey($key);
} else {
$symKeyAlgo = $symmetricKey->getAlgorith();
/* Make sure that the input key has the correct format. */
if ($inputKeyAlgo !== $symKeyAlgo) {
echo sprintf( 'Algorithm mismatch between input key and key in message. ' .
'Key was: ' . var_export($inputKeyAlgo, TRUE) . '; message was: ' .
var_export($symKeyAlgo, TRUE));
exit;
}
$symmetricKey = $inputKey;
}
$algorithm = $symmetricKey->getAlgorith();
if (in_array($algorithm, $blacklist, TRUE)) {
echo sprintf('Algorithm disabled: ' . var_export($algorithm, TRUE));
exit;
}
/** @var string $decrypted */
$decrypted = $enc->decryptNode($symmetricKey, FALSE);
/*
* This is a workaround for the case where only a subset of the XML
* tree was serialized for encryption. In that case, we may miss the
* namespaces needed to parse the XML.
*/
$xml = '<root xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" '.
'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">' .
$decrypted .
'</root>';
$newDoc = new DOMDocument();
if (!@$newDoc->loadXML($xml)) {
echo sprintf('Failed to parse decrypted XML. Maybe the wrong sharedkey was used?');
throw new Exception('Failed to parse decrypted XML. Maybe the wrong sharedkey was used?');
}
$decryptedElement = $newDoc->firstChild->firstChild;
if ($decryptedElement === NULL) {
echo sprintf('Missing encrypted element.');
throw new Exception('Missing encrypted element.');
}
if (!($decryptedElement instanceof DOMElement)) {
echo sprintf('Decrypted element was not actually a DOMElement.');
}
return $decryptedElement;
}
/**
* Decrypt an encrypted element.
*
* @param DOMElement $encryptedData The encrypted data.
* @param MoXMLSecurityKey $inputKey The decryption key.
* @param array $blacklist Blacklisted decryption algorithms.
* @return DOMElement The decrypted element.
* @throws Exception
*/
public static function decryptElement(DOMElement $encryptedData, MoXMLSecurityKey $inputKey, array $blacklist = array(), MoXMLSecurityKey $alternateKey = NULL)
{
try {
return self::doDecryptElement($encryptedData, $inputKey, $blacklist);
} catch (Exception $e) {
//Try with alternate key
try {
return self::doDecryptElement($encryptedData, $alternateKey, $blacklist);
} catch(Exception $t) {
}
/*
* Something went wrong during decryption, but for security
* reasons we cannot tell the user what failed.
*/
//print_r($e->getMessage());
echo sprintf('Failed to decrypt XML element.');
exit;
}
}
/**
* Generates the metadata of the SP based on the settings
*
* @param string $sp The SP data
* @param string $authnsign authnRequestsSigned attribute
* @param string $wsign wantAssertionsSigned attribute
* @param DateTime $validUntil Metadata's valid time
* @param Timestamp $cacheDuration Duration of the cache in seconds
* @param array $contacts Contacts info
* @param array $organization Organization ingo
*
* @return string SAML Metadata XML
*/
public static function metadata_builder($siteUrl)
{
$xml = new DOMDocument();
$url = plugins_url().'/miniorange-saml-20-single-sign-on/sp-metadata.xml';
$xml->load($url);
$xpath = new DOMXPath($xml);
$elements = $xpath->query('//md:EntityDescriptor[@entityID="http://{path-to-your-site}/wp-content/plugins/miniorange-saml-20-single-sign-on/"]');
if ($elements->length >= 1) {
$element = $elements->item(0);
$element->setAttribute('entityID', $siteUrl.'/wp-content/plugins/miniorange-saml-20-single-sign-on/');
}
$elements = $xpath->query('//md:AssertionConsumerService[@Location="http://{path-to-your-site}"]');
if ($elements->length >= 1) {
$element = $elements->item(0);
$element->setAttribute('Location', $siteUrl.'/');
}
//re-save
$xml->save(plugins_url()."/miniorange-saml-20-single-sign-on/sp-metadata.xml");
}
public static function get_mapped_groups($saml_params, $saml_groups)
{
$groups = array();
if (!empty($saml_groups)) {
$saml_mapped_groups = array();
$i=1;
while ($i < 10) {
$saml_mapped_groups_value = $saml_params->get('group'.$i.'_map');
$saml_mapped_groups[$i] = explode(';', $saml_mapped_groups_value);
$i++;
}
}
foreach ($saml_groups as $saml_group) {
if (!empty($saml_group)) {
$i = 0;
$found = false;
while ($i < 9 && !$found) {
if (!empty($saml_mapped_groups[$i]) && in_array($saml_group, $saml_mapped_groups[$i], TRUE)) {
$groups[] = $saml_params->get('group'.$i);
$found = true;
}
$i++;
}
}
}
return array_unique($groups);
}
public static function getEncryptionAlgorithm($method){
switch($method){
case 'http://www.w3.org/2001/04/xmlenc#tripledes-cbc':
return MoXMLSecurityKey::TRIPLEDES_CBC;
break;
case 'http://www.w3.org/2001/04/xmlenc#aes128-cbc':
return MoXMLSecurityKey::AES128_CBC;
case 'http://www.w3.org/2001/04/xmlenc#aes192-cbc':
return MoXMLSecurityKey::AES192_CBC;
break;
case 'http://www.w3.org/2001/04/xmlenc#aes256-cbc':
return MoXMLSecurityKey::AES256_CBC;
break;
case 'http://www.w3.org/2001/04/xmlenc#rsa-1_5':
return MoXMLSecurityKey::RSA_1_5;
break;
case 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p':
return MoXMLSecurityKey::RSA_OAEP_MGF1P;
break;
case 'http://www.w3.org/2000/09/xmldsig#dsa-sha1':
return MoXMLSecurityKey::DSA_SHA1;
break;
case 'http://www.w3.org/2000/09/xmldsig#rsa-sha1':
return MoXMLSecurityKey::RSA_SHA1;
break;
case 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256':
return MoXMLSecurityKey::RSA_SHA256;
break;
case 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha384':
return MoXMLSecurityKey::RSA_SHA384;
break;
case 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512':
return MoXMLSecurityKey::RSA_SHA512;
break;
default:
echo sprintf('Invalid Encryption Method: '. htmlspecialchars($method));
exit;
break;
}
}
public static function sanitize_certificate( $certificate ) {
$certificate = preg_replace("/[\r\n]+/", "", $certificate);
$certificate = str_replace( "-", "", $certificate );
$certificate = str_replace( "BEGIN CERTIFICATE", "", $certificate );
$certificate = str_replace( "END CERTIFICATE", "", $certificate );
$certificate = str_replace( " ", "", $certificate );
$certificate = chunk_split($certificate, 64, "\r\n");
$certificate = "-----BEGIN CERTIFICATE-----\r\n" . $certificate . "-----END CERTIFICATE-----";
return $certificate;
}
public static function desanitize_certificate( $certificate ) {
$certificate = preg_replace("/[\r\n]+/", "", $certificate);
//$certificate = str_replace( "-", "", $certificate );
$certificate = str_replace( "-----BEGIN CERTIFICATE-----", "", $certificate );
$certificate = str_replace( "-----END CERTIFICATE-----", "", $certificate );
$certificate = str_replace( " ", "", $certificate );
//$certificate = chunk_split($certificate, 64, "\r\n");
//$certificate = "-----BEGIN CERTIFICATE-----\r\n" . $certificate . "-----END CERTIFICATE-----";
return $certificate;
}
public static function mo_saml_wp_remote_post($url, $args = array()){
$response = wp_remote_post($url, $args);
if(!is_wp_error($response)){
return $response;
} else {
$show_message = new saml_mo_login();
update_option('mo_saml_message', __('Unable to connect to the Internet. Please try again.','miniorange-saml-20-single-sign-on'));
$show_message->mo_saml_show_error_message();
}
}
public static function mo_saml_wp_remote_get($url, $args = array()){
$response = wp_remote_get($url, $args);
if(!is_wp_error($response)){
return $response;
} else {
$show_message = new saml_mo_login();
update_option('mo_saml_message', __('Unable to connect to the Internet. Please try again.','miniorange-saml-20-single-sign-on'));
$show_message->mo_saml_show_error_message();
}
}
}
?>

View File

@@ -0,0 +1,44 @@
<?php
/**
* @package miniOrange
* @author miniOrange Security Software Pvt. Ltd.
* @license GNU/GPLv3
* @copyright Copyright 2015 miniOrange. All Rights Reserved.
*
*
* This file is part of miniOrange SAML plugin.
*/
class AESEncryption {
/**
* @param string $data - the key=value pairs separated with &
* @return string
*/
public static function encrypt_data($data, $key) {
$key = openssl_digest($key, 'sha256');
$method = 'AES-128-ECB';
$ivSize = openssl_cipher_iv_length($method);
$iv = openssl_random_pseudo_bytes($ivSize);
$strCrypt = openssl_encrypt ($data, $method, $key,OPENSSL_RAW_DATA||OPENSSL_ZERO_PADDING, $iv);
return base64_encode($iv.$strCrypt);
}
/**
* @param string $data - crypt response from Sagepay
* @return string
*/
public static function decrypt_data($data, $key) {
$strIn = base64_decode($data);
$key = openssl_digest($key, 'sha256');
$method = 'AES-128-ECB';
$ivSize = openssl_cipher_iv_length($method);
$iv = substr($strIn,0,$ivSize);
$data = substr($strIn,$ivSize);
$clear = openssl_decrypt ($data, $method, $key, OPENSSL_RAW_DATA||OPENSSL_ZERO_PADDING, $iv);
return $clear;
}
}
?>

View File

@@ -0,0 +1,640 @@
<?php
include "MoSAMLBasicEnum.php";
class mo_saml_options_enum_sso_loginMoSAML extends MoSAMLBasicEnum {
const Relay_state = 'mo_saml_relay_state';
const Redirect_Idp = 'mo_saml_registered_only_access';
const Force_authentication = 'mo_saml_force_authentication';
const Enable_access_RSS = 'mo_saml_enable_rss_access';
const Auto_redirect = 'mo_saml_enable_login_redirect';
}
class mo_saml_options_enum_identity_providerMoSAML extends MoSAMLBasicEnum{
const Broker_service ='mo_saml_enable_cloud_broker';
const SP_Base_Url='mo_saml_sp_base_url';
const SP_Entity_ID = 'mo_saml_sp_entity_id';
}
class mo_saml_options_tab_names extends MoSAMLBasicEnum{
const Service_provider_settings = 'service-provider-setup';
const Identity_provider_settting = 'identity-provider-setup';
const Redirection_sso_links = 'redirection-sso-links';
const Entire_plugin_tour = 'entire-plugin-tour';
const Attribute_role_mapping = 'attribute-role-mapping';
}
class mo_saml_options_enum_pointersMoSAML extends MoSAMLBasicEnum{
public static
$DEFAULT = array(
'custom_admin_pointers4_8_52_default-miniorange-sp-metadata-url',
'custom_admin_pointers4_8_52_default-miniorange-select-your-idp',
'custom_admin_pointers4_8_52_default-miniorange-upload-metadata',
'custom_admin_pointers4_8_52_default-miniorange-test-configuration',
'custom_admin_pointers4_8_52_default-miniorange-attribute-mapping',
'custom_admin_pointers4_8_52_default-miniorange-role-mapping',
'custom_admin_pointers4_8_52_default-minorange-use-widget',
'custom_admin_pointers4_8_52_default-miniorange-addons',
'custom_admin_pointers4_8_52_default-miniorange-support-pointer'
);
public static $DEFAULT_SKIP = array(
'custom_admin_pointers4_8_52_default-miniorange-sp-metadata-url',
'custom_admin_pointers4_8_52_default-miniorange-select-your-idp',
'custom_admin_pointers4_8_52_default-miniorange-upload-metadata',
'custom_admin_pointers4_8_52_default-miniorange-test-configuration',
'custom_admin_pointers4_8_52_default-miniorange-attribute-mapping',
'custom_admin_pointers4_8_52_default-miniorange-role-mapping',
'custom_admin_pointers4_8_52_default-minorange-use-widget',
'custom_admin_pointers4_8_52_default-miniorange-addons',
);
public static $SERVICE_PROVIDER = array(
'custom_admin_pointers4_8_52_miniorange-select-your-idp',
'custom_admin_pointers4_8_52_miniorange-upload-metadata',
'custom_admin_pointers4_8_52_miniorange-test-configuration',
'custom_admin_pointers4_8_52_miniorange-import-config',
'custom_admin_pointers4_8_52_export-import-config',
'custom_admin_pointers4_8_52_configure-service-restart-tour');
public static $IDENTITY_PROVIDER = array(
'custom_admin_pointers4_8_52_metadata_manual',
'custom_admin_pointers4_8_52_miniorange-sp-metadata-url',
'custom_admin_pointers4_8_52_identity-provider-restart-tour'
);
public static $ATTRIBUTE_MAPPING = array(
'custom_admin_pointers4_8_52_miniorange-attribute-mapping',
'custom_admin_pointers4_8_52_miniorange-role-mapping',
'custom_admin_pointers4_8_52_attribute-mapping-restart-tour');
public static $REDIRECTION_LINK = array(
'custom_admin_pointers4_8_52_minorange-use-widget',
'custom_admin_pointers4_8_52_miniorange-auto-redirect',
'custom_admin_pointers4_8_52_miniorange-auto-redirect-login-page',
'custom_admin_pointers4_8_52_miniorange-short-code',
'custom_admin_pointers4_8_52_miniorange-redirection-sso-restart-tour'
);
}
class mo_saml_options_enum_service_providerMoSAML extends MoSAMLBasicEnum{
const Identity_name ='saml_identity_name';
const Login_binding_type='saml_login_binding_type';
const Login_URL = 'saml_login_url';
const Logout_binding_type = 'saml_logout_binding_type';
const Logout_URL = 'saml_logout_url';
const Issuer = 'saml_issuer';
const X509_certificate = 'saml_x509_certificate';
const Request_signed = 'saml_request_signed';
const Guide_name = 'saml_identity_provider_guide_name';
const Is_encoding_enabled = 'mo_saml_encoding_enabled';
}
class mo_saml_options_test_configuration extends MoSAMLBasicEnum{
const SAML_REQUEST = 'MO_SAML_REQUEST';
const SAML_RESPONSE = 'MO_SAML_RESPONSE';
const TEST_CONFIG_ERROR_LOG = 'MO_SAML_TEST';
}
class mo_saml_options_enum_attribute_mappingMoSAML extends MoSAMLBasicEnum{
const Attribute_Username ='saml_am_username';
const Attribute_Email = 'saml_am_email';
const Attribute_First_name ='saml_am_first_name';
const Attribute_Last_name = 'saml_am_last_name';
const Attribute_Group_name ='saml_am_group_name';
const Attribute_Custom_mapping = 'mo_saml_custom_attrs_mapping';
const Attribute_Account_matcher = 'saml_am_account_matcher';
}
class mo_saml_options_enum_role_mappingMoSAML extends MoSAMLBasicEnum{
const Role_do_not_auto_create_users = 'mo_saml_dont_create_user_if_role_not_mapped';
const Role_do_not_assign_role_unlisted = 'saml_am_dont_allow_unlisted_user_role';
const Role_do_not_update_existing_user = 'saml_am_dont_update_existing_user_role';
const Role_default_role ='saml_am_default_user_role';
}
class mo_saml_options_error_constants extends MoSAMLBasicEnum{
const Error_no_certificate = "Unable to find a certificate .";
const Cause_no_certificate = "No signature found in SAML Response or Assertion. Please sign at least one of them.";
const Error_wrong_certificate = "Unable to find a certificate matching the configured fingerprint.";
const Cause_wrong_certificate = "X.509 Certificate field in plugin does not match the certificate found in SAML Response.";
const Error_invalid_audience = "Invalid Audience URI.";
const Cause_invalid_audience = "The value of 'Audience URI' field on Identity Provider's side is incorrect";
const Error_issuer_not_verfied = "Issuer cannot be verified.";
const Cause_issuer_not_verfied = "IdP Entity ID configured and the one found in SAML Response do not match";
}
class mo_saml_options_plugin_constants extends MoSAMLBasicEnum{
const CMS_Name = "WP";
const Application_Name = "WP miniOrange SAML 2.0 SSO Plugin";
const Application_type = "SAML";
const Version = "4.9.05";
const HOSTNAME = "https://login.xecurify.com";
}
class mo_saml_options_plugin_idp extends MoSAMLBasicEnum{
public static $IDP_GUIDES = array(
"Azure AD" => "azure-ad",
"Azure B2C" => "azure-b2c",
"ADFS" => "adfs",
"Okta" => "okta",
"SalesForce" => "salesforce",
"Google Apps" => "google-apps",
"OneLogin" => "onelogin",
"MiniOrange" => "miniorange",
"Keycloak" => "jboss-keycloak",
"AbsorbLMS" => "absorb-lms",
"Degreed" => "degreed",
"JumpCloud" => "jumpcloud",
"PingFederate" => "pingfederate",
"PingOne" => "pingone",
"Centrify" => "centrify",
"Oracle" => "oracle-enterprise-manager",
"Bitium" => "bitium",
"Shibboleth 2" => "shibboleth2",
"Shibboleth 3" => "shibboleth3",
"Gluu Server" => "gluu-server",
"SimpleSAMLphp" => "simplesaml",
"OpenAM" => "openam",
"Authanvil"=>"authanvil",
"Auth0"=>"auth0",
"CA Identity"=>"ca-identity",
"WSO2"=>"wso2",
"RSA SecureID"=>"rsa-secureid",
"Custom IDP"=>"custom-idp"
);
}
class mo_saml_options_plugin_idp_videos extends MoSAMLBasicEnum{
public static $IDP_VIDEOS = array(
"azure-ad"=> "eHen4aiflFU",
"azure-b2c"=> "",
"adfs"=> "rLBHbRbrY5E",
"okta"=> "YHE8iYojUqM",
"salesforce"=> "LRQrmgr255Q",
"google-apps"=> "5BwzEjgZiu4",
"onelogin"=> "_Hsot_RG9YY",
"miniorange"=> "eamf9s6JpbA",
"jboss-keycloak"=> "Io6x1fTNWHI",
"absorb-lms"=> "",
"degreed"=> "",
"jumpcloud"=> "",
"pingfederate"=> "",
"pingone"=> "",
"centrify"=> "",
"oracle-enterprise-manager"=> "",
"bitium"=> "",
"shibboleth2"=> "",
"shibboleth3"=> "",
"gluu-server"=> "",
"simplesaml"=> "",
"openam"=> "",
"authanvil"=> "",
"auth0"=> "54pz6m5h9mk",
"ca-identity" => "",
"wso2" => "",
"rsa-secureid" => "",
"custom-idp" => "gilfhNFYsgc"
);
}
class mo_saml_options_addons extends MoSAMLBasicEnum{
public static $ADDON_URL = array(
'scim' => 'https://plugins.miniorange.com/wordpress-user-provisioning',
'page_restriction' => 'https://plugins.miniorange.com/wordpress-page-restriction',
'file_prevention' => 'https://plugins.miniorange.com/wordpress-media-restriction',
'ssologin' => 'https://plugins.miniorange.com/wordpress-sso-login-audit',
'buddypress' => 'https://plugins.miniorange.com/wordpress-buddypress-integrator',
'learndash' => 'https://plugins.miniorange.com/wordpress-learndash-integrator',
'attribute_based_redirection' => 'https://plugins.miniorange.com/wordpress-attribute-based-redirection-restriction',
'ssosession' => 'https://plugins.miniorange.com/sso-session-management',
'fsso' => 'https://plugins.miniorange.com/incommon-federation-single-sign-on-sso',
'paid_mem_pro' => 'https://plugins.miniorange.com/paid-membership-pro-integrator',
'memberpress' => 'https://plugins.miniorange.com/wordpress-memberpress-integrator',
'wp_members' => 'https://plugins.miniorange.com/wordpress-members-integrator',
'woocommerce' => 'https://plugins.miniorange.com/wordpress-woocommerce-integrator',
'guest_login' => 'https://plugins.miniorange.com/guest-user-login',
'profile_picture_add_on' => 'https://plugins.miniorange.com/wordpress-profile-picture-map'
);
public static $WP_ADDON_URL = array(
'page-restriction' => 'https://wordpress.org/plugins/page-and-post-restriction/embed/',
'scim-user-sync'=> 'https://wordpress.org/plugins/scim-user-provisioning/embed/'
);
public static $ADDON_TITLE = array(
'scim' => 'SCIM User Provisioning',
'page_restriction' => 'Page and Post Restriction',
'file_prevention' => 'Prevent File Access',
'ssologin' => 'SSO Login Audit',
'buddypress' => 'BuddyPress Integrator',
'learndash' => 'Learndash Integrator',
'attribute_based_redirection' => 'Attribute Based Redirection',
'ssosession' => 'SSO Session Management',
'fsso' => 'Federation Single Sign-On',
'memberpress' => 'MemberPress Integrator',
'wp_members' => 'WP-Members Integrator',
'woocommerce' => 'WooCommerce Integrator',
'guest_login' => 'Guest Login',
'profile_picture_add_on' => 'Profile Picture Add-on',
'paid_mem_pro' => 'PaidMembership Pro Integrator'
);
public static $RECOMMENDED_ADDONS_PATH = array(
"learndash" => "sfwd-lms/sfwd_lms.php",
"buddypress" => "buddypress/bp-loader.php",
"paid_mem_pro" => "paid-memberships-pro/paid-memberships-pro.php",
"memberpress" => "memberpress/memberpress.php",
"wp_members" => "wp-members/wp-members.php",
"woocommerce" => "woocommerce/woocommerce.php"
);
}
class mo_saml_license_plans extends MoSAMLBasicEnum {
public static $license_plans = array (
'standard' => 'WP SAML SSO Standard Plan',
'premium' => 'WP SAML SSO Premium Plan',
'enterprise' => 'WP SAML SSO Enterprise Plan',
'enterprise-multiple-idp' => 'WP SAML SSO Enterprise Multiple-IDP Plan',
'all-inclusive' => 'WP SAML SSO All Inclusive Plan',
'premium-multisite' => 'WP SAML SSO Premium Multisite Plan',
'enterprise-multisite' => 'WP SAML SSO Enterprise Multisite Plan',
'all-inclusive-multisite' => 'WP SAML SSO All Inclusive Multisite Plan',
'help' => 'Not Sure'
);
public static $license_plans_slug = array (
'standard' => '16.0.2@16.0.2',
'premium' => '12.0.2@12.0.2',
'enterprise' => '12.0.2@12.0.2',
'enterprise-multiple-idp' => '25.0.1@25.0.1',
'all-inclusive' => '25.0.1@25.0.1',
);
}
class mo_saml_time_zones extends MoSAMLBasicEnum {
public static $time_zones = array(
"(GMT-11:00) Niue Time" => "Pacific/Niue",
"(GMT-11:00) Samoa Standard Time" => "Pacific/Pago_Pago",
"(GMT-10:00) Cook Islands Standard Time" => "Pacific/Rarotonga",
"(GMT-10:00) Hawaii-Aleutian Standard Time" => "Pacific/Honolulu",
"(GMT-10:00) Tahiti Time" => "Pacific/Tahiti",
"(GMT-09:30) Marquesas Time" => "Pacific/Marquesas",
"(GMT-09:00) Gambier Time" => "Pacific/Gambier",
"(GMT-09:00) Hawaii-Aleutian Time (Adak)" => "America/Adak",
"(GMT-08:00) Alaska Time - Anchorage" => "America/Anchorage",
"(GMT-08:00) Alaska Time - Juneau" => "America/Juneau",
"(GMT-08:00) Alaska Time - Metlakatla" => "America/Metlakatla",
"(GMT-08:00) Alaska Time - Nome" => "America/Nome",
"(GMT-08:00) Alaska Time - Sitka" => "America/Sitka",
"(GMT-08:00) Alaska Time - Yakutat" => "America/Yakutat",
"(GMT-08:00) Pitcairn Time" => "Pacific/Pitcairn",
"(GMT-07:00) Mexican Pacific Standard Time" => "America/Hermosillo",
"(GMT-07:00) Mountain Standard Time - Creston" => "America/Creston",
"(GMT-07:00) Mountain Standard Time - Dawson" => "America/Dawson",
"(GMT-07:00) Mountain Standard Time - Dawson Creek" => "America/Dawson_Creek",
"(GMT-07:00) Mountain Standard Time - Fort Nelson" => "America/Fort_Nelson",
"(GMT-07:00) Mountain Standard Time - Phoenix" => "America/Phoenix",
"(GMT-07:00) Mountain Standard Time - Whitehorse" => "America/Whitehorse",
"(GMT-07:00) Pacific Time - Los Angeles" => "America/Los_Angeles",
"(GMT-07:00) Pacific Time - Tijuana" => "America/Tijuana",
"(GMT-07:00) Pacific Time - Vancouver" => "America/Vancouver",
"(GMT-06:00) Central Standard Time - Belize" => "America/Belize",
"(GMT-06:00) Central Standard Time - Costa Rica" => "America/Costa_Rica",
"(GMT-06:00) Central Standard Time - El Salvador" => "America/El_Salvador",
"(GMT-06:00) Central Standard Time - Guatemala" => "America/Guatemala",
"(GMT-06:00) Central Standard Time - Managua" => "America/Managua",
"(GMT-06:00) Central Standard Time - Regina" => "America/Regina",
"(GMT-06:00) Central Standard Time - Swift Current" => "America/Swift_Current",
"(GMT-06:00) Central Standard Time - Tegucigalpa" => "America/Tegucigalpa",
"(GMT-06:00) Easter Island Time" => "Pacific/Easter",
"(GMT-06:00) Galapagos Time" => "Pacific/Galapagos",
"(GMT-06:00) Mexican Pacific Time - Chihuahua" => "America/Chihuahua",
"(GMT-06:00) Mexican Pacific Time - Mazatlan" => "America/Mazatlan",
"(GMT-06:00) Mountain Time - Boise" => "America/Boise",
"(GMT-06:00) Mountain Time - Cambridge Bay" => "America/Cambridge_Bay",
"(GMT-06:00) Mountain Time - Denver" => "America/Denver",
"(GMT-06:00) Mountain Time - Edmonton" => "America/Edmonton",
"(GMT-06:00) Mountain Time - Inuvik" => "America/Inuvik",
"(GMT-06:00) Mountain Time - Ojinaga" => "America/Ojinaga",
"(GMT-06:00) Mountain Time - Yellowknife" => "America/Yellowknife",
"(GMT-05:00) Acre Standard Time - Eirunepe" => "America/Eirunepe",
"(GMT-05:00) Acre Standard Time - Rio Branco" => "America/Rio_Branco",
"(GMT-05:00) Central Time - Bahia Banderas" => "America/Bahia_Banderas",
"(GMT-05:00) Central Time - Beulah, North Dakota" => "America/North_Dakota/Beulah",
"(GMT-05:00) Central Time - Center, North Dakota" => "America/North_Dakota/Center",
"(GMT-05:00) Central Time - Chicago" => "America/Chicago",
"(GMT-05:00) Central Time - Knox, Indiana" => "America/Indiana/Knox",
"(GMT-05:00) Central Time - Matamoros" => "America/Matamoros",
"(GMT-05:00) Central Time - Menominee" => "America/Menominee",
"(GMT-05:00) Central Time - Merida" => "America/Merida",
"(GMT-05:00) Central Time - Mexico City" => "America/Mexico_City",
"(GMT-05:00) Central Time - Monterrey" => "America/Monterrey",
"(GMT-05:00) Central Time - New Salem, North Dakota" => "America/North_Dakota/New_Salem",
"(GMT-05:00) Central Time - Rainy River" => "America/Rainy_River",
"(GMT-05:00) Central Time - Rankin Inlet" => "America/Rankin_Inlet",
"(GMT-05:00) Central Time - Resolute" => "America/Resolute",
"(GMT-05:00) Central Time - Tell City, Indiana" => "America/Indiana/Tell_City",
"(GMT-05:00) Central Time - Winnipeg" => "America/Winnipeg",
"(GMT-05:00) Colombia Standard Time" => "America/Bogota",
"(GMT-05:00) Eastern Standard Time - Atikokan" => "America/Atikokan",
"(GMT-05:00) Eastern Standard Time - Cancun" => "America/Cancun",
"(GMT-05:00) Eastern Standard Time - Jamaica" => "America/Jamaica",
"(GMT-05:00) Eastern Standard Time - Panama" => "America/Panama",
"(GMT-05:00) Ecuador Time" => "America/Guayaquil",
"(GMT-05:00) Peru Standard Time" => "America/Lima",
"(GMT-04:00) Amazon Standard Time - Boa Vista" => "America/Boa_Vista",
"(GMT-04:00) Amazon Standard Time - Campo Grande" => "America/Campo_Grande",
"(GMT-04:00) Amazon Standard Time - Cuiaba" => "America/Cuiaba",
"(GMT-04:00) Amazon Standard Time - Manaus" => "America/Manaus",
"(GMT-04:00) Amazon Standard Time - Porto Velho" => "America/Porto_Velho",
"(GMT-04:00) Atlantic Standard Time - Barbados" => "America/Barbados",
"(GMT-04:00) Atlantic Standard Time - Blanc-Sablon" => "America/Blanc-Sablon",
"(GMT-04:00) Atlantic Standard Time - Curaçao" => "America/Curacao",
"(GMT-04:00) Atlantic Standard Time - Martinique" => "America/Martinique",
"(GMT-04:00) Atlantic Standard Time - Port of Spain" => "America/Port_of_Spain",
"(GMT-04:00) Atlantic Standard Time - Puerto Rico" => "America/Puerto_Rico",
"(GMT-04:00) Atlantic Standard Time - Santo Domingo" => "America/Santo_Domingo",
"(GMT-04:00) Bolivia Time" => "America/La_Paz",
"(GMT-04:00) Chile Time" => "America/Santiago",
"(GMT-04:00) Cuba Time" => "America/Havana",
"(GMT-04:00) Eastern Time - Detroit" => "America/Detroit",
"(GMT-04:00) Eastern Time - Grand Turk" => "America/Grand_Turk",
"(GMT-04:00) Eastern Time - Indianapolis" => "America/Indiana/Indianapolis",
"(GMT-04:00) Eastern Time - Iqaluit" => "America/Iqaluit",
"(GMT-04:00) Eastern Time - Louisville" => "America/Kentucky/Louisville",
"(GMT-04:00) Eastern Time - Marengo, Indiana" => "America/Indiana/Marengo",
"(GMT-04:00) Eastern Time - Monticello, Kentucky" => "America/Kentucky/Monticello",
"(GMT-04:00) Eastern Time - Nassau" => "America/Nassau",
"(GMT-04:00) Eastern Time - New York" => "America/New_York",
"(GMT-04:00) Eastern Time - Nipigon" => "America/Nipigon",
"(GMT-04:00) Eastern Time - Pangnirtung" => "America/Pangnirtung",
"(GMT-04:00) Eastern Time - Petersburg, Indiana" => "America/Indiana/Petersburg",
"(GMT-04:00) Eastern Time - Port-au-Prince" => "America/Port-au-Prince",
"(GMT-04:00) Eastern Time - Thunder Bay" => "America/Thunder_Bay",
"(GMT-04:00) Eastern Time - Toronto" => "America/Toronto",
"(GMT-04:00) Eastern Time - Vevay, Indiana" => "America/Indiana/Vevay",
"(GMT-04:00) Eastern Time - Vincennes, Indiana" => "America/Indiana/Vincennes",
"(GMT-04:00) Eastern Time - Winamac, Indiana" => "America/Indiana/Winamac",
"(GMT-04:00) Guyana Time" => "America/Guyana",
"(GMT-04:00) Paraguay Time" => "America/Asuncion",
"(GMT-04:00) Venezuela Time" => "America/Caracas",
"(GMT-03:00) Argentina Standard Time - Buenos Aires" => "America/Argentina/Buenos_Aires",
"(GMT-03:00) Argentina Standard Time - Catamarca" => "America/Argentina/Catamarca",
"(GMT-03:00) Argentina Standard Time - Cordoba" => "America/Argentina/Cordoba",
"(GMT-03:00) Argentina Standard Time - Jujuy" => "America/Argentina/Jujuy",
"(GMT-03:00) Argentina Standard Time - La Rioja" => "America/Argentina/La_Rioja",
"(GMT-03:00) Argentina Standard Time - Mendoza" => "America/Argentina/Mendoza",
"(GMT-03:00) Argentina Standard Time - Rio Gallegos" => "America/Argentina/Rio_Gallegos",
"(GMT-03:00) Argentina Standard Time - Salta" => "America/Argentina/Salta",
"(GMT-03:00) Argentina Standard Time - San Juan" => "America/Argentina/San_Juan",
"(GMT-03:00) Argentina Standard Time - San Luis" => "America/Argentina/San_Luis",
"(GMT-03:00) Argentina Standard Time - Tucuman" => "America/Argentina/Tucuman",
"(GMT-03:00) Argentina Standard Time - Ushuaia" => "America/Argentina/Ushuaia",
"(GMT-03:00) Atlantic Time - Bermuda" => "Atlantic/Bermuda",
"(GMT-03:00) Atlantic Time - Glace Bay" => "America/Glace_Bay",
"(GMT-03:00) Atlantic Time - Goose Bay" => "America/Goose_Bay",
"(GMT-03:00) Atlantic Time - Halifax" => "America/Halifax",
"(GMT-03:00) Atlantic Time - Moncton" => "America/Moncton",
"(GMT-03:00) Atlantic Time - Thule" => "America/Thule",
"(GMT-03:00) Brasilia Standard Time - Araguaina" => "America/Araguaina",
"(GMT-03:00) Brasilia Standard Time - Bahia" => "America/Bahia",
"(GMT-03:00) Brasilia Standard Time - Belem" => "America/Belem",
"(GMT-03:00) Brasilia Standard Time - Fortaleza" => "America/Fortaleza",
"(GMT-03:00) Brasilia Standard Time - Maceio" => "America/Maceio",
"(GMT-03:00) Brasilia Standard Time - Recife" => "America/Recife",
"(GMT-03:00) Brasilia Standard Time - Santarem" => "America/Santarem",
"(GMT-03:00) Brasilia Standard Time - Sao Paulo" => "America/Sao_Paulo",
"(GMT-03:00) Chile Time" => "America/Santiago",
"(GMT-03:00) Falkland Islands Standard Time" => "Atlantic/Stanley",
"(GMT-03:00) French Guiana Time" => "America/Cayenne",
"(GMT-03:00) Palmer Time" => "Antarctica/Palmer",
"(GMT-03:00) Punta Arenas Time" => "America/Punta_Arenas",
"(GMT-03:00) Rothera Time" => "Antarctica/Rothera",
"(GMT-03:00) Suriname Time" => "America/Paramaribo",
"(GMT-03:00) Uruguay Standard Time" => "America/Montevideo",
"(GMT-02:30) Newfoundland Time" => "America/St_Johns",
"(GMT-02:00) Fernando de Noronha Standard Time" => "America/Noronha",
"(GMT-02:00) South Georgia Time" => "Atlantic/South_Georgia",
"(GMT-02:00) St. Pierre & Miquelon Time" => "America/Miquelon",
"(GMT-02:00) West Greenland Time" => "America/Nuuk",
"(GMT-01:00) Cape Verde Standard Time" => "Atlantic/Cape_Verde",
"(GMT+00:00) Azores Time" => "Atlantic/Azores",
"(GMT+00:00) Coordinated Universal Time" => "UTC",
"(GMT+00:00) East Greenland Time" => "America/Scoresbysund",
"(GMT+00:00) Greenwich Mean Time" => "Etc/GMT",
"(GMT+00:00) Greenwich Mean Time - Abidjan" => "Africa/Abidjan",
"(GMT+00:00) Greenwich Mean Time - Accra" => "Africa/Accra",
"(GMT+00:00) Greenwich Mean Time - Bissau" => "Africa/Bissau",
"(GMT+00:00) Greenwich Mean Time - Danmarkshavn" => "America/Danmarkshavn",
"(GMT+00:00) Greenwich Mean Time - Monrovia" => "Africa/Monrovia",
"(GMT+00:00) Greenwich Mean Time - Reykjavik" => "Atlantic/Reykjavik",
"(GMT+00:00) Greenwich Mean Time - São Tomé" => "Africa/Sao_Tome",
"(GMT+01:00) Central European Standard Time - Algiers" => "Africa/Algiers",
"(GMT+01:00) Central European Standard Time - Tunis" => "Africa/Tunis",
"(GMT+01:00) Ireland Time" => "Europe/Dublin",
"(GMT+01:00) Morocco Time" => "Africa/Casablanca",
"(GMT+01:00) United Kingdom Time" => "Europe/London",
"(GMT+01:00) West Africa Standard Time - Lagos" => "Africa/Lagos",
"(GMT+01:00) West Africa Standard Time - Ndjamena" => "Africa/Ndjamena",
"(GMT+01:00) Western European Time - Canary" => "Atlantic/Canary",
"(GMT+01:00) Western European Time - Faroe" => "Atlantic/Faroe",
"(GMT+01:00) Western European Time - Lisbon" => "Europe/Lisbon",
"(GMT+01:00) Western European Time - Madeira" => "Atlantic/Madeira",
"(GMT+01:00) Western Sahara Time" => "Africa/El_Aaiun",
"(GMT+02:00) Central Africa Time - Khartoum" => "Africa/Khartoum",
"(GMT+02:00) Central Africa Time - Maputo" => "Africa/Maputo",
"(GMT+02:00) Central Africa Time - Windhoek" => "Africa/Windhoek",
"(GMT+02:00) Central European Time - Amsterdam" => "Europe/Amsterdam",
"(GMT+02:00) Central European Time - Andorra" => "Europe/Andorra",
"(GMT+02:00) Central European Time - Belgrade" => "Europe/Belgrade",
"(GMT+02:00) Central European Time - Berlin" => "Europe/Berlin",
"(GMT+02:00) Central European Time - Brussels" => "Europe/Brussels",
"(GMT+02:00) Central European Time - Budapest" => "Europe/Budapest",
"(GMT+02:00) Central European Time - Ceuta" => "Africa/Ceuta",
"(GMT+02:00) Central European Time - Copenhagen" => "Europe/Copenhagen",
"(GMT+02:00) Central European Time - Gibraltar" => "Europe/Gibraltar",
"(GMT+02:00) Central European Time - Luxembourg" => "Europe/Luxembourg",
"(GMT+02:00) Central European Time - Madrid" => "Europe/Madrid",
"(GMT+02:00) Central European Time - Malta" => "Europe/Malta",
"(GMT+02:00) Central European Time - Monaco" => "Europe/Monaco",
"(GMT+02:00) Central European Time - Oslo" => "Europe/Oslo",
"(GMT+02:00) Central European Time - Paris" => "Europe/Paris",
"(GMT+02:00) Central European Time - Prague" => "Europe/Prague",
"(GMT+02:00) Central European Time - Rome" => "Europe/Rome",
"(GMT+02:00) Central European Time - Stockholm" => "Europe/Stockholm",
"(GMT+02:00) Central European Time - Tirane" => "Europe/Tirane",
"(GMT+02:00) Central European Time - Vienna" => "Europe/Vienna",
"(GMT+02:00) Central European Time - Warsaw" => "Europe/Warsaw",
"(GMT+02:00) Central European Time - Zurich" => "Europe/Zurich",
"(GMT+02:00) Eastern European Standard Time - Cairo" => "Africa/Cairo",
"(GMT+02:00) Eastern European Standard Time - Kaliningrad" => "Europe/Kaliningrad",
"(GMT+02:00) Eastern European Standard Time - Tripoli" => "Africa/Tripoli",
"(GMT+02:00) South Africa Standard Time" => "Africa/Johannesburg",
"(GMT+02:00) Troll Time" => "Antarctica/Troll",
"(GMT+03:00) Arabian Standard Time - Baghdad" => "Asia/Baghdad",
"(GMT+03:00) Arabian Standard Time - Qatar" => "Asia/Qatar",
"(GMT+03:00) Arabian Standard Time - Riyadh" => "Asia/Riyadh",
"(GMT+03:00) East Africa Time - Juba" => "Africa/Juba",
"(GMT+03:00) East Africa Time - Nairobi" => "Africa/Nairobi",
"(GMT+03:00) Eastern European Time - Amman" => "Asia/Amman",
"(GMT+03:00) Eastern European Time - Athens" => "Europe/Athens",
"(GMT+03:00) Eastern European Time - Beirut" => "Asia/Beirut",
"(GMT+03:00) Eastern European Time - Bucharest" => "Europe/Bucharest",
"(GMT+03:00) Eastern European Time - Chisinau" => "Europe/Chisinau",
"(GMT+03:00) Eastern European Time - Damascus" => "Asia/Damascus",
"(GMT+03:00) Eastern European Time - Gaza" => "Asia/Gaza",
"(GMT+03:00) Eastern European Time - Hebron" => "Asia/Hebron",
"(GMT+03:00) Eastern European Time - Helsinki" => "Europe/Helsinki",
"(GMT+03:00) Eastern European Time - Kiev" => "Europe/Kiev",
"(GMT+03:00) Eastern European Time - Nicosia" => "Asia/Nicosia",
"(GMT+03:00) Eastern European Time - Riga" => "Europe/Riga",
"(GMT+03:00) Eastern European Time - Sofia" => "Europe/Sofia",
"(GMT+03:00) Eastern European Time - Tallinn" => "Europe/Tallinn",
"(GMT+03:00) Eastern European Time - Uzhhorod" => "Europe/Uzhgorod",
"(GMT+03:00) Eastern European Time - Vilnius" => "Europe/Vilnius",
"(GMT+03:00) Eastern European Time - Zaporozhye" => "Europe/Zaporozhye",
"(GMT+03:00) Famagusta Time" => "Asia/Famagusta",
"(GMT+03:00) Israel Time" => "Asia/Jerusalem",
"(GMT+03:00) Kirov Time" => "Europe/Kirov",
"(GMT+03:00) Moscow Standard Time - Minsk" => "Europe/Minsk",
"(GMT+03:00) Moscow Standard Time - Moscow" => "Europe/Moscow",
"(GMT+03:00) Moscow Standard Time - Simferopol" => "Europe/Simferopol",
"(GMT+03:00) Syowa Time" => "Antarctica/Syowa",
"(GMT+03:00) Turkey Time" => "Europe/Istanbul",
"(GMT+04:00) Armenia Standard Time" => "Asia/Yerevan",
"(GMT+04:00) Astrakhan Time" => "Europe/Astrakhan",
"(GMT+04:00) Azerbaijan Standard Time" => "Asia/Baku",
"(GMT+04:00) Georgia Standard Time" => "Asia/Tbilisi",
"(GMT+04:00) Gulf Standard Time" => "Asia/Dubai",
"(GMT+04:00) Mauritius Standard Time" => "Indian/Mauritius",
"(GMT+04:00) Réunion Time" => "Indian/Reunion",
"(GMT+04:00) Samara Standard Time" => "Europe/Samara",
"(GMT+04:00) Saratov Time" => "Europe/Saratov",
"(GMT+04:00) Seychelles Time" => "Indian/Mahe",
"(GMT+04:00) Ulyanovsk Time" => "Europe/Ulyanovsk",
"(GMT+04:00) Volgograd Standard Time" => "Europe/Volgograd",
"(GMT+04:30) Afghanistan Time" => "Asia/Kabul",
"(GMT+04:30) Iran Time" => "Asia/Tehran",
"(GMT+05:00) French Southern & Antarctic Time" => "Indian/Kerguelen",
"(GMT+05:00) Maldives Time" => "Indian/Maldives",
"(GMT+05:00) Mawson Time" => "Antarctica/Mawson",
"(GMT+05:00) Pakistan Standard Time" => "Asia/Karachi",
"(GMT+05:00) Tajikistan Time" => "Asia/Dushanbe",
"(GMT+05:00) Turkmenistan Standard Time" => "Asia/Ashgabat",
"(GMT+05:00) Uzbekistan Standard Time - Samarkand" => "Asia/Samarkand",
"(GMT+05:00) Uzbekistan Standard Time - Tashkent" => "Asia/Tashkent",
"(GMT+05:00) West Kazakhstan Time - Aqtau" => "Asia/Aqtau",
"(GMT+05:00) West Kazakhstan Time - Aqtobe" => "Asia/Aqtobe",
"(GMT+05:00) West Kazakhstan Time - Atyrau" => "Asia/Atyrau",
"(GMT+05:00) West Kazakhstan Time - Oral" => "Asia/Oral",
"(GMT+05:00) West Kazakhstan Time - Qyzylorda" => "Asia/Qyzylorda",
"(GMT+05:00) Yekaterinburg Standard Time" => "Asia/Yekaterinburg",
"(GMT+05:30) Indian Standard Time - Colombo" => "Asia/Colombo",
"(GMT+05:30) Indian Standard Time - Kolkata" => "Asia/Kolkata",
"(GMT+05:45) Nepal Time" => "Asia/Kathmandu",
"(GMT+06:00) Bangladesh Standard Time" => "Asia/Dhaka",
"(GMT+06:00) Bhutan Time" => "Asia/Thimphu",
"(GMT+06:00) East Kazakhstan Time - Almaty" => "Asia/Almaty",
"(GMT+06:00) East Kazakhstan Time - Kostanay" => "Asia/Qostanay",
"(GMT+06:00) Indian Ocean Time" => "Indian/Chagos",
"(GMT+06:00) Kyrgyzstan Time" => "Asia/Bishkek",
"(GMT+06:00) Omsk Standard Time" => "Asia/Omsk",
"(GMT+06:00) Urumqi Time" => "Asia/Urumqi",
"(GMT+06:00) Vostok Time" => "Antarctica/Vostok",
"(GMT+06:30) Cocos Islands Time" => "Indian/Cocos",
"(GMT+06:30) Myanmar Time" => "Asia/Yangon",
"(GMT+07:00) Barnaul Time" => "Asia/Barnaul",
"(GMT+07:00) Christmas Island Time" => "Indian/Christmas",
"(GMT+07:00) Davis Time" => "Antarctica/Davis",
"(GMT+07:00) Hovd Standard Time" => "Asia/Hovd",
"(GMT+07:00) Indochina Time - Bangkok" => "Asia/Bangkok",
"(GMT+07:00) Indochina Time - Ho Chi Minh City" => "Asia/Ho_Chi_Minh",
"(GMT+07:00) Krasnoyarsk Standard Time - Krasnoyarsk" => "Asia/Krasnoyarsk",
"(GMT+07:00) Krasnoyarsk Standard Time - Novokuznetsk" => "Asia/Novokuznetsk",
"(GMT+07:00) Novosibirsk Standard Time" => "Asia/Novosibirsk",
"(GMT+07:00) Tomsk Time" => "Asia/Tomsk",
"(GMT+07:00) Western Indonesia Time - Jakarta" => "Asia/Jakarta",
"(GMT+07:00) Western Indonesia Time - Pontianak" => "Asia/Pontianak",
"(GMT+08:00) Australian Western Standard Time - Casey" => "Antarctica/Casey",
"(GMT+08:00) Australian Western Standard Time - Perth" => "Australia/Perth",
"(GMT+08:00) Brunei Darussalam Time" => "Asia/Brunei",
"(GMT+08:00) Central Indonesia Time" => "Asia/Makassar",
"(GMT+08:00) China Standard Time - Macao" => "Asia/Macau",
"(GMT+08:00) China Standard Time - Shanghai" => "Asia/Shanghai",
"(GMT+08:00) Hong Kong Standard Time" => "Asia/Hong_Kong",
"(GMT+08:00) Irkutsk Standard Time" => "Asia/Irkutsk",
"(GMT+08:00) Malaysia Time - Kuala Lumpur" => "Asia/Kuala_Lumpur",
"(GMT+08:00) Malaysia Time - Kuching" => "Asia/Kuching",
"(GMT+08:00) Philippine Standard Time" => "Asia/Manila",
"(GMT+08:00) Singapore Standard Time" => "Asia/Singapore",
"(GMT+08:00) Taipei Standard Time" => "Asia/Taipei",
"(GMT+08:00) Ulaanbaatar Standard Time - Choibalsan" => "Asia/Choibalsan",
"(GMT+08:00) Ulaanbaatar Standard Time - Ulaanbaatar" => "Asia/Ulaanbaatar",
"(GMT+08:45) Australian Central Western Standard Time" => "Australia/Eucla",
"(GMT+09:00) East Timor Time" => "Asia/Dili",
"(GMT+09:00) Eastern Indonesia Time" => "Asia/Jayapura",
"(GMT+09:00) Japan Standard Time" => "Asia/Tokyo",
"(GMT+09:00) Korean Standard Time - Pyongyang" => "Asia/Pyongyang",
"(GMT+09:00) Korean Standard Time - Seoul" => "Asia/Seoul",
"(GMT+09:00) Palau Time" => "Pacific/Palau",
"(GMT+09:00) Yakutsk Standard Time - Chita" => "Asia/Chita",
"(GMT+09:00) Yakutsk Standard Time - Khandyga" => "Asia/Khandyga",
"(GMT+09:00) Yakutsk Standard Time - Yakutsk" => "Asia/Yakutsk",
"(GMT+09:30) Australian Central Standard Time" => "Australia/Darwin",
"(GMT+09:30) Central Australia Time - Adelaide" => "Australia/Adelaide",
"(GMT+09:30) Central Australia Time - Broken Hill" => "Australia/Broken_Hill",
"(GMT+10:00) Australian Eastern Standard Time - Brisbane" => "Australia/Brisbane",
"(GMT+10:00) Australian Eastern Standard Time - Lindeman" => "Australia/Lindeman",
"(GMT+10:00) Chamorro Standard Time" => "Pacific/Guam",
"(GMT+10:00) Chuuk Time" => "Pacific/Chuuk",
"(GMT+10:00) Dumont-dUrville Time" => "Antarctica/DumontDUrville",
"(GMT+10:00) Eastern Australia Time - Currie" => "Australia/Currie",
"(GMT+10:00) Eastern Australia Time - Hobart" => "Australia/Hobart",
"(GMT+10:00) Eastern Australia Time - Melbourne" => "Australia/Melbourne",
"(GMT+10:00) Eastern Australia Time - Sydney" => "Australia/Sydney",
"(GMT+10:00) Papua New Guinea Time" => "Pacific/Port_Moresby",
"(GMT+10:00) Vladivostok Standard Time - Ust-Nera" => "Asia/Ust-Nera",
"(GMT+10:00) Vladivostok Standard Time - Vladivostok" => "Asia/Vladivostok",
"(GMT+10:30) Lord Howe Time" => "Australia/Lord_Howe",
"(GMT+11:00) Bougainville Time" => "Pacific/Bougainville",
"(GMT+11:00) Kosrae Time" => "Pacific/Kosrae",
"(GMT+11:00) Macquarie Island Time" => "Antarctica/Macquarie",
"(GMT+11:00) Magadan Standard Time" => "Asia/Magadan",
"(GMT+11:00) New Caledonia Standard Time" => "Pacific/Noumea",
"(GMT+11:00) Norfolk Island Time" => "Pacific/Norfolk",
"(GMT+11:00) Ponape Time" => "Pacific/Pohnpei",
"(GMT+11:00) Sakhalin Standard Time" => "Asia/Sakhalin",
"(GMT+11:00) Solomon Islands Time" => "Pacific/Guadalcanal",
"(GMT+11:00) Srednekolymsk Time" => "Asia/Srednekolymsk",
"(GMT+11:00) Vanuatu Standard Time" => "Pacific/Efate",
"(GMT+12:00) Anadyr Standard Time" => "Asia/Anadyr",
"(GMT+12:00) Fiji Time" => "Pacific/Fiji",
"(GMT+12:00) Gilbert Islands Time" => "Pacific/Tarawa",
"(GMT+12:00) Marshall Islands Time - Kwajalein" => "Pacific/Kwajalein",
"(GMT+12:00) Marshall Islands Time - Majuro" => "Pacific/Majuro",
"(GMT+12:00) Nauru Time" => "Pacific/Nauru",
"(GMT+12:00) New Zealand Time" => "Pacific/Auckland",
"(GMT+12:00) Petropavlovsk-Kamchatski Standard Time" => "Asia/Kamchatka",
"(GMT+12:00) Tuvalu Time" => "Pacific/Funafuti",
"(GMT+12:00) Wake Island Time" => "Pacific/Wake",
"(GMT+12:00) Wallis & Futuna Time" => "Pacific/Wallis",
"(GMT+12:45) Chatham Time" => "Pacific/Chatham",
"(GMT+13:00) Apia Time" => "Pacific/Apia",
"(GMT+13:00) Phoenix Islands Time" => "Pacific/Enderbury",
"(GMT+13:00) Tokelau Time" => "Pacific/Fakaofo",
"(GMT+13:00) Tonga Standard Time" => "Pacific/Tongatapu",
"(GMT+14:00) Line Islands Time" => "Pacific/Kiritimati"
);
}

View File

@@ -0,0 +1,47 @@
<?php
/**
* xmlseclibs.php
*
* Copyright (c) 2007-2016, Robert Richards <rrichards@cdatazone.org>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* * Neither the name of Robert Richards nor the names of his
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* @author Robert Richards <rrichards@cdatazone.org>
* @copyright 2007-2017 Robert Richards <rrichards@cdatazone.org>
* @license http://www.opensource.org/licenses/bsd-license.php BSD License
* @version 3.0.1
*/
$xmlseclibs_srcdir = dirname(__FILE__) . '/SAML2Core';
require $xmlseclibs_srcdir . '/MoXMLSecurityKey.php';
require $xmlseclibs_srcdir . '/MoXMLSecurityDSig.php';
require $xmlseclibs_srcdir . '/MoXMLSecEnc.php';
require $xmlseclibs_srcdir . '/Utils/MoXPath.php';

View File

@@ -1,11 +1,14 @@
<?php
define('CLIENT_PATH', dirname(__FILE__));
use Classes\SAMLManager;use Classes\SettingsManager;use Utils\LogManager;define('CLIENT_PATH', dirname(__FILE__));
include("config.base.php");
include("include.common.php");
include("server.includes.inc.php");
$companyName = \Classes\SettingsManager::getInstance()->getSetting('Company: Name');
$gsuiteEnabled = \Classes\SettingsManager::getInstance()->getSetting('System: G Suite Enabled');
$gsuiteEnabled = SettingsManager::getInstance()->getSetting('System: G Suite Enabled');
$companyName = SettingsManager::getInstance()->getSetting('Company: Name');
$SAMLAutoLogin = SettingsManager::getInstance()->getSetting('SAML: Auto Login') === "1";
$SAMLEnabled = SettingsManager::getInstance()->getSetting("SAML: Enabled") == "1";
$SAMLUserLoaded = false;
if (isset($_REQUEST['logout'])) {
\Utils\SessionUtils::unsetClientSession();
@@ -14,12 +17,19 @@ if (isset($_REQUEST['logout'])) {
if (empty($user) || empty($user->email)) {
if (!empty($_REQUEST['username']) && !empty($_REQUEST['password'])) {
if (!isset($_REQUEST['logout']) && !isset($_POST['SAMLResponse']) && $SAMLAutoLogin && $SAMLEnabled && !empty(SettingsManager::getInstance()->getSetting("SAML: IDP SSO Url"))) {
header("Location:" . SettingsManager::getInstance()->getSetting("SAML: IDP SSO Url"));
exit();
}
if ((!empty($_REQUEST['username']) && !empty($_REQUEST['password']))
|| isset($_POST['SAMLResponse'])
) {
$suser = null;
$ssoUserLoaded = false;
if($_REQUEST['username'] != "admin") {
if (\Classes\SettingsManager::getInstance()->getSetting("LDAP: Enabled") == "1") {
if (SettingsManager::getInstance()->getSetting("LDAP: Enabled") === "1") {
$ldapResp = \Classes\LDAPManager::getInstance()->checkLDAPLogin($_REQUEST['username'], $_REQUEST['password']);
if ($ldapResp->getStatus() == \Classes\IceResponse::ERROR) {
header("Location:" . CLIENT_BASE_URL . "login.php?f=1");
@@ -36,6 +46,39 @@ if (empty($user) || empty($user->email)) {
}
}
if ($SAMLEnabled && isset($_POST['SAMLResponse'])) {
$samlData = $_POST['SAMLResponse'];
if(array_key_exists('RelayState', $_POST) && !empty( $_POST['RelayState'] ) && $_POST['RelayState'] !== '/') {
$relayState = htmlspecialchars($_POST['RelayState']);
} else {
$relayState = '';
}
$ssoUserEmail = (new SAMLManager())->getSSOEmail($samlData, $relayState);
LogManager::getInstance()->info('SSO SAML User Email:'.$ssoUserEmail);
if (false === $ssoUserEmail) {
header("Location:" . CLIENT_BASE_URL . "login.php?f=1");
exit();
} else {
$mapping = SettingsManager::getInstance()->getSetting('SAML: Name ID Mapping');
$suser = new \Users\Common\Model\User();
if ($mapping === 'username') {
$suser->Load("username = ?", array($ssoUserEmail));
} else {
$suser->Load("email = ?", array($ssoUserEmail));
}
LogManager::getInstance()->info('SSO SAML User:'.print_r($suser, true));
if (empty($suser)) {
header("Location:" . CLIENT_BASE_URL . "login.php?f=1");
exit();
}
$ssoUserLoaded = true;
$SAMLUserLoaded = true;
}
}
if (empty($suser)) {
$suser = new \Users\Common\Model\User();
$suser->Load(
@@ -59,7 +102,7 @@ if (empty($user) || empty($user->email)) {
$loginCsrf = \Utils\SessionUtils::getSessionObject('csrf-login');
if ($_REQUEST['csrf'] != $loginCsrf || empty($_REQUEST['csrf'])) {
if (!$SAMLUserLoaded && ($_REQUEST['csrf'] != $loginCsrf || empty($_REQUEST['csrf']))) {
$next = !empty($_REQUEST['next'])?'&next='.$_REQUEST['next']:'';
header("Location:".CLIENT_BASE_URL."login.php?f=1".$next);
exit();
@@ -277,7 +320,7 @@ $csrfToken = sha1(rand(4500, 100000) . time(). CLIENT_BASE_URL);
<div class="col-lg-6 col-md-8 col-xs-10">
<div class="bg-white-2 h-100 px-11 pt-11 pb-7">
<div class="row d-flex justify-content-center">
<img src="<?=$logoFileUrl?>"/>
<img style="max-width: 100%;" src="<?=$logoFileUrl?>"/>
</div>
<hr/>
<?php if ($gsuiteEnabled) {?>

View File

@@ -1,5 +1,9 @@
<?php
$migrationList = [];
$migrationList[] = 'v20210402_280006_modify_attendance_rep';
$migrationList[] = 'v20210327_280005_saml_settings';
$migrationList[] = 'v20210228_280003_add_share_with_employee';
$migrationList[] = 'v20210228_280004_add_visible_to_document';
$migrationList[] = 'v20201028_280002_update_gender';
$migrationList[] = 'v20201028_280001_update_module_names';
$migrationList[] = 'v20201017_271101_switch_off_photo_att';

View File

@@ -0,0 +1,24 @@
<?php
namespace Classes\Migration;
class v20210228_280003_add_share_with_employee extends AbstractMigration
{
public function up()
{
$sql = <<<'SQL'
ALTER TABLE `Documents`
ADD COLUMN `share_with_employee` enum('Yes','No') NULL DEFAULT 'Yes' AFTER `updated`;
SQL;
$this->executeQuery($sql);
return $this->executeQuery($sql);
}
public function down()
{
return true;
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace Classes\Migration;
class v20210228_280004_add_visible_to_document extends AbstractMigration
{
public function up()
{
$sql = <<<'SQL'
ALTER TABLE `EmployeeDocuments`
ADD COLUMN `visible_to` enum('Owner','Manager','Admin') NULL DEFAULT 'Owner' AFTER `expire_notification_last`;
SQL;
$this->executeQuery($sql);
return $this->executeQuery($sql);
}
public function down()
{
return true;
}
}

View File

@@ -0,0 +1,85 @@
<?php
namespace Classes\Migration;
class v20210327_280005_saml_settings extends AbstractMigration {
public function up(){
$sql[] = <<<'SQL'
REPLACE INTO `Settings` (`name`, `value`, `description`, `meta`, `category`)
VALUES (
'SAML: Enabled',
'0',
'Enable SAML Login',
'["value", {"label":"Value","type":"select","source":[["1","Yes"],["0","No"]]}]',
'SAML'
);
SQL;
$sql[] = <<<'SQL'
REPLACE INTO `Settings` (`name`, `value`, `description`, `meta`, `category`)
VALUES (
'SAML: Auto Login',
'0',
'Try to login via SAML by redirecting the user to SSO Url',
'["value", {"label":"Value","type":"select","source":[["1","Yes"],["0","No"]]}]',
'SAML'
);
SQL;
$sql[] = <<<'SQL'
REPLACE INTO `Settings` (`name`, `value`, `description`, `meta`, `category`)
VALUES (
'SAML: IDP SSO Url',
'',
'Identity Provider Single Sign-On URL. Users will be redirected to this URL for authentication',
'',
'SAML'
);
SQL;
$sql[] = <<<'SQL'
REPLACE INTO `Settings` (`name`, `value`, `description`, `meta`, `category`)
VALUES (
'SAML: IDP Issuer',
'',
'Identity Provider Issuer',
'',
'SAML'
);
SQL;
$sql[] = <<<'SQL'
REPLACE INTO `Settings` (`name`, `value`, `description`, `meta`, `category`)
VALUES (
'SAML: X.509 Certificate',
'',
'X.509 Certificate provided by the Identity Provider. This certificate will be encrypted',
'["value", {"label":"Value","type":"textarea"}]',
'SAML'
);
SQL;
$sql[] = <<<'SQL'
REPLACE INTO `Settings` (`name`, `value`, `description`, `meta`, `category`)
VALUES (
'SAML: Name ID Mapping',
'email',
'SAML Name id mapped to can be mapped to icehrm user email or the username',
'["value", {"label":"Value","type":"select","source":[["email","Email"],["username","Username"]]}]',
'SAML'
);
SQL;
$result = true;
foreach ($sql as $query) {
$result = $result && $this->executeQuery($query);
}
return $result;
}
public function down(){
return true;
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace Classes\Migration;
use Model\Report;
class v20210402_280006_modify_attendance_rep extends AbstractMigration {
public function up(){
$report = new Report();
$report->Load('name = ?', ['Employee Time Tracking Report']);
$report->parameters = <<<'JSON'
[
[ "employee", {"label":"Employee","type":"select2","allow-null":false,"remote-source":["Employee","id","first_name+last_name"]}],
[ "date_start", {"label":"Start Date","type":"date", "validation":"none"}],
[ "date_end", {"label":"End Date","type":"date","validation":"none"}],
["period", { "label": "Period", "type": "select", "source": [["Current Month", "Current Month"], ["Last Month", "Last Month"], ["Last Week", "Last Week"], ["Last 2 Weeks", "Last 2 Weeks"]], "validation":"none" }]
]
JSON;
$report->paramOrder = <<<'JSON'
["employee","date_start","date_end","period"]
JSON;
$report->Save();
return true;
}
public function down(){
return true;
}
}

View File

@@ -291,6 +291,12 @@ foreach ($ams as $am) {
}
}
$extensionManager = new \Classes\ExtensionManager();
$extensionData = $extensionManager->setupExtensions();
$extensionIcons = $extensionData[0];
$extensionTemp = $extensionData[1];
$extensionMenus = array_keys($extensionIcons);
foreach ($adminModulesTemp as $k => $v) {
ksort($adminModulesTemp[$k]);
}
@@ -299,6 +305,10 @@ foreach ($userModulesTemp as $k => $v) {
ksort($userModulesTemp[$k]);
}
foreach ($extensionTemp as $k => $v) {
ksort($extensionTemp[$k]);
}
$adminIcons = json_decode(file_get_contents(CLIENT_PATH.'/admin/meta.json'), true);
$adminMenus = array_keys($adminIcons);
@@ -332,8 +342,6 @@ foreach ($userMenus as $menu) {
}
}
$mainIcons = array_merge($adminIcons, $userIcons);
foreach ($userModulesTemp as $k => $v) {
if (!in_array($k, $added)) {
$arr = array("name"=>$k,"menu"=>$userModulesTemp[$k]);
@@ -341,6 +349,25 @@ foreach ($userModulesTemp as $k => $v) {
}
}
$extensions = array();
foreach ($extensionMenus as $menu) {
if (isset($extensionTemp[$menu])) {
$arr = array("name"=>$menu,"menu"=>$extensionTemp[$menu]);
$extensions[] = $arr;
$added[] = $menu;
}
}
foreach ($extensionTemp as $k => $v) {
if (!in_array($k, $added)) {
$arr = array("name"=>$k,"menu"=>$extensionTemp[$k]);
$extensions[] = $arr;
}
}
// Merge icons
$mainIcons = array_merge($adminIcons, $userIcons, $extensionIcons);
//Remove modules having no permissions
if (!empty($user)) {
if (!empty($user->user_roles)) {
@@ -393,4 +420,24 @@ if (!empty($user)) {
}
}
}
foreach ($extensions as $fk => $menu) {
foreach ($menu['menu'] as $key => $item) {
// If the user's once of the user roles are blacklisted for the module
$commonRoles = array_intersect($item['user_roles_blacklist'], $userRoles);
if (!empty($commonRoles)) {
unset($extensions[$fk]['menu'][$key]);
}
if (!in_array($user->user_level, $item['user_levels'])) {
if (!empty($userRoles)) {
$commonRoles = array_intersect($item['user_roles'], $userRoles);
if (empty($commonRoles)) {
unset($extensions[$fk]['menu'][$key]);
}
} else {
unset($extensions[$fk]['menu'][$key]);
}
}
}
}
}

View File

@@ -67,6 +67,8 @@ modJsList['tabEmployeeDocument'].setShowDelete(false);
modJsList['tabEmployeeDocument'].setShowEdit(false);
<?php }?>
modJsList['tabEmployeeDocument'].setRemoteTable(true);
modJsList['tabEmployeeCompanyDocument'] = new EmployeeCompanyDocumentAdapter('CompanyDocument','EmployeeCompanyDocument');
modJsList['tabEmployeeCompanyDocument'].setLoadMoreButton($("#loadMoreEmployeeCompanyDocument"));

View File

@@ -3,49 +3,8 @@ header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: DELETE, POST, GET, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With');
header('Content-Type: application/json');
define('CLIENT_PATH',dirname(__FILE__));
include ("config.base.php");
include ("include.common.php");
include("server.includes.inc.php");
if(\Classes\SettingsManager::getInstance()->getSetting('Api: REST Api Enabled') == '1') {
if (defined('SYM_CLIENT')) {
define('REST_API_PATH', '/'.SYM_CLIENT.'/');
} else if (!defined('REST_API_PATH')){
define('REST_API_PATH', '/');
}
\Utils\LogManager::getInstance()->info("Request: " . $_REQUEST);
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(200);
exit();
}
$echoRoute = \Classes\Macaw::get(REST_API_PATH . 'echo', function () {
echo "Echo " . rand();
});
\Utils\LogManager::getInstance()->debug('Api registered URI: '.$echoRoute);
$moduleManagers = \Classes\BaseService::getInstance()->getModuleManagers();
foreach ($moduleManagers as $moduleManagerObj) {
$moduleManagerObj->setupRestEndPoints();
}
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$method = $_SERVER['REQUEST_METHOD'];
\Utils\LogManager::getInstance()->debug('Api dispatch URI: '.$uri);
\Utils\LogManager::getInstance()->debug('Api dispatch method: '.$uri);
if (!defined('SYM_CLIENT')) {
//For hosted installations, dispatch will be done in app/index
\Classes\Macaw::dispatch();
}
}else{
echo "REST Api is not enabled. Please set 'Api: REST Api Enabled' setting to true";
if (isset($_REQUEST['method']) && isset($_REQUEST['url'])) {
include(APP_BASE_PATH . 'api-url-based.php');
} else {
include(APP_BASE_PATH . 'api-rest.php');
}

View File

@@ -7,6 +7,7 @@ use Classes\Migration\MigrationManager;
use Classes\NotificationManager;
use Classes\ReportHandler;
use Classes\SettingsManager;
use Model\BaseModel;
use Utils\LogManager;
if (!defined("AWS_REGION")) {
@@ -79,6 +80,18 @@ if (defined('REDIS_SERVER_URI')
);
}
$samlEnabled = SettingsManager::getInstance()->getSetting("SAML: Enabled");
if ($samlEnabled === '1') {
include APP_BASE_PATH . 'lib/saml2/Utilities.php';
include APP_BASE_PATH . 'lib/saml2/Response.php';
include APP_BASE_PATH . 'lib/saml2/encryption.php';
include APP_BASE_PATH . 'lib/saml2/mo-saml-options-enum.php';
}
$instanceId = SettingsManager::getInstance()->getSetting("Instance : ID");
$instanceKey = SettingsManager::getInstance()->getSetting("Instance: Key");
if(!defined('APP_SEC')){define('APP_SEC',sha1($instanceId.$instanceKey));}
$noJSONRequests = SettingsManager::getInstance()->getSetting("System: Do not pass JSON in request");
$debugMode = SettingsManager::getInstance()->getSetting("System: Debug Mode");
@@ -123,10 +136,12 @@ if (defined('CLIENT_PATH')) {
$modelClassList = $moduleManagerObj->getModelClasses();
$metaData = $moduleManagerObj->getModuleObject();
/** @var BaseModel $modelClass */
foreach ($modelClassList as $modelClass) {
$modelClassWithNameSpace = $metaData['model_namespace']."\\".$modelClass;
$modelClassWithNameSpace::SetDatabaseAdapter($dbLocal);
$baseService->addModelClass($modelClass, $modelClassWithNameSpace);
$modelClassObject = new $modelClassWithNameSpace();
}
}
}

View File

@@ -33,8 +33,7 @@ class AttendanceActionManager extends SubActionManager
//Find any open punch
$attendance = new Attendance();
$attendance->Load(
"employee = ? and DATE_FORMAT( in_time, '%Y-%m-%d' ) = ? and (out_time is NULL
or out_time = '0000-00-00 00:00:00')",
"employee = ? and DATE_FORMAT( in_time, '%Y-%m-%d' ) = ? and out_time is NULL",
array($employee->id,$date)
);

View File

@@ -7,6 +7,7 @@
*/
namespace Classes;
use Model\BaseModel;
use Utils\LogManager;
abstract class AbstractModuleManager
@@ -254,7 +255,17 @@ abstract class AbstractModuleManager
protected function addModelClass($className)
{
$this->modelClasses[] = $className;
BaseService::getInstance()->addModelClass($className, $this->moduleObject['model_namespace']."\\".$className);
$classWithNamespace = $this->moduleObject['model_namespace']."\\".$className;
BaseService::getInstance()->addModelClass($className, $classWithNamespace);
/** @var BaseModel $modelClass */
$modelClass = new $classWithNamespace();
if ($modelClass->isCustomFieldsEnabled()) {
$objectName = $modelClass->getObjectName();
BaseService::getInstance()->addCustomFieldClass(
$className,
(null === $objectName)? $className : $objectName
);
}
}
protected function addHistoryGeneric($type, $table, $refName, $refId, $field, $oldValue, $newValue)
@@ -278,4 +289,12 @@ abstract class AbstractModuleManager
{
BaseService::getInstance()->addCalculationHook($code, $name, $class, $method);
}
public function install()
{
}
public function uninstall()
{
}
}

View File

@@ -19,6 +19,7 @@ use Employees\Common\Model\EmployeeApproval;
use FieldNames\Common\Model\CustomField;
use FieldNames\Common\Model\FieldNameMapping;
use Metadata\Common\Model\CalculationHook;
use Model\BaseModel;
use Model\DataEntryBackup;
use Model\Setting;
use Modules\Common\Model\Module;
@@ -51,7 +52,8 @@ class BaseService
public $calculationHooks = array();
public $customFieldManager = null;
public $migrationManager = null;
public $modelClassMap = array();
public $modelClassMap = [];
public $customFieldsClassMap = [];
public $currentProfileId = false;
protected $cacheService = null;
@@ -167,6 +169,20 @@ class BaseService
$this->modelClassMap[$modelClass] = $fullQualifiedName;
}
public function getCustomFieldClassMap()
{
$map = [];
foreach($this->customFieldsClassMap as $key => $val) {
$map[] = [$key, $val];
}
return $map;
}
public function addCustomFieldClass($customFieldsClass, $objectName)
{
$this->customFieldsClassMap[$customFieldsClass] = $objectName;
}
public function getModelClassName($name)
{
return $this->getFullQualifiedModelClassName($name);
@@ -292,8 +308,8 @@ class BaseService
$childCompaniesIds = array();
if (\Classes\SettingsManager::getInstance()->getSetting(
'System: Child Company Structure Managers Enabled'
) == '1'
'System: Child Company Structure Managers Enabled'
) == '1'
) {
$childCompaniesResp = \Company\Common\Model\CompanyStructure::getAllChildCompanyStructures(
$cempObj->department
@@ -486,8 +502,8 @@ class BaseService
$childCompaniesIds = array();
if (SettingsManager::getInstance()->getSetting(
'System: Child Company Structure Managers Enabled'
) == '1'
'System: Child Company Structure Managers Enabled'
) == '1'
) {
$childCompaniesResp = CompanyStructure::getAllChildCompanyStructures($cempObj->department);
$childCompanies = $childCompaniesResp->getObject();
@@ -567,8 +583,8 @@ class BaseService
$childCompaniesIds = array();
if (SettingsManager::getInstance()->getSetting(
'System: Child Company Structure Managers Enabled'
) == '1'
'System: Child Company Structure Managers Enabled'
) == '1'
) {
$childCompaniesResp = CompanyStructure::getAllChildCompanyStructures($cempObj->department);
$childCompanies = $childCompaniesResp->getObject();
@@ -637,7 +653,10 @@ class BaseService
$processedList = array();
foreach ($list as $obj) {
$processedList[] = $this->cleanUpAdoDB($obj->postProcessGetData($obj));
$processedObj = $this->cleanUpAdoDB($obj->postProcessGetData($obj));
if (null !== $processedObj) {
$processedList[] = $processedObj;
}
}
$list = $processedList;
@@ -749,7 +768,7 @@ class BaseService
$obj = $this->enrichObjectCustomFields($table, $obj);
$obj = $obj->postProcessGetElement($obj);
return $this->cleanUpAdoDB($obj->postProcessGetData($obj));
return $this->cleanUpAdoDB($obj);
}
return null;
}
@@ -928,6 +947,7 @@ class BaseService
{
$fileFields = $this->fileFields;
$nsTable = $this->getFullQualifiedModelClassName($table);
/** @var BaseModel $ele */
$ele = new $nsTable();
$ele->Load('id = ?', array($id));
@@ -985,7 +1005,7 @@ class BaseService
$dataEntryBackup->data = json_encode($newObj);
$dataEntryBackup->Save();
}
$ele->executePostDeleteActions($ele);
$this->audit(IceConstants::AUDIT_DELETE, "Deleted an object in ".$table." [id:".$ele->id."]");
}
@@ -998,6 +1018,7 @@ class BaseService
}
$cfs = $this->customFieldManager->getCustomFields($table, $id);
/** @var CustomField $cf */
foreach ($cfs as $cf) {
$cf->Delete();
}
@@ -1783,8 +1804,8 @@ END;
) {
$departmentHeadFound = true;
} elseif (SettingsManager::getInstance()->getSetting(
'System: Child Company Structure Managers Enabled'
) == '1'
'System: Child Company Structure Managers Enabled'
) == '1'
) {
$companyStructure = new CompanyStructure();
$companyStructure->Load('id = ?', array($subordinate->department));
@@ -1804,7 +1825,7 @@ END;
$companyStructure->Load('id = ?', array($parentCompanyStructure));
}
} while (!empty($companyStructure->id)
&& !empty($parentCompanyStructure)
&& !empty($parentCompanyStructure)
);
}

View File

@@ -61,6 +61,7 @@ class DomainAwareInputCleaner
$filterData = json_decode($filters, true);
foreach ($filterData as $name => $value) {
if (!$this->isValidColumnName($name) || !$this->isValidFilterValue($value)) {
return '';
}
@@ -90,6 +91,14 @@ class DomainAwareInputCleaner
private function isValidFilterValue($input)
{
if (is_array($input)) {
$isValid = true;
foreach ($input as $val) {
$isValid = $isValid && !!preg_match('/^[-_: \d\p{L}]+$/u', $val);
}
return $isValid;
}
return !!preg_match('/^[-_: \d\p{L}]+$/u', $input);
}
}

View File

@@ -0,0 +1,146 @@
<?php
namespace Classes;
use Utils\LogManager;
class ExtensionManager
{
const GROUP = 'extension';
protected function processExtensionInDB()
{
$dbModule = new \Modules\Common\Model\Module();
$extensions = $dbModule->Find("mod_group = ?", array(self::GROUP));
$extensionsInDB = [];
foreach ($extensions as $dbm) {
$extensionsInDB[$dbm->name] = $dbm;
ModuleAccessService::getInstance()->setModule($dbm->name, self::GROUP, $dbm);
}
return $extensionsInDB;
}
public function getExtensionsPath()
{
return APP_BASE_PATH.'../extensions/';
}
public function getExtensionMetaData($extensionName)
{
return json_decode(file_get_contents($this->getExtensionsPath().$extensionName.'/meta.json'));
}
public function setupExtensions()
{
$menu = [];
$extensions = [];
$extensionDirs = scandir($this->getExtensionsPath());
$currentLocation = 0;
$extensionsInDB = $this->processExtensionInDB();
$needToInstall = false;
foreach ($extensionDirs as $extensionDir) {
if (is_dir($this->getExtensionsPath().$extensionDir) && $extensionDir != '.' && $extensionDir != '..') {
$meta = $this->getExtensionMetaData($extensionDir);
$arr = [];
$arr['name'] = $extensionDir;
$arr['label'] = $meta->label;
$arr['icon'] = $meta->icon;
$arr['menu'] = $meta->menu[0];
$arr['order'] = 0;
$arr['status'] = 'Enabled';
$arr['user_levels'] = $meta->user_levels;
$arr['user_roles'] = isset($meta->user_roles)?$meta->user_roles:"";
$arr['model_namespace'] = $meta->model_namespace;
$arr['manager'] = $meta->manager;
// Add menu
$menu[$meta->menu[0]] = $meta->menu[1];
//Check in admin dbmodules
if (isset($extensionsInDB[$arr['name']])) {
$dbModule = $extensionsInDB[$arr['name']];
$arr['name'] = $dbModule->name;
$arr['label'] = $dbModule->label;
$arr['icon'] = $dbModule->icon;
$arr['menu'] = $dbModule->menu;
$arr['status'] = $dbModule->status;
$arr['user_levels'] = json_decode($dbModule->user_levels);
$arr['user_roles'] = empty($dbModule->user_roles)
? [] : json_decode($dbModule->user_roles);
$arr['user_roles_blacklist'] = empty($dbModule->user_roles_blacklist)
? [] : json_decode($dbModule->user_roles_blacklist);
} else {
$dbModule = new \Modules\Common\Model\Module();
$dbModule->menu = $arr['menu'];
$dbModule->name = $arr['name'];
$dbModule->label = $arr['label'];
$dbModule->icon = $arr['icon'];
$dbModule->mod_group = self::GROUP;
$dbModule->mod_order = $arr['order'];
$dbModule->status = "Enabled";
$dbModule->version = isset($meta->version)?$meta->version:"";
$dbModule->update_path = self::GROUP.">".$extensionDir;
$dbModule->user_levels = isset($meta->user_levels)?json_encode($meta->user_levels):"";
$dbModule->user_roles = isset($meta->user_roles)?json_encode($meta->user_roles):"";
$ok = $dbModule->Save();
if (!$ok) {
LogManager::getInstance()->error('Error saving module: '.$dbModule->name);
}
$needToInstall = $ok;
}
/* @var \Classes\AbstractModuleManager */
$manager = $this->includeModuleManager($extensionDir, $arr);
if ($dbModule->status == 'Disabled') {
continue;
}
if ($needToInstall) {
$manager->install();
}
$menuName = $arr['menu'];
if (!isset($extensions[$menuName])) {
$extensions[$menuName] = array();
}
if (!$meta->headless) {
if ($arr['order'] == '0' || $arr['order'] == '') {
$extensions[$menuName]["Z".$currentLocation] = $arr;
$currentLocation++;
} else {
$extensions[$arr['menu']]["A".$arr['order']] = $arr;
}
}
$initializer = $manager->getInitializer();
if ($initializer !== null) {
$initializer->setBaseService(BaseService::getInstance());
$initializer->init();
}
}
}
return [$menu, $extensions];
}
public function includeModuleManager($name, $data)
{
include($this->getExtensionsPath().$name.'/'.$name.'.php');
$moduleManagerClass = $data['manager'];
/* @var \Classes\AbstractModuleManager $moduleManagerObj*/
$moduleManagerObj = new $moduleManagerClass();
$moduleManagerObj->setModuleObject($data);
$moduleManagerObj->setModuleType(self::GROUP);
$moduleManagerObj->setModulePath(CLIENT_PATH.'/'.self::GROUP.'/'.$name);
\Classes\BaseService::getInstance()->addModuleManager($moduleManagerObj);
return $moduleManagerObj;
}
}

View File

@@ -94,6 +94,8 @@ class FileService
$s3FileSys = new S3FileSystem($uploadFilesToS3Key, $uploadFilesToS3Secret);
$result = $s3FileSys->putObject($s3Bucket, $uploadname, $localFile, 'authenticated-read');
$file->size = filesize($localFile);
unlink("/tmp/".$file->filename);
unlink("/tmp/".$file->filename."_orig");
@@ -101,7 +103,6 @@ class FileService
$file->employee = $profileImage->employee;
$file->file_group = 'profile_image_small';
$file->size = filesize(CLIENT_BASE_PATH.'data/'.$file->filename);
$file->size_text = $this->getReadableSize($file->size);
if (!empty($result)) {
@@ -293,8 +294,7 @@ class FileService
if ($file->employee == $profileId) {
$ok = $file->Delete();
if ($ok) {
LogManager::getInstance()->info("Delete File:".CLIENT_BASE_PATH.$file->filename);
unlink(CLIENT_BASE_PATH.'data/'.$file->filename);
$this->deleteFileFromDisk($file);
} else {
return false;
}
@@ -306,8 +306,7 @@ class FileService
if ($file->employee == $profileId) {
$ok = $file->Delete();
if ($ok) {
LogManager::getInstance()->info("Delete File:".CLIENT_BASE_PATH.$file->filename);
unlink(CLIENT_BASE_PATH.'data/'.$file->filename);
$this->deleteFileFromDisk($file);
} else {
return false;
}
@@ -317,6 +316,30 @@ class FileService
return true;
}
public function deleteFileFromDisk($file)
{
$uploadFilesToS3 = SettingsManager::getInstance()->getSetting("Files: Upload Files to S3");
if ($uploadFilesToS3 == "1") {
$uploadFilesToS3Key = SettingsManager::getInstance()->getSetting(
"Files: Amazon S3 Key for File Upload"
);
$uploadFilesToS3Secret = SettingsManager::getInstance()->getSetting(
"Files: Amazone S3 Secret for File Upload"
);
$s3Bucket = SettingsManager::getInstance()->getSetting("Files: S3 Bucket");
$uploadname = CLIENT_NAME."/".$file->filename;
LogManager::getInstance()->info("Delete from S3:".$uploadname);
$s3FileSys = new S3FileSystem($uploadFilesToS3Key, $uploadFilesToS3Secret);
$s3FileSys->deleteObject($s3Bucket, $uploadname);
} else {
LogManager::getInstance()->info("Delete:".CLIENT_BASE_PATH.'data/'.$file->filename);
unlink(CLIENT_BASE_PATH.'data/'.$file->filename);
}
}
public function deleteFileByField($value, $field)
{
LogManager::getInstance()->info("Delete file by field: $field / value: $value");

View File

@@ -0,0 +1,20 @@
<?php
namespace Classes;
abstract class IceExtension extends AbstractModuleManager
{
public function initializeUserClasses()
{
// TODO: Implement initializeUserClasses() method.
}
public function initializeFieldMappings()
{
// TODO: Implement initializeFieldMappings() method.
}
public function initializeDatabaseErrorMappings()
{
// TODO: Implement initializeDatabaseErrorMappings() method.
}
}

177
core/src/Classes/IceRoute.php Executable file
View File

@@ -0,0 +1,177 @@
<?php
namespace Classes;
/**
* @method static IceRoute get(string $route, Callable $callback)
* @method static IceRoute post(string $route, Callable $callback)
* @method static IceRoute put(string $route, Callable $callback)
* @method static IceRoute delete(string $route, Callable $callback)
* @method static IceRoute options(string $route, Callable $callback)
* @method static IceRoute head(string $route, Callable $callback)
*/
class IceRoute
{
public static $halts = false;
public static $routes = array();
public static $methods = array();
public static $callbacks = array();
public static $patterns = array(
':any' => '[^/]+',
':num' => '[0-9]+',
':all' => '.*'
);
public static $error_callback;
/**
* Defines a route w/ callback and method
*/
public static function __callstatic($method, $params)
{
$uri = $params[0][0];
$callback = $params[0][1];
array_push(self::$routes, $uri);
array_push(self::$methods, strtoupper($method));
array_push(self::$callbacks, $callback);
return $uri;
}
/**
* Defines callback if route is not found
*/
public static function error($callback)
{
self::$error_callback = $callback;
}
public static function haltOnMatch($flag = true)
{
self::$halts = $flag;
}
/**
* Runs the callback for the given request
*/
public static function dispatch($uri, $method)
{
$searches = array_keys(static::$patterns);
$replaces = array_values(static::$patterns);
$found_route = false;
self::$routes = str_replace('//', '/', self::$routes);
// check if route is defined without regex
if (in_array($uri, self::$routes)) {
$route_pos = array_keys(self::$routes, $uri);
foreach ($route_pos as $route) {
//using an ANY option to match both GET and POST requests
if (self::$methods[$route] == $method || self::$methods[$route] == 'ANY') {
$found_route = true;
//if route is not an object
if (!is_object(self::$callbacks[$route])) {
//grab all parts based on a / separator
$parts = explode('/', self::$callbacks[$route]);
//collect the last index of the array
$last = end($parts);
//grab the controller name and method call
$segments = explode('@', $last);
//instanitate controller
$controller = new $segments[0]();
//call method
$controller->$segments[1]();
if (self::$halts) {
return;
}
} else {
//call closure
call_user_func(self::$callbacks[$route]);
if (self::$halts) {
return;
}
}
}
}
} else {
// check if defined with regex
$pos = 0;
foreach (self::$routes as $route) {
if (strpos($route, ':') !== false) {
$route = str_replace($searches, $replaces, $route);
}
if (preg_match('#^' . $route . '$#', $uri, $matched)) {
if (self::$methods[$pos] == $method) {
$found_route = true;
array_shift($matched); //remove $matched[0] as [1] is the first parameter.
if (!is_object(self::$callbacks[$pos])) {
//grab all parts based on a / separator
$parts = explode('/', self::$callbacks[$pos]);
//collect the last index of the array
$last = end($parts);
//grab the controller name and method call
$segments = explode('@', $last);
//instanitate controller
$controller = new $segments[0]();
//fix multi parameters
if (!method_exists($controller, $segments[1])) {
echo "controller and action not found";
} else {
call_user_func_array(array($controller, $segments[1]), $matched);
}
//call method and pass any extra parameters to the method
// $controller->$segments[1](implode(",", $matched));
if (self::$halts) {
return;
}
} else {
call_user_func_array(self::$callbacks[$pos], $matched);
if (self::$halts) {
return;
}
}
}
}
$pos++;
}
}
// run the error callback if the route was not found
if ($found_route == false) {
if (!self::$error_callback) {
self::$error_callback = function () {
header($_SERVER['SERVER_PROTOCOL']." 404 Not Found");
echo '404';
};
}
call_user_func(self::$error_callback);
}
}
}

View File

@@ -3,6 +3,7 @@
namespace Classes;
use Firebase\JWT\JWT;
use Firebase\JWT\SignatureInvalidException;
class JwtTokenService
{
@@ -22,7 +23,11 @@ class JwtTokenService
public function getBaseToken($jwtToken)
{
$secret = APP_SEC.APP_PASSWORD;
$jwt = JWT::decode($jwtToken, $secret, array('HS256'));
try {
$jwt = JWT::decode($jwtToken, $secret, array('HS256'));
} catch (SignatureInvalidException $e) {
return null;
}
if (time() > intval($jwt->expire)) {
return null;

View File

@@ -42,6 +42,8 @@ class Macaw
array_push(self::$methods, strtoupper($method));
array_push(self::$callbacks, $callback);
call_user_func('\Classes\IceRoute::'.$method, $params);
return $uri;
}

View File

@@ -15,6 +15,8 @@ class MemcacheService
public static $openConnections = array();
private static $me = null;
protected $inMemoryStore = [];
private function __construct()
{
}
@@ -56,6 +58,15 @@ class MemcacheService
}
public function set($key, $value, $expiry = 3600)
{
if (!$this->setInServer($key, $value, $expiry)) {
$this->inMemoryStore[$this->compressKey($key)] = $value;
}
return true;
}
public function setInServer($key, $value, $expiry = 3600)
{
if (!class_exists('\\Memcached')) {
return false;
@@ -74,6 +85,19 @@ class MemcacheService
}
public function get($key)
{
$data = $this->getFromServer($key);
if ($data) {
return $data;
}
if (isset($this->inMemoryStore[$this->compressKey($key)])) {
return $this->inMemoryStore[$this->compressKey($key)];
}
return false;
}
public function getFromServer($key)
{
if (!class_exists('\\Memcached')) {
return false;

View File

@@ -8,6 +8,8 @@
namespace Classes\Migration;
use Utils\LogManager;
abstract class AbstractMigration
{
protected $file;
@@ -16,7 +18,7 @@ abstract class AbstractMigration
protected $lastError;
public function __construct($file)
public function __construct($file = null)
{
$this->file = $file;
}
@@ -50,6 +52,7 @@ abstract class AbstractMigration
$ret = $this->db()->Execute($sql);
if (!$ret) {
$this->lastError = $this->db()->ErrorMsg();
LogManager::getInstance()->error('Error in migration: '.$this->lastError);
}
return $ret;
}

View File

@@ -12,7 +12,7 @@ class ModuleAccess
* @param $name
* @param $group
*/
public function __construct($name, $group)
public function __construct($name, $group = 'extension')
{
$this->name = $name;
$this->group = $group;

View File

@@ -0,0 +1,98 @@
<?php
/**
* Created by PhpStorm.
* User: Thilina
* Date: 8/20/17
* Time: 9:47 AM
*/
namespace Classes\ModuleBuilderV2;
use Classes\PermissionManager;
class ModuleBuilder
{
public $modules = array();
public $user = null;
function __construct()
{
$this->user = \Classes\BaseService::getInstance()->getCurrentUser();
}
/**
* @param ModuleTab $module
*/
public function addModuleOrGroup($module)
{
$this->modules[] = $module;
}
public function getTabHeadersHTML()
{
$html = "";
foreach ($this->modules as $module) {
$html .= $module->getHTML()."\r\n";
}
return $html;
}
public function getTabPagesHTML()
{
$html = "";
/* @var ModuleTab $module */
foreach ($this->modules as $module) {
if (get_class($module) === ModuleTab::class) {
$html .= $module->getPageHTML()."\r\n";
} else {
/* @var ModuleTab $mod */
foreach ($module->modules as $mod) {
$html .= $mod->getPageHTML()."\r\n";
}
}
}
return $html;
}
public function getModJsHTML()
{
$moduleData = [
'user_level' => $this->user->user_level,
'permissions' => [
]
];
$html = "var modJsList = new Array();\r\n";
$activeModule = "";
/* @var ModuleTab $module */
foreach ($this->modules as $module) {
if (get_class($module) == ModuleTab::class) {
$html .= $module->getJSObjectCode()."\r\n";
$modelClass = $module->modelPath;
$moduleData['permissions'][$module->name] = PermissionManager::checkGeneralAccess(new $modelClass());
if ($module->isActive) {
$activeModule = $module->name;
}
} else {
/* @var ModuleTab $mod */
foreach ($module->modules as $mod) {
$modelClass = $mod->modelPath;
$moduleData['permissions'][$mod->name] = PermissionManager::checkGeneralAccess(new $modelClass());
if ($module->isActive && $activeModule == "") {
$activeModule = $mod->name;
}
$html .= $mod->getJSObjectCode()."\r\n";
}
}
}
$html .= "var modJs = modJsList['tab".$activeModule."'];\r\n";
$html = "var data = ".json_encode($moduleData).";\r\n".$html;
return $html;
}
}

View File

@@ -0,0 +1,96 @@
<?php
/**
* Created by PhpStorm.
* User: Thilina
* Date: 8/20/17
* Time: 9:47 AM
*/
namespace Classes\ModuleBuilderV2;
class ModuleTab
{
public $modelPath;
public $name;
public $class;
public $label;
public $adapterName;
public $filter;
public $orderBy;
public $isActive = false;
public $isInsideGroup = false;
public $options = array();
public function __construct(
$modelPath,
$name,
$class,
$label,
$adapterName,
$filter,
$orderBy,
$isActive = false,
$options = array()
)
{
$this->modelPath = $modelPath;
$this->name = $name;
$this->class = $class;
$this->label = $label;
$this->adapterName = $adapterName;
$this->filter = $filter;
$this->orderBy = $orderBy;
$this->isActive = $isActive;
$this->options = array_merge(
$options, [
"setObjectTypeName" => "'{$this->name}'",
"setAccess" => "data.permissions.{$this->name} ? data.permissions.{$this->name} : {}",
"setDataPipe" => 'new IceDataPipe(modJsList.tab' . $this->name . ')',
"setRemoteTable" => true,
]);
}
public function getHTML()
{
$active = ($this->isActive)?"active":"";
if (!$this->isInsideGroup) {
return '<li class="' . $active . '"><a id="tab' . $this->name
. '" href="#tabPage' . $this->name . '">' . t($this->label) . '</a></li>';
} else {
return '<li class="' . $active . '"><a id="tab' . $this->name
. '" href="#tabPage' . $this->name . '">' . t($this->label) . '</a></li>';
}
}
public function getPageHTML()
{
$active = ($this->isActive)?" active":"";
$html = '<div class="tab-pane'.$active.'" id="tabPage'.$this->name.'">'.
'<div id="'.$this->name.'Table" class="reviewBlock" data-content="List" style="padding-left:5px;"></div>'.
'<div id="'.$this->name.'Form"></div>'.
'<div id="'.$this->name.'FilterForm"></div>'.
'</div>';
return $html;
}
public function getJSObjectCode()
{
$js = "";
if (empty($this->filter)) {
$js.= "modJsList['tab" . $this->name . "'] = new " .
$this->adapterName . "('" . $this->class . "','" . $this->name . "','','".$this->orderBy. "');\r\n";
} else {
$js.= "modJsList['tab" . $this->name . "'] = new " .
$this->adapterName . "('" . $this->class . "','" . $this->name . "'," .
$this->filter . ",'".$this->orderBy. "');\r\n";
}
foreach ($this->options as $key => $val) {
$js.= "modJsList['tab" . $this->name . "'].".$key."(".$val. ");\r\n";
}
return $js;
}
}

View File

@@ -0,0 +1,53 @@
<?php
/**
* Created by PhpStorm.
* User: Thilina
* Date: 8/20/17
* Time: 9:48 AM
*/
namespace Classes\ModuleBuilderV2;
class ModuleTabGroup
{
public $name;
public $label;
public $isActive = false;
public $modules = array();
public function __construct($name, $label)
{
$this->name = $name;
$this->label = $label;
}
public function addModuleTab($moduleTab)
{
if ($moduleTab->isActive) {
$this->isActive = true;
$moduleTab->isActive = false;
}
$moduleTab->isInsideGroup = true;
$this->modules[] = $moduleTab;
}
public function getHTML()
{
$html = "";
$active = ($this->isActive)?" active":"";
$html.= '<li class="dropdown'.$active.'">'."\r\n".
'<a href="#" id="'.$this->name.
'" class="dropdown-toggle" data-toggle="dropdown" aria-controls="'.$this->name.
'-contents">'.$this->label.' <span class="caret"></span></a>'."\r\n".
'<ul class="dropdown-menu" role="menu" aria-labelledby="'.$this->name.'" id="'.$this->name.'-contents">';
foreach ($this->modules as $module) {
$html.= $module->getHTML();
}
$html .= "</ul></li>";
return $html;
}
}

View File

@@ -103,7 +103,7 @@ class PasswordManager
return new IceResponse(IceResponse::ERROR, $error);
}
if (strlen($password) > 20) {
if (strlen($password) > 30) {
$error = "Password too long";
return new IceResponse(IceResponse::ERROR, $error);

View File

@@ -420,12 +420,12 @@ class RestEndPoint
if (preg_match('/Bearer\s(\S+)/', $headers, $matches)) {
$token = $matches[1];
}
} else {
$token = $_GET['token'];
}
if (strlen($token) > 32) {
$tokenService = new JwtTokenService();
$token = $tokenService->getBaseToken($token);
}
$tokenService = new JwtTokenService();
$token = $tokenService->getBaseToken($token);
return $token;
}

View File

@@ -0,0 +1,85 @@
<?php
namespace Classes;
use \RobRichards\XMLSecLibs\MoXMLSecurityKey;
use Utils\LogManager;
class SAMLManager
{
public function getSSOEmail($samlData, $relayState) {
// Service Providers Assertion Consumer Service (ACS) URL
$acsUrl = CLIENT_BASE_URL.'login.php';
$samlResponse = htmlspecialchars($samlData);
$samlResponse = base64_decode($samlResponse);
$document = new \DOMDocument();
$document->loadXML($samlResponse);
$samlResponseXml = $document->firstChild;
$doc = $document->documentElement;
$xpath = new \DOMXpath($document);
$xpath->registerNamespace('samlp', 'urn:oasis:names:tc:SAML:2.0:protocol');
$xpath->registerNamespace('saml', 'urn:oasis:names:tc:SAML:2.0:assertion');
$status = $xpath->query('/samlp:Response/samlp:Status/samlp:StatusCode', $doc);
$statusString = $status->item(0)->getAttribute('Value');
$statusArray = explode(':',$statusString);
if(array_key_exists(7, $statusArray)){
$status = $statusArray[7];
}
if ('Success' !== $status) {
$StatusMessage = $xpath->query('/samlp:Response/samlp:Status/samlp:StatusMessage', $doc)->item(0);
LogManager::getInstance()->error('SAML login failed: status = '. $status);
if(!empty($StatusMessage)) {
$StatusMessage = $StatusMessage->nodeValue;
LogManager::getInstance()->error('SAML login failed: status message = '. $StatusMessage);
}
return false;
}
$x509cert = SettingsManager::getInstance()->getSetting('SAML: X.509 Certificate');
$samlResponse = new \SAML2_Response($samlResponseXml);
$responseSignatureData = $samlResponse->getSignatureData();
$assertionSignatureData = current($samlResponse->getAssertions())->getSignatureData();
$certFingerPrint = MoXMLSecurityKey::getRawThumbprint($x509cert);
$certFingerPrint = preg_replace('/\s+/', '', $certFingerPrint);
$validSignature = false;
if(!empty($responseSignatureData)) {
$validSignature = \Utilities::processResponse($acsUrl, $certFingerPrint, $responseSignatureData, $samlResponse, 0, $relayState);
LogManager::getInstance()->error('SAML: response signature validity :'.$validSignature);
}
if(!empty($assertionSignatureData)) {
$validSignature = \Utilities::processResponse($acsUrl, $certFingerPrint, $assertionSignatureData, $samlResponse, 0, $relayState);
LogManager::getInstance()->error('SAML: response signature validity :'.$validSignature);
}
if(!$validSignature) {
LogManager::getInstance()->error('Invalid response or assertion signature');
return false;
}
$issuer = current($samlResponse->getAssertions())->getIssuer();
$assertion = current($samlResponse->getAssertions());
$audiences = $assertion->getValidAudiences();
$expectedIssuer = SettingsManager::getInstance()->getSetting('SAML: IDP Issuer');
if ($issuer !== $expectedIssuer) {
LogManager::getInstance()->error('SAML Invalid Issuer :'.$issuer.' expected :'.$expectedIssuer);
return false;
}
$ssoEmail = current(current($samlResponse->getAssertions())->getNameId());
if (!$ssoEmail) {
return false;
}
return $ssoEmail;
}
}

View File

@@ -1,15 +1,22 @@
<?php
namespace Classes;
use Classes\Crypt\AesCtr;
use Model\Setting;
class SettingsManager
{
const ENCRYPTED_PREFIX = 'iceenc_';
private static $me = null;
private $encryptedSettings = [];
private function __construct()
{
$this->addEncryptedSetting('SAML: X.509 Certificate');
$this->addEncryptedSetting('LDAP: Manager Password');
}
public static function getInstance()
@@ -21,9 +28,62 @@ class SettingsManager
return self::$me;
}
public function addEncryptedSetting($name) {
if (!$this->isEncryptedSetting($name)) {
$this->encryptedSettings[] = $name;
}
}
public function isEncryptedSetting($name) {
return in_array($name, $this->encryptedSettings);
}
public function getInstanceKey() {
$settings = new Setting();
$settings->Load("name = ?", array("Instance: Key"));
if ($settings->name != "Instance: Key") {
return null;
}
return $settings->value;
}
private function encrypt($value) {
$id = BaseService::getInstance()->getInstanceId();
$key = $this->getInstanceKey();
return AesCtr::encrypt($value, $id.$key, 256);
}
public function encryptSetting($name, $value) {
// check the existence of prefix and encrypt only if need to avoid double encryption
if (
$this->isEncryptedSetting($name)
&& substr($value, 0, strlen(self::ENCRYPTED_PREFIX)) !== self::ENCRYPTED_PREFIX
) {
$value = self::ENCRYPTED_PREFIX.$this->encrypt($value);
}
return $value;
}
private function decrypt($value) {
$id = BaseService::getInstance()->getInstanceId();
$key = $this->getInstanceKey();
return AesCtr::decrypt($value, $id.$key, 256);
}
public function decryptSetting($name, $value) {
if (
$this->isEncryptedSetting($name)
&& substr($value, 0, strlen(self::ENCRYPTED_PREFIX)) === self::ENCRYPTED_PREFIX
) {
$value = $this->decrypt(substr($value, strlen(self::ENCRYPTED_PREFIX)));
}
return $value;
}
public function getSetting($name)
{
if (class_exists("\\Classes\\ProVersion")) {
$pro = new ProVersion();
$val = $pro->getSetting($name);
@@ -34,20 +94,28 @@ class SettingsManager
$setting = new Setting();
$setting->Load("name = ?", array($name));
$value = null;
if ($setting->name == $name) {
return $setting->value;
$value = $setting->value;
}
return null;
if (null === $value) {
return null;
}
return $this->decryptSetting($name, $value);
}
public function setSetting($name, $value)
{
$setting = new Setting();
$setting->Load("name = ?", array($name));
if ($setting->name == $name) {
$setting->value = $value;
$setting->Save();
if ($setting->name !== $name) {
return;
}
$setting->value = $this->encryptSetting($name, $value);
$setting->Save();
}
public function addSetting($name, $value)
@@ -55,14 +123,21 @@ class SettingsManager
$setting = new Setting();
$setting->Load("name = ?", array($name));
if ($setting->name == $name) {
$setting->value = $value;
$setting->value = $this->encryptSetting($name, $value);
$setting->Save();
} else {
$setting->name = $name;
$setting->value = $value;
$setting->description = $value;
$setting->value = $this->encryptSetting($name, $value);
$setting->description = '';
$setting->meta = '';
$setting->Save();
}
}
public function getDeprecatedSettings() {
return [
'Attendance: Work Week Start Day',
'Attendance: Overtime Calculation Class'
];
}
}

View File

@@ -5,9 +5,14 @@ use Classes\IceResponse;
use Classes\ModuleAccess;
use Employees\Common\Model\Employee;
use Model\BaseModel;
use Model\CustomFieldTrait;
class CompanyStructure extends BaseModel
{
use CustomFieldTrait;
public $objectName = 'Company Structures';
protected $allowCustomFields = true;
public $table = 'CompanyStructures';
public function getAdminAccess()

View File

@@ -0,0 +1,25 @@
<?php
namespace CustomField\Admin\Api;
use Classes\AbstractModuleManager;
class CustomFieldAdminManager extends AbstractModuleManager
{
public function initializeUserClasses()
{
}
public function initializeFieldMappings()
{
}
public function initializeDatabaseErrorMappings()
{
}
public function setupModuleClassDefinitions()
{
$this->addModelClass('CustomField');
}
}

View File

@@ -45,10 +45,11 @@ class DocumentTaskCreator implements TaskCreator
return 0;
}
$query = "select count(id) as c from EmployeeDocuments where employee = ? and valid_until < ?";
$query = "select count(id) as c from EmployeeDocuments where employee = ? and valid_until < ? and visible_to = ?";
$user->DB()->SetFetchMode(ADODB_FETCH_ASSOC);
$rs = $user->DB()->Execute($query, [$employee->id, date('Y-m-d')]);
// TODO - sending notifications only for Owner documents, this need to be extended later
$rs = $user->DB()->Execute($query, [$employee->id, date('Y-m-d'), 'Owner']);
$count = $rs->fields['c'];
return $count;

View File

@@ -1,6 +1,7 @@
<?php
namespace Documents\Common\Model;
use Classes\BaseService;
use Classes\ModuleAccess;
use Model\BaseModel;
@@ -26,4 +27,23 @@ class Document extends BaseModel
new ModuleAccess('documents', 'user'),
];
}
public function fieldValueMethods()
{
return ['getDocumentTypesForUser'];
}
public function getDocumentTypesForUser()
{
$documents = new Document();
if (BaseService::getInstance()->currentUser->user_level === 'Employee'
|| BaseService::getInstance()->currentUser->user_level === 'Restricted Employee'
) {
$documents = $documents->Find('share_with_employee = ?', ['Yes']);
} else {
$documents = $documents->Find('1 = 1');
}
return $documents;
}
}

View File

@@ -8,14 +8,65 @@
namespace Documents\Common\Model;
use Classes\BaseService;
use Classes\IceResponse;
use Classes\ModuleAccess;
use Employees\Common\Model\Employee;
use Model\BaseModel;
class EmployeeDocument extends BaseModel
{
public $table = 'EmployeeDocuments';
private function getHiddenDocumentTypeIds()
{
}
// @codingStandardsIgnoreStart
public function Find($whereOrderBy, $bindarr = false, $cache = false, $pkeysArr = false, $extra = array())
{
$find = '';
$user = BaseService::getInstance()->getCurrentUser();
if ($user->user_level == 'Employee') {
$find = ' visible_to = \'Owner\' AND ';
$document = new Document();
$hiddenDocumentTypes = $document->Find(
"share_with_employee = ?",
['No']
);
$hiddenTypeIds = [];
foreach ($hiddenDocumentTypes as $hiddenDocumentType) {
$hiddenTypeIds[] = $hiddenDocumentType->id;
}
if(count($hiddenTypeIds) > 0) {
$find .= ' document NOT IN (\''.implode('\',\'', $hiddenTypeIds).'\') AND ';
}
return parent::Find($find.$whereOrderBy, $bindarr, $pkeysArr, $extra);
} else if ($user->user_level == 'Manager') {
// Original $whereOrderBy already contain employee selection
// So here if isSubOrdinates is true if the query coming from Employee -> Document Management
// In that case we need to show documents from sub ordinates
// These docs can can be owner and manager both
if (isset($isSubOrdinates) && $isSubOrdinates) {
$find .= ' visible_to in (\'Owner\', \'Manager\') AND ';
} else {
// Here we are showing the documents for the manager
// If someone upload a document for this manager and make it visible to manager,
// that means only the manager of this manager can see the document
// So it should not be visible to this manager
$find .= ' visible_to in (\'Owner\') AND ';
}
}
return parent::Find($find.$whereOrderBy, $bindarr, $pkeysArr, $extra);
}
// @codingStandardsIgnoreEnd
public function getAdminAccess()
{
return array("get","element","save","delete");
@@ -26,6 +77,8 @@ class EmployeeDocument extends BaseModel
return array("get","element","save","delete");
}
public function getUserAccess()
{
return array("get");

View File

@@ -8,6 +8,7 @@ use Classes\ModuleAccess;
use Company\Common\Model\CompanyStructure;
use Metadata\Common\Model\Country;
use Model\BaseModel;
use Model\CustomFieldTrait;
class Employee extends BaseModel
{
@@ -90,6 +91,11 @@ class Employee extends BaseModel
$this->oldObj = BaseService::getInstance()->getElement('Employee', $obj->id, $mapping, true);
}
public function isCustomFieldsEnabled()
{
return true;
}
private function saveHistory($obj)
{

View File

@@ -147,6 +147,10 @@ class EmployeesActionManager extends SubActionManager
return new IceResponse(IceResponse::ERROR, "Error occurred while changing password");
}
if (!PasswordManager::verifyPassword($req->current, $user->password)) {
return new IceResponse(IceResponse::ERROR, "Current password is incorrect");
}
$passwordStrengthResponse = PasswordManager::isQualifiedPassword($req->pwd);
if ($passwordStrengthResponse->getStatus() === IceResponse::ERROR) {
return $passwordStrengthResponse;
@@ -158,6 +162,6 @@ class EmployeesActionManager extends SubActionManager
return new IceResponse(IceResponse::ERROR, $user->ErrorMsg());
}
return new IceResponse(IceResponse::SUCCESS, $user);
return new IceResponse(IceResponse::SUCCESS, []);
}
}

View File

@@ -12,6 +12,7 @@ use Classes\BaseService;
use Classes\IceResponse;
use Classes\ModuleAccess;
use Model\BaseModel;
use Utils\LogManager;
class CustomField extends BaseModel
{
@@ -58,6 +59,22 @@ class CustomField extends BaseModel
return new IceResponse(IceResponse::SUCCESS, "");
}
/**
* @param CustomField $obj
*/
public function executePostDeleteActions($obj)
{
$ret = $this->DB()->Execute(
'DELETE FROM CustomFieldValues where type = ? and name = ?',
[$obj->type, $obj->name]
);
if (!$ret) {
$this->lastError = $this->db()->ErrorMsg();
LogManager::getInstance()->error('Error deleting custom field values: '.$this->DB()->ErrorMsg());
}
}
public function getModuleAccess()
{
return [

View File

@@ -11,6 +11,8 @@ use Utils\LogManager;
class BaseModel extends \ADOdb_Active_Record
{
public $objectName = null;
protected $allowCustomFields = false;
public $keysToIgnore = array(
"_table",
@@ -195,6 +197,16 @@ class BaseModel extends \ADOdb_Active_Record
{
}
public function executePostDeleteActions($obj)
{
}
/**
* If null is returned the object wont be included in the response
*
* @param $obj
* @return mixed
*/
public function postProcessGetData($obj)
{
return $obj;
@@ -340,4 +352,14 @@ class BaseModel extends \ADOdb_Active_Record
return $ok;
}
// @codingStandardsIgnoreEnd
public function getObjectName()
{
return null;
}
public function isCustomFieldsEnabled()
{
return false;
}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace Model;
trait CustomFieldTrait
{
public function getObjectName()
{
return $this->objectName;
}
public function isCustomFieldsEnabled()
{
return $this->allowCustomFields;
}
}

View File

@@ -12,6 +12,7 @@ use Classes\BaseService;
use Classes\IceResponse;
use Classes\ModuleAccess;
use Classes\RestApiManager;
use Classes\SettingsManager;
use Users\Common\Model\User;
class Setting extends BaseModel
@@ -74,6 +75,12 @@ class Setting extends BaseModel
return new IceResponse(IceResponse::SUCCESS, "");
}
public function executePreSaveActions($obj)
{
$obj->value = SettingsManager::getInstance()->encryptSetting($obj->name, $obj->value);
return new IceResponse(IceResponse::SUCCESS, $obj);
}
public function executePreUpdateActions($obj)
{
return $this->executePreSaveActions($obj);
@@ -87,5 +94,17 @@ class Setting extends BaseModel
{
}
public function postProcessGetData($obj)
{
if (in_array($obj->name, SettingsManager::getInstance()->getDeprecatedSettings())) {
return null;
}
if (strlen($obj->value) > 30) {
$obj->value = substr($obj->value,0, 30).'...';
}
return $obj;
}
public $table = 'Settings';
}

View File

@@ -2,6 +2,7 @@
namespace Reports\Admin\Reports;
use Attendance\Common\Model\Attendance;
use Company\Common\Model\CompanyStructure;
use Employees\Common\Model\Employee;
use Reports\Admin\Api\ClassBasedReportBuilder;
use Reports\Admin\Api\ReportBuilderInterface;
@@ -16,6 +17,18 @@ class EmployeeTimeTrackReport extends ClassBasedReportBuilder implements ReportB
LogManager::getInstance()->info(json_encode($report));
LogManager::getInstance()->info(json_encode($req));
if (
empty($req['period'])
&& (
empty($req['date_start'])
|| 'NULL' === $req['date_start']
|| empty($req['date_end'])
|| 'NULL' === $req['date_end']
)
) {
$req['period'] = 'Current Month';
}
$employeeTimeEntry = new EmployeeTimeEntry();
$timeEntryList = $employeeTimeEntry->Find(
@@ -38,6 +51,8 @@ class EmployeeTimeTrackReport extends ClassBasedReportBuilder implements ReportB
//$minutes = (int)($seconds/60);
//Find Attendance Entries
$req = $this->setRequestDatesBasedOnThePeriod($req);
$attendance = new Attendance();
$atteandanceList = $attendance->Find(
"employee = ? and date(in_time) >= ? and date(out_time) <= ? and in_time < out_time",
@@ -66,21 +81,34 @@ class EmployeeTimeTrackReport extends ClassBasedReportBuilder implements ReportB
$employeeObject = new Employee();
$employeeObject->Load("id = ?", array($req['employee']));
$company = new CompanyStructure();
$company->Load('id = ?', [$employeeObject->department]);
$reportData = array();
//$reportData[] = array($employeeObject->first_name." ".$employeeObject->last_name,"","","","");
$reportData[] = array("Date","First Punch-In Time","Last Punch-Out Time","Time in Office","Time in Timesheets");
$reportData = [];
$reportData[] = ["Date","First Punch-In Time","Last Punch-Out Time","Time in Attendance (Hours)","Time in Time-sheets (Hours)"];
$reportData[] = ["Employee:",$employeeObject->first_name." ".$employeeObject->last_name,"","",""];
$reportData[] = ["Department:",$company->title,"","",""];
$reportData[] = ["Total Days:","","","",""];
//Iterate date range
$interval = \DateInterval::createFromDateString('1 day');
$period = new \DatePeriod(new \DateTime($req['date_start']), $interval, new \DateTime($req['date_end']));
$period = new \DatePeriod(new \DateTime($req['date_start']), $interval, (new \DateTime($req['date_end']))->modify('+1 day'));
$totalHoursOffice = 0;
$totalHoursTimeSheets = 0;
$totalDaysForThePeriod = 0;
foreach ($period as $dt) {
$dataRow = array();
$key = $dt->format("Y-m-d");
if (!isset($firstTimeInArray[$key])) {
continue;
}
$totalDaysForThePeriod++;
$dataRow[] = $key;
if (isset($firstTimeInArray[$key])) {
@@ -107,8 +135,44 @@ class EmployeeTimeTrackReport extends ClassBasedReportBuilder implements ReportB
$dataRow[] = 0;
}
$totalHoursOffice += $dataRow[3];
$totalHoursTimeSheets += $dataRow[4];
$dataRow[3] = number_format($dataRow[3], 2, '.', '');
$dataRow[4] = number_format($dataRow[4], 2, '.', '');
$reportData[] = $dataRow;
}
$reportData[3][1] = $totalDaysForThePeriod;
$totalHoursOffice = number_format($totalHoursOffice, 2, '.', '');
$totalHoursTimeSheets = number_format($totalHoursTimeSheets, 2, '.', '');
$reportData[] = ["Total","","",$totalHoursOffice,$totalHoursTimeSheets];
return $reportData;
}
private function setRequestDatesBasedOnThePeriod($req) {
if (empty($req['period'])) {
return $req;
}
if ($req['period'] === 'Current Month') {
$req['date_start'] = date('Y-m-01', strtotime('now'));
$req['date_end'] = date('Y-m-d', strtotime('now'));
} else if ($req['period'] === 'Last Month') {
$req['date_start'] = date('Y-m-d', strtotime('first day of last month'));
$req['date_end'] = date('Y-m-d', strtotime('last day of last month'));
} else if ($req['period'] === 'Last Week') {
$req['date_start'] = date("Y-m-d", strtotime("-7 days"));
$req['date_end'] = date('Y-m-d', strtotime('now'));
} else if ($req['period'] === 'Last 2 Weeks') {
$req['date_start'] = date("Y-m-d", strtotime("-14 days"));
$req['date_end'] = date('Y-m-d', strtotime('now'));
}
return $req;
}
}

View File

@@ -24,10 +24,9 @@ class StaffDirectory extends Employee
foreach ($res as $entry) {
$emp = new BaseModel();
$emp->id = $entry->id;
$emp = FileService::getInstance()->updateProfileImage($emp);
//$emp->image = str_replace("_img_",$emp->image,$img);
$emp->first_name = $entry->first_name;
$emp->last_name = $entry->last_name;
$emp = FileService::getInstance()->updateSmallProfileImage($emp);
$emp->job_title = $entry->job_title;
$emp->department = $entry->department;
$emp->work_phone = $entry->work_phone;
@@ -41,6 +40,11 @@ class StaffDirectory extends Employee
return $data;
}
public function isCustomFieldsEnabled()
{
return false;
}
// @codingStandardsIgnoreStart
public function Insert()
{

View File

@@ -11,10 +11,15 @@ namespace Travel\Common\Model;
use Classes\ModuleAccess;
use Classes\SettingsManager;
use Model\ApproveModel;
use Model\CustomFieldTrait;
class EmployeeTravelRecord extends ApproveModel
{
use CustomFieldTrait;
public $table = 'EmployeeTravelRecords';
public $objectName = 'Travel Request';
protected $allowCustomFields = true;
public $notificationModuleName = "Travel Management";
public $notificationUnitName = "TravelRequest";

View File

@@ -10,6 +10,7 @@ namespace Travel\Common\Model;
class EmployeeTravelRecordApproval extends EmployeeTravelRecord
{
protected $allowCustomFields = false;
// @codingStandardsIgnoreStart
public function Find($whereOrderBy, $bindarr = false, $cache = false, $pkeysArr = false, $extra = array())

BIN
favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -243,6 +243,7 @@ gulp.task('admin-js', (done) => {
const files = [
'attendance',
'company_structure',
'custom_fields',
'clients',
'charts',
'dashboard',

18
package-lock.json generated
View File

@@ -3824,7 +3824,7 @@
},
"doctrine": {
"version": "1.5.0",
"resolved": "http://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz",
"integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=",
"dev": true,
"requires": {
@@ -4759,12 +4759,6 @@
"dev": true,
"optional": true
},
"ini": {
"version": "1.3.5",
"bundled": true,
"dev": true,
"optional": true
},
"is-fullwidth-code-point": {
"version": "1.0.0",
"bundled": true,
@@ -6796,9 +6790,9 @@
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"ini": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
"integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==",
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
"dev": true
},
"inline-source-map": {
@@ -6988,7 +6982,7 @@
},
"is-accessor-descriptor": {
"version": "0.1.6",
"resolved": "http://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
"resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
"integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=",
"dev": true,
"requires": {
@@ -7034,7 +7028,7 @@
},
"is-data-descriptor": {
"version": "0.1.4",
"resolved": "http://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
"resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
"integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=",
"dev": true,
"requires": {

View File

@@ -1,5 +1,3 @@
<img src="web/images/logo-sq.png" align="right" />
IceHrm
===========
[![Build Status](https://travis-ci.org/gamonoid/icehrm.svg?branch=master)](https://travis-ci.org/gamonoid/icehrm)

View File

@@ -1,3 +1,19 @@
# Release Notes IceHrm Open Source
## Release note v28.2.0.OS
### New features
* 🦠 💉 Custom extensions [https://icehrm.gitbook.io/icehrm/developer-guide/creating-first-extension](https://icehrm.gitbook.io/icehrm/developer-guide/creating-first-extension)
## Release note v28.1.1.OS
### 🐛 Bug fixes
* Fixing inability to filter employee documents
* Fixed the issue with selecting projects when adding timesheets details
* Fix issues occurred due to incorrectly configured API
## Release note v28.1.0.OS
### 🧲 New features
@@ -16,441 +32,477 @@
* New REST endpoints for employee qualifications
### 🐛 Bug fixes
* Fixed, issue with managers being able to create performance reviews for employees who are not their direct reports
* Fixed, issues related to using full profile image instead of using smaller version of profile image
* Changing third gender to other
* Improvements and fixes for internal frontend data caching
## Release note v27.0.2.OS
This fixes some major issues found in v27.0.1.OS
### 🐛 Bug fixes
* Filtering across whole application was broken and now it's fixed
* Fixed the issue related to photo not being shown to the admin when photo attendance is enabled
### 🧑🏻‍💻 For developers
* We have added support for vagrant development environment based on Debian 10 / PHP 7.3 \(with Xdebug\) / Nginx / MySQL
## Release note v27.0.0.OS
### New features
* Employee document management is now available for open-source version
* UI/UX improvements (new fonts / better spacing)
* UI/UX improvements \(new fonts / better spacing\)
* Payroll module improvements
* Security improvements to password policy
* Albanian language is now available
* Ability to deploy using docker
### For developers
* Developer environment based on docker [https://www.youtube.com/watch?v=sz8OV_ON6S8](https://www.youtube.com/watch?v=sz8OV_ON6S8)
* Developer environment based on docker [https://www.youtube.com/watch?v=sz8OV\_ON6S8](https://www.youtube.com/watch?v=sz8OV_ON6S8)
* [Developer guide](https://icehrm.gitbook.io/icehrm/developer-guide/create-new-module)
* Fully supports all php versions >= 5.6 upto v7.3 (php 5.6 support is deprecated and not recommended)
* Fully supports all php versions &gt;= 5.6 upto v7.3 \(php 5.6 support is deprecated and not recommended\)
### Bug fixes
* Fixes to newly found vulnerabilities (https://github.com/gamonoid/icehrm/issues/213): credits to: [Talos](https://talosintelligence.com/)
* Fixes to newly found vulnerabilities \([https://github.com/gamonoid/icehrm/issues/213](https://github.com/gamonoid/icehrm/issues/213)\): credits to: [Talos](https://talosintelligence.com/)
* Fixed the travel request approval for managers
* Fixed the issue with attendance source IP display
* Fixing Api issues in PHP 7.3
Release note v26.6.0.OS
------------------------
## Release note v26.6.0.OS
### Features
* Some Improvements to UI such as updating Icons and upgrading font-awesome to its latest version
* Tracking IP and location of the employee when marking attendance, this is done when updating attendance via mobile
* Ability to control location tracking via mobile using server side settings
* Improvements to translations
* Compatible with location tracking with icehrm mobile app
* Some Improvements to UI such as updating Icons and upgrading font-awesome to its latest version
* Tracking IP and location of the employee when marking attendance, this is done when updating attendance via mobile
* Ability to control location tracking via mobile using server side settings
* Improvements to translations
### Mobile App
* This release is coupled with mobile application release on AppStore (https://apple.co/2Yrtxoy) and Google Play (http://bit.ly/2OkMmKe)
* This release is coupled with mobile application release on AppStore \([https://apple.co/2Yrtxoy](https://apple.co/2Yrtxoy)\) and Google Play \([http://bit.ly/2OkMmKe](http://bit.ly/2OkMmKe)\)
### Fixes
* Order projects by name on Timesheet project listing (This is to make it easier to edit timesheets with many projects)
* Link home page user profile to employee profile update page
* Fix issues related to configuring Api with mobile app
* Order projects by name on Timesheet project listing \(This is to make it easier to edit timesheets with many projects\)
* Link home page user profile to employee profile update page
* Fix issues related to configuring Api with mobile app
### Security Improvements
* Upgrade npm missing dependencies
Release note v26.2.0.OS
------------------------
* Upgrade npm missing dependencies
## Release note v26.2.0.OS
### Features
* Add staff directory module
* Update client-side js to ES6
* Compatible with IceHrm Mobile App
* Use npm libraries when possible
* Add gulp build for frontend assets
* Allow generating QR code with rest api key (https://github.com/gamonoid/icehrm/issues/169)
* Updated readme for development setup with vagrant
* Add staff directory module
* Update client-side js to ES6
* Compatible with IceHrm Mobile App
* Use npm libraries when possible
* Add gulp build for frontend assets
* Allow generating QR code with rest api key \([https://github.com/gamonoid/icehrm/issues/169](https://github.com/gamonoid/icehrm/issues/169)\)
* Updated readme for development setup with vagrant
### Fixes
* Add missing employee details report
* Fix: Labels of 'Employee Custom Fields' not displayed (https://github.com/gamonoid/icehrm/issues/146)
* Fix: Work week for all counties can not be edited
* Fix: Custom fields are not shown under employee profile (https://github.com/gamonoid/icehrm/issues/159)
* Fix: Additional buttons shown below timesheet list (https://github.com/gamonoid/icehrm/issues/171)
* Updates to Italian translations (https://github.com/gamonoid/icehrm/pull/166) by https://github.com/nightwatch75
Release note v24.0.0.OS
------------------------
* Add missing employee details report
* Fix: Labels of 'Employee Custom Fields' not displayed \([https://github.com/gamonoid/icehrm/issues/146](https://github.com/gamonoid/icehrm/issues/146)\)
* Fix: Work week for all counties can not be edited
* Fix: Custom fields are not shown under employee profile \([https://github.com/gamonoid/icehrm/issues/159](https://github.com/gamonoid/icehrm/issues/159)\)
* Fix: Additional buttons shown below timesheet list \([https://github.com/gamonoid/icehrm/issues/171](https://github.com/gamonoid/icehrm/issues/171)\)
* Updates to Italian translations \([https://github.com/gamonoid/icehrm/pull/166](https://github.com/gamonoid/icehrm/pull/166)\) by [https://github.com/nightwatch75](https://github.com/nightwatch75)
## Release note v24.0.0.OS
### Features
* Allow passing additional parameters to payroll predefined methods
* Pass leave type name in function field to get leave count for a given type
* Add employee name to payroll report
* Show supervisor name on employee profile
* Add custom fields to employee report
* Add filter by status feature to subordinate time sheets
* Allow passing additional parameters to payroll predefined methods
* Pass leave type name in function field to get leave count for a given type
* Add employee name to payroll report
* Show supervisor name on employee profile
* Add custom fields to employee report
* Add filter by status feature to subordinate time sheets
### Security Fixes
* Fix missing login form CSRF token
* Fix risky usage of hashed password in request
* Fixing permission issues on module access for each user level
* Prevent manager from accessing sensitive user records
* Fix missing login form CSRF token
* Fix risky usage of hashed password in request
* Fixing permission issues on module access for each user level
* Prevent manager from accessing sensitive user records
### Other Fixes
* Hide employee salary from managers
* Prevent manager from accessing audit, cron and notifications
* Prevent managers from deleting employees
* Validate overtime start and end times
* Fix issue: employee can download draft payroll
Release note v23.0.1.OS
------------------------
* Hide employee salary from managers
* Prevent manager from accessing audit, cron and notifications
* Prevent managers from deleting employees
* Validate overtime start and end times
* Fix issue: employee can download draft payroll
## Release note v23.0.1.OS
This release include some very critical security fixes. We recommend upgrading your installation to latest release.
### Fixes
* Fix missing login form CSRF token
* Fix risky usage of hashed password in request
Release note v23.0.0.OS
------------------------
### Features
* Loading last used module when revisiting application
* Finnish language support (Beta)
* Improvements to German, Italian and Chinese language translations
* Allow quickly switching languages
* Improvements to security for preventing possible LFI attacks
* Allow manual date inputs
* Custom fields for travel requests
* Allow importing approved overtime hours into payroll
* Add date and time masks
### Fixes
* Fix logout cookie issue, by clearing remember me cookie when logging out
* Improve privacy for GDPR
* Improvements to file upload field
* Fix issue: attendance rest end point not working on php 5.6
Release note v22.0.0.OS
------------------------
### Features
* Improvements to module naming
### Fixes
* Fix issue: filter dialog default values are not selected
* Fix issue: department head can be an employee outside the department
* Fix issue: department head or supervisor (who has manager leave access) can't use switch employee feature
* Fix issue: employee name is not visible on report if middle name is empty
* Fix missing login form CSRF token
* Fix risky usage of hashed password in request
Release note v21.1.0.OS
------------------------
### Features
* UI improvements (help button and error messages)
* Allow adding placeholders to text fields
* Improvements to German Translations
### Fixes
* Fixing notification issues
Release note v21.0.0.OS
------------------------
### Features
* Fully compatible with php 7.1
* Add Net_SMTP via composer (no pear installation needed)
### Fixes
* Fixes for web servers not supporting JSON in GET request
Release note v21.0.0.OS
------------------------
### Features
* Fully compatible with php 7.1
* Add Net_SMTP via composer (no pear installation needed)
### Fixes
* Fixes for web servers not supporting JSON in GET request
Release note v20.3.0.PRO
------------------------
### Features
* Employee and Attendance REST Api Released
* Import/Export for Payroll Configurations
* Ability to import employee approved time sheet hours to payroll
* Swift Mailer based SMTP support (no need to install Net_SMTP anymore)
* Add direct Edit button on employee list
### Fixes
* Fix DB connection issues due to special characters in password
* Fixes for custom field saving issues in mysql v5.7.x
Release note v20.2
------------------
### Fixes
* Fix for resetting modules
Release note v20.1
------------------
### Features
* Compatible with MySQL 5.7 Strict Mode
* PSR-2 compatible code
* Employee History Module
* Staff Directory
### Fixes
* Fix: password reset not working
* Fix: limiting selectable countries via Settings
* Fix for resetting modules
Release note v20.0
------------------
### Features
* Payroll Module
* Compatible with MySQL 5.7 Strict Mode
* Namespaced Classes
* LDAP Module
### Fixes
* Fix: limiting selectable countries via Settings
Release note v19.0
------------------
### Features
* Development environment
* Overtime module
* Department heads who can manage all employees attached to a company structure
Release note v18.0
------------------
### Features
* Translations (beta) for German, French, Polish, Italian, Sinhala, Chinese, Japanese, Hindi and Spanish
* PDF Reports
* Ability to specify department heads
* Add advanced custom fields to employees via UI
* Allow indirect admins to approve travel requests
* Adding more languages to Language meta data table
* Improvements to report module
* Ability to select sections for placing custom fields on employee detail view screen
* Introducing clone button
* Unlimited custom fields for employees
* PDF report for monitoring time employee spent on projects
* Report files module - Allow downloading all previously generated reports
### Fixes
* Fix: subordinates are not showing beyond first page issue.
Release note v16.1
------------------
### Fixes
* Fix LDAP user login issue
* Allow creating users with username having dot and dash
Release note v16.0
------------------
### Features
* Advanced Employee Management Module is now included in IceHrm Open Source Edition
* LDAP Module which was only available in IceHrm Enterprise is now included in open source also
* Initial implementation of icehrm REST Api for reading employee details
* Improvements to data filtering
* Multiple tabs for settings module
* Overtime reports - now its possible to calculate overtime for employees.compatible with US overtime rules
* Logout the user if tried accessing an unauthorized module
* Setting for updating module names
### Fixes
* Fix issue: classes should be loaded even the module is disabled
* Deleting the only Admin user is not allowed
* Fixes for handling non UTF-8
* Fix for non-mandatory select boxes are shown as mandatory
Release note v15.2
------------------
## Release note v23.0.0.OS
### Features
* Overtime Reports
* Overtime calculation for california
### Fixes
* Fix issue: uncaught error when placeholder value is empty
* Log email sending success status
* Fix broken longer company name issue
* Make the application accessible when client on an intranet with no internet connection
* Fix issue: when a module is disabled other modules depend on it stops working
Release note v15.0
------------------
### Features
* Clear HTML5 local storage when logging in and switching users
* Showing a loading message while getting data from server
* Adding a new field to show total time of each time sheet
* New report added for listing Employee Time Sheets
* Company logo uploaded via settings will be used for all email headers
### Fixes
* Fix issue: default module URL is incorrect for Employees
* Fix date parsing issue in time sheets
* AWS phar is included only when required
Release note v14.1
------------------
### Features
* Add Quick access menu
* Loading last used module when revisiting application
* Finnish language support \(Beta\)
* Improvements to German, Italian and Chinese language translations
* Allow quickly switching languages
* Improvements to security for preventing possible LFI attacks
* Allow manual date inputs
* Custom fields for travel requests
* Allow importing approved overtime hours into payroll
* Add date and time masks
### Fixes
* Fix issue: salary module not loading
* Add travel report
Release note v14.0
------------------
* Fix logout cookie issue, by clearing remember me cookie when logging out
* Improve privacy for GDPR
* Improvements to file upload field
* Fix issue: attendance rest end point not working on php 5.6
## Release note v22.0.0.OS
### Features
* IceHrm is now fully compatible with PHP 7
* Improvements to travel management module to change the process of applying for travel requests
* New report add for getting travel requests
* Improvements to user interface
* Bunch of UI improvements including changing menu order and font sizes
* Add a setting to use server time for time zone defined on department that a user is attached to create new attendance records
* Improvements to admin/manager and user dashboard
* Managers allowed to view/add/edit employee documents
* New reports added for employee expenses and travel
### Fixes
* Fix unavailable help links
Release note v13.4
-----------------
### Features
### Fixes
* Fix employee leave report leave type field
Release note v13.0
-----------------
### Features
* Recruitment module
* Allow managers to edit attendance of direct report employees
### Fixes
* Employee switching issue fixed
* Fix terminated employee labels
* Fix issue with punch-in
Release note v12.6
-----------------
### Features
* Charts module
* Code level security improvements
### Fixes
* Employee switching issue fixed
Release note v11.1
-----------------
### Features
* Add/Edit or remove employee fields
Release note v11.0
-----------------
### Features
* Employee data archiving
* Leave cancellation requests
* Adding view employee feature
* Improvements to module naming
### Fixes
* Improvements to date time pickers
* Fix issue: filter dialog default values are not selected
* Fix issue: department head can be an employee outside the department
* Fix issue: department head or supervisor \(who has manager leave access\) can't use switch employee feature
* Fix issue: employee name is not visible on report if middle name is empty
Release note v10.1
-----------------
## Release note v21.1.0.OS
### Features
* Integration with ice-framework (http://githun.com/thilinah/ice-framework)
* Option for only allow users to add an entry to a timesheet only if they have marked atteandance for the selected period
* Restricting availability of leave types to employees using leave groups
* Admins and add notes to employees
Release note v9.1
-----------------
* UI improvements \(help button and error messages\)
* Allow adding placeholders to text fields
* Improvements to German Translations
### Fixes
* Add missing S3FileSystem class
* Fix issue: passing result of a method call directly into empty method is not supported in php v5.3
* Fixing notification issues
Release note v9.0
-----------------
## Release note v21.0.0.OS
### Features
* New user interface
* Decimal leave counts supported
Update icehrm v8.4 to v9.0
--------------------------
* Make a backup of your icehrm db
* Run db script "icehrmdb_update_v8.4_to_v9.0.sql" which can be found inside script folder of icehrm_v9.0
* remove all folders except app folder in icehrm root folder
* copy all folders except app folder from new installation to icehrm root folder
Release note v8.4
-----------------
* Fully compatible with php 7.1
* Add Net\_SMTP via composer \(no pear installation needed\)
### Fixes
* Fix leave carry forward rounding issues
* Fix issue: select2 default value not getting set for select2
* Fix issue: email not sent when admin changing leave status
Release note v8.3
-----------------
* Fixes for web servers not supporting JSON in GET request
### Fixes
* Fix user table issue on windows, this will resolve errors such as: (Note that this fix has no effect on unix based installations)
* Admin not able to view user uploaded documents
* Admin not able to upload documants for users
* Admin can not view employee attendance records
* Employee projects can not be added
Release note v8.2
-----------------
## Release note v21.0.0.OS
### Features
* Fully compatible with php 7.1
* Add Net\_SMTP via composer \(no pear installation needed\)
### Fixes
* Fixes for web servers not supporting JSON in GET request
## Release note v20.3.0.PRO
### Features
* Employee and Attendance REST Api Released
* Import/Export for Payroll Configurations
* Ability to import employee approved time sheet hours to payroll
* Swift Mailer based SMTP support \(no need to install Net\_SMTP anymore\)
* Add direct Edit button on employee list
### Fixes
* Fix DB connection issues due to special characters in password
* Fixes for custom field saving issues in mysql v5.7.x
## Release note v20.2
### Fixes
* Fix for resetting modules
## Release note v20.1
### Features
* Compatible with MySQL 5.7 Strict Mode
* PSR-2 compatible code
* Employee History Module
* Staff Directory
### Fixes
* Fix: password reset not working
* Fix: limiting selectable countries via Settings
* Fix for resetting modules
## Release note v20.0
### Features
* Payroll Module
* Compatible with MySQL 5.7 Strict Mode
* Namespaced Classes
* LDAP Module
### Fixes
* Fix: limiting selectable countries via Settings
## Release note v19.0
### Features
* Development environment
* Overtime module
* Department heads who can manage all employees attached to a company structure
## Release note v18.0
### Features
* Translations \(beta\) for German, French, Polish, Italian, Sinhala, Chinese, Japanese, Hindi and Spanish
* PDF Reports
* Ability to specify department heads
* Add advanced custom fields to employees via UI
* Allow indirect admins to approve travel requests
* Adding more languages to Language meta data table
* Improvements to report module
* Ability to select sections for placing custom fields on employee detail view screen
* Introducing clone button
* Unlimited custom fields for employees
* PDF report for monitoring time employee spent on projects
* Report files module - Allow downloading all previously generated reports
### Fixes
* Fix: subordinates are not showing beyond first page issue.
## Release note v16.1
### Fixes
* Fix LDAP user login issue
* Allow creating users with username having dot and dash
## Release note v16.0
### Features
* Advanced Employee Management Module is now included in IceHrm Open Source Edition
* LDAP Module which was only available in IceHrm Enterprise is now included in open source also
* Initial implementation of icehrm REST Api for reading employee details
* Improvements to data filtering
* Multiple tabs for settings module
* Overtime reports - now its possible to calculate overtime for employees.compatible with US overtime rules
* Logout the user if tried accessing an unauthorized module
* Setting for updating module names
### Fixes
* Fix issue: classes should be loaded even the module is disabled
* Deleting the only Admin user is not allowed
* Fixes for handling non UTF-8
* Fix for non-mandatory select boxes are shown as mandatory
## Release note v15.2
### Features
* Overtime Reports
* Overtime calculation for california
### Fixes
* Fix issue: uncaught error when placeholder value is empty
* Log email sending success status
* Fix broken longer company name issue
* Make the application accessible when client on an intranet with no internet connection
* Fix issue: when a module is disabled other modules depend on it stops working
## Release note v15.0
### Features
* Clear HTML5 local storage when logging in and switching users
* Showing a loading message while getting data from server
* Adding a new field to show total time of each time sheet
* New report added for listing Employee Time Sheets
* Company logo uploaded via settings will be used for all email headers
### Fixes
* Fix issue: default module URL is incorrect for Employees
* Fix date parsing issue in time sheets
* AWS phar is included only when required
## Release note v14.1
### Features
* Add Quick access menu
### Fixes
* Fix issue: salary module not loading
* Add travel report
## Release note v14.0
### Features
* IceHrm is now fully compatible with PHP 7
* Improvements to travel management module to change the process of applying for travel requests
* New report add for getting travel requests
* Improvements to user interface
* Bunch of UI improvements including changing menu order and font sizes
* Add a setting to use server time for time zone defined on department that a user is attached to create new attendance records
* Improvements to admin/manager and user dashboard
* Managers allowed to view/add/edit employee documents
* New reports added for employee expenses and travel
### Fixes
* Fix unavailable help links
## Release note v13.4
### Features
### Fixes
* Fix employee leave report leave type field
## Release note v13.0
### Features
* Recruitment module
* Allow managers to edit attendance of direct report employees
### Fixes
* Employee switching issue fixed
* Fix terminated employee labels
* Fix issue with punch-in
## Release note v12.6
### Features
* Charts module
* Code level security improvements
### Fixes
* Employee switching issue fixed
## Release note v11.1
### Features
* Add/Edit or remove employee fields
## Release note v11.0
### Features
* Employee data archiving
* Leave cancellation requests
* Adding view employee feature
### Fixes
* Improvements to date time pickers
## Release note v10.1
### Features
* Integration with ice-framework \([http://githun.com/thilinah/ice-framework](http://githun.com/thilinah/ice-framework)\)
* Option for only allow users to add an entry to a timesheet only if they have marked atteandance for the selected period
* Restricting availability of leave types to employees using leave groups
* Admins and add notes to employees
## Release note v9.1
### Fixes
* Add missing S3FileSystem class
* Fix issue: passing result of a method call directly into empty method is not supported in php v5.3
## Release note v9.0
### Features
* New user interface
* Decimal leave counts supported
## Update icehrm v8.4 to v9.0
* Make a backup of your icehrm db
* Run db script "icehrmdb\_update\_v8.4\_to\_v9.0.sql" which can be found inside script folder of icehrm\_v9.0
* remove all folders except app folder in icehrm root folder
* copy all folders except app folder from new installation to icehrm root folder
## Release note v8.4
### Fixes
* Fix leave carry forward rounding issues
* Fix issue: select2 default value not getting set for select2
* Fix issue: email not sent when admin changing leave status
## Release note v8.3
### Fixes
* Fix user table issue on windows, this will resolve errors such as: \(Note that this fix has no effect on unix based installations\)
* Admin not able to view user uploaded documents
* Admin not able to upload documants for users
* Admin can not view employee attendance records
* Employee projects can not be added
## Release note v8.2
### Features
* Instance verification added
Release note v8.1
-----------------
## Release note v8.1
### Fixes
* Fixed bug that caused a fatal error in php v5.4
* aws2.7.11 phar file replaced by a aws2.7.11 extracted files
* old aws sdk removed
Release note v8.0
-----------------
## Release note v8.0
### Features
* Admin dashbord module
* If the employee joined in current leave period, his leave entitlement is calculated proportional to joined date
* Improvements to reporting module
@@ -462,22 +514,22 @@ Release note v8.0
* Upgrade aws sdk to v2.7.11
* Allow employees to change password
* Use only the email address defined under user for sending mails
* Making work_email and private_email fields optional
* Making work\_email and private\_email fields optional
### Fixes
* Upload dialog close button issue fixed
Release note v7.2
-----------------
## Release note v7.2
### Fixes
* Some critical vulnerabilities are fixed as recommend by http://zeroscience.mk/en/
Release note v7.1
-----------------
* Some critical vulnerabilities are fixed as recommend by [http://zeroscience.mk/en/](http://zeroscience.mk/en/)
## Release note v7.1
### Features
* Improved company structure graph
* Leave notes implementation <20> Supervisor can add a note when approving or rejecting leaves
* Filtering support
@@ -486,20 +538,20 @@ Release note v7.1
* Add ability to disable employee information editing
### Fixes
* Make loans editable only by admin
* Fix: permissions not getting applied to employee documents
* Fix error adding employee documents when no user assigned to the admin
### Code Quality
* Moving all module related code and data into module folders
Release note v6.1
-----------------
## Release note v6.1
Leave carry forwared related isue fixed
Release note v6.0
-----------------
## Release note v6.0
* Features
* Notifications for leaves and timesheets
@@ -513,21 +565,18 @@ Release note v6.0
* Admin can make all projects available to employees or just the set of prjects assigned to them using Setting "Projects: Make All Projects Available to Employees"
* Employee document, date added field can not be changed by the employee anymore
* About dialog added for admins
* Fixes
* Fix default employee delete issue (when the default employee is deleted the admin user attached to it also get deleted)
* Fix default employee delete issue \(when the default employee is deleted the admin user attached to it also get deleted\)
* Fix user duplicate email issue
* Fix manager can not logout from switched employee
* Remove admin guide from non admin users
Release note v5.3
-----------------
## Release note v5.3
* Fixes
* Fix missing employee name in employee details report
Release note v5.2
-----------------
## Release note v5.2
* Fixes
* Remove unwanted error logs
@@ -536,38 +585,33 @@ Release note v5.2
* Remove add new button from subordinates module
* Adding administrators' guide
Release note v5.1
-----------------
## Release note v5.1
* Fixes
* Fixing for non updating null fields
* https://bitbucket.org/thilina/icehrm-opensource/commits/df57308b53484a2e43ef5c72967ed1cd0dc756cc
* [https://bitbucket.org/thilina/icehrm-opensource/commits/df57308b53484a2e43ef5c72967ed1cd0dc756cc](https://bitbucket.org/thilina/icehrm-opensource/commits/df57308b53484a2e43ef5c72967ed1cd0dc756cc)
Release note v5.0
-----------------
## Release note v5.0
* Features
* New user permission implementation
* Adding new user level - Manager
* Fixes
* Fixing remote table loading issue
Release note v4.2
-----------------
## Release note v4.2
### Fixes
* https://bitbucket.org/thilina/icehrm-opensource/issue/23/subordinate-leaves-pagination-not-working
* https://bitbucket.org/thilina/icehrm-opensource/issue/20/error-occured-while-time-punch
* [https://bitbucket.org/thilina/icehrm-opensource/issue/23/subordinate-leaves-pagination-not-working](https://bitbucket.org/thilina/icehrm-opensource/issue/23/subordinate-leaves-pagination-not-working)
* [https://bitbucket.org/thilina/icehrm-opensource/issue/20/error-occured-while-time-punch](https://bitbucket.org/thilina/icehrm-opensource/issue/20/error-occured-while-time-punch)
Release note v4.1
-----------------
## Release note v4.1
### Features
* Better email format for notifications
* Convert upload dialog to a bootstrp model
* Fixes
* Fix error sending emails with amazon SES
* Fix errors related to XAMPP and WAMPP servers
@@ -576,3 +620,4 @@ Release note v4.1
* Allow icehrm client to work without an internet connection
* Fix installer incorrect base url issue
* Fix empty user creation issue

View File

@@ -3,13 +3,21 @@
Developer: Thilina Hasantha (http://lk.linkedin.com/in/thilinah | https://github.com/thilinah)
*/
import AdapterBase from '../../../api/AdapterBase';
import ReactModalAdapterBase from '../../../api/ReactModalAdapterBase';
/**
* ClientAdapter
*/
class ClientAdapter extends AdapterBase {
class ClientAdapter extends ReactModalAdapterBase {
constructor(endPoint, tab, filter, orderBy) {
super(endPoint, tab, filter, orderBy);
this.fieldNameMap = {};
this.hiddenFields = {};
this.tableFields = {};
this.formOnlyFields = {};
}
getDataMapping() {
return [
'id',
@@ -30,6 +38,32 @@ class ClientAdapter extends AdapterBase {
];
}
getTableColumns() {
return [
{
title: 'Name',
dataIndex: 'name',
sorter: true,
},
{
title: 'Details',
dataIndex: 'details',
sorter: true,
},
{
title: 'Address',
dataIndex: 'address',
sorter: true,
},
{
title: 'Contact Number',
dataIndex: 'contact_number',
sorter: true,
}
];
}
getFormFields() {
if (this.showSave) {
return [

View File

@@ -1,6 +1,5 @@
import { CompanyStructureAdapter, CompanyGraphAdapter } from './lib';
import IceDataPipe from "../../../api/IceDataPipe";
import CustomFieldAdapter from "../../../api/ReactCustomFieldAdapter";
function init(data) {
@@ -9,6 +8,7 @@ function init(data) {
modJsList.tabCompanyStructure.setObjectTypeName('Company Structure');
modJsList.tabCompanyStructure.setDataPipe(new IceDataPipe(modJsList.tabCompanyStructure));
modJsList.tabCompanyStructure.setAccess(data.permissions.CompanyStructure);
modJsList.tabCompanyStructure.setCustomFields(data.customFields);
modJsList.tabCompanyGraph = new CompanyGraphAdapter('CompanyStructure');

View File

@@ -64,7 +64,7 @@ class CompanyStructureAdapter extends ReactModalAdapterBase {
}
getFormFields() {
return [
return this.addCustomFields([
['id', { label: 'ID', type: 'hidden', validation: '' }],
['title', { label: 'Name', type: 'text', validation: '' }],
['description', { label: 'Details', type: 'textarea', validation: '' }],
@@ -80,7 +80,7 @@ class CompanyStructureAdapter extends ReactModalAdapterBase {
['heads', {
label: 'Heads', type: 'select2multi', 'allow-null': true, 'remote-source': ['Employee', 'id', 'first_name+last_name'],
}],
];
]);
}
postRenderForm(object, $tempDomObj) {

View File

@@ -0,0 +1,18 @@
import { CommonCustomFieldAdapter } from './lib';
import IceDataPipe from '../../../api/IceDataPipe';
function init(data) {
const modJsList = {};
modJsList.tabCustomField = new CommonCustomFieldAdapter('CustomField', 'CustomField', {}, '');
modJsList.tabCustomField.setRemoteTable(true);
modJsList.tabCustomField.setObjectTypeName('Custom Field');
modJsList.tabCustomField.setDataPipe(new IceDataPipe(modJsList.tabCustomField));
modJsList.tabCustomField.setAccess(data.permissions.CustomField);
modJsList.tabCustomField.setTypes(data.types);
window.modJs = modJsList.tabCustomField;
window.modJsList = modJsList;
}
window.initAdminCustomFields = init;

View File

@@ -0,0 +1,166 @@
/**
* Author: Thilina Hasantha
*/
import ReactCustomFieldAdapter from '../../../api/ReactCustomFieldAdapter';
/**
* AssetTypeAdapter
*/
class CommonCustomFieldAdapter extends ReactCustomFieldAdapter {
getDataMapping() {
return [
'id',
'name',
'type',
'field_type',
'field_label',
'display',
'display_order',
];
}
getHeaders() {
return [
{ sTitle: 'ID', bVisible: false },
{ sTitle: 'Name' },
{ sTitle: 'Object Type' },
{ sTitle: 'Field Type' },
{ sTitle: 'Field Label' },
{ sTitle: 'Display Status' },
{ sTitle: 'Priority' },
];
}
getTableColumns() {
return [
{
title: 'Name',
dataIndex: 'name',
sorter: true,
},
{
title: 'Object Type',
dataIndex: 'type',
sorter: true,
},
{
title: 'Field Label',
dataIndex: 'field_label',
},
{
title: 'Field Type',
dataIndex: 'field_type',
},
{
title: 'Display Status',
dataIndex: 'display',
sorter: true,
},
{
title: 'Priority',
dataIndex: 'display_order',
sorter: true,
},
];
}
setTypes(tables) {
this.types = tables;
}
getFormFields() {
return [
['id', { label: 'ID', type: 'hidden' }],
['field_label', { label: 'Field Label', type: 'text', validation: '' }],
['type',
{
label: 'Object Type',
type: 'select2',
source: this.types,
},
],
['field_type', { label: 'Field Type', type: 'select', source: [['text', 'Text Field'], ['textarea', 'Text Area'], ['select', 'Select'], ['select2', 'Select2'], ['select2multi', 'Multi Select'], ['fileupload', 'File Upload'], ['date', 'Date'], ['datetime', 'Date Time'], ['time', 'Time'], ['signature', 'Signature']] }],
['field_validation', {
label: 'Validation', type: 'select2', validation: 'none', sort: 'none', 'null-label': 'Required', 'allow-null': true, source: [['none', 'None'], ['number', 'Number'], ['numberOrEmpty', 'Number or Empty'], ['float', 'Decimal'], ['email', 'Email'], ['emailOrEmpty', 'Email or Empty']],
}],
['field_options', {
label: 'Field Options',
type: 'datagroup',
form: [
['label', { label: 'Label', type: 'text', validation: '' }],
['value', { label: 'Value', type: 'text', validation: 'none' }],
],
html: '<div id="#_id_#" class="panel panel-default"><div class="panel-body">#_delete_##_edit_#<span style="color:#999;font-size:13px;font-weight:bold">#_label_#</span>:#_value_#</div></div>',
columns: [
{
title: 'Label',
dataIndex: 'label',
key: 'label',
},
{
title: 'Option Value',
dataIndex: 'value',
key: 'value',
},
],
validation: 'none',
}],
['display_order', { label: 'Priority', type: 'text', validation: 'none' }],
];
}
getFilters() {
return [
['type',
{
label: 'Object Type',
type: 'select2',
'allow-null': true,
source: this.types,
},
],
];
}
forceInjectValuesBeforeSave(params) {
const data = ['', {}];
const options = [];
let optionsData;
data[1].label = params.field_label;
data[1].type = params.field_type;
data[1].validation = params.field_validation;
if (['select', 'select2', 'select2multi'].indexOf(params.field_type) >= 0) {
optionsData = (params.field_options === '' || params.field_options === undefined)
? [] : JSON.parse(params.field_options);
for (const index in optionsData) {
options.push([optionsData[index].value, optionsData[index].label]);
}
data[1].source = options;
}
if (params.field_validation == null || params.field_validation === undefined) {
params.field_validation = '';
}
if (this.currentElement == null || this.currentElement.name == null || this.currentElement.name === '') {
params.name = this.getNameFromFieldName(params.field_label);
} else {
params.name = this.currentElement.name;
}
data[0] = params.name;
params.data = JSON.stringify(data);
params.display = 'Form';
params.display_order = parseInt(params.display_order);
if (!Number.isInteger(params.display_order)) {
params.display_order = 1;
}
return params;
}
}
module.exports = { CommonCustomFieldAdapter };

View File

@@ -98,6 +98,7 @@ class DashboardAdapter extends AdapterBase {
}
drawEmployeeDistributionChart() {
const that = this;
document.getElementById('EmployeeDistributionChart').style.display = 'none';
ReactDOM.render(
this.getSpinner(),
@@ -115,7 +116,7 @@ class DashboardAdapter extends AdapterBase {
forceFit: true,
title: {
visible: true,
text: 'Employee Distribution',
text: that.gt('Employee Distribution'),
},
description: {
visible: false,
@@ -125,7 +126,7 @@ class DashboardAdapter extends AdapterBase {
visible: true,
content: {
value: chartData.reduce((acc, item) => acc + item.value, 0),
name: 'Total',
name: that.gt('Total'),
},
},
legend: {
@@ -151,6 +152,7 @@ class DashboardAdapter extends AdapterBase {
}
drawOnlineOfflineEmployeeChart() {
const that = this;
document.getElementById('EmployeeOnlineOfflineChart').style.display = 'none';
ReactDOM.render(
this.getSpinner(),
@@ -168,7 +170,7 @@ class DashboardAdapter extends AdapterBase {
forceFit: true,
title: {
visible: true,
text: 'Employee Check-Ins',
text: that.gt('Employee Check-Ins'),
},
description: {
visible: false,
@@ -178,7 +180,7 @@ class DashboardAdapter extends AdapterBase {
visible: true,
content: {
value: chartData.reduce((acc, item) => acc + item.value, 0),
name: 'Total',
name: that.gt('Total'),
},
},
legend: {
@@ -199,6 +201,7 @@ class DashboardAdapter extends AdapterBase {
}
drawCompanyLeaveEntitlementChart() {
const that = this;
document.getElementById('CompanyLeaveEntitlementChart').style.display = 'none';
ReactDOM.render(
this.getSpinner(),
@@ -216,7 +219,7 @@ class DashboardAdapter extends AdapterBase {
forceFit: true,
title: {
visible: true,
text: 'Company Vacation Usage',
text: that.gt('Company Vacation Usage'),
},
description: {
visible: false,
@@ -226,7 +229,7 @@ class DashboardAdapter extends AdapterBase {
visible: true,
content: {
value: chartData.reduce((acc, item) => acc + item.value, 0),
name: 'Total',
name: that.gt('Total'),
},
},
legend: {

View File

@@ -34,6 +34,8 @@ class DocumentAdapter extends AdapterBase {
['expire_notification_month', { label: 'Notify Expiry Before One Month', type: 'select', source: [['Yes', 'Yes'], ['No', 'No']] }],
['expire_notification_week', { label: 'Notify Expiry Before One Week', type: 'select', source: [['Yes', 'Yes'], ['No', 'No']] }],
['expire_notification_day', { label: 'Notify Expiry Before One Day', type: 'select', source: [['Yes', 'Yes'], ['No', 'No']] }],
['share_with_employee', { label: 'Share with Employee', type: 'select', source: [['Yes', 'Yes'], ['No', 'No']] }],
// [ "sign", {"label":"Require Signature","type":"select","source":[["Yes","Yes"],["No","No"]]}],
// [ "sign", {"label":"Require Signature","type":"select","source":[["Yes","Yes"],["No","No"]]}],
// [ "sign_label", {"label":"Signature Description","type":"textarea","validation":"none"}],
['details', { label: 'Details', type: 'textarea', validation: 'none' }],
@@ -146,6 +148,7 @@ class EmployeeDocumentAdapter extends AdapterBase {
['date_added', { label: 'Date Added', type: 'date', validation: '' }],
['valid_until', { label: 'Valid Until', type: 'date', validation: 'none' }],
['status', { label: 'Status', type: 'select', source: [['Active', 'Active'], ['Inactive', 'Inactive'], ['Draft', 'Draft']] }],
['visible_to', { label: 'Visible To', type: 'select', source: [['Owner', 'Owner'], ['Manager', 'Manager'], ['Admin', 'Admin']] }],
['details', { label: 'Details', type: 'textarea', validation: 'none' }],
['attachment', { label: 'Attachment', type: 'fileupload', validation: '' }],
];
@@ -154,7 +157,7 @@ class EmployeeDocumentAdapter extends AdapterBase {
getFilters() {
return [
['employee', { label: 'Employee', type: 'select2', 'remote-source': ['Employee', 'id', 'first_name+last_name'] }],
['employee', { label: 'Employee', type: 'select2', 'remote-source': ['Employee', 'id', 'first_name+last_name', 'getActiveSubordinateEmployees'] }],
];
}

Some files were not shown because too many files have changed in this diff Show More