Compare commits

...

126 Commits

Author SHA1 Message Date
Roland Gruber 72ef7f1ac5 identification method description 2020-08-17 20:22:07 +02:00
Roland Gruber 4f8b1e81ea identification method description 2020-08-14 16:47:32 +02:00
Roland Gruber c5b6c8132c fixed PHP message 2020-08-13 17:30:35 +02:00
Roland Gruber 6c306bcb9e translation 2020-08-11 19:58:30 +02:00
Roland Gruber 3c7fdca8eb 7.3 2020-08-10 20:04:38 +02:00
Roland Gruber d0b18ad8b5 translation 2020-08-10 19:47:10 +02:00
Roland Gruber 4455a5a15d translation 2020-08-09 20:28:20 +02:00
Roland Gruber 598fa546a9 translation 2020-08-07 22:07:47 +02:00
Roland Gruber df0e02da9f refactoring 2020-07-30 20:41:39 +02:00
Roland Gruber 2ee58dd737 7.3 2020-07-30 20:26:35 +02:00
Roland Gruber 8375f9e0d0 code fixes 2020-07-29 17:02:21 +02:00
Roland Gruber ad23fd0fc2 codeql 2020-07-29 16:47:51 +02:00
Roland Gruber 5972e94fdb codeql 2020-07-29 16:37:15 +02:00
gruberroland 9a9bb3dce7
Create codeql-analysis.yml 2020-07-29 16:18:54 +02:00
Roland Gruber bfa22c6aa3 allow to hide a part of the DN in display 2020-07-26 21:28:28 +02:00
Roland Gruber 09989ee804 fixed rtl display 2020-07-26 21:24:45 +02:00
Roland Gruber 08a65e2608 webcam support 2020-07-26 20:50:04 +02:00
gruberroland 3368b4ab10
Merge pull request #102 from LDAPAccountManager/feature/webcam
Feature/webcam
2020-07-24 21:05:57 +02:00
Roland Gruber b3905d73ca fixed test 2020-07-24 21:00:36 +02:00
Roland Gruber e5344b0568 fixed test 2020-07-24 20:56:20 +02:00
Roland Gruber b00fd8d83e typo 2020-07-24 20:54:19 +02:00
Roland Gruber 018513ac95 webcam support 2020-07-24 20:49:04 +02:00
Roland Gruber 75120fc25d webcam support 2020-07-24 10:06:22 +02:00
Roland Gruber 3ad5dcf65a webcam support 2020-07-22 13:28:17 +02:00
Roland Gruber 6768c7e7ef webcam support 2020-07-22 08:23:49 +02:00
Roland Gruber 1dd0f64f5b fixed removing of memberUid on forced sync 2020-07-20 20:59:24 +02:00
Roland Gruber cb58a27972 fixed removing of memberUid on forced sync 2020-07-20 20:56:31 +02:00
Roland Gruber 9394eeeabd fixed unbound variable 2020-07-19 13:08:33 +02:00
Roland Gruber ffb3ed9357 use additional LDAP filter for search 2020-07-18 20:36:08 +02:00
Roland Gruber 4158ebb91d use additional LDAP filter for search 2020-07-18 20:33:36 +02:00
Roland Gruber 2698995cc2 added job to send users group summary 2020-07-18 20:13:38 +02:00
Roland Gruber 840289e360 fixed test 2020-07-18 13:22:00 +02:00
Roland Gruber 60109eb47c config export/import 2020-07-12 10:13:30 +02:00
Roland Gruber e070accf18 active export/import 2020-07-12 09:48:49 +02:00
Roland Gruber 21e7e4a95d export webauthn data 2020-07-08 20:46:56 +02:00
Roland Gruber 5e770d8920 fixed warning 2020-07-03 20:02:48 +02:00
Roland Gruber e03cd1f57c moved message 2020-06-30 20:51:20 +02:00
Roland Gruber d32d8d3a0d moved error message 2020-06-30 20:24:38 +02:00
Roland Gruber 1523f0ee8f enhanced error messages 2020-06-28 20:52:10 +02:00
Roland Gruber bc277e4c0a enhanced error messages 2020-06-28 20:46:32 +02:00
Roland Gruber f75f813a9a enhanced error messages 2020-06-28 11:16:37 +02:00
Roland Gruber 87986e93cc PHP notice 2020-06-27 20:47:43 +02:00
Roland Gruber 780daded11 show info if login failed and account can be determined as locked 2020-06-21 21:33:43 +02:00
Roland Gruber 38cbfb9dab fixed translation 2020-06-21 21:12:44 +02:00
Roland Gruber 60199a41b5 fixed layout 2020-06-19 12:42:15 +02:00
Roland Gruber aed8ce867c 7.3.PRE1 2020-06-19 09:10:18 +02:00
Roland Gruber 30909b385a show password prompt if user with expired password logs in 2020-06-18 13:08:23 +02:00
gruberroland 55ccddbf1e
Merge pull request #101 from LDAPAccountManager/configImportExport
Config import export
2020-06-17 13:05:01 +02:00
Roland Gruber ee75385e7d fixed tests 2020-06-17 12:57:18 +02:00
gruberroland 9ec8d2ce57
Merge branch 'develop' into configImportExport 2020-06-17 12:27:25 +02:00
Roland Gruber 605713a181 better error messages on login 2020-06-17 11:28:05 +02:00
Roland Gruber fd8e7c1de3 fixed tests 2020-06-16 20:14:33 +02:00
Roland Gruber bdd3dd39b9 sync primary group 2020-06-16 09:55:28 +02:00
Roland Gruber 4d0a6d92e7 round with precision 2 2020-06-15 20:26:33 +02:00
Roland Gruber e58459d351 PHP 7.4 2020-06-15 20:23:15 +02:00
Roland Gruber 316c979ef2 PHP 7.4 2020-06-15 20:20:35 +02:00
Roland Gruber 3bd507a47d make group display configurable 2020-06-15 15:34:44 +02:00
Roland Gruber 01844a0d05 Merge remote-tracking branch 'remotes/origin/develop' into configImportExport
# Conflicts:
#	lam/lib/selfService.inc
2020-06-14 21:36:48 +02:00
Roland Gruber 57dcbd880d PHPDoc 2020-06-14 21:34:19 +02:00
Roland Gruber 12581a5dc0 format usage 2020-06-14 21:22:50 +02:00
Roland Gruber 2fcabf0c07 Merge branch 'develop' of github.com:LDAPAccountManager/lam into develop 2020-06-14 19:58:24 +02:00
Roland Gruber db523af70f use full width for groups 2020-06-14 19:58:02 +02:00
Roland Gruber 69796a98f3 fixed formatting issue 2020-06-12 09:42:54 +02:00
gruberroland 82905cb4e5
Merge pull request #99 from steffen-moser/fixAutoSyncForGroupOfMembers
Adding groupOfMembers to the GON types
2020-06-06 10:41:54 +02:00
Steffen Moser 6f88ec8d9d Adding groupOfMembers to the GON types 2020-06-06 02:51:26 +02:00
Roland Gruber 75fd361116 Merge branch 'develop' of github.com:LDAPAccountManager/lam into develop 2020-06-05 16:01:24 +02:00
Roland Gruber f36446fe43 PHP 7.4 2020-06-05 16:00:59 +02:00
gruberroland 1196c56287
Update FUNDING.yml 2020-06-04 20:43:14 +02:00
gruberroland 48ace3c3f9
Create FUNDING.yml 2020-06-04 20:42:08 +02:00
Roland Gruber 5a09f8159f typos 2020-06-03 17:51:21 +02:00
Roland Gruber 6ade23ce65 7.3 2020-06-02 19:44:34 +02:00
Roland Gruber 03f6fd858b fixed build 2020-06-02 19:40:57 +02:00
Roland Gruber f23d2a4455 set user verification to discouraged 2020-06-01 20:08:58 +02:00
Roland Gruber f091b653b8 store self service profiles as JSON 2020-06-01 10:52:55 +02:00
Roland Gruber aa43b4721b store self service profiles as JSON 2020-06-01 10:34:34 +02:00
Roland Gruber 7bd799bee3 PDF global templates export 2020-05-30 12:35:22 +02:00
gruberroland 3ba7fa18bd
Create SECURITY.md 2020-05-28 20:32:31 +02:00
Roland Gruber 2e61e2dca9 #96 added option to disable certificate check 2020-05-28 20:25:39 +02:00
Roland Gruber 71af42442b phpmailer update 2020-05-28 20:11:36 +02:00
Roland Gruber 3018a887c0 PHP 7.4 2020-05-27 17:33:29 +02:00
Roland Gruber aaddb6eead TCPDF 6.3.5 2020-05-27 17:25:59 +02:00
Roland Gruber 95bdb04949 PHP 7.4 fix 2020-05-27 17:18:35 +02:00
Roland Gruber 93a7ffdd65 remove dev files before building packages 2020-05-24 09:17:14 +02:00
Roland Gruber ed28d3b8e7 fix version 2020-05-24 09:04:41 +02:00
gruberroland 3c305f26fa
Merge pull request #94 from patbaumgartner/develop
Providing better env variables to customize users and groups DN
2020-05-24 08:33:15 +02:00
Patrick Baumgartner ecdd23e843
Renaming env variables and providing better defaults 2020-05-23 14:07:47 +02:00
Patrick Baumgartner 1081b51fe9
Adding LDAP_GROUPS_DN und LDAP_USERS_DN 2020-05-23 14:05:27 +02:00
Patrick Baumgartner b91333ff12
Allowing to have a DN for users and groups
Users and groups are usually not in the same DN/OU, therefore we need the possibility to adjust them as well via environment variables and still keeping backward compatibility.
2020-05-23 14:00:02 +02:00
Roland Gruber 2e5419b3ac Merge branch 'develop' into configImportExport
# Conflicts:
#	lam/VERSION
2020-05-22 21:12:24 +02:00
Roland Gruber 9198187a26 typos 2020-05-22 21:10:48 +02:00
Roland Gruber 32b5a14226 import and export account profile templates 2020-05-22 21:02:13 +02:00
Roland Gruber 5151d96592 refactoring 2020-05-16 11:10:37 +02:00
Roland Gruber 0a72bc9635 config import and export 2020-05-13 20:47:12 +02:00
Roland Gruber 6fc259d718 config import and export 2020-05-12 20:58:56 +02:00
Roland Gruber 40fd19e3bf refactoring 2020-05-07 21:31:21 +02:00
Roland Gruber 9b4261ca36 refactoring 2020-05-07 21:18:19 +02:00
Roland Gruber 2db6bf23eb refactoring 2020-05-07 21:15:26 +02:00
Roland Gruber ffd74d88e4 config export 2020-05-07 21:10:47 +02:00
Roland Gruber a246fde0e2 refactoring 2020-05-07 20:27:50 +02:00
Roland Gruber 665ca9daad refactoring 2020-05-07 20:22:41 +02:00
Roland Gruber 81587a9b00 refactoring 2020-05-03 10:59:35 +02:00
Roland Gruber 9c6e30a03b account profile export 2020-05-03 10:32:35 +02:00
Roland Gruber c9d32bf2de refactoring 2020-05-02 18:51:33 +02:00
Roland Gruber a6b39d522f 7.2 2020-05-01 08:54:14 +02:00
Roland Gruber 8e9700d230 7.2 2020-05-01 08:41:53 +02:00
Roland Gruber e44f3d3243 refactoring 2020-04-29 20:33:43 +02:00
Roland Gruber 8743285719 #93 added missing locales 2020-04-28 21:06:15 +02:00
Roland Gruber 7fcc2cf38d error handling 2020-04-27 21:53:50 +02:00
Roland Gruber 1d107c4a24 i18n 2020-04-27 20:25:35 +02:00
Roland Gruber ec2a017fc7 i18n 2020-04-26 19:43:36 +02:00
Roland Gruber a724638886 i18n 2020-04-26 13:46:16 +02:00
Roland Gruber acbdec11dd i18n 2020-04-26 08:57:49 +02:00
Roland Gruber 8d50dd59b0 server profiles import 2020-04-26 08:55:09 +02:00
Roland Gruber ea72ab63a9 Merge remote-tracking branch 'remotes/origin/develop' into configImportExport 2020-04-25 20:50:33 +02:00
Roland Gruber d0e88cf80b i18n 2020-04-25 20:34:47 +02:00
Roland Gruber 2331e42e34 fixed text alignment 2020-04-23 21:18:27 +02:00
Roland Gruber 24a6e14251 export server profiles 2020-04-19 20:40:40 +02:00
Roland Gruber 9936c834db onclick for span 2020-04-19 20:39:57 +02:00
Roland Gruber e8d421ae04 added certificates to export 2020-04-13 15:40:33 +02:00
Roland Gruber 00c5a014b4 import main config 2020-04-12 21:51:19 +02:00
Roland Gruber 38293656b6 check which settings are imported 2020-04-12 14:00:23 +02:00
Roland Gruber 9ed53f51de added export 2020-04-12 13:52:11 +02:00
Roland Gruber f0f81c085b added export 2020-04-12 13:51:36 +02:00
Roland Gruber 8af2132926 import/export config 2020-04-12 12:39:52 +02:00
Roland Gruber 541684d49f responsive 2020-04-11 15:41:26 +02:00
Roland Gruber b65125beaf added dev mode 2020-04-10 20:34:36 +02:00
135 changed files with 51201 additions and 38414 deletions

3
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,3 @@
github: [LDAPAccountManager]

11
.github/codeql/codeql-config.yml vendored Normal file
View File

@ -0,0 +1,11 @@
name: "LAM CodeQL config"
queries:
- uses: security-and-quality
paths-ignore:
- '**/3rdParty/**/*.*'
- '**/lib/extra/**/*.*'
- '**/lib/*jquery*.js'
paths:
- lam

56
.github/workflows/codeql-analysis.yml vendored Normal file
View File

@ -0,0 +1,56 @@
name: "CodeQL"
on:
push:
branches: [develop]
pull_request:
# The branches below must be a subset of the branches above
branches: [develop]
schedule:
- cron: '0 10 * * 0'
jobs:
analyse:
name: Analyse
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
fetch-depth: 2
# If this run was triggered by a pull request event, then checkout
# the head of the pull request instead of the merge commit.
- run: git checkout HEAD^2
if: ${{ github.event_name == 'pull_request' }}
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
# Override language selection by uncommenting this and choosing your languages
# with:
# languages: go, javascript, csharp, python, cpp, java
with:
config-file: ./.github/codeql/codeql-config.yml
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

9
SECURITY.md Normal file
View File

@ -0,0 +1,9 @@
# Security Policy
## Supported Versions
Security updates are always created based on the latest release.
## Reporting a Vulnerability
Please report all security issues to post@rolandgruber.de. Reports will be answered within 48h.

View File

@ -4,6 +4,7 @@
"squizlabs/php_codesniffer" : "3.4.0"
},
"require": {
"ext-ldap": "*"
"ext-ldap": "*",
"ext-json": "*"
}
}

View File

@ -72,7 +72,7 @@ install-lam:
LIST4="`(cd $(srcdir)/$(LIST3) ; ls -d *)`" ; \
(cd $(srcdir)/$(LIST3) ; $(TAR) cf - .) | \
(cd $(DESTDIR)$(sysconfdir) ; $(TAR) xf -) ; \
$(LN_S) $(sysconfdir) ${LIST3} ; \
[ -e ${LIST3} ] || $(LN_S) $(sysconfdir) ${LIST3} ; \
(cd $(srcdir) ; $(TAR) cf - $(LIST1)) | $(TAR) xf - ; \
[ -d $(DESTDIR)$(prefix)/docs ] || \
$(MKDIR) -p $(DESTDIR)$(prefix)/docs || exit 1 ; \

View File

@ -70,7 +70,8 @@ export VERSION=`./getVersion`
# remove files which are not in the final release
rm -r lam/po
rm -r lam/tests
rm lam/lib/3rdParty/tcpdf/fonts/*.ttf
rm -f lam/lib/3rdParty/tcpdf/fonts/*.ttf
rm -r lam/templates/lib/extra/ckeditor/plugins/*/dev
find . -name .svnignore -exec rm {} \;
find . -name .gitignore -exec rm {} \;
mv lam ldap-account-manager-$VERSION

View File

@ -1,8 +1,14 @@
ldap-account-manager (7.2.RC1-1) unstable; urgency=medium
ldap-account-manager (7.3.RC1-1) unstable; urgency=medium
* new upstream release
-- Roland Gruber <post@rolandgruber.de> Wed, 22 Apr 2020 20:12:31 +0200
-- Roland Gruber <post@rolandgruber.de> Mon, 10 Aug 2020 19:25:33 +0200
ldap-account-manager (7.2-1) unstable; urgency=medium
* new upstream release
-- Roland Gruber <post@rolandgruber.de> Fri, 01 May 2020 08:04:56 +0200
ldap-account-manager (7.1-1) unstable; urgency=medium

View File

@ -17,6 +17,7 @@ Depends: php (>= 7), php-ldap,
apache2 (>= 2.4.0) | httpd, fonts-dejavu, debconf (>= 0.2.26) | debconf-2.0, ${misc:Depends}
Recommends: php-opcache
Suggests: ldap-server, php-mcrypt, ldap-account-manager-lamdaemon, perl
Conflicts: libapache2-mod-php5, php5, php5-fpm
Description: webfrontend for managing accounts in an LDAP directory
LDAP Account Manager (LAM) runs on an existing webserver.
It manages user, group and host accounts. Currently LAM

View File

@ -966,9 +966,8 @@ lib/3rdParty/composer/ramsey B 2018 Be
lib/3rdParty/composer/spomky-labs B 2018 Spomky-Labs
lib/3rdParty/composer/symfony B 2019 Fabien Potencier
lib/3rdParty/composer/web-auth B 2018 Spomky-Labs
lib/3rdParty/tcpdf D 2018 Nicola Asuni - Tecnick.com LTD
lib/3rdParty/tcpdf/fonts/DejaVu*.ttf A Public Domain, Bitstream, Inc., Tavmjong Bah
lib/3rdParty/tcpdf/fonts/DejaVu*.z A Public Domain, Bitstream, Inc., Tavmjong Bah
lib/3rdParty/tcpdf D 2020 Nicola Asuni - Tecnick.com LTD
lib/3rdParty/tcpdf/fonts/dejavu*.z A Public Domain, Bitstream, Inc., Tavmjong Bah
lib/3rdParty/phpseclib B 2019 TerraFrost and other contributors
lib/3rdParty/Monolog B 2011 Jordi Boggiano
lib/3rdParty/Psr B 2012 PHP Framework Interoperability Group

View File

@ -8,6 +8,11 @@ LAM_SKIP_PRECONFIGURE=false
LDAP_DOMAIN=my-domain.com
# LDAP base DN to overwrite value generated by LDAP_DOMAIN
LDAP_BASE_DN=dc=my-domain,dc=com
# LDAP users DN to overwrite value provided by LDAP_BASE_DN
LDAP_USERS_DN=ou=people,dc=my-domain,dc=com
# LDAP groups DN to overwrite value provided by LDAP_BASE_DN
LDAP_GROUPS_DN=ou=groups,dc=my-domain,dc=com
# LDAP server URL
LDAP_SERVER=ldap://ldap:389
# LDAP admin user (set as login user for LAM)
@ -17,6 +22,9 @@ LAM_LANG=en_US
# LAM configuration master password and password for server profile "lam"
LAM_PASSWORD=lam
# deactivate TLS certificate checks, activate for development only
LAM_DISABLE_TLS_CHECK=false
#
# docker-compose only, LDAP server setup
#

View File

@ -29,7 +29,7 @@
FROM debian:buster-slim
LABEL maintainer="Roland Gruber <post@rolandgruber.de>"
ARG LAM_RELEASE=7.2.RC1
ARG LAM_RELEASE=7.3.RC1
EXPOSE 80
ENV \
@ -37,7 +37,32 @@ ENV \
DEBUG=''
RUN apt-get update && \
apt-get install --no-install-recommends -y \
apt-get upgrade -y
# install locales
RUN apt-get install -y locales
RUN sed -i 's/^# *\(ca_ES.UTF-8\)/\1/' /etc/locale.gen && \
sed -i 's/^# *\(cz_CZ.UTF-8\)/\1/' /etc/locale.gen && \
sed -i 's/^# *\(de_DE.UTF-8\)/\1/' /etc/locale.gen && \
sed -i 's/^# *\(en_GB.UTF-8\)/\1/' /etc/locale.gen && \
sed -i 's/^# *\(en_US.UTF-8\)/\1/' /etc/locale.gen && \
sed -i 's/^# *\(es_ES.UTF-8\)/\1/' /etc/locale.gen && \
sed -i 's/^# *\(fr_FR.UTF-8\)/\1/' /etc/locale.gen && \
sed -i 's/^# *\(it_IT.UTF-8\)/\1/' /etc/locale.gen && \
sed -i 's/^# *\(hu_HU.UTF-8\)/\1/' /etc/locale.gen && \
sed -i 's/^# *\(nl_NL.UTF-8\)/\1/' /etc/locale.gen && \
sed -i 's/^# *\(pl_PL.UTF-8\)/\1/' /etc/locale.gen && \
sed -i 's/^# *\(pt_BR.UTF-8\)/\1/' /etc/locale.gen && \
sed -i 's/^# *\(ru_RU.UTF-8\)/\1/' /etc/locale.gen && \
sed -i 's/^# *\(sk_SK.UTF-8\)/\1/' /etc/locale.gen && \
sed -i 's/^# *\(tr_TR.UTF-8\)/\1/' /etc/locale.gen && \
sed -i 's/^# *\(uk_UA.UTF-8\)/\1/' /etc/locale.gen && \
sed -i 's/^# *\(ja_JP.UTF-8\)/\1/' /etc/locale.gen && \
sed -i 's/^# *\(zh_TW.UTF-8\)/\1/' /etc/locale.gen && \
sed -i 's/^# *\(zh_CN.UTF-8\)/\1/' /etc/locale.gen && \
locale-gen
RUN apt-get install --no-install-recommends -y \
apache2 \
ca-certificates \
dumb-init \

View File

@ -3,9 +3,7 @@ services:
ldap-account-manager:
build:
context: .
args:
- LAM_RELEASE=7.2.RC1
image: ldapaccountmanager/lam:latest
image: ldapaccountmanager/lam:7.3.RC1
restart: unless-stopped
ports:
- "8080:80"

View File

@ -23,6 +23,11 @@
set -eu # unset variables are errors & non-zero return values exit the whole script
[ "$DEBUG" ] && set -x
if [ "${LAM_DISABLE_TLS_CHECK:-}" == "true" ]; then
ln -s /etc/ldap/ldap.conf /etc/ldap.conf
echo "TLS_REQCERT never" >> /etc/ldap/ldap.conf
fi
LAM_SKIP_PRECONFIGURE="${LAM_SKIP_PRECONFIGURE:-false}"
if [ "$LAM_SKIP_PRECONFIGURE" != "true" ]; then
@ -32,8 +37,10 @@ if [ "$LAM_SKIP_PRECONFIGURE" != "true" ]; then
LDAP_SERVER="${LDAP_SERVER:-ldap://ldap:389}"
LDAP_DOMAIN="${LDAP_DOMAIN:-my-domain.com}"
LDAP_BASE_DN="${LDAP_BASE_DN:-dc=${LDAP_DOMAIN//\./,dc=}}"
LDAP_USERS_DN="${LDAP_USERS_DN:-${LDAP_BASE_DN}}"
LDAP_GROUPS_DN="${LDAP_GROUPS_DN:-${LDAP_BASE_DN}}"
LDAP_ADMIN_USER="${LDAP_USER:-cn=admin,${LDAP_BASE_DN}}"
sed -i -f- /etc/ldap-account-manager/config.cfg <<- EOF
s|^password:.*|password: ${LAM_PASSWORD_SSHA}|;
EOF
@ -45,8 +52,8 @@ EOF
s|^Passwd:.*|Passwd: ${LAM_PASSWORD_SSHA}|;
s|^treesuffix:.*|treesuffix: ${LDAP_BASE_DN}|;
s|^defaultLanguage:.*|defaultLanguage: ${LAM_LANG}.utf8|;
s|^.*suffix_user:.*|types: suffix_user: ${LDAP_BASE_DN}|;
s|^.*suffix_group:.*|types: suffix_group: ${LDAP_BASE_DN}|;
s|^.*suffix_user:.*|types: suffix_user: ${LDAP_USERS_DN}|;
s|^.*suffix_group:.*|types: suffix_group: ${LDAP_GROUPS_DN}|;
EOF
fi

View File

@ -1,4 +1,17 @@
June 2020 7.2
September 2020
- PHP 7.4 compatibility
- Configuration export and import
- Server profiles support to specify a part of the DN to hide
- Show password prompt when a user with expired password logs into LAM admin interface (requires PHP 7.2)
- Better error messages on login when account is expired/deactivated/...
- Personal/Windows: photo can be uploaded via webcam
- Windows users: group display format can be configured (cn/dn)
- LAM Pro:
-> Windows: new cron job to send users a summary of their managed groups
- Fixed bugs:
-> Unix groups: memberUid was not deleted correctly when forced sync with group of names is active
01.05.2020 7.2
- Unix: allow to create group with same name during user creation
- LAM Pro:
-> EMail sending can be done via SMTP without local mail server
@ -6,11 +19,12 @@ June 2020 7.2
- Fixed bugs:
-> Captcha don't show anymore in Self Service login page (213)
-> Unix memberships cannot be changed. This issue can also affect other membership relations.
-> Missing locales on Docker image
17.03.2020 7.1
- PHP 7 required
- Webauthn/FIDO2 support for 2-factor-authentication (requires PHP 7.2)
- WebAuthn/FIDO2 support for 2-factor-authentication (requires PHP 7.2)
- IMAP: changed library to support latest TLS versions
- Personal: support display name (hidden by default in server profile)
- Windows users: support allowed workstations, more profile options

View File

@ -1 +1 @@
7.2.RC1
7.3.RC1

10
lam/composer.lock generated
View File

@ -773,16 +773,16 @@
},
{
"name": "phpmailer/phpmailer",
"version": "v6.1.5",
"version": "v6.1.6",
"source": {
"type": "git",
"url": "https://github.com/PHPMailer/PHPMailer.git",
"reference": "a8bf068f64a580302026e484ee29511f661b2ad3"
"reference": "c2796cb1cb99d7717290b48c4e6f32cb6c60b7b3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/a8bf068f64a580302026e484ee29511f661b2ad3",
"reference": "a8bf068f64a580302026e484ee29511f661b2ad3",
"url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/c2796cb1cb99d7717290b48c4e6f32cb6c60b7b3",
"reference": "c2796cb1cb99d7717290b48c4e6f32cb6c60b7b3",
"shasum": ""
},
"require": {
@ -831,7 +831,7 @@
}
],
"description": "PHPMailer is a full-featured email creation and transfer class for PHP",
"time": "2020-03-14T14:23:48+00:00"
"time": "2020-05-27T12:24:03+00:00"
},
{
"name": "psr/http-client",

View File

@ -965,9 +965,8 @@ lib/3rdParty/composer/ramsey B 2018 Be
lib/3rdParty/composer/spomky-labs B 2018 Spomky-Labs
lib/3rdParty/composer/symfony B 2019 Fabien Potencier
lib/3rdParty/composer/web-auth B 2018 Spomky-Labs
lib/3rdParty/tcpdf D 2018 Nicola Asuni - Tecnick.com LTD
lib/3rdParty/tcpdf/fonts/DejaVu*.ttf A Public Domain, Bitstream, Inc., Tavmjong Bah
lib/3rdParty/tcpdf/fonts/DejaVu*.z A Public Domain, Bitstream, Inc., Tavmjong Bah
lib/3rdParty/tcpdf D 2020 Nicola Asuni - Tecnick.com LTD
lib/3rdParty/tcpdf/fonts/dejavu*.z A Public Domain, Bitstream, Inc., Tavmjong Bah
lib/3rdParty/phpseclib B 2019 TerraFrost and other contributors
lib/3rdParty/Monolog B 2011 Jordi Boggiano
lib/3rdParty/Psr B 2012 PHP Framework Interoperability Group

View File

@ -257,7 +257,7 @@ semodule -i httpdlocal.pp</programlisting>
<listitem>
<para>directory contents must be accessible by browser but directory
itself needs not to be browseable</para>
itself needs not to be browsable</para>
</listitem>
</itemizedlist>
</section>

View File

@ -503,6 +503,9 @@
</listitem>
</itemizedlist>
<para>Hide password prompt for expired password: Hides the password
prompt when a user with expired password logs into LAM.</para>
<literallayout>
</literallayout>
@ -1138,6 +1141,11 @@ mysql&gt; GRANT ALL PRIVILEGES ON lam_cron.* TO 'lam_cron'@'localhost';
move expired accounts</link></para>
</listitem>
<listitem>
<para><link linkend="job_windows_notify_groups">Windows: Notify
users about their managed groups</link></para>
</listitem>
<listitem>
<para><link linkend="job_freeradius_move_expired">FreeRadius:
Delete or move expired accounts</link></para>
@ -1826,6 +1834,95 @@ mysql&gt; GRANT ALL PRIVILEGES ON lam_cron.* TO 'lam_cron'@'localhost';
</table>
</section>
<section id="job_windows_notify_groups">
<title>Windows: Notify users about their managed groups</title>
<para>This will send your users an email with the groups they
manage. This also includes a list of users in these groups. The
users and groups are searched using the user+group account types
that are specified in server profile.</para>
<para>You need to activate the Windows module for users to be able
to add this job. The job can be added multiple times.</para>
<screenshot>
<graphic fileref="images/jobs_windowsNotifyGroups.png"/>
</screenshot>
<para><table>
<title>Options</title>
<tgroup cols="2">
<tbody>
<row>
<entry><emphasis role="bold">Option</emphasis></entry>
<entry><emphasis
role="bold">Description</emphasis></entry>
</row>
<row>
<entry>From address</entry>
<entry>The email address to set as FROM.</entry>
</row>
<row>
<entry>Reply-to address</entry>
<entry>Optional Reply-to address for email.</entry>
</row>
<row>
<entry>CC address</entry>
<entry>Optional CC mail address.</entry>
</row>
<row>
<entry>BCC address</entry>
<entry>Optional BCC mail address.</entry>
</row>
<row>
<entry>Subject</entry>
<entry>The email subject line. Supports wildcards, see
below.</entry>
</row>
<row>
<entry>HTML format</entry>
<entry>Send email as HTML instead of plain text.</entry>
</row>
<row>
<entry>Text</entry>
<entry>The email body text. Supports wildcards, see
below.</entry>
</row>
<row>
<entry>Period</entry>
<entry>Defines how often the mail is sent (e.g.
quarterly).</entry>
</row>
</tbody>
</tgroup>
</table>Wildcards:</para>
<para>You can enter LDAP attributes as wildcards in the form
@@ATTRIBUTE_NAME@@. E.g. to add the user's common name use "@@cn@@".
For the common name it would be "@@cn@@".</para>
<para>Use the wildcard "@@LAM_MANAGED_GROUPS@@" to insert the group
listing. This wildcard is mandatory.</para>
</section>
<section id="job_freeradius_move_expired">
<title>FreeRadius: Delete or move expired accounts</title>
@ -2208,4 +2305,50 @@ mysql&gt; GRANT ALL PRIVILEGES ON lam_cron.* TO 'lam_cron'@'localhost';
</section>
</section>
</section>
<section>
<title>Self Service (LAM Pro)</title>
<para>See <link linkend="a_selfService">Self Service
chapter</link>.</para>
</section>
<section>
<title>Import and export configuration</title>
<para>Here you can export and import LAM's whole configuration. You can
use this to backup the configuration or migrate from one server to
another.</para>
<para>You will need to login with the configuration master password to use
this feature.</para>
<screenshot>
<graphic fileref="images/confImportExport1.png"/>
</screenshot>
<para><emphasis role="bold">Export</emphasis></para>
<para>This will dump the whole configuration to one big single file. It is
not possible to dump only parts of the configuration. During import you
can select what exactly to import.</para>
<para><emphasis role="bold">Import</emphasis></para>
<para>Please select the import file first and submit. LAM will then
present you possible import data. You can select what to import using the
checkboxes.</para>
<para>Please note that LAM will not delete e.g. server profiles that are
not in the import file.</para>
<para>Example: You have profile1+profile2 in your LAM installation and
profile2+profile3 in your import file. When you select to import all
server profiles then profile1 stays untouched, profile2 will be
overwritten and profile3 will be added.</para>
<screenshot>
<graphic fileref="images/confImportExport2.png"/>
</screenshot>
</section>
</chapter>

View File

@ -613,6 +613,12 @@
version. Unless explicitly noticed there is no need to install an
intermediate release.</para>
<section>
<title>7.2 -&gt; 7.3</title>
<para>No actions required.</para>
</section>
<section>
<title>7.1 -&gt; 7.2</title>

View File

@ -2283,7 +2283,7 @@ AuthorizedKeysCommandUser root</literallayout>
security reasons.</para>
<para>The user name can either be a fixed name (e.g. "admin") or it can
be generated with LDAP attributes of the LAM admn user. E.g. $uid$ will
be generated with LDAP attributes of the LAM admin user. E.g. $uid$ will
be transformed to "myUser" if you login with
"uid=myUser,ou=people,dc=example,dc=com".</para>
@ -5840,7 +5840,7 @@ OK (10 msec)</programlisting>
<para>LAM Pro allows you to execute scripts whenever an account is
created, modified or deleted. This can be useful to automate processes
which needed manual work afterwards (e.g. sending your user a welcome mail
or register a mailbox). Additionally, you can specify manual scipts that
or register a mailbox). Additionally, you can specify manual scripts that
can be executed from within LAM Pro.</para>
<para>To activate this feature please add the "Custom scripts" module to

View File

@ -1153,7 +1153,7 @@
<para>To enable this feature please activate the checkbox "Enable
password self reset link".</para>
<para><emphasis role="bold">Hint:</emphasis> Plese note that LAM Pro
<para><emphasis role="bold">Hint:</emphasis> Please note that LAM Pro
uses security questions by default. Activate confirmation mails and then
deactivate security questions if you want to use only email
validation.</para>
@ -1166,6 +1166,35 @@
</mediaobject>
</screenshot>
<para>Identification method, used LDAP attributes:</para>
<itemizedlist>
<listitem>
<para>Email: mail</para>
</listitem>
<listitem>
<para>Employee number: employeeNumber</para>
</listitem>
<listitem>
<para>Self service login attribute: same as configured on first tab
of self service profile</para>
</listitem>
<listitem>
<para>User name: uid</para>
</listitem>
<listitem>
<para>User name and email address: uid and mail</para>
</listitem>
<listitem>
<para>User name or email address: uid and mail</para>
</listitem>
</itemizedlist>
<para>You can now configure the minimum answer length for password reset
answers. This is checked when you allow you users to specify their
answers via the self service. Additionally, you can specify the text of
@ -1195,9 +1224,8 @@
The mail can include the new password by using the special wildcard
"@@newPassword@@". Additionally, you may want to insert other wildcards
that are replaced by the corresponding LDAP attributes. E.g. "@@uid@@"
will be replaced by the user name.
See <link linkend="mailSetup">here</link> for setting up your SMTP
server.</para>
will be replaced by the user name. See <link
linkend="mailSetup">here</link> for setting up your SMTP server.</para>
<literallayout> </literallayout>

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

BIN
lam/graphics/webcam.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@ -243,6 +243,10 @@ $helpArray = array (
"Text" => _('This email address will be set as sender address of the mails.')),
'290' => array ("Headline" => _('TO address'),
"Text" => _('This email address will be set as TO address for the mails.')),
"291" => array ("Headline" => _('Hide password prompt for expired password'),
"Text" => _('Hides the password prompt when a user with expired password logs into LAM.')),
"292" => array ("Headline" => _('DN part to hide'),
"Text" => _('Hides the given part of the DN when displaying a DN. E.g. if you set this to "dc=example,dc=com" then "ou=department,dc=example,dc=com" will be displayed as "ou=department". Use this if you have very long DNs.')),
// 300 - 399
// profile editor, file upload
"301" => array ("Headline" => _("RDN identifier"),
@ -428,6 +432,16 @@ $helpArray = array (
"Headline" => _('Target DN'),
"Text" => _('The expired accounts will be moved to this DN.')
),
'810' => array(
"Headline" => _('Text'),
"Text" => _('The mail text of all mails.') .
_('You can use wildcards for LDAP attributes in the form @@attribute@@ (e.g. @@uid@@ for the user name).')
. ' ' . _('The managed groups need to be added with @@LAM_MANAGED_GROUPS@@.')
),
'811' => array(
"Headline" => _('Period'),
"Text" => _('This defines how often the email is sent (e.g. each month).')
),
);
/* This is a sample help entry. Just copy this line an modify the values between the [] brackets.

View File

@ -526,16 +526,16 @@ class WebauthnProvider extends BaseProvider {
*/
public function addCustomInput(&$row, $userDn) {
if (version_compare(phpversion(), '7.2.0') < 0) {
$row->add(new htmlStatusMessage('ERROR', 'Webauthn requires PHP 7.2.'), 12);
$row->add(new htmlStatusMessage('ERROR', 'WebAuthn requires PHP 7.2.'), 12);
return;
}
if (!extension_loaded('PDO')) {
$row->add(new htmlStatusMessage('ERROR', 'Webauthn requires the PDO extension for PHP.'), 12);
$row->add(new htmlStatusMessage('ERROR', 'WebAuthn requires the PDO extension for PHP.'), 12);
return;
}
$pdoDrivers = \PDO::getAvailableDrivers();
if (!in_array('sqlite', $pdoDrivers)) {
$row->add(new htmlStatusMessage('ERROR', 'Webauthn requires the sqlite PDO driver for PHP.'), 12);
$row->add(new htmlStatusMessage('ERROR', 'WebAuthn requires the sqlite PDO driver for PHP.'), 12);
return;
}
include_once __DIR__ . '/webauthn.inc';
@ -567,7 +567,7 @@ class WebauthnProvider extends BaseProvider {
}
$errorMessageDiv = new htmlDiv('generic-webauthn-error', new htmlOutputText(''));
$errorMessageDiv->addDataAttribute('button', _('Ok'));
$errorMessageDiv->addDataAttribute('title', _('Webauthn failed'));
$errorMessageDiv->addDataAttribute('title', _('WebAuthn failed'));
$row->add($errorMessageDiv, 12);
$row->add(new htmlJavaScript('window.lam.webauthn.start(\'' . $pathPrefix . '\', ' . $selfServiceParam . ');'), 0);
}
@ -606,7 +606,7 @@ class WebauthnProvider extends BaseProvider {
return $webauthnManager->storeNewRegistration($registrationObject, $response);
}
else {
logNewMessage(LOG_DEBUG, 'Checking webauthn response of ' . $userDn);
logNewMessage(LOG_DEBUG, 'Checking WebAuthn response of ' . $userDn);
$response = base64_decode($_POST['sig_response']);
return $webauthnManager->isValidAuthentication($response, $userDn);
}

View File

@ -808,17 +808,17 @@
},
{
"name": "phpmailer/phpmailer",
"version": "v6.1.5",
"version_normalized": "6.1.5.0",
"version": "v6.1.6",
"version_normalized": "6.1.6.0",
"source": {
"type": "git",
"url": "https://github.com/PHPMailer/PHPMailer.git",
"reference": "a8bf068f64a580302026e484ee29511f661b2ad3"
"reference": "c2796cb1cb99d7717290b48c4e6f32cb6c60b7b3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/a8bf068f64a580302026e484ee29511f661b2ad3",
"reference": "a8bf068f64a580302026e484ee29511f661b2ad3",
"url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/c2796cb1cb99d7717290b48c4e6f32cb6c60b7b3",
"reference": "c2796cb1cb99d7717290b48c4e6f32cb6c60b7b3",
"shasum": ""
},
"require": {
@ -839,7 +839,7 @@
"stevenmaguire/oauth2-microsoft": "Needed for Microsoft XOAUTH2 authentication",
"symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)"
},
"time": "2020-03-14T14:23:48+00:00",
"time": "2020-05-27T12:24:03+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {

View File

@ -2,6 +2,8 @@
Please disclose any vulnerabilities found responsibly - report any security problems found to the maintainers privately.
PHPMailer versions 6.1.5 and earlier contain an output escaping bug that occurs in `Content-Type` and `Content-Disposition` when filenames passed into `addAttachment` and other methods that accept attachment names contain double quote characters, in contravention of RFC822 3.4.1. No specific vulnerability has been found relating to this, but it could allow file attachments to bypass attachment filters that are based on matching filename extensions. Recorded as [CVE-2020-13625](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2020-13625). Reported by Elar Lang of Clarified Security.
PHPMailer versions prior to 6.0.6 and 5.2.27 are vulnerable to an object injection attack by passing `phar://` paths into `addAttachment()` and other functions that may receive unfiltered local paths, possibly leading to RCE. Recorded as [CVE-2018-19296](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2018-19296). See [this article](https://knasmueller.net/5-answers-about-php-phar-exploitation) for more info on this type of vulnerability. Mitigated by blocking the use of paths containing URL-protocol style prefixes such as `phar://`. Reported by Sehun Oh of cyberone.kr.
PHPMailer versions prior to 5.2.24 (released July 26th 2017) have an XSS vulnerability in one of the code examples, [CVE-2017-11503](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2017-11503). The `code_generator.phps` example did not filter user input prior to output. This file is distributed with a `.phps` extension, so it it not normally executable unless it is explicitly renamed, and the file is not included when PHPMailer is loaded through composer, so it is safe by default. There was also an undisclosed potential XSS vulnerability in the default exception handler (unused by default). Patches for both issues kindly provided by Patrick Monnerat of the Fedora Project.

View File

@ -1 +1 @@
6.1.5
6.1.6

View File

@ -19,6 +19,12 @@
"name": "Brent R. Matzelle"
}
],
"funding": [
{
"url": "https://github.com/synchro",
"type": "github"
}
],
"require": {
"php": ">=5.5.0",
"ext-ctype": "*",

View File

@ -745,7 +745,7 @@ class PHPMailer
*
* @var string
*/
const VERSION = '6.1.5';
const VERSION = '6.1.6';
/**
* Error severity: message only, continue processing.
@ -2086,6 +2086,7 @@ class PHPMailer
'se' => 'sv',
'rs' => 'sr',
'tg' => 'tl',
'am' => 'hy',
];
if (isset($renamed_langcodes[$langcode])) {
@ -2606,7 +2607,7 @@ class PHPMailer
$altBodyEncoding = static::ENCODING_QUOTED_PRINTABLE;
}
//Use this as a preamble in all multipart message types
$mimepre = 'This is a multi-part message in MIME format.' . static::$LE . static::$LE;
$mimepre = 'This is a multi-part message in MIME format.' . static::$LE . static::$LE;
switch ($this->message_type) {
case 'inline':
$body .= $mimepre;
@ -3063,9 +3064,9 @@ class PHPMailer
//Only include a filename property if we have one
if (!empty($name)) {
$mime[] = sprintf(
'Content-Type: %s; name="%s"%s',
'Content-Type: %s; name=%s%s',
$type,
$this->encodeHeader($this->secureHeader($name)),
static::quotedString($this->encodeHeader($this->secureHeader($name))),
static::$LE
);
} else {
@ -3085,24 +3086,14 @@ class PHPMailer
$mime[] = 'Content-ID: <' . $this->encodeHeader($this->secureHeader($cid)) . '>' . static::$LE;
}
// If a filename contains any of these chars, it should be quoted,
// but not otherwise: RFC2183 & RFC2045 5.1
// Fixes a warning in IETF's msglint MIME checker
// Allow for bypassing the Content-Disposition header totally
// Allow for bypassing the Content-Disposition header
if (!empty($disposition)) {
$encoded_name = $this->encodeHeader($this->secureHeader($name));
if (preg_match('/[ ()<>@,;:"\/\[\]?=]/', $encoded_name)) {
$mime[] = sprintf(
'Content-Disposition: %s; filename="%s"%s',
$disposition,
$encoded_name,
static::$LE . static::$LE
);
} elseif (!empty($encoded_name)) {
if (!empty($encoded_name)) {
$mime[] = sprintf(
'Content-Disposition: %s; filename=%s%s',
$disposition,
$encoded_name,
static::quotedString($encoded_name),
static::$LE . static::$LE
);
} else {
@ -3162,6 +3153,7 @@ class PHPMailer
if ($this->exceptions) {
throw $exc;
}
return '';
}
}
@ -4726,6 +4718,28 @@ class PHPMailer
return (bool) preg_match('/^(.{' . (self::MAX_LINE_LENGTH + strlen(static::$LE)) . ',})/m', $str);
}
/**
* If a string contains any "special" characters, double-quote the name,
* and escape any double quotes with a backslash.
*
* @param string $str
*
* @return string
*
* @see RFC822 3.4.1
*/
public static function quotedString($str)
{
if (preg_match('/[ ()<>@,;:"\/\[\]?=]/', $str)) {
//If the string contains any of these chars, it must be double-quoted
//and any double quotes must be escaped with a backslash
return '"' . str_replace('"', '\\"', $str) . '"';
}
//Return the string untouched, it doesn't need quoting
return $str;
}
/**
* Allows for public read access to 'to' property.
* Before the send() call, queued addresses (i.e. with IDN) are not yet included.

View File

@ -45,7 +45,7 @@ class POP3
*
* @var string
*/
const VERSION = '6.1.5';
const VERSION = '6.1.6';
/**
* Default POP3 port number.

View File

@ -34,7 +34,7 @@ class SMTP
*
* @var string
*/
const VERSION = '6.1.5';
const VERSION = '6.1.6';
/**
* SMTP line break constant.
@ -1168,7 +1168,7 @@ class SMTP
//Must pass vars in here as params are by reference
if (!stream_select($selR, $selW, $selW, $this->Timelimit)) {
$this->edebug(
'SMTP -> get_lines(): timed-out (' . $this->Timeout . ' sec)',
'SMTP -> get_lines(): select timed-out in (' . $this->Timelimit . ' sec)',
self::DEBUG_LOWLEVEL
);
break;
@ -1187,7 +1187,7 @@ class SMTP
$info = stream_get_meta_data($this->smtp_conn);
if ($info['timed_out']) {
$this->edebug(
'SMTP -> get_lines(): timed-out (' . $this->Timeout . ' sec)',
'SMTP -> get_lines(): stream timed-out (' . $this->Timeout . ' sec)',
self::DEBUG_LOWLEVEL
);
break;

View File

@ -6,7 +6,7 @@
* **category** Library
* **author** Nicola Asuni <info@tecnick.com>
* **copyright** 2002-2018 Nicola Asuni - Tecnick.com LTD
* **copyright** 2002-2020 Nicola Asuni - Tecnick.com LTD
* **license** http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT)
* **link** http://www.tcpdf.org
* **source** https://github.com/tecnickcom/TCPDF

1
lam/lib/3rdParty/tcpdf/VERSION vendored Normal file
View File

@ -0,0 +1 @@
6.3.5

View File

@ -1,6 +1,6 @@
{
"name": "tecnickcom/tcpdf",
"version": "6.2.26",
"version": "6.3.5",
"homepage": "http://www.tcpdf.org/",
"type": "library",
"description": "TCPDF is a PHP class for generating PDF documents and barcodes.",
@ -13,7 +13,7 @@
"pdf417",
"barcodes"
],
"license": "LGPL-3.0",
"license": "LGPL-3.0-only",
"authors": [
{
"name": "Nicola Asuni",

View File

@ -63,7 +63,7 @@
* Default images directory.
* By default it is automatically set but you can also set it as a fixed string to improve performances.
*/
define ('K_PATH_IMAGES', '');
//define ('K_PATH_IMAGES', '');
/**
* Deafult image logo used be the default Header() method.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@ -629,7 +629,7 @@ class Datamatrix {
if ($numch[ENC_C40] == $numch[ENC_X12]) {
$k = ($pos + $charscount + 1);
while ($k < $data_length) {
$tmpchr = ord($data{$k});
$tmpchr = ord($data[$k]);
if ($this->isCharMode($tmpchr, ENC_X12)) {
return ENC_X12;
} elseif (!($this->isCharMode($tmpchr, ENC_X12) OR $this->isCharMode($tmpchr, ENC_C40))) {

View File

@ -878,7 +878,7 @@ class PDF417 {
$txtarr = array(); // array of characters and sub-mode switching characters
$codelen = strlen($code);
for ($i = 0; $i < $codelen; ++$i) {
$chval = ord($code{$i});
$chval = ord($code[$i]);
if (($k = array_search($chval, $this->textsubmodes[$submode])) !== false) {
// we are on the same sub-mode
$txtarr[] = $k;
@ -888,7 +888,7 @@ class PDF417 {
// search new sub-mode
if (($s != $submode) AND (($k = array_search($chval, $this->textsubmodes[$s])) !== false)) {
// $s is the new submode
if (((($i + 1) == $codelen) OR ((($i + 1) < $codelen) AND (array_search(ord($code{($i + 1)}), $this->textsubmodes[$submode]) !== false))) AND (($s == 3) OR (($s == 0) AND ($submode == 1)))) {
if (((($i + 1) == $codelen) OR ((($i + 1) < $codelen) AND (array_search(ord($code[($i + 1)]), $this->textsubmodes[$submode]) !== false))) AND (($s == 3) OR (($s == 0) AND ($submode == 1)))) {
// shift (temporary change only for this char)
if ($s == 3) {
// shift to puntuaction
@ -952,7 +952,7 @@ class PDF417 {
$cw = array_merge($cw, $cw6);
} else {
for ($i = 0; $i < $sublen; ++$i) {
$cw[] = ord($code{$i});
$cw[] = ord($code[$i]);
}
}
$code = $rest;

View File

@ -1686,7 +1686,7 @@ class QRcode {
/**
* Append data to an input object.
* The data is copied and appended to the input object.
* @param $items (array) input items
* @param $items (arrray) input items
* @param $mode (int) encoding mode.
* @param $size (int) size of data (byte).
* @param $data (array) array of input data.

BIN
lam/lib/3rdParty/tcpdf/include/sRGB.icc vendored Normal file

Binary file not shown.

View File

@ -358,7 +358,7 @@ class TCPDF_COLORS {
$color_code = self::$webcolor[$color];
} else {
// spot color
$returncolor = self::getSpotColor($color, $spotc);
$returncolor = self::getSpotColor($hcolor, $spotc);
if ($returncolor === false) {
$returncolor = $defcol;
}

View File

@ -279,7 +279,7 @@ class TCPDF_FILTERS {
// convert string to binary string
$bitstring = '';
for ($i = 0; $i < $data_length; ++$i) {
$bitstring .= sprintf('%08b', ord($data{$i}));
$bitstring .= sprintf('%08b', ord($data[$i]));
}
// get the number of bits
$data_length = strlen($bitstring);
@ -376,7 +376,7 @@ class TCPDF_FILTERS {
$i = 0;
while($i < $data_length) {
// get current byte value
$byte = ord($data{$i});
$byte = ord($data[$i]);
if ($byte == 128) {
// a length value of 128 denote EOD
break;
@ -389,7 +389,7 @@ class TCPDF_FILTERS {
} else {
// if length is in the range 129 to 255,
// the following single byte shall be copied 257 - length (2 to 128) times during decompression
$decoded .= str_repeat($data{($i + 1)}, (257 - $byte));
$decoded .= str_repeat($data[($i + 1)], (257 - $byte));
// move to next block
$i += 2;
}

View File

@ -1664,6 +1664,7 @@ class TCPDF_FONTS {
* @public static
*/
public static function unichr($c, $unicode=true) {
$c = intval($c);
if (!$unicode) {
return chr($c);
} elseif ($c <= 0x7F) {

View File

@ -311,7 +311,7 @@ class TCPDF_IMAGES {
if ($n > 0) {
$trns = array();
for ($i = 0; $i < $n; ++ $i) {
$trns[] = ord($t{$i});
$trns[] = ord($t[$i]);
}
}
}

View File

@ -1,9 +1,9 @@
<?php
//============================================================+
// File name : tcpdf_static.php
// Version : 1.1.3
// Version : 1.1.4
// Begin : 2002-08-03
// Last Update : 2015-04-28
// Last Update : 2019-11-01
// Author : Nicola Asuni - Tecnick.com LTD - www.tecnick.com - info@tecnick.com
// License : GNU-LGPL v3 (http://www.gnu.org/copyleft/lesser.html)
// -------------------------------------------------------------------
@ -55,7 +55,7 @@ class TCPDF_STATIC {
* Current TCPDF version.
* @private static
*/
private static $tcpdf_version = '6.2.26';
private static $tcpdf_version = '6.3.5';
/**
* String alias for total number of pages.
@ -1829,6 +1829,8 @@ class TCPDF_STATIC {
*/
public static function url_exists($url) {
$crs = curl_init();
// encode query params in URL to get right response form the server
$url = self::encodeUrlQuery($url);
curl_setopt($crs, CURLOPT_URL, $url);
curl_setopt($crs, CURLOPT_NOBODY, true);
curl_setopt($crs, CURLOPT_FAILONERROR, true);
@ -1846,6 +1848,26 @@ class TCPDF_STATIC {
return ($code == 200);
}
/**
* Encode query params in URL
*
* @param string $url
* @return string
* @since 6.3.3 (2019-11-01)
* @public static
*/
public static function encodeUrlQuery($url) {
$urlData = parse_url($url);
if (isset($urlData['query']) && $urlData['query']) {
$urlQueryData = [];
parse_str(urldecode($urlData['query']), $urlQueryData);
$updatedUrl = $urlData['scheme'] . '://' . $urlData['host'] . $urlData['path'] . '?' . http_build_query($urlQueryData);
} else {
$updatedUrl = $url;
}
return $updatedUrl;
}
/**
* Wrapper for file_exists.
* Checks whether a file or directory exists.
@ -1926,10 +1948,10 @@ class TCPDF_STATIC {
$alt = array_unique($alt);
foreach ($alt as $path) {
if (!self::file_exists($path)) {
return false;
continue;
}
$ret = @file_get_contents($path);
if ($ret !== false) {
if ( $ret != false ) {
return $ret;
}
// try to use CURL for URLs

View File

@ -1,13 +1,13 @@
<?php
//============================================================+
// File name : tcpdf.php
// Version : 6.2.26
// Version : 6.3.2
// Begin : 2002-08-03
// Last Update : 2018-09-14
// Last Update : 2019-09-20
// Author : Nicola Asuni - Tecnick.com LTD - www.tecnick.com - info@tecnick.com
// License : GNU-LGPL v3 (http://www.gnu.org/copyleft/lesser.html)
// -------------------------------------------------------------------
// Copyright (C) 2002-2018 Nicola Asuni - Tecnick.com LTD
// Copyright (C) 2002-2019 Nicola Asuni - Tecnick.com LTD
//
// This file is part of TCPDF software library.
//
@ -45,7 +45,7 @@
// * font subsetting;
// * methods to publish some XHTML + CSS code, Javascript and Forms;
// * images, graphic (geometric figures) and transformation methods;
// * supports JPEG, PNG and SVG images natively, all images supported by GD (GD, GD2, GD2PART, GIF, JPEG, PNG, BMP, XBM, XPM) and all images supported via ImagMagick (http://www.imagemagick.org/www/formats.html)
// * supports JPEG, PNG and SVG images natively, all images supported by GD (GD, GD2, GD2PART, GIF, JPEG, PNG, BMP, XBM, XPM) and all images supported via ImageMagick (http://www.imagemagick.org/www/formats.html)
// * 1D and 2D barcodes: CODE 39, ANSI MH10.8M-1983, USD-3, 3 of 9, CODE 93, USS-93, Standard 2 of 5, Interleaved 2 of 5, CODE 128 A/B/C, 2 and 5 Digits UPC-Based Extension, EAN 8, EAN 13, UPC-A, UPC-E, MSI, POSTNET, PLANET, RMS4CC (Royal Mail 4-state Customer Code), CBC (Customer Bar Code), KIX (Klant index - Customer index), Intelligent Mail Barcode, Onecode, USPS-B-3200, CODABAR, CODE 11, PHARMACODE, PHARMACODE TWO-TRACKS, Datamatrix, QR-Code, PDF417;
// * JPEG and PNG ICC profiles, Grayscale, RGB, CMYK, Spot Colors and Transparencies;
// * automatic page header and footer management;
@ -80,7 +80,7 @@
* <li>font subsetting;</li>
* <li>methods to publish some XHTML + CSS code, Javascript and Forms;</li>
* <li>images, graphic (geometric figures) and transformation methods;
* <li>supports JPEG, PNG and SVG images natively, all images supported by GD (GD, GD2, GD2PART, GIF, JPEG, PNG, BMP, XBM, XPM) and all images supported via ImagMagick (http://www.imagemagick.org/www/formats.html)</li>
* <li>supports JPEG, PNG and SVG images natively, all images supported by GD (GD, GD2, GD2PART, GIF, JPEG, PNG, BMP, XBM, XPM) and all images supported via ImageMagick (http://www.imagemagick.org/www/formats.html)</li>
* <li>1D and 2D barcodes: CODE 39, ANSI MH10.8M-1983, USD-3, 3 of 9, CODE 93, USS-93, Standard 2 of 5, Interleaved 2 of 5, CODE 128 A/B/C, 2 and 5 Digits UPC-Based Extension, EAN 8, EAN 13, UPC-A, UPC-E, MSI, POSTNET, PLANET, RMS4CC (Royal Mail 4-state Customer Code), CBC (Customer Bar Code), KIX (Klant index - Customer index), Intelligent Mail Barcode, Onecode, USPS-B-3200, CODABAR, CODE 11, PHARMACODE, PHARMACODE TWO-TRACKS, Datamatrix, QR-Code, PDF417;</li>
* <li>JPEG and PNG ICC profiles, Grayscale, RGB, CMYK, Spot Colors and Transparencies;</li>
* <li>automatic page header and footer management;</li>
@ -104,7 +104,7 @@
* Tools to encode your unicode fonts are on fonts/utils directory.</p>
* @package com.tecnick.tcpdf
* @author Nicola Asuni
* @version 6.2.26
* @version 6.3.2
*/
// TCPDF configuration
@ -128,7 +128,7 @@ require_once(dirname(__FILE__).'/include/tcpdf_static.php');
* TCPDF project (http://www.tcpdf.org) has been originally derived in 2002 from the Public Domain FPDF class by Olivier Plathey (http://www.fpdf.org), but now is almost entirely rewritten.<br>
* @package com.tecnick.tcpdf
* @brief PHP class for generating PDF documents without requiring external extensions.
* @version 6.2.26
* @version 6.3.2
* @author Nicola Asuni - info@tecnick.com
* @IgnoreAnnotation("protected")
* @IgnoreAnnotation("public")
@ -1760,6 +1760,13 @@ class TCPDF {
*/
protected $pdfa_mode = false;
/**
* version of PDF/A mode (1 - 3).
* @protected
* @since 6.2.26 (2019-03-12)
*/
protected $pdfa_version = 1;
/**
* Document creation date-time
* @protected
@ -1781,6 +1788,13 @@ class TCPDF {
*/
protected $custom_xmp = '';
/**
* Custom XMP RDF data.
* @protected
* @since 6.3.0 (2019-09-19)
*/
protected $custom_xmp_rdf = '';
/**
* Overprint mode array.
* (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
@ -1834,7 +1848,7 @@ class TCPDF {
* @param $unicode (boolean) TRUE means that the input text is unicode (default = true)
* @param $encoding (string) Charset encoding (used only when converting back html entities); default is UTF-8.
* @param $diskcache (boolean) DEPRECATED FEATURE
* @param $pdfa (boolean) If TRUE set the document to PDF/A mode.
* @param $pdfa (integer) If not false, set the document to PDF/A mode and the good version (1 or 3).
* @public
* @see getPageSizeFromFormat(), setPageFormat()
*/
@ -1850,8 +1864,14 @@ class TCPDF {
$this->font_obj_ids = array();
$this->page_obj_id = array();
$this->form_obj_id = array();
// set pdf/a mode
$this->pdfa_mode = $pdfa;
if ($pdfa != false) {
$this->pdfa_mode = true;
$this->pdfa_version = $pdfa; // 1 or 3
} else
$this->pdfa_mode = false;
$this->force_srgb = false;
// set language direction
$this->rtl = false;
@ -1960,7 +1980,7 @@ class TCPDF {
// set default JPEG quality
$this->jpeg_quality = 75;
// initialize some settings
TCPDF_FONTS::utf8Bidi(array(''), '', false, $this->isunicode, $this->CurrentFont);
TCPDF_FONTS::utf8Bidi(array(), '', false, $this->isunicode, $this->CurrentFont);
// set default font
$this->SetFont($this->FontFamily, $this->FontStyle, $this->FontSizePt);
$this->setHeaderFont(array($this->FontFamily, $this->FontStyle, $this->FontSizePt));
@ -1986,6 +2006,7 @@ class TCPDF {
$this->default_graphic_vars = $this->getGraphicVars();
$this->header_xobj_autoreset = false;
$this->custom_xmp = '';
$this->custom_xmp_rdf = '';
// Call cleanup method after script execution finishes or exit() is called.
// NOTE: This will not be executed if the process is killed with a SIGTERM or SIGKILL signal.
register_shutdown_function(array($this, '_destroy'), true);
@ -2828,10 +2849,13 @@ class TCPDF {
* @since 1.4
*/
public function SetCompression($compress=true) {
$this->compress = false;
if (function_exists('gzcompress')) {
$this->compress = $compress ? true : false;
} else {
$this->compress = false;
if ($compress) {
if ( !$this->pdfa_mode) {
$this->compress = true;
}
}
}
}
@ -4691,14 +4715,14 @@ class TCPDF {
* Defines the page and position a link points to.
* @param $link (int) The link identifier returned by AddLink()
* @param $y (float) Ordinate of target position; -1 indicates the current position. The default value is 0 (top of page)
* @param $page (int) Number of target page; -1 indicates the current page (default value). If you prefix a page number with the * character, then this page will not be changed when adding/deleting/moving pages.
* @param $page (int|string) Number of target page; -1 indicates the current page (default value). If you prefix a page number with the * character, then this page will not be changed when adding/deleting/moving pages.
* @public
* @since 1.5
* @see AddLink()
*/
public function SetLink($link, $y=0, $page=-1) {
$fixed = false;
if (!empty($page) AND ($page[0] == '*')) {
if (!empty($page) AND (substr($page, 0, 1) == '*')) {
$page = intval(substr($page, 1));
// this page number will not be changed when moving/add/deleting pages
$fixed = true;
@ -4807,7 +4831,7 @@ class TCPDF {
$this->PageAnnots[$page] = array();
}
$this->PageAnnots[$page][] = array('n' => ++$this->n, 'x' => $x, 'y' => $y, 'w' => $w, 'h' => $h, 'txt' => $text, 'opt' => $opt, 'numspaces' => $spaces);
if (!$this->pdfa_mode) {
if (!$this->pdfa_mode || ($this->pdfa_mode && $this->pdfa_version == 3)) {
if ((($opt['Subtype'] == 'FileAttachment') OR ($opt['Subtype'] == 'Sound')) AND (!TCPDF_STATIC::empty_string($opt['FS']))
AND (@TCPDF_STATIC::file_exists($opt['FS']) OR TCPDF_STATIC::isValidURL($opt['FS']))
AND (!isset($this->embeddedfiles[basename($opt['FS'])]))) {
@ -4833,8 +4857,8 @@ class TCPDF {
* @see Annotation()
*/
protected function _putEmbeddedFiles() {
if ($this->pdfa_mode) {
// embedded files are not allowed in PDF/A mode
if ($this->pdfa_mode && $this->pdfa_version != 3) {
// embedded files are not allowed in PDF/A mode version 1 and 2
return;
}
reset($this->embeddedfiles);
@ -4847,7 +4871,10 @@ class TCPDF {
$this->efnames[$filename] = $filedata['f'].' 0 R';
// embedded file specification object
$out = $this->_getobj($filedata['f'])."\n";
$out .= '<</Type /Filespec /F '.$this->_datastring($filename, $filedata['f']).' /EF <</F '.$filedata['n'].' 0 R>> >>';
$out .= '<</Type /Filespec /F '.$this->_datastring($filename, $filedata['f']);
$out .= ' /UF '.$this->_datastring($filename, $filedata['f']);
$out .= ' /AFRelationship /Source';
$out .= ' /EF <</F '.$filedata['n'].' 0 R>> >>';
$out .= "\n".'endobj';
$this->_out($out);
// embedded file object
@ -4856,6 +4883,11 @@ class TCPDF {
$data = gzcompress($data);
$filter = ' /Filter /FlateDecode';
}
if ($this->pdfa_version == 3) {
$filter = ' /Subtype /text#2Fxml';
}
$stream = $this->_getrawstream($data, $filedata['n']);
$out = $this->_getobj($filedata['n'])."\n";
$out .= '<< /Type /EmbeddedFile'.$filter.' /Length '.strlen($stream).' /Params <</Size '.$rawsize.'>> >>';
@ -6219,12 +6251,12 @@ class TCPDF {
* $this->setPage($page);
* if ($page == $start_page) {
* // first page
* $height = $this->h - $start_y - $this->bMargin;
* $height += $this->h - $start_y - $this->bMargin;
* } elseif ($page == $end_page) {
* // last page
* $height = $end_y - $this->tMargin;
* $height += $end_y - $this->tMargin;
* } else {
* $height = $this->h - $this->tMargin - $this->bMargin;
* $height += $this->h - $this->tMargin - $this->bMargin;
* }
* }
* }
@ -7133,31 +7165,20 @@ class TCPDF {
$info['i'] = $this->setImageBuffer($file, $info);
}
// set alignment
$this->img_rb_x = $x + $w;
$this->img_rb_y = $y + $h;
// set alignment
if ($this->rtl) {
if ($palign == 'L') {
$ximg = $this->lMargin;
} elseif ($palign == 'C') {
$ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
} elseif ($palign == 'R') {
$ximg = $this->w - $this->rMargin - $w;
} else {
$ximg = $x - $w;
}
$this->img_rb_x = $ximg;
if ($palign == 'L') {
$ximg = $this->lMargin;
} elseif ($palign == 'C') {
$ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
} elseif ($palign == 'R') {
$ximg = $this->w - $this->rMargin - $w;
} else {
if ($palign == 'L') {
$ximg = $this->lMargin;
} elseif ($palign == 'C') {
$ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
} elseif ($palign == 'R') {
$ximg = $this->w - $this->rMargin - $w;
} else {
$ximg = $x;
}
$this->img_rb_x = $ximg + $w;
$ximg = $x;
}
if ($ismask OR $hidden) {
// image is not displayed
return $info['i'];
@ -7737,6 +7758,7 @@ class TCPDF {
return '';
}
protected static $cleaned_ids = array();
/**
* Unset all class variables except the following critical variables.
* @param $destroyall (boolean) if true destroys all class variables, otherwise preserves critical variables.
@ -7749,11 +7771,26 @@ class TCPDF {
if (isset($this->internal_encoding) AND !empty($this->internal_encoding)) {
mb_internal_encoding($this->internal_encoding);
}
if (isset(self::$cleaned_ids[$this->file_id])) {
$destroyall = false;
}
if ($destroyall AND !$preserve_objcopy) {
self::$cleaned_ids[$this->file_id] = true;
// remove all temporary files
$tmpfiles = glob(K_PATH_CACHE.'__tcpdf_'.$this->file_id.'_*');
if (!empty($tmpfiles)) {
array_map('unlink', $tmpfiles);
if ($handle = @opendir(K_PATH_CACHE)) {
while ( false !== ( $file_name = readdir( $handle ) ) ) {
if (strpos($file_name, '__tcpdf_'.$this->file_id.'_') === 0) {
unlink(K_PATH_CACHE.$file_name);
}
}
closedir($handle);
}
if (isset($this->imagekeys)) {
foreach($this->imagekeys as $file) {
if (strpos($file, K_PATH_CACHE) === 0) {
@unlink($file);
}
}
}
}
$preserve = array(
@ -7763,6 +7800,7 @@ class TCPDF {
'bufferlen',
'buffer',
'cached_files',
'imagekeys',
'sign',
'signature_data',
'signature_max_length',
@ -8368,7 +8406,7 @@ class TCPDF {
if (is_string($pl['txt']) && !empty($pl['txt'])) {
if ($pl['txt'][0] == '#') {
// internal destination
$annots .= ' /Dest /'.TCPDF_STATIC::encodeNameObject(substr($pl['txt'], 1));
$annots .= ' /A <</S /GoTo /D '.TCPDF_STATIC::encodeNameObject(substr($pl['txt'], 1)).'>>';
} elseif ($pl['txt'][0] == '%') {
// embedded PDF file
$filename = basename(substr($pl['txt'], 1));
@ -8380,7 +8418,7 @@ class TCPDF {
$annots .= ' /A << /S /JavaScript /JS '.$this->_textstring($jsa, $annot_obj_id).'>>';
} else {
$parsedUrl = parse_url($pl['txt']);
if (empty($parsedUrl['scheme']) AND (strtolower(substr($parsedUrl['path'], -4)) == '.pdf')) {
if (empty($parsedUrl['scheme']) AND (!empty($parsedUrl['path']) && strtolower(substr($parsedUrl['path'], -4)) == '.pdf')) {
// relative link to a PDF file
$dest = '[0 /Fit]'; // default page 0
if (!empty($parsedUrl['fragment'])) {
@ -8487,8 +8525,8 @@ class TCPDF {
break;
}
case 'fileattachment': {
if ($this->pdfa_mode) {
// embedded files are not allowed in PDF/A mode
if ($this->pdfa_mode && $this->pdfa_version != 3) {
// embedded files are not allowed in PDF/A mode version 1 and 2
break;
}
if (!isset($pl['opt']['fs'])) {
@ -9500,6 +9538,17 @@ class TCPDF {
$this->custom_xmp = $xmp;
}
/**
* Set additional XMP data to be added on the default XMP data just before the end of "rdf:RDF" tag.
* IMPORTANT: This data is added as-is without controls, so you have to validate your data before using this method!
* @param $xmp (string) Custom XMP RDF data.
* @since 6.3.0 (2019-09-19)
* @public
*/
public function setExtraXMPRDF($xmp) {
$this->custom_xmp_rdf = $xmp;
}
/**
* Put XMP data object and return ID.
* @return (int) The object ID.
@ -9569,7 +9618,7 @@ class TCPDF {
$xmp .= "\t\t".'</rdf:Description>'."\n";
if ($this->pdfa_mode) {
$xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:pdfaid="http://www.aiim.org/pdfa/ns/id/">'."\n";
$xmp .= "\t\t\t".'<pdfaid:part>1</pdfaid:part>'."\n";
$xmp .= "\t\t\t".'<pdfaid:part>'.$this->pdfa_version.'</pdfaid:part>'."\n";
$xmp .= "\t\t\t".'<pdfaid:conformance>B</pdfaid:conformance>'."\n";
$xmp .= "\t\t".'</rdf:Description>'."\n";
}
@ -9581,6 +9630,16 @@ class TCPDF {
$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:namespaceURI>http://ns.adobe.com/pdf/1.3/</pdfaSchema:namespaceURI>'."\n";
$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:prefix>pdf</pdfaSchema:prefix>'."\n";
$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:schema>Adobe PDF Schema</pdfaSchema:schema>'."\n";
$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:property>'."\n";
$xmp .= "\t\t\t\t\t\t\t".'<rdf:Seq>'."\n";
$xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>Adobe PDF Schema</pdfaProperty:description>'."\n";
$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>InstanceID</pdfaProperty:name>'."\n";
$xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>URI</pdfaProperty:valueType>'."\n";
$xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
$xmp .= "\t\t\t\t\t\t\t".'</rdf:Seq>'."\n";
$xmp .= "\t\t\t\t\t\t".'</pdfaSchema:property>'."\n";
$xmp .= "\t\t\t\t\t".'</rdf:li>'."\n";
$xmp .= "\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
$xmp .= "\t\t\t\t\t\t".'<pdfaSchema:namespaceURI>http://ns.adobe.com/xap/1.0/mm/</pdfaSchema:namespaceURI>'."\n";
@ -9627,6 +9686,7 @@ class TCPDF {
$xmp .= "\t\t\t\t".'</rdf:Bag>'."\n";
$xmp .= "\t\t\t".'</pdfaExtension:schemas>'."\n";
$xmp .= "\t\t".'</rdf:Description>'."\n";
$xmp .= $this->custom_xmp_rdf;
$xmp .= "\t".'</rdf:RDF>'."\n";
$xmp .= $this->custom_xmp;
$xmp .= '</x:xmpmeta>'."\n";
@ -12221,7 +12281,7 @@ class TCPDF {
$x = $this->w;
}
$fixed = false;
if (!empty($page) AND ($page[0] == '*')) {
if (!empty($page) AND (substr($page, 0, 1) == '*')) {
$page = intval(substr($page, 1));
// this page number will not be changed when moving/add/deleting pages
$fixed = true;
@ -12324,7 +12384,8 @@ class TCPDF {
$x = $this->w;
}
$fixed = false;
if (!empty($page) AND ($page[0] == '*')) {
$pageAsString = (string) $page;
if ($pageAsString && $pageAsString[0] == '*') {
$page = intval(substr($page, 1));
// this page number will not be changed when moving/add/deleting pages
$fixed = true;
@ -13988,7 +14049,7 @@ class TCPDF {
* @since 3.1.000 (2008-06-09)
*/
public function setPDFVersion($version='1.7') {
if ($this->pdfa_mode) {
if ($this->pdfa_mode && $this->pdfa_version == 1 ) {
// PDF/A mode
$this->PDFVersion = '1.4';
} else {
@ -15502,8 +15563,7 @@ class TCPDF {
* <li>int $style['module_height'] height of a single module in points</li>
* <li>array $style['fgcolor'] color array for bars and text</li>
* <li>mixed $style['bgcolor'] color array for background or false for transparent</li>
* <li>string $style['position'] barcode position on the page: L = left margin; C = center; R = right margin; S = stretch</li><li>$style['module_width'] width of a single module in points</li>
* <li>$style['module_height'] height of a single module in points</li></ul>
* <li>string $style['position'] barcode position on the page: L = left margin; C = center; R = right margin; S = stretch</li>
* @param $align (string) Indicates the alignment of the pointer next to barcode insertion relative to barcode height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul>
* @param $distort (boolean) if true distort the barcode to fit width and height, otherwise preserve aspect ratio
* @author Nicola Asuni
@ -16897,10 +16957,10 @@ class TCPDF {
if (($dom[$key]['value'] == 'pre') OR ($dom[$key]['value'] == 'tt')) {
$dom[$key]['fontname'] = $this->default_monospaced_font;
}
if (!empty($dom[$key]['value']) AND ($dom[$key]['value'][0] == 'h') AND (intval($dom[$key]['value']{1}) > 0) AND (intval($dom[$key]['value']{1}) < 7)) {
if (!empty($dom[$key]['value']) AND ($dom[$key]['value'][0] == 'h') AND (intval($dom[$key]['value'][1]) > 0) AND (intval($dom[$key]['value'][1]) < 7)) {
// headings h1, h2, h3, h4, h5, h6
if (!isset($dom[$key]['attribute']['size']) AND !isset($dom[$key]['style']['font-size'])) {
$headsize = (4 - intval($dom[$key]['value']{1})) * 2;
$headsize = (4 - intval($dom[$key]['value'][1])) * 2;
$dom[$key]['fontsize'] = $dom[0]['fontsize'] + $headsize;
}
if (!isset($dom[$key]['style']['font-weight'])) {
@ -18686,7 +18746,7 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value:
$hbz = 0; // distance from y to line bottom
$hb = 0; // vertical space between block tags
// calculate vertical space for block tags
if (isset($this->tagvspaces[$tag['value']][0]['h']) AND ($this->tagvspaces[$tag['value']][0]['h'] >= 0)) {
if (isset($this->tagvspaces[$tag['value']][0]['h']) && !empty($this->tagvspaces[$tag['value']][0]['h']) && ($this->tagvspaces[$tag['value']][0]['h'] >= 0)) {
$cur_h = $this->tagvspaces[$tag['value']][0]['h'];
} elseif (isset($tag['fontsize'])) {
$cur_h = $this->getCellHeight($tag['fontsize'] / $this->k);
@ -18718,7 +18778,7 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value:
}
// closing vertical space
$hbc = 0;
if (isset($this->tagvspaces[$tag['value']][1]['h']) AND ($this->tagvspaces[$tag['value']][1]['h'] >= 0)) {
if (isset($this->tagvspaces[$tag['value']][1]['h']) && !empty($this->tagvspaces[$tag['value']][1]['h']) && ($this->tagvspaces[$tag['value']][1]['h'] >= 0)) {
$pre_h = $this->tagvspaces[$tag['value']][1]['h'];
} elseif (isset($parent['fontsize'])) {
$pre_h = $this->getCellHeight($parent['fontsize'] / $this->k);
@ -19379,7 +19439,7 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value:
$hbz = 0; // distance from y to line bottom
$hb = 0; // vertical space between block tags
// calculate vertical space for block tags
if (isset($this->tagvspaces[$tag['value']][1]['h']) AND ($this->tagvspaces[$tag['value']][1]['h'] >= 0)) {
if (isset($this->tagvspaces[$tag['value']][1]['h']) && !empty($this->tagvspaces[$tag['value']][1]['h']) && ($this->tagvspaces[$tag['value']][1]['h'] >= 0)) {
$pre_h = $this->tagvspaces[$tag['value']][1]['h'];
} elseif (isset($parent['fontsize'])) {
$pre_h = $this->getCellHeight($parent['fontsize'] / $this->k);
@ -23349,7 +23409,7 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value:
*/
protected function SVGPath($d, $style='') {
if ($this->state != 2) {
return;
return;
}
// set fill/stroke style
$op = TCPDF_STATIC::getPathPaintOperator($style, '');
@ -23369,6 +23429,8 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value:
$xmax = 0;
$ymin = 2147483647;
$ymax = 0;
$xinitial = 0;
$yinitial = 0;
$relcoord = false;
$minlen = (0.01 / $this->k); // minimum acceptable length (3 point)
$firstcmd = true; // used to print first point
@ -23413,6 +23475,8 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value:
if ($ck == 1) {
$this->_outPoint($x, $y);
$firstcmd = false;
$xinitial = $x;
$yinitial = $y;
} else {
$this->_outLine($x, $y);
}
@ -23600,8 +23664,8 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value:
if ((($ck + 1) % 7) == 0) {
$x0 = $x;
$y0 = $y;
$rx = abs($params[($ck - 6)]);
$ry = abs($params[($ck - 5)]);
$rx = max(abs($params[($ck - 6)]), .000000001);
$ry = max(abs($params[($ck - 5)]), .000000001);
$ang = -$rawparams[($ck - 4)];
$angle = deg2rad($ang);
$fa = $rawparams[($ck - 3)]; // large-arc-flag
@ -23688,6 +23752,8 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value:
}
case 'Z': {
$this->_out('h');
$x = $x0 = $xinitial;
$y = $y0 = $yinitial;
break;
}
}
@ -23995,7 +24061,7 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value:
case 'stop': {
// gradient stops
if (substr($attribs['offset'], -1) == '%') {
$offset = floatval(substr($attribs['offset'], -1)) / 100;
$offset = floatval(substr($attribs['offset'], 0, -1)) / 100;
} else {
$offset = floatval($attribs['offset']);
if ($offset > 1) {

View File

@ -453,7 +453,7 @@ class TCPDFBarcode {
$k = 0;
$clen = strlen($code);
for ($i = 0; $i < $clen; ++$i) {
$char = $code{$i};
$char = $code[$i];
if(!isset($chr[$char])) {
// invalid character
return false;
@ -464,7 +464,7 @@ class TCPDFBarcode {
} else {
$t = false; // space
}
$w = $chr[$char]{$j};
$w = $chr[$char][$j];
$bararray['bcode'][$k] = array('t' => $t, 'w' => $w, 'h' => 1, 'p' => 0);
$bararray['maxw'] += $w;
++$k;
@ -520,10 +520,10 @@ class TCPDFBarcode {
$code_ext = '';
$clen = strlen($code);
for ($i = 0 ; $i < $clen; ++$i) {
if (ord($code{$i}) > 127) {
if (ord($code[$i]) > 127) {
return false;
}
$code_ext .= $encode[$code{$i}];
$code_ext .= $encode[$code[$i]];
}
return $code_ext;
}
@ -543,7 +543,7 @@ class TCPDFBarcode {
$sum = 0;
$clen = strlen($code);
for ($i = 0 ; $i < $clen; ++$i) {
$k = array_keys($chars, $code{$i});
$k = array_keys($chars, $code[$i]);
$sum += $k[0];
}
$j = ($sum % 43);
@ -643,10 +643,10 @@ class TCPDFBarcode {
$code_ext = '';
$clen = strlen($code);
for ($i = 0 ; $i < $clen; ++$i) {
if (ord($code{$i}) > 127) {
if (ord($code[$i]) > 127) {
return false;
}
$code_ext .= $encode[$code{$i}];
$code_ext .= $encode[$code[$i]];
}
// checksum
$code_ext .= $this->checksum_code93($code_ext);
@ -656,7 +656,7 @@ class TCPDFBarcode {
$k = 0;
$clen = strlen($code);
for ($i = 0; $i < $clen; ++$i) {
$char = ord($code{$i});
$char = ord($code[$i]);
if(!isset($chr[$char])) {
// invalid character
return false;
@ -667,7 +667,7 @@ class TCPDFBarcode {
} else {
$t = false; // space
}
$w = $chr[$char]{$j};
$w = $chr[$char][$j];
$bararray['bcode'][$k] = array('t' => $t, 'w' => $w, 'h' => 1, 'p' => 0);
$bararray['maxw'] += $w;
++$k;
@ -699,7 +699,7 @@ class TCPDFBarcode {
$p = 1;
$check = 0;
for ($i = ($len - 1); $i >= 0; --$i) {
$k = array_keys($chars, $code{$i});
$k = array_keys($chars, $code[$i]);
$check += ($k[0] * $p);
++$p;
if ($p > 20) {
@ -713,7 +713,7 @@ class TCPDFBarcode {
$p = 1;
$check = 0;
for ($i = $len; $i >= 0; --$i) {
$k = array_keys($chars, $code{$i});
$k = array_keys($chars, $code[$i]);
$check += ($k[0] * $p);
++$p;
if ($p > 15) {
@ -738,11 +738,11 @@ class TCPDFBarcode {
$len = strlen($code);
$sum = 0;
for ($i = 0; $i < $len; $i+=2) {
$sum += $code{$i};
$sum += $code[$i];
}
$sum *= 3;
for ($i = 1; $i < $len; $i+=2) {
$sum += ($code{$i});
$sum += ($code[$i]);
}
$r = $sum % 10;
if($r > 0) {
@ -783,7 +783,7 @@ class TCPDFBarcode {
$p = 2;
$check = 0;
for ($i = ($clen - 1); $i >= 0; --$i) {
$check += (hexdec($code{$i}) * $p);
$check += (hexdec($code[$i]) * $p);
++$p;
if ($p > 7) {
$p = 2;
@ -798,7 +798,7 @@ class TCPDFBarcode {
$seq = '110'; // left guard
$clen = strlen($code);
for ($i = 0; $i < $clen; ++$i) {
$digit = $code{$i};
$digit = $code[$i];
if (!isset($chr[$digit])) {
// invalid character
return false;
@ -841,7 +841,7 @@ class TCPDFBarcode {
$seq = '11011010';
$clen = strlen($code);
for ($i = 0; $i < $clen; ++$i) {
$digit = $code{$i};
$digit = $code[$i];
if (!isset($chr[$digit])) {
// invalid character
return false;
@ -867,8 +867,8 @@ class TCPDFBarcode {
$k = 0;
for ($i = 0; $i < $len; ++$i) {
$w += 1;
if (($i == ($len - 1)) OR (($i < ($len - 1)) AND ($seq{$i} != $seq{($i+1)}))) {
if ($seq{$i} == '1') {
if (($i == ($len - 1)) OR (($i < ($len - 1)) AND ($seq[$i] != $seq[($i+1)]))) {
if ($seq[$i] == '1') {
$t = true; // bar
} else {
$t = false; // space
@ -919,8 +919,8 @@ class TCPDFBarcode {
$k = 0;
$clen = strlen($code);
for ($i = 0; $i < $clen; $i = ($i + 2)) {
$char_bar = $code{$i};
$char_space = $code{$i+1};
$char_bar = $code[$i];
$char_space = $code[$i+1];
if((!isset($chr[$char_bar])) OR (!isset($chr[$char_space]))) {
// invalid character
return false;
@ -929,7 +929,7 @@ class TCPDFBarcode {
$seq = '';
$chrlen = strlen($chr[$char_bar]);
for ($s = 0; $s < $chrlen; $s++){
$seq .= $chr[$char_bar]{$s} . $chr[$char_space]{$s};
$seq .= $chr[$char_bar][$s] . $chr[$char_space][$s];
}
$seqlen = strlen($seq);
for ($j = 0; $j < $seqlen; ++$j) {
@ -938,7 +938,7 @@ class TCPDFBarcode {
} else {
$t = false; // space
}
$w = $seq{$j};
$w = $seq[$j];
$bararray['bcode'][$k] = array('t' => $t, 'w' => $w, 'h' => 1, 'p' => 0);
$bararray['maxw'] += $w;
++$k;
@ -1085,7 +1085,7 @@ class TCPDFBarcode {
case 'A': { // MODE A
$startid = 103;
for ($i = 0; $i < $len; ++$i) {
$char = $code{$i};
$char = $code[$i];
$char_id = ord($char);
if (($char_id >= 241) AND ($char_id <= 244)) {
$code_data[] = $fnc_a[$char_id];
@ -1100,7 +1100,7 @@ class TCPDFBarcode {
case 'B': { // MODE B
$startid = 104;
for ($i = 0; $i < $len; ++$i) {
$char = $code{$i};
$char = $code[$i];
$char_id = ord($char);
if (($char_id >= 241) AND ($char_id <= 244)) {
$code_data[] = $fnc_b[$char_id];
@ -1124,7 +1124,7 @@ class TCPDFBarcode {
return false;
}
for ($i = 0; $i < $len; $i+=2) {
$chrnum = $code{$i}.$code{$i+1};
$chrnum = $code[$i].$code[$i+1];
if (preg_match('/([0-9]{2})/', $chrnum) > 0) {
$code_data[] = intval($chrnum);
} else {
@ -1180,7 +1180,7 @@ class TCPDFBarcode {
}
}
for ($i = 0; $i < $seq[2]; ++$i) {
$char = $seq[1]{$i};
$char = $seq[1][$i];
$char_id = ord($char);
if (($char_id >= 241) AND ($char_id <= 244)) {
$code_data[] = $fnc_a[$char_id];
@ -1223,7 +1223,7 @@ class TCPDFBarcode {
}
}
for ($i = 0; $i < $seq[2]; ++$i) {
$char = $seq[1]{$i};
$char = $seq[1][$i];
$char_id = ord($char);
if (($char_id >= 241) AND ($char_id <= 244)) {
$code_data[] = $fnc_b[$char_id];
@ -1240,7 +1240,7 @@ class TCPDFBarcode {
$code_data[] = 99;
}
for ($i = 0; $i < $seq[2]; $i+=2) {
$chrnum = $seq[1]{$i}.$seq[1]{$i+1};
$chrnum = $seq[1][$i].$seq[1][$i+1];
$code_data[] = intval($chrnum);
}
break;
@ -1271,7 +1271,7 @@ class TCPDFBarcode {
} else {
$t = false; // space
}
$w = $seq{$j};
$w = $seq[$j];
$bararray['bcode'][] = array('t' => $t, 'w' => $w, 'h' => 1, 'p' => 0);
$bararray['maxw'] += $w;
}
@ -1337,14 +1337,14 @@ class TCPDFBarcode {
// calculate check digit
$sum_a = 0;
for ($i = 1; $i < $data_len; $i+=2) {
$sum_a += $code{$i};
$sum_a += $code[$i];
}
if ($len > 12) {
$sum_a *= 3;
}
$sum_b = 0;
for ($i = 0; $i < $data_len; $i+=2) {
$sum_b += ($code{$i});
$sum_b += ($code[$i]);
}
if ($len < 13) {
$sum_b *= 3;
@ -1356,7 +1356,7 @@ class TCPDFBarcode {
if ($code_len == $data_len) {
// add check digit
$code .= $r;
} elseif ($r !== intval($code{$data_len})) {
} elseif ($r !== intval($code[$data_len])) {
// wrong checkdigit
return false;
}
@ -1467,7 +1467,7 @@ class TCPDFBarcode {
$bararray = array('code' => $upce_code, 'maxw' => 0, 'maxh' => 1, 'bcode' => array());
$p = $upce_parities[$code[1]][$r];
for ($i = 0; $i < 6; ++$i) {
$seq .= $codes[$p[$i]][$upce_code{$i}];
$seq .= $codes[$p[$i]][$upce_code[$i]];
}
$seq .= '010101'; // right guard bar
} else {
@ -1475,17 +1475,17 @@ class TCPDFBarcode {
$half_len = intval(ceil($len / 2));
if ($len == 8) {
for ($i = 0; $i < $half_len; ++$i) {
$seq .= $codes['A'][$code{$i}];
$seq .= $codes['A'][$code[$i]];
}
} else {
$p = $parities[$code[0]];
for ($i = 1; $i < $half_len; ++$i) {
$seq .= $codes[$p[$i-1]][$code{$i}];
$seq .= $codes[$p[$i-1]][$code[$i]];
}
}
$seq .= '01010'; // center guard bar
for ($i = $half_len; $i < $len; ++$i) {
$seq .= $codes['C'][$code{$i}];
$seq .= $codes['C'][$code[$i]];
}
$seq .= '101'; // right guard bar
}
@ -1493,8 +1493,8 @@ class TCPDFBarcode {
$w = 0;
for ($i = 0; $i < $clen; ++$i) {
$w += 1;
if (($i == ($clen - 1)) OR (($i < ($clen - 1)) AND ($seq{$i} != $seq{($i+1)}))) {
if ($seq{$i} == '1') {
if (($i == ($clen - 1)) OR (($i < ($clen - 1)) AND ($seq[$i] != $seq[$i+1]))) {
if ($seq[$i] == '1') {
$t = true; // bar
} else {
$t = false; // space
@ -1578,7 +1578,7 @@ class TCPDFBarcode {
$seq .= $codes[$p[0]][$code[0]];
for ($i = 1; $i < $len; ++$i) {
$seq .= '01'; // separator
$seq .= $codes[$p[$i]][$code{$i}];
$seq .= $codes[$p[$i]][$code[$i]];
}
$bararray = array('code' => $code, 'maxw' => 0, 'maxh' => 1, 'bcode' => array());
return $this->binseq_to_array($seq, $bararray);
@ -1629,7 +1629,7 @@ class TCPDFBarcode {
// calculate checksum
$sum = 0;
for ($i = 0; $i < $len; ++$i) {
$sum += intval($code{$i});
$sum += intval($code[$i]);
}
$chkd = ($sum % 10);
if($chkd > 0) {
@ -1643,7 +1643,7 @@ class TCPDFBarcode {
$bararray['maxw'] += 2;
for ($i = 0; $i < $len; ++$i) {
for ($j = 0; $j < 5; ++$j) {
$h = $barlen[$code{$i}][$j];
$h = $barlen[$code[$i]][$j];
$p = floor(1 / $h);
$bararray['bcode'][$k++] = array('t' => 1, 'w' => 1, 'h' => $h, 'p' => $p);
$bararray['bcode'][$k++] = array('t' => 0, 'w' => 1, 'h' => 2, 'p' => 0);
@ -1756,8 +1756,8 @@ class TCPDFBarcode {
$row = 0;
$col = 0;
for ($i = 0; $i < $len; ++$i) {
$row += $checktable[$code{$i}][0];
$col += $checktable[$code{$i}][1];
$row += $checktable[$code[$i]][0];
$col += $checktable[$code[$i]][1];
}
$row %= 6;
$col %= 6;
@ -1774,7 +1774,7 @@ class TCPDFBarcode {
}
for ($i = 0; $i < $len; ++$i) {
for ($j = 0; $j < 4; ++$j) {
switch ($barmode[$code{$i}][$j]) {
switch ($barmode[$code[$i]][$j]) {
case 1: {
$p = 0;
$h = 2;
@ -1846,17 +1846,17 @@ class TCPDFBarcode {
$code = 'A'.strtoupper($code).'A';
$len = strlen($code);
for ($i = 0; $i < $len; ++$i) {
if (!isset($chr[$code{$i}])) {
if (!isset($chr[$code[$i]])) {
return false;
}
$seq = $chr[$code{$i}];
$seq = $chr[$code[$i]];
for ($j = 0; $j < 8; ++$j) {
if (($j % 2) == 0) {
$t = true; // bar
} else {
$t = false; // space
}
$w = $seq{$j};
$w = $seq[$j];
$bararray['bcode'][$k] = array('t' => $t, 'w' => $w, 'h' => 1, 'p' => 0);
$bararray['maxw'] += $w;
++$k;
@ -1896,7 +1896,7 @@ class TCPDFBarcode {
$p = 1;
$check = 0;
for ($i = ($len - 1); $i >= 0; --$i) {
$digit = $code{$i};
$digit = $code[$i];
if ($digit == '-') {
$dval = 10;
} else {
@ -1918,7 +1918,7 @@ class TCPDFBarcode {
$p = 1;
$check = 0;
for ($i = $len; $i >= 0; --$i) {
$digit = $code{$i};
$digit = $code[$i];
if ($digit == '-') {
$dval = 10;
} else {
@ -1937,17 +1937,17 @@ class TCPDFBarcode {
$code = 'S'.$code.'S';
$len += 3;
for ($i = 0; $i < $len; ++$i) {
if (!isset($chr[$code{$i}])) {
if (!isset($chr[$code[$i]])) {
return false;
}
$seq = $chr[$code{$i}];
$seq = $chr[$code[$i]];
for ($j = 0; $j < 6; ++$j) {
if (($j % 2) == 0) {
$t = true; // bar
} else {
$t = false; // space
}
$w = $seq{$j};
$w = $seq[$j];
$bararray['bcode'][$k] = array('t' => $t, 'w' => $w, 'h' => 1, 'p' => 0);
$bararray['maxw'] += $w;
++$k;
@ -2016,7 +2016,7 @@ class TCPDFBarcode {
$bararray = array('code' => $code, 'maxw' => 0, 'maxh' => 2, 'bcode' => array());
$len = strlen($seq);
for ($i = 0; $i < $len; ++$i) {
switch ($seq{$i}) {
switch ($seq[$i]) {
case '1': {
$p = 1;
$h = 1;
@ -2255,7 +2255,7 @@ class TCPDFBarcode {
$bitval = 1;
$len = strlen($hex);
for($pos = ($len - 1); $pos >= 0; --$pos) {
$dec = bcadd($dec, bcmul(hexdec($hex{$pos}), $bitval));
$dec = bcadd($dec, bcmul(hexdec($hex[$pos]), $bitval));
$bitval = bcmul($bitval, 16);
}
return $dec;

View File

@ -531,10 +531,10 @@ class TCPDF_PARSER {
if ($char == '(') {
$open_bracket = 1;
while ($open_bracket > 0) {
if (!isset($this->pdfdata{$strpos})) {
if (!isset($this->pdfdata[$strpos])) {
break;
}
$ch = $this->pdfdata{$strpos};
$ch = $this->pdfdata[$strpos];
switch ($ch) {
case '\\': { // REVERSE SOLIDUS (5Ch) (Backslash)
// skip next character
@ -578,7 +578,7 @@ class TCPDF_PARSER {
}
case '<': // \x3C LESS-THAN SIGN
case '>': { // \x3E GREATER-THAN SIGN
if (isset($this->pdfdata{($offset + 1)}) AND ($this->pdfdata{($offset + 1)} == $char)) {
if (isset($this->pdfdata[($offset + 1)]) AND ($this->pdfdata[($offset + 1)] == $char)) {
// dictionary object
$objtype = $char.$char;
$offset += 2;

View File

@ -227,7 +227,7 @@ function generateSalt($len) {
$salt = '';
for ($i = 0; $i < $len; $i++) {
$pos = abs(getRandomNumber() % strlen($chars));
$salt .= $chars{$pos};
$salt .= $chars[$pos];
}
return $salt;
}
@ -802,6 +802,16 @@ function searchLDAPByFilter($filter, $attributes, $scopes, $attrsOnly = false) {
$typeManager = new \LAM\TYPES\TypeManager();
$types = $typeManager->getConfiguredTypesForScopes($scopes);
foreach ($types as $type) {
$additionalFilter = $type->getAdditionalLdapFilter();
if (!empty($additionalFilter)) {
if (strpos($additionalFilter, '(') !== 0) {
$additionalFilter = '(' . $additionalFilter . ')';
}
if (strpos($filter, '(') !== 0) {
$filter = '(' . $filter . ')';
}
$filter = '(&' . $additionalFilter . $filter . ')';
}
// search LDAP
$entries = searchLDAPPaged($_SESSION['ldap']->server(), escapeDN($type->getSuffix()),
$filter, $attributes, $readAttributesOnly, $_SESSION['config']->get_searchLimit());
@ -1059,6 +1069,10 @@ function getAbstractDN($dn) {
return '';
}
$dn = str_replace('\\,', '\\2C', $dn);
if (!empty($_SESSION['config']) && !empty($_SESSION['config']->getHideDnPart())) {
$partToCut = ',' . $_SESSION['config']->getHideDnPart();
$dn = str_replace($partToCut, '', $dn);
}
$parts = explode(',', $dn);
for ($i = 0; $i < sizeof($parts); $i++) {
$subparts = explode('=', $parts[$i]);
@ -1483,17 +1497,74 @@ function getExtendedLDAPErrorMessage($server) {
function getDefaultLDAPErrorString($server) {
$extError = htmlspecialchars(getExtendedLDAPErrorMessage($server));
// Active Directory message translations
if ((strpos($extError, 'DSID-031A120C') !== false) && (strpos($extError, '5003') !== false)) {
logNewMessage(LOG_DEBUG, 'Password change failed because of ' . $extError);
$extError = _('Your password does not meet the password strength qualifications. Please retry with another one.');
if (strpos($extError, 'DSID') !== false) {
if (strpos($extError, '5003') !== false) {
logNewMessage(LOG_DEBUG, 'Password change failed because of ' . $extError);
$extError = _('Your password does not meet the password strength qualifications. Please retry with another one.');
}
elseif (strpos($extError, 'data 530,') !== false) {
logNewMessage(LOG_DEBUG, 'Login failed because of ' . $extError);
$extError = _('Logon not permitted at this time');
}
elseif (strpos($extError, 'data 532,') !== false) {
logNewMessage(LOG_DEBUG, 'Login failed because of ' . $extError);
$extError = _('Password expired');
}
elseif (strpos($extError, 'data 533,') !== false) {
logNewMessage(LOG_DEBUG, 'Login failed because of ' . $extError);
$extError = _('Account is deactivated');
}
elseif (strpos($extError, 'data 701,') !== false) {
logNewMessage(LOG_DEBUG, 'Login failed because of ' . $extError);
$extError = _('Account is expired');
}
elseif (strpos($extError, 'data 773,') !== false) {
logNewMessage(LOG_DEBUG, 'Login failed because of ' . $extError);
$extError = _('Password change required');
}
elseif (strpos($extError, 'data 775,') !== false) {
logNewMessage(LOG_DEBUG, 'Login failed because of ' . $extError);
$extError = _('Account is locked');
}
}
$message = _('LDAP error, server says:') . ' ' . ldap_error($server);
if (!empty($extError)) {
$genericErrorMessage = ldap_error($server);
$message = _('LDAP error, server says:') . ' ' . $genericErrorMessage;
if (!empty($extError) && ($genericErrorMessage != $extError)) {
$message .= ' - ' . $extError;
}
return $message;
}
/**
* Tries to get additional information why invalid credentials was returned. E.g. account is locked.
*
* @param handle $ldap LDAP object to connect for getting extra data
* @param string $userDn failed DN
* @return string extra message
*/
function getExtraInvalidCredentialsMessage($ldap, $userDn) {
$attributes = array('dn', 'pwdaccountlockedtime', 'krbprincipalexpiration',
'krbpasswordexpiration', 'passwordexpirationtime');
$userData = ldapGetDN($userDn, $attributes, $ldap);
$now = new DateTime('now', getTimeZone());
if (!empty($userData['pwdaccountlockedtime'][0])) {
return _('Account is locked');
}
if (!empty($userData['krbprincipalexpiration'][0])) {
$kerberosExpirationDate = parseLDAPTimestamp($userData['krbprincipalexpiration'][0]);
if ($now >= $kerberosExpirationDate) {
return _('Kerberos account is expired');
}
}
if (!empty($userData['krbpasswordexpiration'][0])) {
$kerberosExpirationDate = parseLDAPTimestamp($userData['krbpasswordexpiration'][0]);
if ($now >= $kerberosExpirationDate) {
return _('Kerberos password is expired');
}
}
return null;
}
/**
* Returns the URL under which the page was loaded.
* This includes any GET parameters set.
@ -1751,6 +1822,16 @@ function getLAMVersionText() {
return $text . ' - ' . LAMVersion();
}
/**
* Returns if the given release is a developer version.
*
* @param string version
* @return bool is developer version
*/
function isDeveloperVersion($version) {
return strpos($version, 'DEV') !== false;
}
/**
* LAM exception with title and message.
*
@ -1759,6 +1840,8 @@ function getLAMVersionText() {
class LAMException extends Exception {
private $title;
private $ldapErrorCode;
/**
* Constructor.
@ -1766,10 +1849,12 @@ class LAMException extends Exception {
* @param string $title title
* @param string $message message (optional)
* @param Exception $cause (optional)
* @param int $ldapErrorCode original LDAP error code
*/
public function __construct($title, $message = null, $cause = null) {
public function __construct($title, $message = null, $cause = null, $ldapErrorCode = null) {
parent::__construct($message, null, $cause);
$this->title = $title;
$this->ldapErrorCode = $ldapErrorCode;
}
/**
@ -1781,6 +1866,15 @@ class LAMException extends Exception {
return $this->title;
}
/**
* Returns the original LDAP error code.
*
* @return int error code
*/
public function getLdapErrorCode() {
return $this->ldapErrorCode;
}
}
?>

View File

@ -1026,7 +1026,7 @@ abstract class baseModule {
* <li><b>Text (required)</b><br>
* The text of the help entry which may contain any alphanumeric characters.</li>
* <li><b>SeeAlso (optional)</b><br>
* A reference to anonther related web site. It must be an array containing a field called "text" with the link text
* A reference to another related web site. It must be an array containing a field called "text" with the link text
* that should be displayed and a field called "link" which is the link target.</li>
* </ul>
* <br>

View File

@ -100,9 +100,9 @@ function setlanguage() {
/**
* Checks whether a specific flag in the rights string is set.
*
* @param $right read,write or execute
* @param $target owner,group or other
* @param $chmod the chmod rights
* @param string $right read, write or execute
* @param string $target owner, group or other
* @param string $chmod the chmod rights
*
* @return true, if the chmod $right for $target were set
*/
@ -128,7 +128,7 @@ function checkChmod($right, $target, $chmod) {
}
// Cut the number from the chmod:
$chmod_num = $chmod{$chmod_num};
$chmod_num = $chmod[$chmod_num];
// Now check, if the chmod_num can be right with the $right
// What numbers allow "read"
@ -467,6 +467,9 @@ class LAMConfig {
/** overlay for referential integrity is activated */
private $referentialIntegrityOverlay = 'false';
/** hide password prompt for expired passwords */
private $hidePasswordPromptForExpiredPasswords = 'false';
/** Array of string: users with admin rights */
private $Admins;
@ -614,6 +617,8 @@ class LAMConfig {
private $twoFactorAuthenticationCaption = '';
private $twoFactorAuthenticationAttribute = '';
private $hideDnPart = '';
/** List of all settings in config file */
private $settings = array("ServerURL", "useTLS", "followReferrals", 'pagedResults', "Passwd", "Admins", "treesuffix",
"defaultLanguage", "scriptPath", "scriptServer", "scriptRights", "cachetimeout", 'serverDisplayName',
@ -626,7 +631,8 @@ class LAMConfig {
'scriptUserName', 'scriptSSHKey', 'scriptSSHKeyPassword', 'twoFactorAuthentication', 'twoFactorAuthenticationURL',
'twoFactorAuthenticationInsecure', 'twoFactorAuthenticationLabel', 'twoFactorAuthenticationOptional',
'twoFactorAuthenticationCaption', 'twoFactorAuthenticationClientId', 'twoFactorAuthenticationSecretKey',
'twoFactorAuthenticationDomain', 'twoFactorAuthenticationAttribute', 'referentialIntegrityOverlay'
'twoFactorAuthenticationDomain', 'twoFactorAuthenticationAttribute', 'referentialIntegrityOverlay',
'hidePasswordPromptForExpiredPasswords', 'hideDnPart'
);
@ -649,6 +655,78 @@ class LAMConfig {
$this->reload();
}
/**
* Returns the server profile data.
*
* @return array data
*/
public function exportData() {
$data = array();
$settingsToIgnore = array('modules', 'types', 'tools', 'jobs');
foreach ($this->settings as $setting) {
if (in_array($setting, $settingsToIgnore)) {
continue;
}
$data[$setting] = $this->$setting;
}
$data['typeSettings'] = $this->typeSettings;
$data['moduleSettings'] = $this->moduleSettings;
$data['toolSettings'] = $this->toolSettings;
$data['jobSettings'] = $this->jobSettings;
if ($this->jobsDatabase === 'SQLite') {
$dbFileName = __DIR__ . '/../config/' . $this->getName() . '.sqlite';
if (is_file($dbFileName) && is_readable($dbFileName)) {
$file = @fopen($dbFileName, "r");
if ($file) {
$dbData = fread($file, 100000000);
fclose($file);
$data['jobSQLite'] = base64_encode($dbData);
}
}
}
return $data;
}
/**
* Imports server profile data.
*
* @param array $data config data
* @throws LAMException import error
*/
public function importData($data) {
$settingsToIgnore = array('modules', 'types', 'tools', 'jobs', 'typeSettings',
'moduleSettings', 'toolSettings', 'jobSettings', 'jobSQLite');
foreach ($data as $dataKey => $dataValue) {
if (in_array($dataKey, $settingsToIgnore)) {
continue;
}
if (!in_array($dataKey, $this->settings)) {
logNewMessage(LOG_WARNING, 'Ignored setting during import: ' . $dataKey);
continue;
}
if (!(($dataValue === null) || is_array($dataValue) || is_string($dataValue) || is_int($dataValue) || is_bool($dataValue))) {
throw new LAMException('Invalid import data type for ' . htmlspecialchars($dataKey) . ': ' . gettype($dataValue));
}
$this->$dataKey = $dataValue;
}
$typeSettingsData = !empty($data['typeSettings']) && is_array($data['typeSettings']) ? $data['typeSettings'] : array();
$this->typeSettings = $typeSettingsData;
$moduleSettingsData = !empty($data['moduleSettings']) && is_array($data['moduleSettings']) ? $data['moduleSettings'] : array();
$this->moduleSettings = $moduleSettingsData;
$toolSettingsData = !empty($data['toolSettings']) && is_array($data['toolSettings']) ? $data['toolSettings'] : array();
$this->toolSettings = $toolSettingsData;
$jobSettingsData = !empty($data['jobSettings']) && is_array($data['jobSettings']) ? $data['jobSettings'] : array();
$this->jobSettings = $jobSettingsData;
if (!empty($data['jobSQLite'])) {
$dbFileName = __DIR__ . '/../config/' . $this->getName() . '.sqlite';
$file = @fopen($dbFileName, "wb");
if ($file) {
fputs($file, base64_decode($data['jobSQLite']));
fclose($file);
}
}
}
/**
* Reloads preferences from config file
*
@ -759,6 +837,10 @@ class LAMConfig {
/** Saves preferences to config file */
public function save() {
$conffile = $this->getPath();
if (!file_exists($conffile)) {
$newFile = fopen($conffile, 'wb');
fclose($newFile);
}
if (is_file($conffile) && is_readable($conffile)) {
$file = fopen($conffile, "r");
$file_array = array();
@ -857,6 +939,9 @@ class LAMConfig {
if (!in_array("referentialIntegrityOverlay", $saved)) {
array_push($file_array, "\n" . "referentialIntegrityOverlay: " . $this->referentialIntegrityOverlay . "\n");
}
if (!in_array("hidePasswordPromptForExpiredPasswords", $saved)) {
array_push($file_array, "\n" . "hidePasswordPromptForExpiredPasswords: " . $this->hidePasswordPromptForExpiredPasswords . "\n");
}
if (!in_array("Passwd", $saved)) {
array_push($file_array, "\n\n# password to change these preferences via webfrontend\n" . "Passwd: " . $this->Passwd . "\n");
}
@ -1010,6 +1095,9 @@ class LAMConfig {
if (!in_array("twoFactorAuthenticationAttribute", $saved)) {
array_push($file_array, "\n" . "twoFactorAuthenticationAttribute: " . $this->twoFactorAuthenticationAttribute . "\n");
}
if (!in_array("hideDnPart", $saved)) {
array_push($file_array, "\n" . "hideDnPart: " . $this->hideDnPart . "\n");
}
// check if all module settings were added
$m_settings = array_keys($this->moduleSettings);
for ($i = 0; $i < sizeof($m_settings); $i++) {
@ -1100,9 +1188,11 @@ class LAMConfig {
* @return boolean true if $value has correct format
*/
public function set_ServerURL($value) {
if (is_string($value)) $this->ServerURL = $value;
else return false;
return true;
if (is_string($value)) {
$this->ServerURL = $value;
return true;
}
return false;
}
/**
@ -1133,9 +1223,11 @@ class LAMConfig {
* @return boolean true if $value has correct format
*/
public function setServerDisplayName($value) {
if (is_string($value)) $this->serverDisplayName = $value;
else return false;
return true;
if (is_string($value)) {
$this->serverDisplayName = $value;
return true;
}
return false;
}
/**
@ -1224,6 +1316,33 @@ class LAMConfig {
return $this->referentialIntegrityOverlay === 'true';
}
/**
* Hide password prompt for expired passwords.
*
* @return String true or false
*/
public function getHidePasswordPromptForExpiredPasswords() {
return $this->hidePasswordPromptForExpiredPasswords;
}
/**
* Sets if password prompt for expired passwords is hidden.
*
* @param String $hidePasswordPromptForExpiredPasswords true or false
*/
public function setHidePasswordPromptForExpiredPasswords($hidePasswordPromptForExpiredPasswords) {
$this->hidePasswordPromptForExpiredPasswords = $hidePasswordPromptForExpiredPasswords;
}
/**
* Hide password prompt for expired passwords.
*
* @return bool is hidden
*/
public function isHidePasswordPromptForExpiredPasswords() {
return $this->hidePasswordPromptForExpiredPasswords === 'true';
}
/**
* Returns an array of string with all admin names
*
@ -1250,11 +1369,11 @@ class LAMConfig {
*/
public function set_Adminstring($value) {
if (is_string($value) &&
preg_match("/^[^;]+(;[^;]+)*$/", $value)) {
preg_match("/^[^;]+(;[^;]+)*$/", $value)) {
$this->Admins = $value;
return true;
}
else return false;
return true;
return false;
}
/**
@ -1330,7 +1449,9 @@ class LAMConfig {
* @return boolean true if $value has correct format
*/
public function set_Suffix($scope, $value) {
if (!$value) $value = "";
if (!$value) {
$value = "";
}
elseif (!is_string($value)) {
return false;
}
@ -1695,9 +1816,9 @@ class LAMConfig {
public function set_searchLimit($value) {
if (is_numeric($value) && ($value > -1)) {
$this->searchLimit = $value;
return true;
}
else return false;
return true;
return false;
}
/**
@ -1775,7 +1896,7 @@ class LAMConfig {
/**
* Returns a list of active account types.
*
* @return array list of types
* @return string[] list of types
*/
public function get_ActiveTypes() {
if (($this->activeTypes == '') || !isset($this->activeTypes)) {
@ -1789,7 +1910,7 @@ class LAMConfig {
/**
* Sets the list of active types.
*
* @param array list of types
* @param string[] list of types
*/
public function set_ActiveTypes($types) {
$this->activeTypes = implode(",", $types);
@ -2531,6 +2652,24 @@ class LAMConfig {
$this->twoFactorAuthenticationAttribute = $twoFactorAuthenticationAttribute;
}
/**
* Returns the DN part to hide.
*
* @return string DN part
*/
public function getHideDnPart() {
return $this->hideDnPart;
}
/**
* Sets the DN part to hide.
*
* @param string $hideDnPart DN part
*/
public function setHideDnPart($hideDnPart) {
$this->hideDnPart = $hideDnPart;
}
}
@ -2681,6 +2820,83 @@ class LAMCfgMain {
$this->reload();
}
/**
* Exports the configuration data.
*
* @return array config data
*/
public function exportData() {
$data = array();
foreach ($this->settings as $setting) {
$data[$setting] = $this->$setting;
}
return $data;
}
/**
* Imports configuration data.
*
* @param array $data config data
* @throws LAMException import error
*/
public function importData($data) {
foreach ($data as $dataKey => $dataValue) {
if (!in_array($dataKey, $this->settings)) {
logNewMessage(LOG_WARNING, 'Ignored setting during import: ' . $dataKey);
continue;
}
if (!(($dataValue === null) || is_array($dataValue) || is_string($dataValue) || is_int($dataValue) || is_bool($dataValue))) {
throw new LAMException('Invalid import data type for ' . htmlspecialchars($dataKey) . ': ' . gettype($dataValue));
}
$this->$dataKey = $dataValue;
}
}
/**
* Returns the content of the server certificates file
*
* @return null|string certificates
*/
public function exportCertificates() {
$fileName = $this->getSSLCaCertPath();
if ($fileName === null) {
return null;
}
$content = null;
$handle = @fopen($fileName, "r");
if ($handle) {
$content = fread($handle, 10000000);
fclose($handle);
}
return $content;
}
/**
* Imports the server certificates.
*
* @param null|string $certsContent certificates
* @throws LAMException write to file failed
*/
public function importCertificates($certsContent) {
$fileName = $this->getSSLCaCertPath();
if (empty($certsContent)) {
if ($fileName !== null) {
unlink($fileName);
}
return;
}
$fileName = $this->getInternalSSLCaCertFileName();
$handle = @fopen($fileName, "wb");
if ($handle) {
fputs($handle, $certsContent);
fclose($handle);
@chmod($fileName, 0600);
}
else {
throw new LAMException(printf(_('Unable to write file %s.'), $fileName));
}
}
/**
* Reloads preferences from config file config.cfg
*

View File

@ -3078,10 +3078,25 @@ class htmlStatusMessage extends htmlElement {
* @return array List of input field names and their type (name => type)
*/
public function generateHTML($module, $input, $values, $restricted, &$tabindex, $scope) {
if (!empty($this->cssClasses)) {
echo '<div class="' . implode(' ', $this->cssClasses) . '">';
}
StatusMessage($this->type, $this->title, $this->text, $this->params);
if (!empty($this->cssClasses)) {
echo '</div>';
}
return array();
}
/**
* Returns the message type.
*
* @return String type
*/
public function getType() {
return $this->type;
}
}
/**
@ -3335,6 +3350,8 @@ class htmlLink extends htmlElement {
private $onClick = null;
/** show as button */
private $showAsButton = false;
/** link id */
private $id;
/**
* Constructor.
@ -3384,8 +3401,8 @@ class htmlLink extends htmlElement {
$onClick = ' onclick="' . $this->onClick . '"';
}
$idAttr = '';
if ($this->showAsButton) {
$id = 'a_' . preg_replace('/[^a-zA-Z0-9_]+/', '_', $this->target);
if ($this->showAsButton || !empty($this->id)) {
$id = !empty($this->id) ? $this->id : 'a_' . preg_replace('/[^a-zA-Z0-9_]+/', '_', $this->target);
$idAttr = ' id="' . $id . '"';
}
$classAttr = '';
@ -3448,6 +3465,15 @@ class htmlLink extends htmlElement {
$this->onClick = htmlspecialchars($event);
}
/**
* Sets the element id.
*
* @param string $id unique id
*/
public function setId($id) {
$this->id = $id;
}
}
/**
@ -3644,6 +3670,8 @@ class htmlSpan extends htmlElement {
/** htmlElement that generates inner content */
private $content = null;
/** onclick handler */
private $onclick = null;
/**
* Constructor.
@ -3674,13 +3702,27 @@ class htmlSpan extends htmlElement {
if (($this->cssClasses != null) && (sizeof($this->cssClasses) > 0)) {
$classesValue = ' class="' . implode(' ', $this->cssClasses) . '"';
}
echo '<span' . $classesValue . '>';
$onclickHandler = '';
if (!empty($this->onclick)) {
$onclickHandler = ' onclick="' . $this->onclick . '"';
}
echo '<span' . $classesValue . $onclickHandler . '>';
if ($this->content != null) {
$return = $this->content->generateHTML($module, $input, $values, $restricted, $tabindex, $scope);
}
echo '</span>';
return $return;
}
/**
* Sets the onclick event.
*
* @param string $event event handler code
*/
public function setOnclick($event) {
$this->onclick = $event;
}
}
/**
@ -4715,6 +4757,8 @@ class htmlResponsiveInputCheckbox extends htmlInputCheckbox {
private $renderParentHtml = false;
/** long label */
private $longLabel = false;
/** label after checkbox */
private $labelAfterCheckbox = false;
/**
* Constructor.
@ -4751,14 +4795,16 @@ class htmlResponsiveInputCheckbox extends htmlInputCheckbox {
$row = new htmlResponsiveRow();
$tabletColumnsLabel = 6;
$tabletColumnsBox = 6;
$mobileColumnsLabel = 10;
$mobileColumnsBox = 2;
if ($this->longLabel) {
$tabletColumnsLabel = 10;
$tabletColumnsBox = 2;
}
// label text
$labelGroup = new htmlGroup();
$labelGroup->addElement(new htmlOutputText($this->label));
$row->add($labelGroup, 10, $tabletColumnsLabel, $tabletColumnsLabel, 'responsiveLabel');
$text = new htmlSpan(new htmlOutputText($this->label));
$text->setCSSClasses($this->cssClasses);
$text->setOnclick('jQuery(\'#' . $this->name . '\').prop(\'checked\',!jQuery(\'#' . $this->name . '\').prop(\'checked\')); jQuery(\'#' . $this->name . '\').change();');
// input field
$fieldGroup = new htmlGroup();
$fieldGroup->addElement($this);
@ -4767,7 +4813,14 @@ class htmlResponsiveInputCheckbox extends htmlInputCheckbox {
$helpLink->setCSSClasses(array('margin-left5 align-unset-img'));
$fieldGroup->addElement($helpLink);
}
$row->add($fieldGroup, 2, $tabletColumnsBox, $tabletColumnsBox, 'responsiveField nowrap');
if ($this->labelAfterCheckbox) {
$row->add($fieldGroup, $mobileColumnsBox, $tabletColumnsBox, $tabletColumnsBox, 'responsiveLabel nowrap');
$row->add($text, $mobileColumnsLabel, $tabletColumnsLabel, $tabletColumnsLabel, 'responsiveField');
}
else {
$row->add($text, $mobileColumnsLabel, $tabletColumnsLabel, $tabletColumnsLabel, 'responsiveLabel');
$row->add($fieldGroup, $mobileColumnsBox, $tabletColumnsBox, $tabletColumnsBox, 'responsiveField nowrap');
}
return $row->generateHTML($module, $input, $values, $restricted, $tabindex, $scope);
}
@ -4779,6 +4832,15 @@ class htmlResponsiveInputCheckbox extends htmlInputCheckbox {
return '.row';
}
/**
* Sets if the label should be shown after the checkbox instead before it.
*
* @param bool $labelAfterCheckbox show label after box
*/
public function setLabelAfterCheckbox($labelAfterCheckbox = true) {
$this->labelAfterCheckbox = $labelAfterCheckbox;
}
}
/**
@ -4928,5 +4990,68 @@ class htmlResponsiveTable extends htmlElement {
}
/**
* Renders a canvas.
*
* @author Roland Gruber
*/
class htmlCanvas extends htmlElement {
private $id;
/**
* Constructor
*
* @param string $id html id
*/
public function __construct($id) {
$this->id = $id;
}
/**
* @inheritDoc
*/
function generateHTML($module, $input, $values, $restricted, &$tabindex, $scope) {
$classesValue = '';
if (!empty($this->cssClasses)) {
$classesValue = ' class="' . implode(' ', $this->cssClasses) . '"';
}
echo '<canvas id="' . $this->id . '" ' . $classesValue . '>';
echo '</canvas>';
return array();
}
}
/**
* Renders a video.
*
* @author Roland Gruber
*/
class htmlVideo extends htmlElement {
private $id;
/**
* Constructor
*
* @param string $id html id
*/
public function __construct($id) {
$this->id = $id;
}
/**
* @inheritDoc
*/
function generateHTML($module, $input, $values, $restricted, &$tabindex, $scope) {
$classesValue = '';
if (!empty($this->cssClasses)) {
$classesValue = ' class="' . implode(' ', $this->cssClasses) . '"';
}
echo '<video id="' . $this->id . '" ' . $classesValue . '>';
echo '</video>';
return array();
}
}
?>

View File

@ -63,11 +63,12 @@ class Importer {
$entries = array();
$currentEntry = array();
foreach ($lines as $line) {
if (substr(trim($line), 0, 1) === '#') {
$trimmedLine = trim($line);
if (substr($trimmedLine, 0, 1) === '#') {
// skip comments
continue;
}
if (empty(trim($line))) {
if (empty($trimmedLine)) {
// end of entry
if (!empty($currentEntry)) {
$entries[] = $currentEntry;

View File

@ -67,19 +67,19 @@ class Ldap{
}
/**
* Connects to the server using the given username and password
*
* @param string $user user name
* @param string $passwd password
* @param boolean $allowAnonymous specifies if anonymous binds are allowed
* @return mixed if connect succeeds the 0 is returned, else false or error number
*/
* Connects to the server using the given username and password
*
* @param string $user user name
* @param string $passwd password
* @param boolean $allowAnonymous specifies if anonymous binds are allowed
* @throws LAMException unable to connect
*/
public function connect($user, $passwd, $allowAnonymous=false) {
// close any prior connection
@$this->close();
// do not allow anonymous bind
if (!$allowAnonymous && ((!$user)||($user == "")||(!$passwd))) {
return false;
throw new LAMException(_("Cannot connect to specified LDAP server. Please try again."));
}
// save password und username encrypted
$this->encrypt_login($user, $passwd);
@ -92,24 +92,36 @@ class Ldap{
ldap_set_option($this->server,LDAP_OPT_REFERRALS, $followReferrals);
$bind = @ldap_bind($this->server, $user, $passwd);
if ($bind) {
$return = ldap_errno($this->server);
$this->is_connected = true;
// return success number
return $return;
return;
}
// return error number
$errorNumber = ldap_errno($this->server);
$clientSource = empty($_SERVER['REMOTE_ADDR']) ? '' : $_SERVER['REMOTE_ADDR'];
if (($errorNumber === False)
|| ($errorNumber == 81)) {
// connection failed
logNewMessage(LOG_ERR, 'User ' . $user . ' (' . $clientSource . ') failed to log in (LDAP error: ' . getDefaultLDAPErrorString($this->server) . ').');
throw new LAMException(_("Cannot connect to specified LDAP server. Please try again."), null, null, $errorNumber);
}
elseif ($errorNumber == 49) {
// user name/password invalid. Return to login page.
logNewMessage(LOG_ERR, 'User ' . $user . ' (' . $clientSource . ') failed to log in (wrong password). ' . getDefaultLDAPErrorString($this->server));
throw new LAMException(_("Wrong password/user name combination. Please try again."), getDefaultLDAPErrorString($this->server), null, $errorNumber);
}
else {
return ldap_errno($this->server);
// other errors
logNewMessage(LOG_ERR, 'User ' . $user . ' (' . $clientSource . ') failed to log in (LDAP error: ' . getDefaultLDAPErrorString($this->server) . ').');
throw new LAMException(_("Cannot connect to specified LDAP server. Please try again."), "($errorNumber) " . getDefaultLDAPErrorString($this->server), null, $errorNumber);
}
}
else {
return false;
}
throw new LAMException(_("Cannot connect to specified LDAP server. Please try again."));
}
/** Closes connection to server */
public function close() {
if ($this->server != null) {
$this->is_connected = false;
@ldap_close($this->server);
}
}
@ -121,8 +133,13 @@ class Ldap{
*/
public function server() {
if (!$this->is_connected) {
$this->connect($this->getUserName(), $this->getPassword());
$this->is_connected = true;
try {
$this->connect($this->getUserName(), $this->getPassword());
$this->is_connected = true;
}
catch (LAMException $e) {
logNewMessage(LOG_ERR, $e->getTitle() . ' ' . $e->getMessage());
}
}
return $this->server;
}

View File

@ -759,7 +759,7 @@ class lamList {
$selAccounts[] = $id;
}
// get possible PDF structures
$pdf_structures = \LAM\PDF\getPDFStructures($this->type->getId());
$pdf_structures = \LAM\PDF\getPDFStructures($this->type->getId(), $_SESSION['config']->getName());
$this->printHeader();

View File

@ -1069,6 +1069,7 @@ class accountContainer {
$suffixSelect->setHasDescriptiveElements(true);
$suffixSelect->setRightToLeftTextDirection(true);
$suffixSelect->setShortLabel();
$suffixSelect->setSortElements(false);
$titleBarSuffixRdn->add($suffixSelect, 12, 12, 7);
// RDN selection
$rdnlist = getRDNAttributes($this->type->getId());
@ -1454,7 +1455,7 @@ class accountContainer {
*/
private function loadProfileIfRequested() {
if (isset($_POST['accountContainerLoadProfile']) && isset($_POST['accountContainerSelectLoadProfile'])) {
$profile = \LAM\PROFILES\loadAccountProfile($_POST['accountContainerSelectLoadProfile'], $this->type->getId());
$profile = \LAM\PROFILES\loadAccountProfile($_POST['accountContainerSelectLoadProfile'], $this->type->getId(), $_SESSION['config']->getName());
$this->lastLoadedProfile = $_POST['accountContainerSelectLoadProfile'];
// pass profile to each module
$modules = array_keys($this->module);
@ -1775,7 +1776,7 @@ class accountContainer {
$this->lastLoadedProfile = $cookieProfileName;
}
}
$profile = \LAM\PROFILES\loadAccountProfile($profileName, $this->type->getId());
$profile = \LAM\PROFILES\loadAccountProfile($profileName, $this->type->getId(), $_SESSION['config']->getName());
// pass profile to each module
$modules = array_keys($this->module);
foreach ($modules as $module) $this->module[$module]->load_profile($profile);

View File

@ -1629,7 +1629,7 @@ class inetOrgPerson extends baseModule implements passwordService {
if ($this->isAdminReadOnly('jpegPhoto')) {
return array();
}
if (isset($_POST['form_subpage_' . get_class($this) . '_photo_upload'])) {
if (isset($_POST['form_subpage_' . get_class($this) . '_photo_upload']) || isset($_POST['webcamData'])) {
return $this->uploadPhoto();
}
if (isset($_POST['form_subpage_' . get_class($this) . '_attributes_crop'])) {
@ -1656,40 +1656,46 @@ class inetOrgPerson extends baseModule implements passwordService {
*/
private function uploadPhoto() {
$messages = array();
if ($_FILES['photoFile'] && ($_FILES['photoFile']['size'] > 0)) {
if ((empty($_FILES['photoFile']) || ($_FILES['photoFile']['size'] <= 0)) && empty($_POST['webcamData'])) {
$messages[] = $this->messages['file'][0];
return $messages;
}
if (!empty($_FILES['photoFile']['tmp_name'])) {
$handle = fopen($_FILES['photoFile']['tmp_name'], "r");
$data = fread($handle, 100000000);
fclose($handle);
if (!empty($this->moduleSettings['inetOrgPerson_jpegPhoto_maxSize'][0]) && (strlen($data) > (1024 * $this->moduleSettings['inetOrgPerson_jpegPhoto_maxSize'][0]))) {
$errMsg = $this->messages['file'][3];
$errMsg[] = null;
$errMsg[] = array($this->moduleSettings['inetOrgPerson_jpegPhoto_maxSize'][0]);
return array($errMsg);
}
fclose($handle);
// convert to JPG
try {
include_once dirname(__FILE__) . '/../imageutils.inc';
$imageManipulator = ImageManipulationFactory::getImageManipulator($data);
// resize if maximum values specified
if (!empty($this->moduleSettings['inetOrgPerson_jpegPhoto_maxWidth'][0]) || !empty($this->moduleSettings['inetOrgPerson_jpegPhoto_maxHeight'][0])) {
$maxWidth = empty($this->moduleSettings['inetOrgPerson_jpegPhoto_maxWidth'][0]) ? $imageManipulator->getWidth() : $this->moduleSettings['inetOrgPerson_jpegPhoto_maxWidth'][0];
$maxHeight = empty($this->moduleSettings['inetOrgPerson_jpegPhoto_maxHeight'][0]) ? $imageManipulator->getHeight() : $this->moduleSettings['inetOrgPerson_jpegPhoto_maxHeight'][0];
$imageManipulator->thumbnail($maxWidth, $maxHeight);
}
$imageManipulator->convertToJpeg();
$data = $imageManipulator->getImageData();
}
catch (Exception $e) {
$msg = $this->messages['file'][2];
$msg[] = htmlspecialchars($e->getMessage());
$messages[] = $msg;
return $messages;
}
$this->attributes['jpegPhoto'][0] = $data;
}
else {
$messages[] = $this->messages['file'][0];
elseif (isset($_POST['webcamData'])) {
$data = $_POST['webcamData'];
$data = str_replace('data:image/png;base64,', '', $data);
$data = base64_decode($data);
}
// convert to JPG
try {
include_once dirname(__FILE__) . '/../imageutils.inc';
$imageManipulator = ImageManipulationFactory::getImageManipulator($data);
// resize if maximum values specified
if (!empty($this->moduleSettings['inetOrgPerson_jpegPhoto_maxWidth'][0]) || !empty($this->moduleSettings['inetOrgPerson_jpegPhoto_maxHeight'][0])) {
$maxWidth = empty($this->moduleSettings['inetOrgPerson_jpegPhoto_maxWidth'][0]) ? $imageManipulator->getWidth() : $this->moduleSettings['inetOrgPerson_jpegPhoto_maxWidth'][0];
$maxHeight = empty($this->moduleSettings['inetOrgPerson_jpegPhoto_maxHeight'][0]) ? $imageManipulator->getHeight() : $this->moduleSettings['inetOrgPerson_jpegPhoto_maxHeight'][0];
$imageManipulator->thumbnail($maxWidth, $maxHeight);
}
$imageManipulator->convertToJpeg();
$data = $imageManipulator->getImageData();
}
catch (Exception $e) {
$msg = $this->messages['file'][2];
$msg[] = htmlspecialchars($e->getMessage());
$messages[] = $msg;
return $messages;
}
$this->attributes['jpegPhoto'][0] = $data;
return $messages;
}
@ -1704,9 +1710,33 @@ class inetOrgPerson extends baseModule implements passwordService {
$container->add(new htmlSubTitle(_('Upload image')), 12);
$label = _('Photo file');
$container->add(new htmlResponsiveInputFileUpload('photoFile', $label, 'photoUpload'), 12);
$container->addVerticalSpacer('0.5rem');
$container->addLabel(new htmlOutputText('&nbsp;', false));
$container->addField(new htmlAccountPageButton(get_class($this), 'photo', 'upload', _('Upload')));
$container->addVerticalSpacer('1rem');
$container->addLabel(new htmlAccountPageButton(get_class($this), 'photo', 'upload', _('Upload')));
$container->addField(new htmlAccountPageButton(get_class($this), 'attributes', 'back', _('Back')));
$webcamContent = new htmlResponsiveRow();
$webcamContent->add(new htmlSubTitle(_('Use webcam')), 12);
$errorMessage = new htmlStatusMessage('ERROR', '');
$errorMessage->setCSSClasses(array('hidden', 'lam-webcam-message'));
$webcamContent->add($errorMessage, 12);
$captureButton = new htmlButton('lam-webcam-capture', _('Start capture'));
$captureButton->setOnClick('window.lam.tools.webcam.capture(event);');
$webcamContent->add($captureButton, 12, 12, 12, 'text-center');
$video = new htmlVideo('lam-webcam-video');
$video->setCSSClasses(array('hidden'));
$webcamContent->add($video, 12, 12, 12, 'text-center');
$webcamContent->addVerticalSpacer('0.5rem');
$webcamUploadButton = new htmlButton('uploadWebcam', _('Upload'));
$webcamUploadButton->setCSSClasses(array('btn-lam-webcam-upload', 'hidden'));
$webcamUploadButton->setOnClick('window.lam.tools.webcam.upload();');
$webcamContent->add($webcamUploadButton, 12, 12, 12, 'text-center');
$canvas = new htmlCanvas('lam-webcam-canvas');
$canvas->setCSSClasses(array('hidden'));
$webcamContent->add($canvas, 12);
$webcamDiv = new htmlDiv('lam_webcam_div', $webcamContent, array('hidden'));
$container->add($webcamDiv, 12);
$container->addVerticalSpacer('1rem');
$container->add(new htmlAccountPageButton(get_class($this), 'attributes', 'back', _('Back')), 12);
}
else {
$container->add(new htmlSubTitle(_('Crop image')), 12);
@ -3062,6 +3092,33 @@ class inetOrgPerson extends baseModule implements passwordService {
$uploadStatus = new htmlDiv('inetOrgPersonPhotoUploadStatus', new htmlOutputText(''));
$uploadStatus->setCSSClasses(array('qq-upload-list'));
$row->add($uploadStatus, 12);
// webcam button
$webcamContent = new htmlResponsiveRow();
$webcamContent->addVerticalSpacer('0.5rem');
$errorMessage = new htmlStatusMessage('ERROR', '');
$errorMessage->setCSSClasses(array('hidden', 'lam-webcam-message'));
$webcamContent->add($errorMessage, 12);
$webcamContent->addVerticalSpacer('0.5rem');
$captureButton = new htmlLink(_('Use webcam'), '#', '../../graphics/webcam.png', true);
$captureButton->setId('btn_lam-webcam-capture');
$captureButton->setOnClick('window.lam.tools.webcam.capture(event);');
$webcamContent->add($captureButton, 12, 12, 12);
$video = new htmlVideo('lam-webcam-video');
$video->setCSSClasses(array('hidden'));
$webcamContent->add($video, 12, 12, 12, 'text-center');
$webcamContent->addVerticalSpacer('1rem');
$webcamUploadButton = new htmlLink(_('Upload'), '#', '../../graphics/up.gif', true);
$webcamUploadButton->setId('btn-lam-webcam-upload');
$webcamUploadButton->setCSSClasses(array('btn-lam-webcam-upload', 'hidden'));
$webcamUploadButton->setOnClick('window.lam.tools.webcam.uploadSelfService(event, "' . getSecurityTokenName()
. '", "' . getSecurityTokenValue() . '", "inetOrgPerson", "user", "' . _('File upload failed!') . '", "inetOrgPersonPhotoUploadContent");');
$webcamContent->add($webcamUploadButton, 12, 12, 12);
$canvas = new htmlCanvas('lam-webcam-canvas');
$canvas->setCSSClasses(array('hidden'));
$webcamContent->add($canvas, 12);
$webcamDiv = new htmlDiv('lam_webcam_div', $webcamContent, array('hidden'));
$webcamContent->addVerticalSpacer('1rem');
$row->add($webcamDiv, 12);
return $row;
}
@ -3095,6 +3152,7 @@ class inetOrgPerson extends baseModule implements passwordService {
if (data.success) {
if (data.html) {
jQuery(\'#inetOrgPersonPhotoUploadContent\').html(data.html);
window.lam.tools.webcam.init();
}
}
else {
@ -3119,6 +3177,7 @@ class inetOrgPerson extends baseModule implements passwordService {
function inetOrgPersonDeletePhotoHandleReply(data) {
if (data.errorsOccured == "false") {
jQuery(\'#inetOrgPersonPhotoUploadContent\').html(data.html);
window.lam.tools.webcam.init();
}
else {
alert(data.errormessage);
@ -3790,13 +3849,20 @@ class inetOrgPerson extends baseModule implements passwordService {
*/
private function ajaxUploadPhoto() {
$result = array('success' => true);
if (!isset($_FILES['qqfile']) || ($_FILES['qqfile']['size'] < 100)) {
if ((!isset($_FILES['qqfile']) || ($_FILES['qqfile']['size'] < 100)) && empty($_POST['webcamData'])) {
$result = array('error' => _('No file received.'));
}
else {
$handle = fopen($_FILES['qqfile']['tmp_name'], "r");
$data = fread($handle, 100000000);
fclose($handle);
if (empty($_POST['webcamData'])) {
$handle = fopen($_FILES['qqfile']['tmp_name'], "r");
$data = fread($handle, 100000000);
fclose($handle);
}
else {
$data = $_POST['webcamData'];
$data = str_replace('data:image/png;base64,', '', $data);
$data = base64_decode($data);
}
try {
include_once dirname(__FILE__) . '/../imageutils.inc';
$imageManipulator = ImageManipulationFactory::getImageManipulator($data);

View File

@ -631,7 +631,7 @@ class posixAccount extends baseModule implements passwordService {
// Remove primary group from additional groups
if (!isset($this->moduleSettings['posixAccount_primaryGroupAsSecondary'][0])
|| ($this->moduleSettings['posixAccount_primaryGroupAsSecondary'][0] != 'true')) {
for ($i=0; $i<count($this->groups); $i++) {
for ($i = 0; $i < count($this->groups); $i++) {
if ($this->groups[$i] == $this->getGroupName($this->attributes['gidNumber'][0])) {
unset($this->groups[$i]);
}
@ -639,8 +639,21 @@ class posixAccount extends baseModule implements passwordService {
}
else {
// add user as memberuid in primary group
if (!in_array($this->getGroupName($this->attributes['gidNumber'][0]), $this->groups)) {
$this->groups[] = $this->getGroupName($this->attributes['gidNumber'][0]);
$primaryGroupName = $this->getGroupName($this->attributes['gidNumber'][0]);
if (!in_array($primaryGroupName, $this->groups)) {
$this->groups[] = $primaryGroupName;
}
// add user as member in group of names if auto-sync is activated
if ($this->isBooleanConfigOptionSet('posixGroup_autoSyncGon')) {
$allGons = $this->findGroupOfNames();
foreach ($allGons as $gonDn => $gonData) {
if (in_array_ignore_case('posixGroup', $gonData['objectclass'])) {
$gonCn = $gonData['cn'][0];
if (($gonCn === $primaryGroupName) && !in_array($gonDn, $this->gonList)) {
$this->gonList[] = $gonDn;
}
}
}
}
}
@ -1034,6 +1047,21 @@ class posixAccount extends baseModule implements passwordService {
if (!empty($oldGroupName) && !empty($newGroupName)) {
$this->groups = array_delete(array($oldGroupName), $this->groups);
$this->groups[] = $newGroupName;
// sync group of names if needed
if ($this->isBooleanConfigOptionSet('posixGroup_autoSyncGon')) {
$allGons = $this->findGroupOfNames();
foreach ($allGons as $gonDn => $gonData) {
if (in_array_ignore_case('posixGroup', $gonData['objectclass'])) {
$gonCn = $gonData['cn'][0];
if (($gonCn === $newGroupName) && !in_array($gonDn, $this->gonList)) {
$this->gonList[] = $gonDn;
}
if (($gonCn === $oldGroupName) && in_array($gonDn, $this->gonList)) {
$this->gonList = array_delete(array($gonDn), $this->gonList);
}
}
}
}
}
}
}

View File

@ -201,7 +201,7 @@ class posixGroup extends baseModule implements passwordService {
if ($this->autoAddObjectClasses || (isset($this->attributes['objectClass']) && in_array('posixGroup', $this->attributes['objectClass']))) {
// auto sync group members
if ($this->isBooleanConfigOptionSet('posixGroup_autoSyncGon')) {
$this->syncGon();
$this->syncGon(true);
}
// group name
if ($this->manageCnAndDescription($modules)) {
@ -327,6 +327,9 @@ class posixGroup extends baseModule implements passwordService {
if ($gon == null) {
$gon = $this->getAccountContainer()->getAccountModule('groupOfUniqueNames');
}
if ($gon == null) {
$gon = $this->getAccountContainer()->getAccountModule('groupOfMembers');
}
if ($gon != null) {
$return->addVerticalSpacer('2rem');
$syncButton = new htmlButton('syncGON', sprintf(_('Sync from %s'), $gon->get_alias()));
@ -514,7 +517,7 @@ class posixGroup extends baseModule implements passwordService {
$this->addAccountSpecificConfigOptions($configContainer, $typeId);
$configContainer->addVerticalSpacer('2rem');
}
$gonModules = array('groupOfNames', 'groupOfUniqueNames');
$gonModules = array('groupOfNames', 'groupOfUniqueNames', 'groupOfMembers');
$gonFound = false;
foreach ($gonModules as $gonModule) {
if (!empty($allScopes[$gonModule])) {
@ -969,15 +972,19 @@ class posixGroup extends baseModule implements passwordService {
/**
* Syncs with group of names members.
*
* @param bool $forceDelete force deletion of members
* @return array list of status messages
*/
protected function syncGon() {
$delete = isset($_POST['syncGON_delete']) && ($_POST['syncGON_delete'] == 'on');
protected function syncGon($forceDelete = false) {
$delete = $forceDelete || (isset($_POST['syncGON_delete']) && ($_POST['syncGON_delete'] == 'on'));
$return = array();
$gon = $this->getAccountContainer()->getAccountModule('groupOfNames');
if ($gon == null) {
$gon = $this->getAccountContainer()->getAccountModule('groupOfUniqueNames');
}
if ($gon == null) {
$gon = $this->getAccountContainer()->getAccountModule('groupOfMembers');
}
if ($gon == null) {
return;
}
@ -1000,12 +1007,12 @@ class posixGroup extends baseModule implements passwordService {
}
$added = array_delete($oldValues, $this->attributes['memberUid']);
if (!empty($added)) {
$return[] = array('INFO', _('Added users'), htmlspecialchars(implode($added, ', ')));
$return[] = array('INFO', _('Added users'), htmlspecialchars(implode(', ', $added)));
}
if ($delete) {
$deleted = array_delete($this->attributes['memberUid'], $oldValues);
if (!empty($deleted)) {
$return[] = array('INFO', _('Removed users'), htmlspecialchars(implode($deleted, ', ')));
$return[] = array('INFO', _('Removed users'), htmlspecialchars(implode(', ', $deleted)));
}
}
return $return;
@ -1043,12 +1050,12 @@ class posixGroup extends baseModule implements passwordService {
}
$added = array_delete($oldValues, $this->attributes['memberUid']);
if (!empty($added)) {
$return[] = array('INFO', _('Added users'), htmlspecialchars(implode($added, ', ')));
$return[] = array('INFO', _('Added users'), htmlspecialchars(implode(', ', $added)));
}
if ($delete) {
$deleted = array_delete($this->attributes['memberUid'], $oldValues);
if (!empty($deleted)) {
$return[] = array('INFO', _('Removed users'), htmlspecialchars(implode($deleted, ', ')));
$return[] = array('INFO', _('Removed users'), htmlspecialchars(implode(', ', $deleted)));
}
}
return $return;
@ -1077,7 +1084,7 @@ class posixGroup extends baseModule implements passwordService {
}
// auto sync group members
if ($this->isBooleanConfigOptionSet('posixGroup_autoSyncGon')) {
$this->syncGon();
$this->syncGon(true);
}
$return = $this->getAccountContainer()->save_module_attributes($this->attributes, $this->orig);
// Change gids of users and hosts?
@ -1324,13 +1331,6 @@ class posixGroup extends baseModule implements passwordService {
if ($this->isWindows()) {
$filter = '(&(objectClass=user)(gidNumber=*))';
}
$typeFilter = $type->getAdditionalLdapFilter();
if (!empty($typeFilter)) {
if (strpos($typeFilter, '(') !== 0) {
$typeFilter = '(' . $typeFilter . ')';
}
$filter = '(&' . $filter . $typeFilter . ')';
}
$result = searchLDAPByFilter($filter, array('uid', 'gidNumber', 'cn'), array('user'));
$resultCount = sizeof($result);
for ($i = 0; $i < $resultCount; $i++) {

View File

@ -206,8 +206,10 @@ class quota extends baseModule {
}
$allQuotas[$i] = substr($allQuotas[$i], strlen(self::$QUOTA_PREFIX));
$singleQuota = explode(",", $allQuotas[$i]);
$singleQuota[1] = $this->formatBlockUsage($singleQuota[1]);
$singleQuota[2] = $this->addBlockUnits($singleQuota[2]);
$singleQuota[3] = $this->addBlockUnits($singleQuota[3]);
$singleQuota[5] = $this->formatInodeUsage($singleQuota[5]);
$singleQuota[6] = $this->addInodeUnits($singleQuota[6]);
$singleQuota[7] = $this->addInodeUnits($singleQuota[7]);
$this->quota[$server][$i] = $singleQuota;
@ -231,8 +233,9 @@ class quota extends baseModule {
* Adds units (M/G/T) for block numbers.
*
* @param int $value raw value
* @return string value with unit
*/
private function addBlockUnits($value) {
public function addBlockUnits($value) {
$mebibytes = 1024;
$gibibytes = 1024 * $mebibytes;
$tebibytes = 1024 * $gibibytes;
@ -248,19 +251,45 @@ class quota extends baseModule {
if (($value >= $mebibytes) && (($value % $mebibytes) === 0)) {
return ($value / $mebibytes) . 'M';
}
return $value;
}
/**
* Formats block usage.
*
* @param int $value raw value
*/
public function formatBlockUsage($value) {
$mebibytes = 1024;
$gibibytes = 1024 * $mebibytes;
$tebibytes = 1024 * $gibibytes;
if (empty($value) || !get_preg($value, 'digit') || ($value < $mebibytes)) {
return $value;
}
if ($value >= $tebibytes) {
return round($value / $tebibytes, 2) . 'T';
}
if ($value >= $gibibytes) {
return round($value / $gibibytes, 2) . 'G';
}
if ($value >= $mebibytes) {
return round($value / $mebibytes, 2) . 'M';
}
return $value;
}
/**
* Adds units (m/g/t) for inode numbers.
*
* @param int $value raw value
* @return string value with unit
*/
private function addInodeUnits($value) {
public function addInodeUnits($value) {
$kilo = 1000;
$million = 1000 * $kilo;
$billion = 1000 * $million;
$trillion = 1000 * $billion;
if (empty($value) || !get_preg($value, 'digit') || ($value < $million)) {
if (empty($value) || !get_preg($value, 'digit')) {
return $value;
}
if (($value >= $trillion) && (($value % $trillion) === 0)) {
@ -275,6 +304,36 @@ class quota extends baseModule {
if (($value >= $kilo) && (($value % $kilo) === 0)) {
return ($value / $kilo) . 'k';
}
return $value;
}
/**
* Formats the inode usage.
*
* @param int $value raw value
* @return string value with unit
*/
public function formatInodeUsage($value) {
$kilo = 1000;
$million = 1000 * $kilo;
$billion = 1000 * $million;
$trillion = 1000 * $billion;
if (empty($value) || !get_preg($value, 'digit')) {
return $value;
}
if ($value >= $trillion) {
return round($value / $trillion, 2) . 't';
}
if ($value >= $billion) {
return round($value / $billion, 2) . 'g';
}
if ($value >= $million) {
return round($value / $million, 2) . 'm';
}
if ($value >= $kilo) {
return round($value / $kilo, 2) . 'k';
}
return $value;
}
/**

View File

@ -45,6 +45,11 @@ class windowsUser extends baseModule implements passwordService {
/** account is disabled */
const AC_ACCOUNT_DISABLED = 0x00000002;
/** display groups as dn */
const DISPLAY_GROUPS_DN = 'DN';
/** display groups as cn */
const DISPLAY_GROUPS_CN = 'CN';
/** current group list */
private $groupList = array();
/** original group list */
@ -412,6 +417,10 @@ class windowsUser extends baseModule implements passwordService {
"Headline" => _("Workstations"), 'attr' => 'userWorkstations',
"Text" => _("Comma separated list of workstations the user is allowed to login. Empty means every workstation."). ' '. _("Can be left empty.")
),
'displayGroups' => array(
"Headline" => _('Display format'),
"Text" => _('Specifies how groups are displayed.')
),
);
// upload fields
$return['upload_columns'] = array(
@ -1359,24 +1368,62 @@ class windowsUser extends baseModule implements passwordService {
$containerRight->add(new htmlAccountPageButton(get_class($this), 'group', 'edit', _('Edit groups')), 12);
$containerRight->addVerticalSpacer('1rem');
$groupsList = new htmlGroup();
$groupCNs = array();
for ($i = 0; $i < sizeof($this->groupList); $i++) {
$groupCNs[] = extractRDNValue($this->groupList[$i]);
$groupNames = array();
if ($this->groupDisplayContainsDn()) {
usort($this->groupList, 'compareDN');
}
natcasesort($groupCNs);
foreach ($groupCNs as $cn) {
foreach ($this->groupList as $groupDn) {
$groupCn = extractRDNValue($groupDn);
$groupNames[] = $this->formatGroupName($groupCn, $groupDn);
}
if (!$this->groupDisplayContainsDn()) {
natcasesort($groupNames);
}
foreach ($groupNames as $cn) {
$groupsList->addElement(new htmlOutputText($cn));
$groupsList->addElement(new htmlOutputText('<br>', false));
}
$containerRight->add($groupsList, 12);
$groupsListClass = $this->groupDisplayContainsDn() ? 'rightToLeftText' : '';
$groupsListDiv = new htmlDiv(null, $groupsList, array($groupsListClass));
$containerRight->add($groupsListDiv, 12);
$container = new htmlResponsiveRow();
$container->add($containerLeft, 12, 7);
$container->add(new htmlSpacer('1rem', null), 0, 1);
$container->add($containerRight, 12, 4);
$container->add($containerLeft, 12, 12, 7);
$container->add(new htmlSpacer('1rem', null), 0, 0, 1);
$container->add($containerRight, 12, 12, 4);
return $container;
}
/**
* Formats a group name for the display.
*
* @param string $cn common name
* @param string $dn DN
* @return string formatted name
*/
private function formatGroupName($cn, $dn) {
$mode = empty($this->moduleSettings['windowsUser_displayGroups'][0]) ? 'dn' : $this->moduleSettings['windowsUser_displayGroups'][0];
switch ($mode) {
case self::DISPLAY_GROUPS_CN:
return $cn;
break;
case self::DISPLAY_GROUPS_DN:
default:
return getAbstractDN($dn);
break;
}
}
/**
* Returns if the group display name contains the DN.
*
* @return bool contains DN.
*/
private function groupDisplayContainsDn() {
$mode = empty($this->moduleSettings['windowsUser_displayGroups'][0]) ? 'dn' : $this->moduleSettings['windowsUser_displayGroups'][0];
return ($mode == self::DISPLAY_GROUPS_DN);
}
/**
* Returns if any of the work details attributes should be managed.
*
@ -1817,28 +1864,48 @@ class windowsUser extends baseModule implements passwordService {
*/
public function display_html_group() {
$return = new htmlResponsiveRow();
$return->setCSSClasses(array('maxrow'));
$return->add(new htmlSubTitle(_("Groups")), 12);
$groups = $this->findGroups();
$groupDisplayContainsDn = $this->groupDisplayContainsDn();
// sort by DN
usort($groups, 'compareDN');
if ($groupDisplayContainsDn) {
usort($groups, 'compareDN');
}
$selectedGroups = array();
// sort by DN
usort($this->groupList, 'compareDN');
if ($groupDisplayContainsDn) {
usort($this->groupList, 'compareDN');
}
for ($i = 0; $i < sizeof($this->groupList); $i++) {
if (in_array($this->groupList[$i], $groups)) {
$selectedGroups[getAbstractDN($this->groupList[$i])] = $this->groupList[$i];
$groupDn = $this->groupList[$i];
$groupCn = extractRDNValue($groupDn);
$displayName = $this->formatGroupName($groupCn, $groupDn);
$selectedGroups[$displayName] = $groupDn;
}
}
$availableGroups = array();
foreach ($groups as $dn) {
if (!in_array($dn, $this->groupList)) {
$availableGroups[getAbstractDN($dn)] = $dn;
$groupCn = extractRDNValue($dn);
$displayName = $this->formatGroupName($groupCn, $dn);
$availableGroups[$displayName] = $dn;
}
}
if (!$groupDisplayContainsDn) {
$selectedGroups = array_flip($selectedGroups);
natcasesort($selectedGroups);
$selectedGroups = array_flip($selectedGroups);
$availableGroups = array_flip($availableGroups);
natcasesort($availableGroups);
$availableGroups = array_flip($availableGroups);
}
$this->addDoubleSelectionArea($return, _("Selected groups"), _("Available groups"),
$selectedGroups, null, $availableGroups, null, 'groups', true, true);
$selectedGroups, null, $availableGroups, null, 'groups', $groupDisplayContainsDn, true);
// sync options
$typeManager = new TypeManager();
@ -2020,9 +2087,33 @@ class windowsUser extends baseModule implements passwordService {
$container->add(new htmlSubTitle(_('Upload image')), 12);
$label = _('Photo file');
$container->add(new htmlResponsiveInputFileUpload('photoFile', $label, 'photoUpload'), 12);
$container->addVerticalSpacer('2rem');
$container->addLabel(new htmlAccountPageButton(get_class($this), 'photo', 'upload', _('Upload')));
$container->addField(new htmlAccountPageButton(get_class($this), 'attributes', 'back', _('Back')));
$container->addVerticalSpacer('0.5rem');
$container->addLabel(new htmlOutputText('&nbsp;', false));
$container->addField(new htmlAccountPageButton(get_class($this), 'photo', 'upload', _('Upload')));
$container->addVerticalSpacer('1rem');
$webcamContent = new htmlResponsiveRow();
$webcamContent->add(new htmlSubTitle(_('Use webcam')), 12);
$errorMessage = new htmlStatusMessage('ERROR', '');
$errorMessage->setCSSClasses(array('hidden', 'lam-webcam-message'));
$webcamContent->add($errorMessage, 12);
$captureButton = new htmlButton('lam-webcam-capture', _('Start capture'));
$captureButton->setOnClick('window.lam.tools.webcam.capture(event);');
$webcamContent->add($captureButton, 12, 12, 12, 'text-center');
$video = new htmlVideo('lam-webcam-video');
$video->setCSSClasses(array('hidden'));
$webcamContent->add($video, 12, 12, 12, 'text-center');
$webcamContent->addVerticalSpacer('0.5rem');
$webcamUploadButton = new htmlButton('uploadWebcam', _('Upload'));
$webcamUploadButton->setCSSClasses(array('btn-lam-webcam-upload', 'hidden'));
$webcamUploadButton->setOnClick('window.lam.tools.webcam.upload();');
$webcamContent->add($webcamUploadButton, 12, 12, 12, 'text-center');
$canvas = new htmlCanvas('lam-webcam-canvas');
$canvas->setCSSClasses(array('hidden'));
$webcamContent->add($canvas, 12);
$webcamDiv = new htmlDiv('lam_webcam_div', $webcamContent, array('hidden'));
$container->add($webcamDiv, 12);
$container->addVerticalSpacer('1rem');
$container->add(new htmlAccountPageButton(get_class($this), 'attributes', 'back', _('Back')), 12);
}
else {
$container->add(new htmlSubTitle(_('Crop image')), 12);
@ -2048,7 +2139,7 @@ class windowsUser extends baseModule implements passwordService {
if (isset($_POST['form_subpage_' . get_class($this) . '_attributes_back'])) {
return array();
}
if (isset($_POST['form_subpage_' . get_class($this) . '_photo_upload'])) {
if (isset($_POST['form_subpage_' . get_class($this) . '_photo_upload']) || isset($_POST['webcamData'])) {
return $this->uploadPhoto();
}
if (isset($_POST['form_subpage_' . get_class($this) . '_attributes_crop'])) {
@ -2075,40 +2166,46 @@ class windowsUser extends baseModule implements passwordService {
*/
private function uploadPhoto() {
$messages = array();
if ($_FILES['photoFile'] && ($_FILES['photoFile']['size'] > 0)) {
if ((empty($_FILES['photoFile']) || ($_FILES['photoFile']['size'] <= 0)) && empty($_POST['webcamData'])) {
$messages[] = $this->messages['file'][0];
return $messages;
}
if (!empty($_FILES['photoFile']['tmp_name'])) {
$handle = fopen($_FILES['photoFile']['tmp_name'], "r");
$data = fread($handle, 10000000);
fclose($handle);
if (!empty($this->moduleSettings['windowsUser_jpegPhoto_maxSize'][0]) && (strlen($data) > (1024 * $this->moduleSettings['windowsUser_jpegPhoto_maxSize'][0]))) {
$errMsg = $this->messages['file'][3];
$errMsg[] = null;
$errMsg[] = array($this->moduleSettings['windowsUser_jpegPhoto_maxSize'][0]);
return array($errMsg);
}
fclose($handle);
// convert to JPG
try {
include_once dirname(__FILE__) . '/../imageutils.inc';
$imageManipulator = ImageManipulationFactory::getImageManipulator($data);
// resize if maximum values specified
if (!empty($this->moduleSettings['windowsUser_jpegPhoto_maxWidth'][0]) || !empty($this->moduleSettings['windowsUser_jpegPhoto_maxHeight'][0])) {
$maxWidth = empty($this->moduleSettings['windowsUser_jpegPhoto_maxWidth'][0]) ? $imageManipulator->getWidth() : $this->moduleSettings['windowsUser_jpegPhoto_maxWidth'][0];
$maxHeight = empty($this->moduleSettings['windowsUser_jpegPhoto_maxHeight'][0]) ? $imageManipulator->getHeight() : $this->moduleSettings['windowsUser_jpegPhoto_maxHeight'][0];
$imageManipulator->thumbnail($maxWidth, $maxHeight);
}
$imageManipulator->convertToJpeg();
$data = $imageManipulator->getImageData();
}
catch (Exception $e) {
$msg = $this->messages['file'][2];
$msg[] = htmlspecialchars($e->getMessage());
$messages[] = $msg;
return $messages;
}
$this->attributes['jpegPhoto'][0] = $data;
}
else {
$messages[] = $this->messages['file'][0];
elseif (isset($_POST['webcamData'])) {
$data = $_POST['webcamData'];
$data = str_replace('data:image/png;base64,', '', $data);
$data = base64_decode($data);
}
// convert to JPG
try {
include_once dirname(__FILE__) . '/../imageutils.inc';
$imageManipulator = ImageManipulationFactory::getImageManipulator($data);
// resize if maximum values specified
if (!empty($this->moduleSettings['windowsUser_jpegPhoto_maxWidth'][0]) || !empty($this->moduleSettings['windowsUser_jpegPhoto_maxHeight'][0])) {
$maxWidth = empty($this->moduleSettings['windowsUser_jpegPhoto_maxWidth'][0]) ? $imageManipulator->getWidth() : $this->moduleSettings['windowsUser_jpegPhoto_maxWidth'][0];
$maxHeight = empty($this->moduleSettings['windowsUser_jpegPhoto_maxHeight'][0]) ? $imageManipulator->getHeight() : $this->moduleSettings['windowsUser_jpegPhoto_maxHeight'][0];
$imageManipulator->thumbnail($maxWidth, $maxHeight);
}
$imageManipulator->convertToJpeg();
$data = $imageManipulator->getImageData();
}
catch (Exception $e) {
$msg = $this->messages['file'][2];
$msg[] = htmlspecialchars($e->getMessage());
$messages[] = $msg;
return $messages;
}
$this->attributes['jpegPhoto'][0] = $data;
return $messages;
}
@ -3688,6 +3785,13 @@ class windowsUser extends baseModule implements passwordService {
// configuration options
$configContainer = new htmlResponsiveRow();
$configContainer->add(new htmlResponsiveInputTextarea('windowsUser_domains', '', 30, 3, _('Domains'), 'domains'), 12);
$displayOptions = array(
'dn' => self::DISPLAY_GROUPS_DN,
'cn' => self::DISPLAY_GROUPS_CN,
);
$groupDisplaySelect = new htmlResponsiveSelect('windowsUser_displayGroups', $displayOptions, array(), _('Display format'), 'displayGroups');
$groupDisplaySelect->setHasDescriptiveElements(true);
$configContainer->add($groupDisplaySelect, 12);
$configHiddenGroup = new htmlGroup();
$configHiddenGroup->addElement(new htmlOutputText(_('Hidden options')));
$configHiddenGroup->addElement(new htmlHelpLink('hiddenOptions'));
@ -3841,7 +3945,8 @@ class windowsUser extends baseModule implements passwordService {
return array(
new WindowsPasswordNotifyJob(),
new WindowsAccountExpirationCleanupJob(),
new WindowsAccountExpirationNotifyJob()
new WindowsAccountExpirationNotifyJob(),
new WindowsManagedGroupsNotifyJob()
);
}
@ -4073,6 +4178,299 @@ if (interface_exists('\LAM\JOB\Job', false)) {
}
/**
* Job to notify users about their managed groups.
*
* @package jobs
*/
class WindowsManagedGroupsNotifyJob extends \LAM\JOB\PasswordExpirationJob {
const MANAGED_GROUPS = 'LAM_MANAGED_GROUPS';
const PERIOD_MONTHLY = 'MONTHLY';
const PERIOD_QUARTERLY = 'QUARTERLY';
const PERIOD_HALF_YEARLY = 'HALF_YEARLY';
const PERIOD_YEARLY = 'YEARLY';
/**
* Returns the alias name of the job.
*
* @return String name
*/
public function getAlias() {
return _('Windows') . ': ' . _('Notify users about their managed groups');
}
/**
* @inheritDoc
*/
public function getDescription() {
return _('This will send each user a summary of the managed groups and their members.');
}
/**
* @inheritDoc
*/
public function getConfigOptions($jobID) {
$prefix = $this->getConfigPrefix();
$container = new htmlResponsiveRow();
$container->add(new htmlResponsiveInputField(_('From address'), $prefix . '_mailFrom' . $jobID, null, '800', true), 12);
$container->add(new htmlResponsiveInputField(_('Reply-to address'), $prefix . '_mailReplyTo' . $jobID, null, '801'), 12);
$container->add(new htmlResponsiveInputField(_('CC address'), $prefix . '_mailCC' . $jobID, null, '805'), 12);
$container->add(new htmlResponsiveInputField(_('BCC address'), $prefix . '_mailBCC' . $jobID, null, '806'), 12);
$container->add(new htmlResponsiveInputField(_('Subject'), $prefix . '_mailSubject' . $jobID, null, '802'), 12);
$container->add(new htmlResponsiveInputCheckbox($prefix . '_mailIsHTML' . $jobID, false, _('HTML format'), '553'), 12);
$container->add(new htmlResponsiveInputTextarea($prefix . '_mailtext' . $jobID, '', 50, 4, _('Text'), '810'), 12);
$periodOptions = array(
_('Monthly') => self::PERIOD_MONTHLY,
_('Quarterly') => self::PERIOD_QUARTERLY,
_('Half-yearly') => self::PERIOD_HALF_YEARLY,
_('Yearly') => self::PERIOD_YEARLY,
);
$periodSelect = new htmlResponsiveSelect($prefix . '_period' . $jobID, $periodOptions, array(), _('Period'), '811');
$periodSelect->setHasDescriptiveElements(true);
$periodSelect->setSortElements(false);
$container->add($periodSelect, 12);
return $container;
}
/**
* @inheritDoc
*/
public function checkConfigOptions($jobID, $options) {
$prefix = $this->getConfigPrefix();
$errors = array();
// from address
if (empty($options[$prefix . '_mailFrom' . $jobID][0])
|| !(get_preg($options[$prefix . '_mailFrom' . $jobID][0], 'email')
|| get_preg($options[$prefix . '_mailFrom' . $jobID][0], 'emailWithName'))) {
$errors[] = array('ERROR', _('Please enter a valid email address!'), _('From address'));
}
// reply-to
if (!empty($options[$prefix . '_mailReplyTo' . $jobID][0])
&& !get_preg($options[$prefix . '_mailReplyTo' . $jobID][0], 'email')
&& !get_preg($options[$prefix . '_mailReplyTo' . $jobID][0], 'emailWithName')) {
$errors[] = array('ERROR', _('Please enter a valid email address!'), _('Reply-to address'));
}
// CC address
if (!empty($options[$prefix . '_mailCC' . $jobID][0])
&& !get_preg($options[$prefix . '_mailCC' . $jobID][0], 'email')
&& !get_preg($options[$prefix . '_mailCC' . $jobID][0], 'emailWithName')) {
$errors[] = array('ERROR', _('Please enter a valid email address!'), _('CC address'));
}
// BCC address
if (!empty($options[$prefix . '_mailBCC' . $jobID][0])
&& !get_preg($options[$prefix . '_mailBCC' . $jobID][0], 'email')
&& !get_preg($options[$prefix . '_mailBCC' . $jobID][0], 'emailWithName')) {
$errors[] = array('ERROR', _('Please enter a valid email address!'), _('BCC address'));
}
// text
$mailText = implode('', $options[$prefix . '_mailtext' . $jobID]);
if (empty($mailText)) {
$errors[] = array('ERROR', _('Please set a email text.'));
}
if (strpos($mailText, '@@' . self::MANAGED_GROUPS . '@@') === false) {
$errors[] = array('ERROR', _('Please add the wildcard for the list of managed groups.'), '@@' . self::MANAGED_GROUPS . '@@');
}
return $errors;
}
/**
* @inheritDoc
*/
protected function getPolicyOptions() {
return array();
}
/**
* Searches for users in LDAP.
*
* @param String $jobID unique job identifier
* @param array $options config options (name => value)
* @return array list of user attributes
*/
protected function findUsers($jobID, $options) {
// read users
$sysAttrs = array('managedObjects', 'mail');
$attrs = $this->getAttrWildcards($jobID, $options);
$attrs = array_values(array_unique(array_merge($attrs, $sysAttrs)));
$users = searchLDAPByFilter('(&(mail=*)(managedObjects=*))', $attrs, array('user'));
$groups = searchLDAPByFilter('(managedBy=*)', array('cn', 'member'), array('group'));
$groupByDn = array();
foreach ($groups as $group) {
$groupByDn[$group['dn']] = $group;
}
$groups = null;
foreach ($users as $index => $user) {
$managedObjectDns = $user['managedobjects'];
$managedGroups = array();
foreach ($managedObjectDns as $managedObjectDn) {
if (array_key_exists($managedObjectDn, $groupByDn)) {
$managedGroups[] = $groupByDn[$managedObjectDn];
}
}
$users[$index][strtolower(self::MANAGED_GROUPS)] = $managedGroups;
}
return $users;
}
/**
* @inheritDoc
*/
public function execute($jobID, $options, &$pdo, $isDryRun, &$resultLog) {
$this->jobResultLog = &$resultLog;
$this->jobResultLog->logDebug("Configuration options:");
foreach ($options as $key => $value) {
if (strpos($key, $jobID) === false) {
continue;
}
$this->jobResultLog->logDebug($key . ': ' . implode(', ', $value));
}
$now = new DateTime(null, getTimeZone());
$baseDate = $this->getBaseDate($now);
$monthInterval = $this->getMonthInterval($options, $jobID);
if (!$this->shouldRun($pdo, $options, $jobID, $baseDate, $monthInterval)) {
$this->jobResultLog->logDebug('No run needed yet');
return;
}
$userResults = $this->findUsers($jobID, $options);
$this->jobResultLog->logDebug("Found " . sizeof($userResults) . " users to send an email.");
$isHTML = (!empty($options[$this->getConfigPrefix() . '_mailIsHTML' . $jobID][0]) && ($options[$this->getConfigPrefix() . '_mailIsHTML' . $jobID][0] == 'true'));
foreach ($userResults as $user) {
if (empty($user[strtolower(self::MANAGED_GROUPS)])) {
continue;
}
$user[strtolower(self::MANAGED_GROUPS)][0] = $this->formatGroups($user[strtolower(self::MANAGED_GROUPS)], $isHTML);
if ($isDryRun) {
// no action for dry run
$this->jobResultLog->logInfo("Managed groups text for " . $user['dn'] . ":\n" . $user[strtolower(self::MANAGED_GROUPS)][0]);
$this->jobResultLog->logInfo('Not sending email to ' . $user['dn'] . ' because of dry run.');
continue;
}
// send email
$this->sendMail($options, $jobID, $user, null);
}
if (!$isDryRun) {
$this->setDBLastPwdChangeTime($jobID, $pdo, $jobID, self::getLastEffectiveExecutionDate($baseDate, $monthInterval, $this->jobResultLog)->format('Y-m-d'));
}
}
/**
* Returns if the job should run.
*
* @param $pdo PDO
* @param $options job options
* @param $jobId job id
* @param DateTime $baseDate base date
* @param int $monthInterval month interval
* @return bool should run
*/
private function shouldRun(&$pdo, $options, $jobId, $baseDate, $monthInterval) {
$dbLastChange = $this->getDBLastPwdChangeTime($jobId, $pdo, $jobId);
if (empty($dbLastChange)) {
return true;
}
$this->jobResultLog->logDebug('Base date: ' . $baseDate->format('Y-m-d'));
$effectiveDate = self::getLastEffectiveExecutionDate($baseDate, $monthInterval, $this->jobResultLog);
$dbLastChangeDate = DateTime::createFromFormat('Y-m-d', $dbLastChange, getTimeZone());
$this->jobResultLog->logDebug('Last run date: ' . $dbLastChangeDate->format('Y-m-d'));
return $effectiveDate > $dbLastChangeDate;
}
/**
* Returns the month interval.
*
* @param array $options config options
* @param $jobId job id
* @return int interval
*/
private function getMonthInterval($options, $jobId) {
$monthInterval = 12;
switch ($options[$this->getConfigPrefix() . '_period' . $jobId][0]) {
case self::PERIOD_HALF_YEARLY:
$monthInterval = 6;
break;
case self::PERIOD_QUARTERLY:
$monthInterval = 3;
break;
case self::PERIOD_MONTHLY:
$monthInterval = 1;
break;
}
return $monthInterval;
}
/**
* Returns the base date (first of month) for the current date.
*
* @param DateTime $currentDate current date
* @return DateTime base date
*/
private function getBaseDate($currentDate) {
$baseDateText = $currentDate->format('Y-m-') . '1';
return DateTime::createFromFormat('Y-m-d', $baseDateText, getTimeZone());
}
/**
* Returns the last effective execution date.
*
* @param DateTime $baseDate base date
* @param int $monthInterval number of months in interval
* @param \LAM\JOB\JobResultLog $resultLog result log
*/
public static function getLastEffectiveExecutionDate($baseDate, $monthInterval, $resultLog) {
$month = $baseDate->format('m');
$monthIndex = $month - 1;
while (($monthIndex % $monthInterval) !== 0) {
$monthIndex--;
}
$month = $monthIndex + 1;
$effectiveDateString = $baseDate->format('Y-') . $month . '-1';
$effectiveDate = DateTime::createFromFormat('Y-m-d', $effectiveDateString, getTimeZone());
$resultLog->logDebug("Effective date: " . $effectiveDate->format('Y-m-d'));
return $effectiveDate;
}
/**
* @inheritDoc
*/
protected function checkSingleUser($jobID, $options, &$pdo, $now, $policyOptions, $user, $isDryRun) {
// not used
}
/**
* Formats the managed groups.
*
* @param $managedGroups managed groups
* @param bool $isHTML HTML email
* @return string formatted text
*/
private function formatGroups($managedGroups, bool $isHTML) {
$text = '';
foreach ($managedGroups as $managedGroup) {
if ($isHTML) {
$text .= '<br><b>' . $managedGroup['cn'][0] . '</b><br>';
}
else {
$text .= "\r\n" . $managedGroup['cn'][0] . "\r\n\r\n";
}
if (empty($managedGroup['member'])) {
continue;
}
foreach ($managedGroup['member'] as $member) {
$member = getAbstractDN($member);
if ($isHTML) {
$text .= '&nbsp;&nbsp;' . $member . '<br>';
}
else {
$text .= " " . $member . "\r\n";
}
}
}
return $text;
}
}
/**
* Job to notify users about account expiration.
*

View File

@ -61,7 +61,7 @@ include_once('pdfstruct.inc');
function createModulePDF($accounts, $pdf_structure, $font, $returnAsString = false) {
$account_type = $accounts[0]->get_type();
// Get PDF structure from xml file
$reader = new PDFStructureReader();
$reader = new PDFStructureReader($_SESSION['config']->getName());
$structure = $reader->read($account_type->getId(), $pdf_structure);
// get list of PDF keys
$pdfKeys = array();

View File

@ -34,6 +34,11 @@ use \LAM\ImageUtils\ImageManipulationFactory;
/** LAM configuration */
include_once(__DIR__ . "/config.inc");
/**
* Use as server profile name to manage global templates.
*/
const GLOBAL_PROFILE = '__GLOBAL__';
/** LDAP object */
include_once(__DIR__ . "/ldap.inc");
@ -44,18 +49,15 @@ include_once(__DIR__ . "/ldap.inc");
* @param string $typeId the account type
* @param string $profile server profile name
*
* @return array All available PDF structure definitions for the submitted account
* @return string[] All available PDF structure definitions for the submitted account
* scope. Each entry is a string being the filename that may be passed to the
* createModulePDF() function as second argument.
*/
function getPDFStructures($typeId, $profile = null) {
function getPDFStructures($typeId, $profile) {
$return = array();
if (!preg_match('/[a-zA-Z]+/', $typeId)) {
return null;
}
if (!isset($profile)) {
$profile = $_SESSION['config']->getName();
}
$path = dirname(__FILE__) . '/../config/pdf/' . $profile;
if(is_dir($path)) {
$dirHandle = opendir($path);
@ -75,14 +77,14 @@ function getPDFStructures($typeId, $profile = null) {
*
* @param string $typeId account type
* @param string $name Name of definition to delete
*
* @param string $serverProfileName server profile name
* @return boolean True if file was deleted or false if a problem occurred.
*/
function deletePDFStructure($typeId, $name) {
function deletePDFStructure($typeId, $name, $serverProfileName) {
if (!isValidPDFStructureName($name) || !preg_match('/[a-zA-Z]+/',$typeId)) {
return false;
}
$file = dirname(__FILE__) . '/../config/pdf/' . $_SESSION['config']->getName() . '/' . $name . '.' . $typeId . '.xml';
$file = dirname(__FILE__) . '/../config/pdf/' . $serverProfileName . '/' . $name . '.' . $typeId . '.xml';
if(is_file($file) && is_writable($file)) {
return unlink($file);
}
@ -95,11 +97,12 @@ function deletePDFStructure($typeId, $name) {
/**
* This function returns an array with all aviliable logo images.
*
* @return array list of logo files
* @param string $serverProfileName server profile name
* @return array list of logo files (array('filename' => PATH, 'infos' => array(width, height)))
*/
function getAvailableLogos() {
function getAvailableLogos($serverProfileName) {
$return = array();
$dirPath = dirname(__FILE__) . '/../config/pdf/' . $_SESSION['config']->getName() . '/logos/';
$dirPath = dirname(__FILE__) . '/../config/pdf/' . $serverProfileName . '/logos/';
$dirHandle = opendir($dirPath);
while($file = readdir($dirHandle)) {
if(!is_dir($file) && $file != '.' && $file != '..' && preg_match('/\\.(jpg|png)$/i',$file)) {
@ -120,7 +123,7 @@ function getAvailableLogos() {
* @param \LAM\TYPES\ConfiguredType $sourceType source type
* @param string $sourceStructureName structure name
* @param \LAM\TYPES\ConfiguredType $targetType target type
* @throws Exception
* @throws LAMException error during copy
*/
function copyStructure($sourceType, $sourceStructureName, $targetType) {
if (!isValidPDFStructureName($sourceStructureName)) {
@ -165,13 +168,17 @@ function copyStructureToTemplates($sourceType, $sourceName) {
*
* @param String $file full path of temporary file
* @param String $name file name
* @return StatusMessage status message to display
* @param string $serverProfileName server profile name
* @return htmlStatusMessage status message to display
*/
function uploadPDFLogo($file, $name) {
function uploadPDFLogo($file, $name, $serverProfileName) {
if (!preg_match('/[a-zA-Z0-9_-]+\\.(png)|(jpg)/i', $name)) {
return new htmlStatusMessage('ERROR', _('Unable to upload logo file.'), _('The file name must end with ".png" or ".jpg".'));
}
$dirPath = dirname(__FILE__) . '/../config/pdf/' . $_SESSION['config']->getName() . '/logos/';
if ($serverProfileName === GLOBAL_PROFILE) {
$serverProfileName = '../templates/pdf';
}
$dirPath = dirname(__FILE__) . '/../config/pdf/' . $serverProfileName . '/logos/';
$success = copy($file, $dirPath . '/' . $name);
if ($success) {
return new htmlStatusMessage('INFO', _('Uploaded logo file.'), $name);
@ -185,12 +192,13 @@ function uploadPDFLogo($file, $name) {
* Deletes a PDF logo file.
*
* @param String $name file name
* @param string $serverProfileName server profile name
* @return StatusMessage status message to display
*/
function deletePDFLogo($name) {
function deletePDFLogo($name, $serverProfileName) {
// check if valid file
$found = false;
$logos = getAvailableLogos();
$logos = getAvailableLogos($serverProfileName);
foreach ($logos as $logo) {
if ($logo['filename'] === $name) {
$found = true;
@ -203,9 +211,9 @@ function deletePDFLogo($name) {
// check if still in use
$typeManager = new \LAM\TYPES\TypeManager();
$activeTypes = $typeManager->getConfiguredTypes();
$reader = new PDFStructureReader();
$reader = new PDFStructureReader($serverProfileName);
foreach ($activeTypes as $type) {
$structures = getPDFStructures($type->getId());
$structures = getPDFStructures($type->getId(), $serverProfileName);
foreach ($structures as $structure) {
try {
$data = $reader->read($type->getId(), $structure);
@ -220,7 +228,7 @@ function deletePDFLogo($name) {
}
}
// delete file
$dirPath = dirname(__FILE__) . '/../config/pdf/' . $_SESSION['config']->getName() . '/logos/';
$dirPath = dirname(__FILE__) . '/../config/pdf/' . $serverProfileName . '/logos/';
$success = @unlink($dirPath . '/' . $name);
if ($success) {
return new htmlStatusMessage('INFO', _('Logo file deleted.'), $name);
@ -242,21 +250,8 @@ function isValidPDFStructureName($name) {
* Installs template structures to the current server profile.
*/
function installPDFTemplates() {
$templatePath = dirname(__FILE__) . '/../config/templates/pdf';
$templateDir = @dir($templatePath);
$allTemplates = array();
if ($templateDir) {
$entry = $templateDir->read();
while ($entry){
$parts = explode('.', $entry);
if ((strlen($entry) > 3) && (sizeof($parts) == 3)) {
$name = $parts[0];
$scope = $parts[1];
$allTemplates[$scope][] = $name;
}
$entry = $templateDir->read();
}
}
$templatePath = __DIR__ . '/../config/templates/pdf';
$allTemplates = getPdfTemplateNames();
$basePath = dirname(__FILE__) . '/../config/pdf/' . $_SESSION['config']->getName();
if (!file_exists($basePath)) {
mkdir($basePath, 0700, true);
@ -278,20 +273,75 @@ function installPDFTemplates() {
if (!file_exists($basePath . '/logos')) {
mkdir($basePath . '/logos');
}
$templatePath = dirname(__FILE__) . '/../config/templates/pdf/logos';
$logos = getPdfTemplateLogoNames();
foreach ($logos as $logo) {
$path = $basePath . '/logos/' . $logo;
$template = $templatePath . '/logos/' . $logo;
if (!is_file($path)) {
logNewMessage(LOG_DEBUG, 'Copy template ' . $template . ' to ' . $path);
@copy($template, $path);
}
}
}
/**
* Returns all PDF template names.
*
* @return array names (array('user' => array('default')))
*/
function getPdfTemplateNames() {
$templatePath = __DIR__ . '/../config/templates/pdf';
$templateDir = @dir($templatePath);
$allTemplates = array();
if ($templateDir) {
$entry = $templateDir->read();
while ($entry){
$path = $basePath . '/logos/' . $entry;
if ((strpos($entry, '.') !== 0) && !is_file($path)) {
$template = $templatePath . '/' . $entry;
logNewMessage(LOG_DEBUG, 'Copy template ' . $template . ' to ' . $path);
@copy($template, $path);
$parts = explode('.', $entry);
if ((strlen($entry) > 3) && (sizeof($parts) == 3)) {
$name = $parts[0];
$scope = $parts[1];
$allTemplates[$scope][] = $name;
}
$entry = $templateDir->read();
}
}
return $allTemplates;
}
/**
* Returns all PDF template logo names.
*
* @return array names (array('user' => array('default.png')))
*/
function getPdfTemplateLogoNames() {
$templatePath = __DIR__ . '/../config/templates/pdf/logos';
$templateDir = @dir($templatePath);
$logos = array();
if ($templateDir) {
$entry = $templateDir->read();
while ($entry){
if ((strpos($entry, '.') !== 0) && is_file($templatePath . '/' . $entry)) {
$logos[] = $entry;
}
$entry = $templateDir->read();
}
}
return $logos;
}
/**
* Returns the binary data of the PDF template logo.
*
* @param string $name file name (without path)
* @return string binary
*/
function getPdfTemplateLogoBinary($name) {
$templatePath = __DIR__ . '/../config/templates/pdf/logos';
$fileName = $templatePath . '/' . $name;
$handle = fopen($fileName, 'r');
$logoBinary = fread($handle, 100000000);
fclose($handle);
return $logoBinary;
}
/**
@ -301,6 +351,22 @@ function installPDFTemplates() {
*/
class PDFStructureReader {
private $serverProfileName;
/**
* Constructor.
*
* @param $serverProfileName server profile name
*/
public function __construct($serverProfileName) {
if ($serverProfileName === GLOBAL_PROFILE) {
$this->serverProfileName = '../templates/pdf';
}
else {
$this->serverProfileName = $serverProfileName;
}
}
/**
* Reads a PDF structure.
*
@ -324,7 +390,7 @@ class PDFStructureReader {
* @return string file name
*/
protected function getFileName($typeId, $name) {
return dirname(__FILE__) . '/../config/pdf/' . $_SESSION['config']->getName() . '/' . $name . '.' . $typeId . '.xml';
return dirname(__FILE__) . '/../config/pdf/' . $this->serverProfileName . '/' . $name . '.' . $typeId . '.xml';
}
/**
@ -334,6 +400,7 @@ class PDFStructureReader {
* @return PDFStructure structure
*/
private function readPDFFile($file) {
logNewMessage(LOG_DEBUG, $file);
$xml = new \XMLReader();
$xml->open($file);
$structure = new PDFStructure();
@ -411,12 +478,29 @@ class PDFStructureReader {
*/
class PDFStructureWriter {
private $serverProfileName;
/**
* Constructor.
*
* @param string $serverProfileName server profile name
*/
public function __construct($serverProfileName) {
if ($serverProfileName === GLOBAL_PROFILE) {
$this->serverProfileName = '../templates/pdf';
}
else {
$this->serverProfileName = $serverProfileName;
}
}
/**
* Writes the PDF structure to disk.
*
* @param string $typeId type ID
* @param string $name structure name
* @param PDFStructure $structure structure
* @throws LAMException error during write
*/
public function write($typeId, $name, $structure) {
$fileName = $this->getFileName($typeId, $name);
@ -430,16 +514,18 @@ class PDFStructureWriter {
* @param string $typeId type ID
* @param string $name structure name
* @return string file name
* @throws LAMException file not valid or not writable
*/
protected function getFileName($typeId, $name) {
if (!isValidPDFStructureName($name) || !preg_match('/[a-zA-Z]+/', $typeId)) {
throw new \LAMException(_('PDF structure name not valid'),
_('The name for that PDF-structure you submitted is not valid. A valid name must consist of the following characters: \'a-z\',\'A-Z\',\'0-9\',\'_\',\'-\'.'));
}
if(!is_writable(dirname(__FILE__) . '/../config/pdf/' . $_SESSION['config']->getName())) {
throw new \LAMException(_('Could not save PDF structure, access denied.'));
$baseDir = __DIR__ . '/../config/pdf/' . $this->serverProfileName;
if(!is_writable($baseDir)) {
throw new \LAMException(sprintf(_('Could not save PDF structure, access denied to %s.'), $baseDir));
}
return dirname(__FILE__) . '/../config/pdf/' . $_SESSION['config']->getName() . '/' . $name . '.' . $typeId . '.xml';
return $baseDir . '/' . $name . '.' . $typeId . '.xml';
}
/**
@ -488,6 +574,7 @@ class PDFStructureWriter {
*
* @param string $xml XML
* @param string $file file name
* @throws LAMException error during write
*/
protected function writeXML($xml, $file) {
$handle = @fopen($file,'w');
@ -520,6 +607,57 @@ class PDFStructure {
private $sections = array();
/**
* Returns an array representation of the structure.
*
* @return array export data
*/
public function export() {
$data = array();
$data['title'] = $this->title;
$data['foldingMarks'] = $this->foldingMarks;
$data['logo'] = $this->logo;
$data['sections'] = array();
foreach($this->sections as $section) {
$type = ($section instanceof PDFTextSection) ? 'text' : 'entry';
$sectionData = $section->export();
$data['sections'][] = array(
'type' => $type,
'data' => $sectionData
);
}
return $data;
}
/**
* Imports an array representation of the structure.
*
* @param array $data import data
*/
public function import($data) {
if (isset($data['title'])) {
$this->title = $data['title'];
}
if (isset($data['foldingMarks'])) {
$this->foldingMarks = $data['foldingMarks'];
}
if (isset($data['logo'])) {
$this->logo = $data['logo'];
}
if (isset($data['sections'])) {
foreach($data['sections'] as $section) {
if ($section['type'] === 'text') {
$this->sections[] = new PDFTextSection($section['data']);
}
else {
$entrySection = new PDFEntrySection(null);
$entrySection->import($section['data']);
$this->sections[] = $entrySection;
}
}
}
}
/**
* Returns the logo file path.
*
@ -612,6 +750,15 @@ class PDFTextSection {
$this->text = $text;
}
/**
* Exports the section.
*
* @return string text
*/
public function export() {
return $this->getText();
}
/**
* Returns the text.
*
@ -631,7 +778,7 @@ class PDFTextSection {
class PDFEntrySection {
private $title;
private $entries;
private $entries = array();
/**
* Constructor
@ -642,6 +789,37 @@ class PDFEntrySection {
$this->title = $title;
}
/**
* Exports the section.
*
* @return array export data
*/
public function export() {
$data = array();
$data['title'] = $this->title;
$data['entries'] = array();
foreach($this->getEntries() as $entry) {
$data['entries'][] = $entry->getKey();
}
return $data;
}
/**
* Imports the section.
*
* @param array $data import data
*/
public function import($data) {
if (isset($data['title'])) {
$this->title = $data['title'];
}
if ($data['entries']) {
foreach($data['entries'] as $entry) {
$this->entries[] = new PDFSectionEntry($entry);
}
}
}
/**
* Returns if the title is an attribute value.
*

692
lam/lib/persistence.inc Normal file
View File

@ -0,0 +1,692 @@
<?php
namespace LAM\PERSISTENCE;
use LAM\LOGIN\WEBAUTHN\WebauthnManager;
use LAM\PDF\PDFStructure;
use LAM\PDF\PDFStructureReader;
use LAM\PDF\PDFStructureWriter;
use LAMCfgMain;
use LAMConfig;
use LAMException;
use selfServiceProfile;
use function LAM\PDF\getAvailableLogos;
use function LAM\PDF\getPDFStructures;
use function LAM\PDF\getPdfTemplateLogoBinary;
use function LAM\PDF\getPdfTemplateLogoNames;
use function LAM\PDF\getPdfTemplateNames;
use function LAM\PDF\uploadPDFLogo;
use function LAM\PROFILES\getAccountProfiles;
use function LAM\PROFILES\getProfileTemplateNames;
use function LAM\PROFILES\installTemplateAccountProfile;
use function LAM\PROFILES\loadAccountProfile;
use function LAM\PROFILES\loadTemplateAccountProfile;
use function LAM\PROFILES\saveAccountProfile;
/*
This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/)
Copyright (C) 2020 Roland Gruber
This program 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 2 of the License, or
(at your option) any later version.
This program 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 this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/**
* This file includes functions to manage the persistence of LAM's configuration files.
*
* @package configuration
* @author Roland Gruber
*/
include_once __DIR__ . '/config.inc';
include_once __DIR__ . '/profiles.inc';
/**
* Exporter for LAM's configuration data.
*/
class ConfigDataExporter {
/**
* Exports LAM's configuration data in JSON format.
* @throws LAMException error during export
*/
public function exportAsJson() {
$mainCfg = $this->_getMainConfiguration();
$jsonData = array();
$jsonData['mainConfig'] = $this->_getMainConfigData($mainCfg);
$jsonData['certificates'] = $this->_getCertificates($mainCfg);
$serverProfileNames = getConfigProfiles();
$serverProfiles = array();
foreach ($serverProfileNames as $serverProfileName) {
$serverProfiles[$serverProfileName] = new \LAMConfig($serverProfileName);
}
$jsonData['serverProfiles'] = $this->_getServerProfiles($serverProfiles);
$jsonData['accountProfiles'] = $this->_getAccountProfiles($serverProfiles);
$jsonData['accountProfileTemplates'] = $this->_getAccountProfileTemplates();
$jsonData['pdfProfiles'] = $this->_getPdfProfiles($serverProfiles);
$jsonData['pdfProfileTemplates'] = $this->_getPdfProfileTemplates();
$jsonData['selfServiceProfiles'] = $this->_getSelfServiceProfiles();
$jsonData['webauthn'] = $this->_getWebauthn();
return json_encode($jsonData);
}
/**
* Returns the main configuration.
*
* @return LAMCfgMain main config
*/
public function _getMainConfiguration() {
return new LAMCfgMain();
}
/**
* Internal function to read master configuration.
*
* @param LAMCfgMain $mainCfg main config
* @return array data
*/
public function _getMainConfigData($mainCfg) {
return $mainCfg->exportData();
}
/**
* Returns the certificate file content.
*
* @param LAMCfgMain $mainCfg main config
* @return array data
*/
public function _getCertificates($mainCfg) {
return $mainCfg->exportCertificates();
}
/**
* Returns the content of the server profiles.
*
* @param array $serverProfiles list of server profiles (name => object)
* @return array $data
*/
public function _getServerProfiles($serverProfiles) {
$data = array();
foreach ($serverProfiles as $profileName => $serverProfile) {
$data[$profileName] = $serverProfile->exportData();
}
return $data;
}
/**
* Returns the content of the account profiles.
*
* @param array $serverProfiles list of server profiles (name => object)
* @return array $data
*/
public function _getAccountProfiles($serverProfiles) {
$data = array();
foreach ($serverProfiles as $profileName => $serverProfile) {
foreach ($serverProfile->get_ActiveTypes() as $typeId) {
$accountProfileNames = getAccountProfiles($typeId, $profileName);
foreach ($accountProfileNames as $accountProfileName) {
$accountProfile = loadAccountProfile($accountProfileName, $typeId, $profileName);
$data[$profileName][$typeId][$accountProfileName] = $accountProfile;
}
}
}
return $data;
}
/**
* Returns the content of the account profile templates.
*
* @return array $data
* @throws LAMException error reading template
*/
public function _getAccountProfileTemplates() {
$data = array();
$accountProfileTemplateNames = getProfileTemplateNames();
foreach ($accountProfileTemplateNames as $scope => $templateNames) {
foreach ($templateNames as $templateName) {
$accountProfileTemplate = loadTemplateAccountProfile($templateName, $scope);
$data[$scope][$templateName] = $accountProfileTemplate;
}
}
return $data;
}
/**
* Returns the content of the PDF profiles.
*
* @param array $serverProfiles list of server profiles (name => object)
* @return array $data
*/
public function _getPdfProfiles($serverProfiles) {
$data = array();
foreach ($serverProfiles as $profileName => $serverProfile) {
foreach ($serverProfile->get_ActiveTypes() as $typeId) {
$pdfProfileNames = getPDFStructures($typeId, $profileName);
$reader = new PDFStructureReader($profileName);
foreach ($pdfProfileNames as $pdfProfileName) {
$pdfStructure = $reader->read($typeId, $pdfProfileName);
$data[$profileName]['structures'][$typeId][$pdfProfileName] = $pdfStructure->export();
}
}
$logoData = getAvailableLogos($profileName);
foreach ($logoData as $logo) {
$logoFileName = $logo['filename'];
$logoPath = __DIR__ . '/../config/pdf/' . $profileName . '/logos/' . $logoFileName;
$handle = fopen($logoPath, 'r');
$logoBinary = fread($handle, 100000000);
fclose($handle);
$data[$profileName]['logos'][$logoFileName] = base64_encode($logoBinary);
}
}
return $data;
}
/**
* Returns the content of the account profile templates.
*
* @return array $data
* @throws LAMException error reading template
*/
public function _getPdfProfileTemplates() {
$data = array();
$pdfTemplateNames = getPdfTemplateNames();
$reader = new PDFStructureReader(\LAM\PDF\GLOBAL_PROFILE);
foreach ($pdfTemplateNames as $scope => $templateNames) {
foreach ($templateNames as $templateName) {
$pdfStructure = $reader->read($scope, $templateName);
$data['structures'][$scope][$templateName] = $pdfStructure->export();
}
}
$logoNames = getPdfTemplateLogoNames();
foreach ($logoNames as $logoName) {
$data['logos'][$logoName] = base64_encode(getPdfTemplateLogoBinary($logoName));
}
return $data;
}
/**
* Returns the content of the self service profiles.
*
* @return array data
*/
public function _getSelfServiceProfiles() {
$data = array();
$profileTypes = getSelfServiceProfiles();
foreach ($profileTypes as $profileType => $profileNames) {
foreach ($profileNames as $profileName) {
$profile = loadSelfServiceProfile($profileName, $profileType);
if ($profile === false) {
continue;
}
$data[$profileType][$profileName] = $profile->export();
}
}
return $data;
}
/**
* Returns the content of the webauthn database.
*
* @return array data
*/
public function _getWebauthn() {
$data = array();
if ((version_compare(phpversion(), '7.2.0') >= 0)
&& extension_loaded('PDO')
&& in_array('sqlite', \PDO::getAvailableDrivers())) {
include_once __DIR__ . '/webauthn.inc';
$webauthnManager = new WebauthnManager();
$webauthnDatabase = $webauthnManager->getDatabase();
$data = $webauthnDatabase->export();
}
return $data;
}
}
/**
* Importer for LAM's configuration data.
*/
class ConfigDataImporter {
/**
* Returns a list of possible import objects.
*
* @param string $json JSON data
* @return ImporterStep[] steps
* @throws LAMException if invalid format
*/
public function getPossibleImportSteps($json) {
$data = json_decode($json, true);
if ($data === null) {
throw new LAMException(_('Unable to read import file.'));
}
$steps = array();
foreach ($data as $key => $value) {
switch ($key) {
case 'mainConfig':
$steps[] = new ImporterStep(_('General settings'), 'mainConfig', $value);
break;
case 'certificates':
$steps[] = new ImporterStep(_('SSL certificates'), 'certificates', $value);
break;
case 'serverProfiles':
$mainStep = new ImporterStep(_('Server profiles'), 'serverProfiles', $value);
foreach ($value as $profileName => $profileData) {
$mainStep->addSubStep(new ImporterStep($profileName, 'serverProfile_' . $profileName, $profileData));
}
$steps[] = $mainStep;
break;
case 'accountProfiles':
$mainStep = new ImporterStep(_('Account profiles'), 'accountProfiles', $value);
foreach ($value as $profileName => $profileData) {
$mainStep->addSubStep(new ImporterStep($profileName, 'accountProfile_' . $profileName, $profileData));
}
$steps[] = $mainStep;
break;
case 'accountProfileTemplates':
$steps[] = new ImporterStep(_('Account profiles') . ' - ' . _('Global templates'), 'accountProfileTemplates', $value);
break;
case 'pdfProfiles':
$mainStep = new ImporterStep(_('PDF structures'), 'pdfProfiles', $value);
foreach ($value as $profileName => $profileData) {
$mainStep->addSubStep(new ImporterStep($profileName, 'pdfProfile_' . $profileName, $profileData));
}
$steps[] = $mainStep;
break;
case 'pdfProfileTemplates':
$steps[] = new ImporterStep(_('PDF structures') . ' - ' . _('Global templates'), 'pdfProfileTemplates', $value);
break;
case 'selfServiceProfiles':
$steps[] = new ImporterStep(_('Self service profiles'), 'selfServiceProfiles', $value);
break;
case 'webauthn':
if ((version_compare(phpversion(), '7.2.0') >= 0)
&& extension_loaded('PDO')
&& in_array('sqlite', \PDO::getAvailableDrivers())) {
$steps[] = new ImporterStep(_('WebAuthn devices'), 'webauthn', $value);
}
break;
default:
logNewMessage(LOG_WARNING, 'Unknown import type: ' . $key);
}
}
if (empty($steps)) {
throw new LAMException(_('Unable to read import file.'));
}
return $steps;
}
/**
* Runs the actual import.
*
* @param ImporterStep[] $steps import steps
* @throws LAMException if error occurred
*/
public function runImport($steps) {
foreach ($steps as $step) {
if (!$step->isActive()) {
continue;
}
$key = $step->getKey();
switch ($key) {
case 'mainConfig':
$this->importMainConfig($step->getValue());
break;
case 'certificates':
$this->importCertificates($step->getValue());
break;
case 'serverProfiles':
$this->importServerProfiles($step);
break;
case 'accountProfiles':
$this->importAccountProfiles($step);
break;
case 'accountProfileTemplates':
$this->importAccountProfileTemplates($step);
break;
case 'pdfProfiles':
$this->importPdfProfiles($step);
break;
case 'pdfProfileTemplates':
$this->importPdfProfileTemplates($step);
break;
case 'selfServiceProfiles':
$this->importSelfServiceProfiles($step);
break;
case 'webauthn':
$this->importWebauthn($step);
break;
default:
logNewMessage(LOG_WARNING, 'Unknown import type: ' . $key);
}
}
}
/**
* Imports the main configuration.
*
* @param array $data main config data
* @throws LAMException error during import
*/
private function importMainConfig($data) {
$cfgMain = new LAMCfgMain();
$cfgMain->importData($data);
$cfgMain->save();
}
/**
* Imports the SSL certificates.
*
* @param null|string $data file content
* @throws LAMException error during import
*/
private function importCertificates($data) {
$cfgMain = new LAMCfgMain();
$cfgMain->importCertificates($data);
}
/**
* Imports the server profiles.
*
* @param ImporterStep $step step
* @throws LAMException error during import
*/
private function importServerProfiles($step) {
$failedProfiles = array();
foreach ($step->getSubSteps() as $profileStep) {
if (!$profileStep->isActive()) {
continue;
}
$data = $profileStep->getValue();
$profileName = str_replace('serverProfile_', '', $profileStep->getKey());
$serverProfile = new LAMConfig($profileName);
$serverProfile->importData($data);
$result = $serverProfile->save();
if ($result === LAMConfig::SAVE_FAIL) {
$failedProfiles[] = $profileName;
}
}
if (!empty($failedProfiles)) {
throw new LAMException(_('Unable to save server profile.'), implode(', ', $failedProfiles));
}
}
/**
* Imports the account profiles.
*
* @param ImporterStep $step step
* @throws LAMException error during import
*/
private function importAccountProfiles($step) {
$failedProfiles = array();
foreach ($step->getSubSteps() as $profileStep) {
if (!$profileStep->isActive()) {
continue;
}
$data = $profileStep->getValue();
$serverProfileName = str_replace('accountProfile_', '', $profileStep->getKey());
$serverProfile = new LAMConfig($serverProfileName);
foreach ($data as $typeId => $accountProfiles) {
foreach ($accountProfiles as $accountProfileName => $accountProfileData) {
$result = saveAccountProfile($accountProfileData, $accountProfileName, $typeId, $serverProfile);
if (!$result) {
$failedProfiles[] = $serverProfileName . ':' . $typeId . ':' . $accountProfileName;
}
}
}
}
if (!empty($failedProfiles)) {
throw new LAMException(_('Unable to save account profile.'), implode(', ', $failedProfiles));
}
}
/**
* Imports the account profile templates.
*
* @param ImporterStep $step step
* @throws LAMException error during import
*/
private function importAccountProfileTemplates($step) {
$data = $step->getValue();
foreach ($data as $typeId => $accountProfileTemplates) {
foreach ($accountProfileTemplates as $accountProfileTemplateName => $accountProfileData) {
installTemplateAccountProfile($typeId, $accountProfileTemplateName, $accountProfileData);
}
}
}
/**
* Imports the PDF profiles.
*
* @param ImporterStep $step step
* @throws LAMException error during import
*/
private function importPdfProfiles($step) {
$failedProfiles = array();
foreach ($step->getSubSteps() as $profileStep) {
if (!$profileStep->isActive()) {
continue;
}
$data = $profileStep->getValue();
$serverProfileName = str_replace('pdfProfile_', '', $profileStep->getKey());
if (isset($data['structures'])) {
$writer = new PDFStructureWriter($serverProfileName);
foreach ($data['structures'] as $typeId => $pdfProfiles) {
foreach ($pdfProfiles as $pdfProfileName => $pdfProfileData) {
$structure = new PDFStructure();
$structure->import($pdfProfileData);
try {
$writer->write($typeId, $pdfProfileName, $structure);
}
catch (LAMException $e) {
logNewMessage(LOG_ERR, $e->getTitle() . ' ' . $e->getMessage());
$failedProfiles[] = $serverProfileName . ':' . $typeId . ':' . $pdfProfileName;
}
}
}
}
if (isset($data['logos'])) {
foreach ($data['logos'] as $logoFileName => $logoData) {
$tempFilePath = tempnam("/tmp", "lam");
$tempFile = fopen($tempFilePath, "w");
$logoBinary = base64_decode($logoData);
fwrite($tempFile, $logoBinary);
fclose($tempFile);
uploadPDFLogo($tempFilePath, $logoFileName, $serverProfileName);
unlink($tempFilePath);
}
}
}
if (!empty($failedProfiles)) {
throw new LAMException(_('Could not save PDF structure, access denied.'), implode(', ', $failedProfiles));
}
}
/**
* Imports the PDF profile templates.
*
* @param ImporterStep $step step
* @throws LAMException error during import
*/
private function importPdfProfileTemplates($step) {
$failedNames = array();
$data = $step->getValue();
if (isset($data['structures'])) {
$writer = new PDFStructureWriter(\LAM\PDF\GLOBAL_PROFILE);
foreach ($data['structures'] as $typeId => $pdfProfiles) {
foreach ($pdfProfiles as $pdfProfileName => $pdfProfileData) {
$structure = new PDFStructure();
$structure->import($pdfProfileData);
try {
$writer->write($typeId, $pdfProfileName, $structure);
}
catch (LAMException $e) {
$failedNames[] = $typeId . ':' . $pdfProfileName;
logNewMessage(LOG_ERR, $e->getTitle() . ' ' . $e->getMessage());
}
}
}
}
$failedLogos = array();
if (isset($data['logos'])) {
foreach ($data['logos'] as $logoFileName => $logoData) {
$tempFilePath = tempnam("/tmp", "lam");
$tempFile = fopen($tempFilePath, "w");
$logoBinary = base64_decode($logoData);
fwrite($tempFile, $logoBinary);
fclose($tempFile);
$message = uploadPDFLogo($tempFilePath, $logoFileName, \LAM\PDF\GLOBAL_PROFILE);
unlink($tempFilePath);
if ($message->getType() === 'ERROR') {
$failedLogos[] = $logoFileName;
}
}
}
if (!empty($failedNames)) {
throw new LAMException(_('Could not save PDF structure, access denied.'), implode(', ', $failedNames));
}
if (!empty($failedLogos)) {
throw new LAMException(_('Unable to upload logo file.'), implode(', ', $failedLogos));
}
}
/**
* Imports the self service profiles.
*
* @param ImporterStep $step importer step
* @throws LAMException error saving profiles
*/
private function importSelfServiceProfiles($step) {
$failedNames = array();
$data = $step->getValue();
foreach ($data as $typeId => $profileData) {
foreach ($profileData as $profileName => $currentProfileData) {
$profile = selfServiceProfile::import($currentProfileData);
$result = saveSelfServiceProfile($profileName, $typeId, $profile);
if (!$result) {
$failedNames[] = $profileName;
}
}
}
if (!empty($failedNames)) {
throw new LAMException(_('Unable to save profile!'), implode(', ', $failedNames));
}
}
/**
* Imports the webauthn data.
*
* @param ImporterStep $step importer step
* @throws LAMException error saving profiles
*/
private function importWebauthn($step) {
$failedNames = array();
$data = $step->getValue();
include_once __DIR__ . '/webauthn.inc';
$webauthnManager = new WebauthnManager();
$webauthnDatabase = $webauthnManager->getDatabase();
$webauthnDatabase->import($data);
}
}
/**
* Step of the import process.
*/
class ImporterStep {
private $label;
private $key;
private $value;
private $active = false;
private $subSteps = array();
/**
* Constructor.
*
* @param string $label label
* @param string $key key
* @param array $value value
*/
public function __construct($label, $key, $value) {
$this->label = $label;
$this->key = $key;
$this->value = $value;
}
/**
* Returns the label.
*
* @return string label
*/
public function getLabel() {
return $this->label;
}
/**
* Returns the key.
*
* @return string key
*/
public function getKey() {
return $this->key;
}
/**
* Returns if this step should be executed.
*
* @return bool active
*/
public function isActive(): bool {
return $this->active;
}
/**
* Sets if this step should be executed.
*
* @param bool $active active
*/
public function setActive(bool $active) {
$this->active = $active;
}
/**
* Returns the value.
*
* @return string value
*/
public function getValue() {
return $this->value;
}
/**
* Adds a sub-step.
*
* @param ImporterStep $subStep sub-step
*/
public function addSubStep($subStep) {
$this->subSteps[] = $subStep;
}
/**
* Returns the sub-steps.
*
* @return ImporterStep[] sub-steps
*/
public function getSubSteps() {
return $this->subSteps;
}
}

View File

@ -1,11 +1,11 @@
<?php
namespace LAM\PROFILES;
use LAM\TYPES\TypeManager;
use \LAMException;
/*
$Id$
This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/)
Copyright (C) 2003 - 2018 Roland Gruber
Copyright (C) 2003 - 2020 Roland Gruber
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -46,7 +46,6 @@ function getAccountProfiles($typeId, $profile = null) {
$dir = @dir(dirname(__FILE__) . "/../config/profiles/" . $profile);
$ret = array();
$pos = 0;
if ($dir) {
$entry = $dir->read();
while ($entry){
@ -80,30 +79,45 @@ function profileExists($name, $typeId) {
}
/**
* Loads an profile of the given account type
*
* @param string $profile name of the profile (without .<scope> extension)
* @param string $typeId account type
* @return array hash array (attribute => value)
*/
function loadAccountProfile($profile, $typeId) {
$typeManager = new \LAM\TYPES\TypeManager();
$type = $typeManager->getConfiguredType($typeId);
if (!isValidProfileName($profile) || !preg_match("/^[a-z0-9_]+$/i", $typeId) || ($type == null)) {
return false;
* Loads an profile of the given account type
*
* @param string $profile name of the profile (without .<scope> extension)
* @param string $typeId account type
* @param string $serverProfileName server profile name
* @return array hash array (attribute => value)
*/
function loadAccountProfile($profile, $typeId, $serverProfileName) {
if (!isValidProfileName($profile) || !preg_match("/^[a-z0-9_]+$/i", $typeId)) {
logNewMessage(LOG_NOTICE, "Invalid account profile name: $serverProfileName:$profile:$typeId");
return array();
}
$file = substr(__FILE__, 0, strlen(__FILE__) - 17) . "/config/profiles/" . $serverProfileName . '/' . $profile . "." . $typeId;
try {
return readAccountProfileFile($file);
} catch (LAMException $e) {
StatusMessage('ERROR', $e->getTitle(), $e->getMessage());
}
return array();
}
/**
* Reads an account profile from the given file name.
*
* @param string $fileName file name
* @return array hash array (attribute => value)
* @throws LAMException error reading file
*/
function readAccountProfileFile($fileName) {
$settings = array();
$file = substr(__FILE__, 0, strlen(__FILE__) - 17) . "/config/profiles/" . $_SESSION['config']->getName() . '/' . $profile . "." . $typeId;
if (is_file($file)) {
$file = @fopen($file, "r");
if (is_file($fileName)) {
$file = @fopen($fileName, "r");
if ($file) {
while (!feof($file)) {
$line = fgets($file, 1024);
if (($line == "\n")||($line[0] == "#")) {
if (($line === false) || ($line == '') || ($line == "\n") || ($line[0] == "#")) {
continue; // ignore comments
}
// search keywords
$parts = array();
$parts = explode(": ", $line);
if (sizeof($parts) == 2) {
$option = $parts[0];
@ -114,15 +128,15 @@ function loadAccountProfile($profile, $typeId) {
}
}
fclose($file);
return $settings;
}
else {
StatusMessage("ERROR", "", _("Unable to load profile!") . " " . $file);
throw new LAMException(_("Unable to load profile!"), $fileName);
}
}
else {
StatusMessage("ERROR", "", _("Unable to load profile!") . " " . $file);
throw new LAMException(_("Unable to load profile!"), $fileName);
}
return $settings;
}
/**
@ -133,27 +147,40 @@ function loadAccountProfile($profile, $typeId) {
* @param array $attributes hash array (attribute => value)
* @param string $profile name of the account profile (without .<scope> extension)
* @param string $typeId account type
* @param \LAMConfig $serverProfile server profile
* @return boolean true, if saving succeeded
*/
function saveAccountProfile($attributes, $profile, $typeId) {
if (!isLoggedIn()) return false;
function saveAccountProfile($attributes, $profile, $typeId, $serverProfile) {
// check profile name and type id
$typeManager = new \LAM\TYPES\TypeManager();
$typeManager = new TypeManager($serverProfile);
$type = $typeManager->getConfiguredType($typeId);
if (!isValidProfileName($profile) || !preg_match("/^[a-z0-9_]+$/i", $typeId) || ($type == null)) {
logNewMessage(LOG_NOTICE, 'Invalid account profile name: ' . $profile . ':' . $typeId);
return false;
}
if (!is_array($attributes)) {
logNewMessage(LOG_NOTICE, 'Invalid account profile data');
return false;
}
$path = substr(__FILE__, 0, strlen(__FILE__) - 17) . "/config/profiles/" . $_SESSION['config']->getName() . '/' . $profile . "." . $typeId;
$file = @fopen($path, "w");
$path = substr(__FILE__, 0, strlen(__FILE__) - 17) . "/config/profiles/" . $serverProfile->getName() . '/' . $profile . "." . $typeId;
return writeProfileDataToFile($path, $attributes);
}
/**
* Writes the profile data to the given file.
*
* @param string $fileName file name
* @param array $data profile data
* @return bool writing was ok
*/
function writeProfileDataToFile($fileName, $data) {
$file = @fopen($fileName, "w");
if ($file) {
// write attributes
$keys = array_keys($attributes);
// write attributes
$keys = array_keys($data);
for ($i = 0; $i < sizeof($keys); $i++) {
if (isset($attributes[$keys[$i]])) {
$line = $keys[$i] . ": " . implode("+::+", $attributes[$keys[$i]]) . "\n";
if (isset($data[$keys[$i]])) {
$line = $keys[$i] . ": " . implode("+::+", $data[$keys[$i]]) . "\n";
}
else {
$line = $keys[$i] . ": \n";
@ -164,6 +191,7 @@ function saveAccountProfile($attributes, $profile, $typeId) {
fclose($file);
}
else {
logNewMessage(LOG_NOTICE, 'Unable to open account profile file: ' . $fileName);
return false;
}
return true;
@ -180,7 +208,7 @@ function delAccountProfile($file, $typeId) {
if (!isLoggedIn()) {
return false;
}
$typeManager = new \LAM\TYPES\TypeManager();
$typeManager = new TypeManager();
$type = $typeManager->getConfiguredType($typeId);
if (!isValidProfileName($file) || !preg_match("/^[a-z0-9_]+$/i", $typeId) || ($type == null)) {
return false;
@ -207,7 +235,7 @@ function isValidProfileName($name) {
* @param \LAM\TYPES\ConfiguredType $sourceType source type
* @param string $sourceProfileName profile name
* @param \LAM\TYPES\ConfiguredType $targetType target type
* @throws Exception
* @throws LAMException error during copy
*/
function copyAccountProfile($sourceType, $sourceProfileName, $targetType) {
if (!isValidProfileName($sourceProfileName)) {
@ -230,7 +258,7 @@ function copyAccountProfile($sourceType, $sourceProfileName, $targetType) {
*
* @param \LAM\TYPES\ConfiguredType $sourceType source type
* @param string $sourceProfileName profile name
* @throws Exception
* @throws LAMException error during copy
*/
function copyAccountProfileToTemplates($sourceType, $sourceProfileName) {
if (!isValidProfileName($sourceProfileName)) {
@ -251,7 +279,34 @@ function copyAccountProfileToTemplates($sourceType, $sourceProfileName) {
* Installs template profiles to the current server profile.
*/
function installProfileTemplates() {
$templatePath = dirname(__FILE__) . '/../config/templates/profiles';
$allTemplates = getProfileTemplateNames();
$basePath = dirname(__FILE__) . '/../config/profiles/' . $_SESSION['config']->getName();
if (!file_exists($basePath)) {
mkdir($basePath, 0700, true);
}
$typeManager = new TypeManager();
foreach ($typeManager->getConfiguredTypes() as $type) {
if (empty($allTemplates[$type->getScope()])) {
continue;
}
foreach ($allTemplates[$type->getScope()] as $templateName) {
$path = $basePath . '/' . $templateName . '.' . $type->getId();
if (!is_file($path)) {
$template = getProfileTemplateFileName($type->getScope(), $templateName);
logNewMessage(LOG_DEBUG, 'Copy template ' . $template . ' to ' . $path);
@copy($template, $path);
}
}
}
}
/**
* Returns a list of all global profile templates.
*
* @return array names (array('user' => array('default', 'extra')))
*/
function getProfileTemplateNames() {
$templatePath = __DIR__ . '/../config/templates/profiles';
$templateDir = @dir($templatePath);
$allTemplates = array();
if ($templateDir) {
@ -266,24 +321,54 @@ function installProfileTemplates() {
$entry = $templateDir->read();
}
}
$basePath = dirname(__FILE__) . '/../config/profiles/' . $_SESSION['config']->getName();
if (!file_exists($basePath)) {
mkdir($basePath, 0700, true);
return $allTemplates;
}
/**
* Returns the file name of a global template.
*
* @param string $scope e.g. user
* @param string $name profile name
* @return string file name
*/
function getProfileTemplateFileName($scope, $name) {
return __DIR__ . '/../config/templates/profiles' . '/' . $name . '.' . $scope;
}
/**
* Loads a template profile of the given account scope.
*
* @param string $profile name of the profile (without .<scope> extension)
* @param string $scope account type
* @return array hash array (attribute => value)
* @throws LAMException error reading profile template
*/
function loadTemplateAccountProfile($profile, $scope) {
if (!isValidProfileName($profile) || !preg_match("/^[a-z0-9_]+$/i", $scope)) {
logNewMessage(LOG_NOTICE, "Invalid account profile name: $profile:$scope");
return array();
}
$typeManager = new \LAM\TYPES\TypeManager();
foreach ($typeManager->getConfiguredTypes() as $type) {
if (empty($allTemplates[$type->getScope()])) {
continue;
}
foreach ($allTemplates[$type->getScope()] as $templateName) {
$path = $basePath . '/' . $templateName . '.' . $type->getId();
if (!is_file($path)) {
$template = $templatePath . '/' . $templateName . '.' . $type->getScope();
logNewMessage(LOG_DEBUG, 'Copy template ' . $template . ' to ' . $path);
@copy($template, $path);
}
}
$fileName = getProfileTemplateFileName($scope, $profile);
return readAccountProfileFile($fileName);
}
/**
* Installs a single template from the given data.
*
* @param string $scope account type (e.g. user)
* @param string $name template name
* @param array $data profile data
* @throws LAMException error saving file
*/
function installTemplateAccountProfile($scope, $name, $data) {
if (!isValidProfileName($name) || !preg_match("/^[a-z0-9_]+$/i", $scope)) {
logNewMessage(LOG_NOTICE, "Invalid account profile name: $name:$scope");
return;
}
$fileName = getProfileTemplateFileName($scope, $name);
$success = writeProfileDataToFile($fileName, $data);
if (!$success) {
throw new LAMException('Unable to write account profile template: ' . $fileName);
}
}
?>

View File

@ -170,7 +170,7 @@ function checkSelfServiceOptions($scope, $fields, $attributes, $passwordChangeOn
/**
* Returns a list of all available self service profiles (without .conf)
*
* @return array profile names (array(<account type> => array(<profile1>, <profile2>, ...)))
* @return array profile names (array('account type' => array('profile1', 'profile2')))
*/
function getSelfServiceProfiles() {
$types = LAM\TYPES\getTypes();
@ -201,7 +201,7 @@ function getSelfServiceProfiles() {
*
* @param string $name profile name
* @param string $scope account type
* @return selfServiceProfile true if file was readable
* @return false|selfServiceProfile profile or false if file was not readable
*/
function loadSelfServiceProfile($name, $scope) {
if (!preg_match("/^[0-9a-z _-]+$/i", $name) || !preg_match("/^[0-9a-z _-]+$/i", $scope)) {
@ -213,7 +213,13 @@ function loadSelfServiceProfile($name, $scope) {
$file = @fopen($file, "r");
if ($file) {
$data = fread($file, 10000000);
$profile = unserialize($data);
$profileData = @json_decode($data, true);
if ($profileData === null) {
$profile = unserialize($data);
}
else {
$profile = selfServiceProfile::import($profileData);
}
fclose($file);
}
else {
@ -249,7 +255,7 @@ function saveSelfServiceProfile($name, $scope, $profile) {
$file = @fopen($path, "w");
if ($file) {
// write settings to file
fputs($file, serialize($profile));
fputs($file, json_encode($profile->export()));
// close file
fclose($file);
}
@ -521,6 +527,32 @@ class selfServiceProfile {
$this->baseUrl = '';
}
/**
* Converts the export data back to a self service profile.
*
* @param array $data export data
* @return selfServiceProfile profile
*/
public static function import($data) {
$profile = new selfServiceProfile();
$vars = get_class_vars(selfServiceProfile::class);
foreach ($data as $key => $value) {
if (in_array($key, $vars)) {
$profile->$key = $value;
}
}
return $profile;
}
/**
* Returns a representation of the self service profile.
*
* @return array self service profile data
*/
public function export() {
return json_decode(json_encode($this), true);
}
/**
* Returns the server's base URL (e.g. https://www.example.com).
*

View File

@ -43,7 +43,7 @@ class toolWebauthn implements \LAMTool {
* @return string name
*/
function getName() {
return _('Webauthn devices');
return _('WebAuthn devices');
}
/**

View File

@ -110,7 +110,7 @@ class user extends baseType {
'ou' => _('Organisational unit'),
'proxyAddresses' => _('Proxy-Addresses'),
'sambakickofftime' => _('Account expiration date'),
'shadowexpire' => _('Password expiration'),
'shadowexpire' => _('Account expiration date'),
"sn" => _("Last name"),
'streetAddress' => _('Street'),
'telephoneNumber' => _('Telephone number'),

View File

@ -110,6 +110,7 @@ class WebauthnManager {
$credentialParameters = $this->getCredentialParameters();
$excludedKeys = $this->getExcludedKeys($userEntity, $extraExcludedKeys);
$timeout = $this->getTimeout();
$authenticatorSelectionCriteria = new AuthenticatorSelectionCriteria(null, false, AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_DISCOURAGED);
$registrationObject = new PublicKeyCredentialCreationOptions(
$rpEntity,
$userEntity,
@ -117,10 +118,10 @@ class WebauthnManager {
$credentialParameters,
$timeout,
$excludedKeys,
new AuthenticatorSelectionCriteria(),
$authenticatorSelectionCriteria,
PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_NONE,
new AuthenticationExtensionsClientInputs());
logNewMessage(LOG_DEBUG, 'Webauthn registration: ' . json_encode($registrationObject));
logNewMessage(LOG_DEBUG, 'WebAuthn registration: ' . json_encode($registrationObject));
return $registrationObject;
}
@ -157,7 +158,7 @@ class WebauthnManager {
return true;
}
catch (\Throwable $exception) {
logNewMessage(LOG_ERR, 'Webauthn validation failed: ' . $exception->getMessage() . $exception->getTraceAsString());
logNewMessage(LOG_ERR, 'WebAuthn validation failed: ' . $exception->getMessage() . $exception->getTraceAsString());
}
return false;
}
@ -455,7 +456,7 @@ class PublicKeyCredentialSourceRepositorySQLite implements PublicKeyCredentialSo
}
}
catch (\PDOException $e) {
logNewMessage(LOG_ERR, 'Webauthn database error: ' . $e->getMessage());
logNewMessage(LOG_ERR, 'WebAuthn database error: ' . $e->getMessage());
}
return null;
}
@ -479,7 +480,7 @@ class PublicKeyCredentialSourceRepositorySQLite implements PublicKeyCredentialSo
}
}
catch (\PDOException $e) {
logNewMessage(LOG_ERR, 'Webauthn database error: ' . $e->getMessage());
logNewMessage(LOG_ERR, 'WebAuthn database error: ' . $e->getMessage());
}
return $credentials;
}
@ -649,6 +650,53 @@ class PublicKeyCredentialSourceRepositorySQLite implements PublicKeyCredentialSo
$pdo->exec($sql);
}
/**
* Exports all entries.
*
* @return array data
*/
public function export() {
$pdo = $this->getPDO();
$statement = $pdo->prepare('select * from ' . self::TABLE_NAME);
$statement->execute();
$dbData = $statement->fetchAll();
$data = array();
foreach ($dbData as $dbRow) {
$data[] = array(
'userId' => $dbRow['userId'],
'credentialId' => $dbRow['credentialId'],
'credentialSource' => $dbRow['credentialSource'],
'registrationTime' => $dbRow['registrationTime'],
'lastUseTime' => $dbRow['lastUseTime'],
);
}
return $data;
}
/**
* Imports entries from export data.
*
* @param array $data export data
*/
public function import($data) {
$pdo = $this->getPDO();
$statement = $pdo->prepare('delete from ' . self::TABLE_NAME);
$statement->execute();
if (empty($data)) {
return;
}
foreach ($data as $dbRow) {
$statement = $pdo->prepare('insert into ' . self::TABLE_NAME . ' (userId, credentialId, credentialSource, registrationTime, lastUseTime) VALUES (?, ?, ?, ?, ?)');
$statement->execute(array(
$dbRow['userId'],
$dbRow['credentialId'],
$dbRow['credentialSource'],
$dbRow['registrationTime'],
$dbRow['lastUseTime']
));
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

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