From 90d4d77e4bf2106157e880f011a62cc7b1bdf3bc Mon Sep 17 00:00:00 2001 From: Peter Hormanns Date: Tue, 16 Jul 2019 20:00:30 +0200 Subject: [PATCH] test runnung --- .gitignore | 1 + LICENSE | 201 ++++++++++++++++ README.md | 4 + etc/jetty.xml | 12 + etc/realm.properties | 1 + ldif/ldap-example-data.ldif | 53 +++++ ldif/master-passwd.ldif | 7 + pom.xml | 121 ++++++++++ src/main/filters/filter-dev.properties | 1 + src/main/filters/filter-prod.properties | 1 + src/main/filters/filter-test.properties | 1 + .../java/de/jalin/ldapadmin/beans/Group.java | 45 ++++ .../de/jalin/ldapadmin/beans/LDAPBean.java | 9 + .../ldapadmin/beans/MembershipCheck.java | 27 +++ .../java/de/jalin/ldapadmin/beans/User.java | 126 ++++++++++ .../ldapadmin/beans/ValidationException.java | 23 ++ .../ldapadmin/ldap/AlreadyBoundException.java | 17 ++ .../de/jalin/ldapadmin/ldap/GroupsDAO.java | 125 ++++++++++ .../de/jalin/ldapadmin/ldap/LDAPSession.java | 182 +++++++++++++++ .../ldapadmin/ldap/LDAPSessionException.java | 13 ++ .../ldap/NoGroupMembersException.java | 17 ++ .../ldapadmin/ldap/PasswordValidator.java | 40 ++++ .../ldap/RequiredAttributeException.java | 16 ++ .../ldap/SimplePasswordException.java | 11 + .../de/jalin/ldapadmin/ldap/UsersDAO.java | 168 ++++++++++++++ .../ldapadmin/web/AbstractLDAPServlet.java | 82 +++++++ .../de/jalin/ldapadmin/web/GroupServlet.java | 148 ++++++++++++ .../de/jalin/ldapadmin/web/GroupsServlet.java | 40 ++++ .../de/jalin/ldapadmin/web/LogoutServlet.java | 25 ++ .../java/de/jalin/ldapadmin/web/Messages.java | 24 ++ .../ldapadmin/web/NaiveTrustManager.java | 52 +++++ .../jalin/ldapadmin/web/ProfileServlet.java | 119 ++++++++++ .../ldapadmin/web/ResetPasswordServlet.java | 196 ++++++++++++++++ .../de/jalin/ldapadmin/web/UserServlet.java | 217 ++++++++++++++++++ .../de/jalin/ldapadmin/web/UsersServlet.java | 41 ++++ src/main/resources/config.properties | 4 + .../jalin/ldapadmin/accessdenied.properties | 3 + .../ldapadmin/accessdenied_de.properties | 3 + .../ldapadmin/accessdenied_en.properties | 3 + .../de/jalin/ldapadmin/contact.properties | 2 + .../de/jalin/ldapadmin/contact_de.properties | 2 + .../de/jalin/ldapadmin/contact_en.properties | 2 + .../de/jalin/ldapadmin/exception.properties | 1 + .../jalin/ldapadmin/exception_de.properties | 1 + .../jalin/ldapadmin/exception_en.properties | 1 + .../de/jalin/ldapadmin/groups.properties | 14 ++ .../de/jalin/ldapadmin/groups_de.properties | 14 ++ .../de/jalin/ldapadmin/groups_en.properties | 14 ++ .../de/jalin/ldapadmin/login.properties | 13 ++ .../de/jalin/ldapadmin/login_de.properties | 13 ++ .../de/jalin/ldapadmin/login_en.properties | 13 ++ .../de/jalin/ldapadmin/menu.properties | 2 + .../de/jalin/ldapadmin/menu_de.properties | 2 + .../de/jalin/ldapadmin/menu_en.properties | 2 + .../de/jalin/ldapadmin/navbar.properties | 6 + .../de/jalin/ldapadmin/navbar_de.properties | 6 + .../de/jalin/ldapadmin/navbar_en.properties | 6 + .../de/jalin/ldapadmin/users.properties | 23 ++ .../de/jalin/ldapadmin/users_de.properties | 23 ++ .../de/jalin/ldapadmin/users_en.properties | 23 ++ .../jalin/ldapadmin/web/messages.properties | 28 +++ .../ldapadmin/web/messages_de.properties | 28 +++ .../ldapadmin/web/messages_en.properties | 28 +++ .../de/jalin/ldapadmin/wro/less.min.js | 16 ++ src/main/resources/log4j.properties | 4 + src/main/webapp/META-INF/context.xml | 2 + src/main/webapp/WEB-INF/web.xml | 93 ++++++++ src/main/webapp/access-denied.jsp | 20 ++ src/main/webapp/contact.jsp | 19 ++ src/main/webapp/css/style.css | 13 ++ src/main/webapp/group.jsp | 73 ++++++ src/main/webapp/groups.jsp | 38 +++ src/main/webapp/login.jsp | 44 ++++ src/main/webapp/loginfail.jsp | 48 ++++ src/main/webapp/new-password.jsp | 63 +++++ src/main/webapp/reset-password.jsp | 37 +++ src/main/webapp/servlet-exception.jsp | 24 ++ src/main/webapp/template/empty-navbar.jsp | 27 +++ src/main/webapp/template/footer.jsp | 4 + src/main/webapp/template/header.jsp | 15 ++ src/main/webapp/template/navbar.jsp | 33 +++ src/main/webapp/user.jsp | 152 ++++++++++++ src/main/webapp/users.jsp | 40 ++++ .../ldap/DirectoryServiceRunner.java | 126 ++++++++++ .../jalin/ldapadmin/ldap/TestCreateGroup.java | 80 +++++++ .../jalin/ldapadmin/ldap/TestCreateUser.java | 63 +++++ .../jalin/ldapadmin/ldap/TestDeleteUser.java | 66 ++++++ .../de/jalin/ldapadmin/ldap/TestReadUser.java | 66 ++++++ .../ldapadmin/ldap/TestUpdateAsBindUser.java | 76 ++++++ .../ldap/TestUpdateAsSimpleUser.java | 89 +++++++ .../jalin/ldapadmin/ldap/TestUpdateUser.java | 71 ++++++ 91 files changed, 3848 insertions(+) create mode 100644 LICENSE create mode 100644 README.md create mode 100644 etc/jetty.xml create mode 100644 etc/realm.properties create mode 100644 ldif/ldap-example-data.ldif create mode 100644 ldif/master-passwd.ldif create mode 100644 pom.xml create mode 100644 src/main/filters/filter-dev.properties create mode 100644 src/main/filters/filter-prod.properties create mode 100644 src/main/filters/filter-test.properties create mode 100644 src/main/java/de/jalin/ldapadmin/beans/Group.java create mode 100644 src/main/java/de/jalin/ldapadmin/beans/LDAPBean.java create mode 100644 src/main/java/de/jalin/ldapadmin/beans/MembershipCheck.java create mode 100644 src/main/java/de/jalin/ldapadmin/beans/User.java create mode 100644 src/main/java/de/jalin/ldapadmin/beans/ValidationException.java create mode 100644 src/main/java/de/jalin/ldapadmin/ldap/AlreadyBoundException.java create mode 100644 src/main/java/de/jalin/ldapadmin/ldap/GroupsDAO.java create mode 100644 src/main/java/de/jalin/ldapadmin/ldap/LDAPSession.java create mode 100644 src/main/java/de/jalin/ldapadmin/ldap/LDAPSessionException.java create mode 100644 src/main/java/de/jalin/ldapadmin/ldap/NoGroupMembersException.java create mode 100644 src/main/java/de/jalin/ldapadmin/ldap/PasswordValidator.java create mode 100644 src/main/java/de/jalin/ldapadmin/ldap/RequiredAttributeException.java create mode 100644 src/main/java/de/jalin/ldapadmin/ldap/SimplePasswordException.java create mode 100644 src/main/java/de/jalin/ldapadmin/ldap/UsersDAO.java create mode 100644 src/main/java/de/jalin/ldapadmin/web/AbstractLDAPServlet.java create mode 100644 src/main/java/de/jalin/ldapadmin/web/GroupServlet.java create mode 100644 src/main/java/de/jalin/ldapadmin/web/GroupsServlet.java create mode 100644 src/main/java/de/jalin/ldapadmin/web/LogoutServlet.java create mode 100644 src/main/java/de/jalin/ldapadmin/web/Messages.java create mode 100644 src/main/java/de/jalin/ldapadmin/web/NaiveTrustManager.java create mode 100644 src/main/java/de/jalin/ldapadmin/web/ProfileServlet.java create mode 100644 src/main/java/de/jalin/ldapadmin/web/ResetPasswordServlet.java create mode 100644 src/main/java/de/jalin/ldapadmin/web/UserServlet.java create mode 100644 src/main/java/de/jalin/ldapadmin/web/UsersServlet.java create mode 100644 src/main/resources/config.properties create mode 100644 src/main/resources/de/jalin/ldapadmin/accessdenied.properties create mode 100644 src/main/resources/de/jalin/ldapadmin/accessdenied_de.properties create mode 100644 src/main/resources/de/jalin/ldapadmin/accessdenied_en.properties create mode 100644 src/main/resources/de/jalin/ldapadmin/contact.properties create mode 100644 src/main/resources/de/jalin/ldapadmin/contact_de.properties create mode 100644 src/main/resources/de/jalin/ldapadmin/contact_en.properties create mode 100644 src/main/resources/de/jalin/ldapadmin/exception.properties create mode 100644 src/main/resources/de/jalin/ldapadmin/exception_de.properties create mode 100644 src/main/resources/de/jalin/ldapadmin/exception_en.properties create mode 100644 src/main/resources/de/jalin/ldapadmin/groups.properties create mode 100644 src/main/resources/de/jalin/ldapadmin/groups_de.properties create mode 100644 src/main/resources/de/jalin/ldapadmin/groups_en.properties create mode 100644 src/main/resources/de/jalin/ldapadmin/login.properties create mode 100644 src/main/resources/de/jalin/ldapadmin/login_de.properties create mode 100644 src/main/resources/de/jalin/ldapadmin/login_en.properties create mode 100644 src/main/resources/de/jalin/ldapadmin/menu.properties create mode 100644 src/main/resources/de/jalin/ldapadmin/menu_de.properties create mode 100644 src/main/resources/de/jalin/ldapadmin/menu_en.properties create mode 100644 src/main/resources/de/jalin/ldapadmin/navbar.properties create mode 100644 src/main/resources/de/jalin/ldapadmin/navbar_de.properties create mode 100644 src/main/resources/de/jalin/ldapadmin/navbar_en.properties create mode 100644 src/main/resources/de/jalin/ldapadmin/users.properties create mode 100644 src/main/resources/de/jalin/ldapadmin/users_de.properties create mode 100644 src/main/resources/de/jalin/ldapadmin/users_en.properties create mode 100644 src/main/resources/de/jalin/ldapadmin/web/messages.properties create mode 100644 src/main/resources/de/jalin/ldapadmin/web/messages_de.properties create mode 100644 src/main/resources/de/jalin/ldapadmin/web/messages_en.properties create mode 100644 src/main/resources/de/jalin/ldapadmin/wro/less.min.js create mode 100644 src/main/resources/log4j.properties create mode 100644 src/main/webapp/META-INF/context.xml create mode 100644 src/main/webapp/WEB-INF/web.xml create mode 100644 src/main/webapp/access-denied.jsp create mode 100644 src/main/webapp/contact.jsp create mode 100644 src/main/webapp/css/style.css create mode 100644 src/main/webapp/group.jsp create mode 100644 src/main/webapp/groups.jsp create mode 100644 src/main/webapp/login.jsp create mode 100644 src/main/webapp/loginfail.jsp create mode 100644 src/main/webapp/new-password.jsp create mode 100644 src/main/webapp/reset-password.jsp create mode 100644 src/main/webapp/servlet-exception.jsp create mode 100644 src/main/webapp/template/empty-navbar.jsp create mode 100644 src/main/webapp/template/footer.jsp create mode 100644 src/main/webapp/template/header.jsp create mode 100644 src/main/webapp/template/navbar.jsp create mode 100644 src/main/webapp/user.jsp create mode 100644 src/main/webapp/users.jsp create mode 100644 src/test/java/de/jalin/ldapadmin/ldap/DirectoryServiceRunner.java create mode 100644 src/test/java/de/jalin/ldapadmin/ldap/TestCreateGroup.java create mode 100644 src/test/java/de/jalin/ldapadmin/ldap/TestCreateUser.java create mode 100644 src/test/java/de/jalin/ldapadmin/ldap/TestDeleteUser.java create mode 100644 src/test/java/de/jalin/ldapadmin/ldap/TestReadUser.java create mode 100644 src/test/java/de/jalin/ldapadmin/ldap/TestUpdateAsBindUser.java create mode 100644 src/test/java/de/jalin/ldapadmin/ldap/TestUpdateAsSimpleUser.java create mode 100644 src/test/java/de/jalin/ldapadmin/ldap/TestUpdateUser.java diff --git a/.gitignore b/.gitignore index 088836b..eed9b95 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ pom.xml.releaseBackup pom.xml.versionsBackup pom.xml.next release.properties +ldap-data* diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4439006 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and +limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..3e81b48 --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +## LDAP Admin Application + +Webapplikation zur Pflege von LDAP-Accounts und Gruppen, +eingebetteter Apache Directory Server diff --git a/etc/jetty.xml b/etc/jetty.xml new file mode 100644 index 0000000..7abdbc4 --- /dev/null +++ b/etc/jetty.xml @@ -0,0 +1,12 @@ + + + + + + Administration Area + /etc/realm.properties + + + + + diff --git a/etc/realm.properties b/etc/realm.properties new file mode 100644 index 0000000..10f1a85 --- /dev/null +++ b/etc/realm.properties @@ -0,0 +1 @@ +peter: Test123,login,admins diff --git a/ldif/ldap-example-data.ldif b/ldif/ldap-example-data.ldif new file mode 100644 index 0000000..0c1e115 --- /dev/null +++ b/ldif/ldap-example-data.ldif @@ -0,0 +1,53 @@ +version: 1 + +dn: dc=saastest,dc=example,dc=com +objectclass: top +objectclass: domain +administrativeRole: accessControlSpecificArea +dc: saastest + +dn: cn=saastestAuthenticationRequirementsACISubentry,dc=saastest,dc=example,dc=com +objectClass: accessControlSubentry +objectClass: subentry +objectClass: top +subtreeSpecification: { } +prescriptiveACI: { identificationTag "subtreeFullAccessACI", precedence 11, authenticationLevel simple, itemOrUserFirst userFirst: { userClasses { name { "uid=application,ou=bind,dc=saastest,dc=example,dc=com" } }, userPermissions { { protectedItems { entry, allUserAttributeTypesAndValues }, grantsAndDenials { grantCompare, grantBrowse, grantRename, grantRemove, grantAdd, grantRead, grantFilterMatch, grantReturnDN, grantModify } } } } } +prescriptiveACI: { identificationTag "allUsersACI", precedence 9, authenticationLevel none, itemOrUserFirst userFirst: { userClasses { allUsers }, userPermissions { { protectedItems { attributeType { userPassword } }, grantsAndDenials { denyRead, denyFilterMatch, denyCompare } }, { protectedItems { entry, allUserAttributeTypesAndValues }, grantsAndDenials { grantCompare, grantBrowse,grantDiscloseOnError, grantRead, grantFilterMatch, grantReturnDN } } } } } +cn: saastestAuthenticationRequirementsACISubentry + +dn: ou=groups,dc=saastest,dc=example,dc=com +objectClass: top +objectClass: organizationalUnit +ou: groups + +dn: ou=users,dc=saastest,dc=example,dc=com +objectClass: top +objectClass: organizationalUnit +ou: users + +dn: ou=bind,dc=saastest,dc=example,dc=com +objectClass: top +objectClass: organizationalUnit +ou: bind + +dn: uid=admin,ou=users,dc=saastest,dc=example,dc=com +objectClass: top +objectClass: inetOrgPerson +objectClass: person +objectClass: organizationalPerson +cn: system administrator +sn: administrator +displayName: Directory Superuser +uid: admin +userPassword: admin-secret + +dn: uid=application,ou=bind,dc=saastest,dc=example,dc=com +objectClass: top +objectClass: inetOrgPerson +objectClass: person +objectClass: organizationalPerson +cn: application bind user +sn: administrator +displayName: Application User +uid: application +userPassword: app-secret diff --git a/ldif/master-passwd.ldif b/ldif/master-passwd.ldif new file mode 100644 index 0000000..19e7436 --- /dev/null +++ b/ldif/master-passwd.ldif @@ -0,0 +1,7 @@ +version:1 + +dn: uid=admin,ou=system +changetype: modify +replace: userPassword +userPassword: streng-geheim +- diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..6092ad6 --- /dev/null +++ b/pom.xml @@ -0,0 +1,121 @@ + + + 4.0.0 + de.jalin.ldapadmin + ldapadmin + war + 1.0-SNAPSHOT + LDAP Admin Webapp + + + UTF-8 + UTF-8 + dev + + + + + dev + + dev + + + + test + + test + + + + prod + + prod + + + + + + javax.servlet + javax.servlet-api + 4.0.1 + provided + + + javax.servlet.jsp + javax.servlet.jsp-api + 2.3.3 + provided + + + commons-net + commons-net + 3.6 + + + org.webjars + jquery + 3.4.1 + + + org.webjars + bootstrap + 3.4.1 + + + org.apache.directory.server + apacheds-service + 2.0.0.AM25 + test + + + junit + junit + 4.12 + test + + + + + + + src/main/resources + false + + + + + org.apache.maven.plugins + maven-war-plugin + 3.2.3 + + true + + + + org.eclipse.jetty + jetty-maven-plugin + 9.4.19.v20190610 + + etc/jetty.xml + 10 + + / + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + 1.8 + 1.8 + + + + + ldapadmin + + + diff --git a/src/main/filters/filter-dev.properties b/src/main/filters/filter-dev.properties new file mode 100644 index 0000000..e332da3 --- /dev/null +++ b/src/main/filters/filter-dev.properties @@ -0,0 +1 @@ +filtered.stage=dev \ No newline at end of file diff --git a/src/main/filters/filter-prod.properties b/src/main/filters/filter-prod.properties new file mode 100644 index 0000000..6653133 --- /dev/null +++ b/src/main/filters/filter-prod.properties @@ -0,0 +1 @@ +filtered.stage=prod \ No newline at end of file diff --git a/src/main/filters/filter-test.properties b/src/main/filters/filter-test.properties new file mode 100644 index 0000000..a9d7cac --- /dev/null +++ b/src/main/filters/filter-test.properties @@ -0,0 +1 @@ +filtered.stage=test \ No newline at end of file diff --git a/src/main/java/de/jalin/ldapadmin/beans/Group.java b/src/main/java/de/jalin/ldapadmin/beans/Group.java new file mode 100644 index 0000000..141c165 --- /dev/null +++ b/src/main/java/de/jalin/ldapadmin/beans/Group.java @@ -0,0 +1,45 @@ +package de.jalin.ldapadmin.beans; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +public class Group implements Serializable, LDAPBean { + + private static final long serialVersionUID = 1L; + + private String dn; + private String name; + private List members; + + public Group() { + members = new ArrayList(); + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getMembers() { + return members; + } + + public void setMembers(List members) { + this.members = members; + } + + @Override + public String getDn() { + return dn; + } + + @Override + public void setDn(String dn) { + this.dn = dn; + } + +} diff --git a/src/main/java/de/jalin/ldapadmin/beans/LDAPBean.java b/src/main/java/de/jalin/ldapadmin/beans/LDAPBean.java new file mode 100644 index 0000000..f1c92e0 --- /dev/null +++ b/src/main/java/de/jalin/ldapadmin/beans/LDAPBean.java @@ -0,0 +1,9 @@ +package de.jalin.ldapadmin.beans; + +public interface LDAPBean { + + public String getDn(); + + public void setDn(String dn); + +} diff --git a/src/main/java/de/jalin/ldapadmin/beans/MembershipCheck.java b/src/main/java/de/jalin/ldapadmin/beans/MembershipCheck.java new file mode 100644 index 0000000..38e477f --- /dev/null +++ b/src/main/java/de/jalin/ldapadmin/beans/MembershipCheck.java @@ -0,0 +1,27 @@ +package de.jalin.ldapadmin.beans; + +public class MembershipCheck { + + private User user; + private Group group; + + public User getUser() { + return user; + } + + public void setUser(User user) { + this.user = user; + } + + public Group getGroup() { + return group; + } + + public void setGroup(Group group) { + this.group = group; + } + + public String getChecked() { + return user != null && group != null && user.getGroups().contains(group.getDn()) ? "checked" : ""; //$NON-NLS-1$ //$NON-NLS-2$ + } +} diff --git a/src/main/java/de/jalin/ldapadmin/beans/User.java b/src/main/java/de/jalin/ldapadmin/beans/User.java new file mode 100644 index 0000000..1504e7a --- /dev/null +++ b/src/main/java/de/jalin/ldapadmin/beans/User.java @@ -0,0 +1,126 @@ +package de.jalin.ldapadmin.beans; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +import de.jalin.ldapadmin.ldap.PasswordValidator; +import de.jalin.ldapadmin.ldap.SimplePasswordException; + +public class User implements Serializable, LDAPBean { + + private static final long serialVersionUID = 1L; + + private String dn; + private String login; + private String password; + private String firstname; + private String lastname; + private String displayname; + private String email; + private String phone; + private String mobile; + private List groups; + + public User() { + groups = new ArrayList(); + } + + public User(final String login) { + this.login = login; + groups = new ArrayList(); + } + + public String getLogin() { + return login; + } + + public void setLogin(String login) { + this.login = login; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public void setAndValidatePassword(String password) throws SimplePasswordException { + final PasswordValidator validator = new PasswordValidator(); + validator.validate(password); + this.password = password; + } + + public String getFirstname() { + return firstname; + } + + public void setFirstname(String firstname) { + this.firstname = firstname; + } + + public String getLastname() { + return lastname; + } + + public void setLastname(String lastname) { + this.lastname = lastname; + } + + public String getDisplayname() { + return displayname; + } + + public void setDisplayname(String displayname) { + this.displayname = displayname; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public String getMobile() { + return mobile; + } + + public void setMobile(String mobile) { + this.mobile = mobile; + } + + public List getGroups() { + return groups; + } + + public void setGroups(List groups) { + this.groups = groups; + } + + @Override + public String toString() { + return getFirstname() + " " + getLastname() + " (" + getLogin() + ", " + getEmail() + ")"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ + } + + @Override + public String getDn() { + return dn; + } + + @Override + public void setDn(String dn) { + this.dn = dn; + } +} diff --git a/src/main/java/de/jalin/ldapadmin/beans/ValidationException.java b/src/main/java/de/jalin/ldapadmin/beans/ValidationException.java new file mode 100644 index 0000000..db48084 --- /dev/null +++ b/src/main/java/de/jalin/ldapadmin/beans/ValidationException.java @@ -0,0 +1,23 @@ +package de.jalin.ldapadmin.beans; + +public class ValidationException extends Exception { + + private static final long serialVersionUID = 1L; + + private final String fieldname; + private final String condition; + + public ValidationException(final String fieldname, final String condition) { + this.fieldname = fieldname; + this.condition = condition; + } + + public String getFieldname() { + return fieldname; + } + + public String getCondition() { + return condition; + } + +} diff --git a/src/main/java/de/jalin/ldapadmin/ldap/AlreadyBoundException.java b/src/main/java/de/jalin/ldapadmin/ldap/AlreadyBoundException.java new file mode 100644 index 0000000..eda91c2 --- /dev/null +++ b/src/main/java/de/jalin/ldapadmin/ldap/AlreadyBoundException.java @@ -0,0 +1,17 @@ +package de.jalin.ldapadmin.ldap; + +public class AlreadyBoundException extends Exception { + + private static final long serialVersionUID = 1L; + + private final String name; + + public AlreadyBoundException(final String name) { + this.name = name; + } + + public String getName() { + return name; + } + +} diff --git a/src/main/java/de/jalin/ldapadmin/ldap/GroupsDAO.java b/src/main/java/de/jalin/ldapadmin/ldap/GroupsDAO.java new file mode 100644 index 0000000..c3ff670 --- /dev/null +++ b/src/main/java/de/jalin/ldapadmin/ldap/GroupsDAO.java @@ -0,0 +1,125 @@ +package de.jalin.ldapadmin.ldap; + +import java.util.List; +import java.util.SortedMap; +import java.util.TreeMap; + +import javax.naming.directory.Attributes; +import javax.naming.directory.BasicAttribute; +import javax.naming.directory.BasicAttributes; +import javax.naming.directory.DirContext; +import javax.naming.directory.ModificationItem; +import javax.naming.directory.SearchResult; + +import de.jalin.ldapadmin.beans.Group; +import de.jalin.ldapadmin.beans.User; + +public class GroupsDAO { + + private final LDAPSession session; + + public GroupsDAO(final LDAPSession session) { + this.session = session; + } + + public SortedMap loadGroups(final SortedMap users) throws LDAPSessionException + { + final SortedMap list = new TreeMap(); + final List searchResult = session.search("ou=groups"); //$NON-NLS-1$ + for (final SearchResult result : searchResult) { + final Attributes attribs = result.getAttributes(); + final Group grp = new Group(); + grp.setName(session.getStringValue(attribs, "cn")); //$NON-NLS-1$ + grp.setDn(result.getNameInNamespace()); + final List listOfMembers = session.getListOfValues(attribs, "uniqueMember"); //$NON-NLS-1$ + final String dn = grp.getDn(); + for (String userDN : listOfMembers) { + final User user = users.get(userDN); + final List groups = user.getGroups(); + groups.add(dn); + } + grp.setMembers(listOfMembers); + list.put(dn, grp); + } + return list; + } + + public void create(final Group grp) throws LDAPSessionException, AlreadyBoundException { + assert grp != null; + final String name = grp.getName(); + assert name != null; + final BasicAttributes attributes = new BasicAttributes(); + final BasicAttribute objClass = new BasicAttribute("objectClass"); //$NON-NLS-1$ + objClass.add("top"); //$NON-NLS-1$ + objClass.add("groupOfUniqueNames"); //$NON-NLS-1$ + attributes.put(objClass); + attributes.put("cn", name); //$NON-NLS-1$ + final List uniqueMembers = grp.getMembers(); + final BasicAttribute uniqMembers = new BasicAttribute("uniqueMember"); //$NON-NLS-1$ + if (uniqueMembers != null && uniqueMembers.size() > 0) { + for (String dn : uniqueMembers) { + uniqMembers.add(dn); + } + } + attributes.put(uniqMembers); + final String dn = session.createSubcontext("cn=${cn},ou=groups".replace("${cn}", name), attributes); //$NON-NLS-1$ //$NON-NLS-2$ + grp.setDn(dn); + } + + public Group readGroup(final String dn, final SortedMap users) throws LDAPSessionException { + final Attributes attribs = session.getAttributes(dn.substring(0, dn.indexOf("ou=groups") + 9)); //$NON-NLS-1$ + final Group grp = new Group(); + grp.setDn(dn); + grp.setName(session.getStringValue(attribs, "cn")); //$NON-NLS-1$ + final List listOfMembers = session.getListOfValues(attribs, "uniqueMember"); //$NON-NLS-1$ + for (String userDN : listOfMembers) { + final User user = users.get(userDN); + final List groups = user.getGroups(); + groups.add(dn); + } + grp.setMembers(listOfMembers); + return grp; + } + + public void update(final Group grp) throws LDAPSessionException, NoGroupMembersException { + assert grp != null; + final String name = grp.getName(); + assert name != null; + if (grp.getMembers().size() == 0) { + throw new NoGroupMembersException(name); + } + final BasicAttribute membersOfAttrib = new BasicAttribute("uniqueMember"); //$NON-NLS-1$ + for (final String memberDN : grp.getMembers()) { + membersOfAttrib.add(memberDN); + } + final ModificationItem modificationItem = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, membersOfAttrib); + session.modifyAttributes("cn=${cn},ou=groups".replace("${cn}", name), new ModificationItem[] { modificationItem }); //$NON-NLS-1$ //$NON-NLS-2$ + } + + public void updateMemberships(final User usr) throws LDAPSessionException, NoGroupMembersException { + final List searchResult = session.search("ou=groups"); //$NON-NLS-1$ + for (final SearchResult result : searchResult) { + final Attributes attribs = result.getAttributes(); + final Group grp = new Group(); + grp.setName(session.getStringValue(attribs, "cn")); //$NON-NLS-1$ + grp.setDn(result.getNameInNamespace()); + final List listOfGroupMembers = session.getListOfValues(attribs, "uniqueMember"); //$NON-NLS-1$ + grp.setMembers(listOfGroupMembers); + final List listOfUserMemberships = usr.getGroups(); + if (listOfGroupMembers.contains(usr.getDn()) && !listOfUserMemberships.contains(grp.getDn())) { + grp.getMembers().remove(usr.getDn()); + update(grp); + } + if (!listOfGroupMembers.contains(usr.getDn()) && listOfUserMemberships.contains(grp.getDn())) { + grp.getMembers().add(usr.getDn()); + update(grp); + } + } + } + + public void delete(final Group grp) throws LDAPSessionException { + assert grp != null; + session.unbind("cn=${id},ou=groups", grp.getName()); //$NON-NLS-1$ + } + +} diff --git a/src/main/java/de/jalin/ldapadmin/ldap/LDAPSession.java b/src/main/java/de/jalin/ldapadmin/ldap/LDAPSession.java new file mode 100644 index 0000000..fbc67c8 --- /dev/null +++ b/src/main/java/de/jalin/ldapadmin/ldap/LDAPSession.java @@ -0,0 +1,182 @@ +package de.jalin.ldapadmin.ldap; + +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +import javax.naming.Context; +import javax.naming.NameAlreadyBoundException; +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.NoPermissionException; +import javax.naming.directory.Attribute; +import javax.naming.directory.Attributes; +import javax.naming.directory.BasicAttribute; +import javax.naming.directory.BasicAttributes; +import javax.naming.directory.DirContext; +import javax.naming.directory.InitialDirContext; +import javax.naming.directory.ModificationItem; +import javax.naming.directory.SearchResult; + +public class LDAPSession { + +private InitialDirContext ctx; + + public LDAPSession(final String providerURL, final String principal, final String password) throws LDAPSessionException { + final Properties env = new Properties(); + env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); //$NON-NLS-1$ + env.put("com.sun.jndi.ldap.connect.pool", "true"); //$NON-NLS-1$ //$NON-NLS-2$ + env.put(Context.PROVIDER_URL, providerURL); + env.put(Context.SECURITY_AUTHENTICATION, "simple"); //$NON-NLS-1$ + env.put(Context.SECURITY_PRINCIPAL, principal); + env.put(Context.SECURITY_CREDENTIALS, password); + try { + ctx = new InitialDirContext(env); + } catch (NamingException e) { + throw new LDAPSessionException(e); + } + createOrgUnitNodesIfNotExist(); + } + + public String getStringValue(final Attributes attribs, final String attrName) throws LDAPSessionException { + final Attribute attribute = attribs.get(attrName); + if (attribute == null) { + return null; + } + try { + return (String) attribute.get(); + } catch (NamingException e) { + throw new LDAPSessionException(e); + } + } + + public byte[] getBytesValue(final Attributes attribs, final String attrName) throws LDAPSessionException { + final Attribute attribute = attribs.get(attrName); + if (attribute == null) { + return null; + } + try { + return (byte[]) attribute.get(); + } catch (NamingException e) { + throw new LDAPSessionException(e); + } + } + + public List getListOfValues(final Attributes attribs, final String attrName) throws LDAPSessionException { + final Attribute attribute = attribs.get(attrName); + if (attribute == null) { + return null; + } + try { + final int size = attribute.size(); + List listOfValues = new ArrayList(); + for (int idx = 0; idx < size; idx++) { + listOfValues.add((String) attribute.get(idx)); + } + return listOfValues; + } catch (NamingException e) { + throw new LDAPSessionException(e); + } + } + + public String createSubcontext(final String subcontext, final BasicAttributes attributes) throws LDAPSessionException, AlreadyBoundException { + try { + final DirContext dirContext = ctx.createSubcontext(subcontext, attributes); + return dirContext.getNameInNamespace(); + } catch (NameAlreadyBoundException e) { + throw new AlreadyBoundException(subcontext); + } catch (NamingException e) { + throw new LDAPSessionException(e); + } + } + + public List search(final String name, final String attribName, final String attribValue) throws LDAPSessionException { + final List searchResult = new ArrayList(); + try { + Attributes matchingAttributes = new BasicAttributes(); + matchingAttributes.put(attribName, attribValue); + final NamingEnumeration searchEnum = ctx.search(name, matchingAttributes); + while (searchEnum.hasMore()) { + searchResult.add(searchEnum.next()); + } + return searchResult; + } catch (NamingException e) { + throw new LDAPSessionException(e); + } + } + + public List search(final String name) throws LDAPSessionException { + final List searchResult = new ArrayList(); + try { + final NamingEnumeration searchEnum = ctx.search(name, null); + while (searchEnum.hasMore()) { + searchResult.add(searchEnum.next()); + } + return searchResult; + } catch (NamingException e) { + throw new LDAPSessionException(e); + } + } + + public Attributes getAttributes(final String dn) throws LDAPSessionException { + try { + return ctx.getAttributes(dn); + } catch (NamingException e) { + throw new LDAPSessionException(e); + } + } + + public void modifyAttributes(final String name, final ModificationItem[] mods) throws LDAPSessionException { + try { + ctx.modifyAttributes(name, mods); + } catch (NamingException e) { + throw new LDAPSessionException(e); + } + + } + + public void unbind(final String searchPattern, final String id) throws LDAPSessionException { + try { + ctx.unbind(searchPattern.replace("${id}", id)); //$NON-NLS-1$ + } catch (NamingException e) { + throw new LDAPSessionException(e); + } + } + + public void close() throws NamingException { + if (ctx != null) { + ctx.close(); + ctx = null; + } + } + + private void createOrgUnitNodesIfNotExist() throws LDAPSessionException { + try { + final BasicAttributes usersAttributes = new BasicAttributes(); + final BasicAttribute usersObjClass = new BasicAttribute("objectClass"); //$NON-NLS-1$ + usersObjClass.add("top"); //$NON-NLS-1$ + usersObjClass.add("organizationalUnit"); //$NON-NLS-1$ + usersAttributes.put(usersObjClass); + usersAttributes.put("ou", "users"); //$NON-NLS-1$ //$NON-NLS-2$ + ctx.createSubcontext("ou=users", usersAttributes); //$NON-NLS-1$ + } catch (NamingException e) { + if (!((e instanceof NameAlreadyBoundException) || (e instanceof NoPermissionException))) { + throw new LDAPSessionException(e); + } + } + try { + final BasicAttributes groupsAttributes = new BasicAttributes(); + final BasicAttribute groupsObjClass = new BasicAttribute("objectClass"); //$NON-NLS-1$ + groupsObjClass.add("top"); //$NON-NLS-1$ + groupsObjClass.add("organizationalUnit"); //$NON-NLS-1$ + groupsAttributes.put(groupsObjClass); + groupsAttributes.put("ou", "groups"); //$NON-NLS-1$ //$NON-NLS-2$ + ctx.createSubcontext("ou=groups", groupsAttributes); //$NON-NLS-1$ + } catch (NamingException e) { + if (!((e instanceof NameAlreadyBoundException) || (e instanceof NoPermissionException))) { + throw new LDAPSessionException(e); + } + } + } + +} diff --git a/src/main/java/de/jalin/ldapadmin/ldap/LDAPSessionException.java b/src/main/java/de/jalin/ldapadmin/ldap/LDAPSessionException.java new file mode 100644 index 0000000..0b9a313 --- /dev/null +++ b/src/main/java/de/jalin/ldapadmin/ldap/LDAPSessionException.java @@ -0,0 +1,13 @@ +package de.jalin.ldapadmin.ldap; + +import javax.naming.NamingException; + +public class LDAPSessionException extends Exception { + + private static final long serialVersionUID = 1L; + + public LDAPSessionException(NamingException e) { + super(e); + } + +} diff --git a/src/main/java/de/jalin/ldapadmin/ldap/NoGroupMembersException.java b/src/main/java/de/jalin/ldapadmin/ldap/NoGroupMembersException.java new file mode 100644 index 0000000..15b5876 --- /dev/null +++ b/src/main/java/de/jalin/ldapadmin/ldap/NoGroupMembersException.java @@ -0,0 +1,17 @@ +package de.jalin.ldapadmin.ldap; + +public class NoGroupMembersException extends Exception { + + private static final long serialVersionUID = 1L; + + private final String groupName; + + public NoGroupMembersException(final String name) { + groupName = name; + } + + public String getGroupName() { + return groupName; + } + +} diff --git a/src/main/java/de/jalin/ldapadmin/ldap/PasswordValidator.java b/src/main/java/de/jalin/ldapadmin/ldap/PasswordValidator.java new file mode 100644 index 0000000..328216d --- /dev/null +++ b/src/main/java/de/jalin/ldapadmin/ldap/PasswordValidator.java @@ -0,0 +1,40 @@ +package de.jalin.ldapadmin.ldap; + + +public class PasswordValidator { + + private static final int MIN_PASSWORD_LEN = 6; + + public void validate(final String password) throws SimplePasswordException { + if (password == null || password.isEmpty()) { + throw new SimplePasswordException("password required"); + } + if (password.length() < MIN_PASSWORD_LEN) { + throw new SimplePasswordException("minimal password length is " + MIN_PASSWORD_LEN + " characters"); + } + int hasLowerCaseChar = 0; + int hasUpperCaseChar = 0; + int hasDigits = 0; + int hasSpecialChar = 0; + for (int idx = 0; idx < password.length(); idx++) { + final char test = password.charAt(idx); + final int type = Character.getType(test); + if (type == Character.DECIMAL_DIGIT_NUMBER) { + hasDigits = 1; + } else { + if (type == Character.LOWERCASE_LETTER) { + hasLowerCaseChar = 1; + } else + if (type == Character.UPPERCASE_LETTER) { + hasUpperCaseChar = 1; + } else + hasSpecialChar = 1; + } + } + if (hasDigits + hasLowerCaseChar + hasUpperCaseChar + hasSpecialChar < 3) { + throw new SimplePasswordException("a password requires 3 out of 4 " + + "different character types: lowercase, uppercase, digits and special characters"); + } + } + +} diff --git a/src/main/java/de/jalin/ldapadmin/ldap/RequiredAttributeException.java b/src/main/java/de/jalin/ldapadmin/ldap/RequiredAttributeException.java new file mode 100644 index 0000000..6b80fae --- /dev/null +++ b/src/main/java/de/jalin/ldapadmin/ldap/RequiredAttributeException.java @@ -0,0 +1,16 @@ +package de.jalin.ldapadmin.ldap; + +public class RequiredAttributeException extends Exception { + + private static final long serialVersionUID = 1L; + + private final String fieldname; + + public RequiredAttributeException(final String fieldname) { + this.fieldname = fieldname; + } + + public String getFieldname() { + return fieldname; + } +} diff --git a/src/main/java/de/jalin/ldapadmin/ldap/SimplePasswordException.java b/src/main/java/de/jalin/ldapadmin/ldap/SimplePasswordException.java new file mode 100644 index 0000000..12464f0 --- /dev/null +++ b/src/main/java/de/jalin/ldapadmin/ldap/SimplePasswordException.java @@ -0,0 +1,11 @@ +package de.jalin.ldapadmin.ldap; + +public class SimplePasswordException extends Exception { + + private static final long serialVersionUID = 1L; + + public SimplePasswordException(final String message) { + super(message); + } + +} diff --git a/src/main/java/de/jalin/ldapadmin/ldap/UsersDAO.java b/src/main/java/de/jalin/ldapadmin/ldap/UsersDAO.java new file mode 100644 index 0000000..c3727a4 --- /dev/null +++ b/src/main/java/de/jalin/ldapadmin/ldap/UsersDAO.java @@ -0,0 +1,168 @@ +package de.jalin.ldapadmin.ldap; + +import java.util.ArrayList; +import java.util.List; +import java.util.SortedMap; +import java.util.TreeMap; + +import javax.naming.directory.Attributes; +import javax.naming.directory.BasicAttribute; +import javax.naming.directory.BasicAttributes; +import javax.naming.directory.DirContext; +import javax.naming.directory.ModificationItem; +import javax.naming.directory.SearchResult; + +import de.jalin.ldapadmin.beans.User; + +public class UsersDAO { + + private final LDAPSession session; + + public UsersDAO(final LDAPSession session) { + this.session = session; + } + + public SortedMap loadUsers() throws LDAPSessionException + { + final SortedMap usersHash = new TreeMap(); + final List enumeration = session.search("ou=users"); //$NON-NLS-1$ + for (SearchResult result : enumeration) { + final Attributes attribs = result.getAttributes(); + final User usr = new User(); + usr.setFirstname(session.getStringValue(attribs, "givenName")); //$NON-NLS-1$ + usr.setLastname(session.getStringValue(attribs, "sn")); //$NON-NLS-1$ + usr.setEmail(session.getStringValue(attribs, "mail")); //$NON-NLS-1$ + usr.setLogin(session.getStringValue(attribs, "uid")); //$NON-NLS-1$ + usr.setPhone(session.getStringValue(attribs, "telephoneNumber")); //$NON-NLS-1$ + usr.setMobile(session.getStringValue(attribs, "mobile")); //$NON-NLS-1$ + usr.setDisplayname(session.getStringValue(attribs, "displayName")); //$NON-NLS-1$ + usr.setLogin(session.getStringValue(attribs, "uid")); //$NON-NLS-1$ + usr.setDn(result.getNameInNamespace()); + usersHash.put(usr.getDn(), usr); + } + return usersHash; + } + + public void create(final User usr) throws LDAPSessionException, RequiredAttributeException, AlreadyBoundException { + assert usr != null; + final String uid = usr.getLogin(); + if (!hasValue(uid)) { + throw new RequiredAttributeException("uid"); //$NON-NLS-1$ + } + assert uid != null; + final BasicAttributes attributes = new BasicAttributes(); + final BasicAttribute objClass = new BasicAttribute("objectClass"); //$NON-NLS-1$ + objClass.add("top"); //$NON-NLS-1$ + objClass.add("person"); //$NON-NLS-1$ + objClass.add("organizationalPerson"); //$NON-NLS-1$ + objClass.add("inetOrgPerson"); //$NON-NLS-1$ + attributes.put(objClass); + final String firstName = usr.getFirstname(); + if (hasValue(firstName)) { + attributes.put("givenName", firstName); //$NON-NLS-1$ + } + final String lastname = usr.getLastname(); + if (!hasValue(lastname)) { + throw new RequiredAttributeException("lastname"); //$NON-NLS-1$ + } + attributes.put("sn", lastname); //$NON-NLS-1$ + final String email = usr.getEmail(); + if (hasValue(email)) { + attributes.put("mail", email); //$NON-NLS-1$ + } + attributes.put("uid", uid); //$NON-NLS-1$ + attributes.put("cn", uid); //$NON-NLS-1$ + final String telephone = usr.getPhone(); + if (hasValue(telephone)) { + attributes.put("telephoneNumber", telephone); //$NON-NLS-1$ + } + final String mobile = usr.getMobile(); + if (hasValue(mobile)) { + attributes.put("mobile", mobile); //$NON-NLS-1$ + } + final String comment = usr.getDisplayname(); + if (hasValue(comment)) { + attributes.put("displayName", comment); //$NON-NLS-1$ + } else { + attributes.put("displayName", firstName == null ? lastname : lastname + ", " + firstName); //$NON-NLS-1$ //$NON-NLS-2$ + } + final String password = usr.getPassword(); + if (!hasValue(password)) { + throw new RequiredAttributeException("password"); //$NON-NLS-1$ + } + attributes.put("userPassword", password); //$NON-NLS-1$ + final String dn = session.createSubcontext("uid=${uid},ou=users".replace("${uid}", uid), attributes); //$NON-NLS-1$ //$NON-NLS-2$ + usr.setDn(dn); + } + + public User read(final String dn) throws LDAPSessionException { + final Attributes attribs = session.getAttributes(dn.substring(0, dn.indexOf("ou=users") + 8)); //$NON-NLS-1$ + final User usr = new User(); + usr.setFirstname(session.getStringValue(attribs, "givenName")); //$NON-NLS-1$ + usr.setLastname(session.getStringValue(attribs, "sn")); //$NON-NLS-1$ + usr.setEmail(session.getStringValue(attribs, "mail")); //$NON-NLS-1$ + usr.setLogin(session.getStringValue(attribs, "uid")); //$NON-NLS-1$ + usr.setPhone(session.getStringValue(attribs, "telephoneNumber")); //$NON-NLS-1$ + usr.setMobile(session.getStringValue(attribs, "mobile")); //$NON-NLS-1$ + usr.setDisplayname(session.getStringValue(attribs, "displayName")); //$NON-NLS-1$ + usr.setLogin(session.getStringValue(attribs, "uid")); //$NON-NLS-1$ + usr.setDn(dn); + return usr; + } + + public void update(final User usr) throws LDAPSessionException { + assert usr != null; + final String uid = usr.getLogin(); + assert uid != null; + final Attributes attribs = session.getAttributes( + "uid=${uid},ou=users".replace("${uid}", uid)); //$NON-NLS-1$ //$NON-NLS-2$ + final List updates = new ArrayList(); + addStringAttrUpdate(updates, attribs, "displayName", usr.getDisplayname()); //$NON-NLS-1$ + addStringAttrUpdate(updates, attribs, "mail", usr.getEmail()); //$NON-NLS-1$ + addStringAttrUpdate(updates, attribs, "givenName", usr.getFirstname()); //$NON-NLS-1$ + addStringAttrUpdate(updates, attribs, "sn", usr.getLastname()); //$NON-NLS-1$ + addStringAttrUpdate(updates, attribs, "mobile", usr.getMobile()); //$NON-NLS-1$ + addStringAttrUpdate(updates, attribs, "telephoneNumber", usr.getPhone()); //$NON-NLS-1$ + addPasswordUpdate(updates, "userPassword", usr.getPassword()); //$NON-NLS-1$ + session.modifyAttributes("uid=${uid},ou=users".replace("${uid}", uid), updates.toArray(new ModificationItem[] { })); //$NON-NLS-1$ //$NON-NLS-2$ + } + + public void delete(final User user) throws LDAPSessionException { + assert user != null; + session.unbind("uid=${id},ou=users", user.getLogin()); //$NON-NLS-1$ + } + + private boolean hasValue(final String email) { + return email != null && !email.isEmpty(); + } + + private void addStringAttrUpdate(final List updates, + final Attributes attribs, final String attributeName, final String newValue) throws LDAPSessionException { + final String oldValue = session.getStringValue(attribs, attributeName); + final BasicAttribute basicAttribute = new BasicAttribute(attributeName); + if (hasValue(newValue)) { + if (!newValue.equals(oldValue)) { + basicAttribute.add(newValue); + int ldapOp = DirContext.REPLACE_ATTRIBUTE; + if (oldValue == null) { + ldapOp = DirContext.ADD_ATTRIBUTE; + } + final ModificationItem modificationItem = new ModificationItem(ldapOp, basicAttribute); + updates.add(modificationItem); + } + } else { + if (hasValue(oldValue)) { + updates.add(new ModificationItem(DirContext.REMOVE_ATTRIBUTE, basicAttribute)); + } + } + } + + private void addPasswordUpdate(final List updates, + final String attributeName, final String newValue) throws LDAPSessionException { + if (hasValue(newValue)) { + updates.add(new ModificationItem(DirContext.REPLACE_ATTRIBUTE, + new BasicAttribute(attributeName, newValue))); + } + } + +} diff --git a/src/main/java/de/jalin/ldapadmin/web/AbstractLDAPServlet.java b/src/main/java/de/jalin/ldapadmin/web/AbstractLDAPServlet.java new file mode 100644 index 0000000..b7a124e --- /dev/null +++ b/src/main/java/de/jalin/ldapadmin/web/AbstractLDAPServlet.java @@ -0,0 +1,82 @@ +package de.jalin.ldapadmin.web; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; +import java.util.SortedMap; +import java.util.TreeMap; + +import javax.naming.NamingException; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpSession; + +import de.jalin.ldapadmin.beans.Group; +import de.jalin.ldapadmin.beans.User; +import de.jalin.ldapadmin.ldap.LDAPSession; +import de.jalin.ldapadmin.ldap.LDAPSessionException; +import java.util.logging.Logger; + +public class AbstractLDAPServlet extends HttpServlet { + + private static final long serialVersionUID = 1L; + + protected static final Logger LOG = Logger.getLogger("LDAP"); + + private SortedMap users; + private SortedMap groups; + + protected LDAPSession ldapSession; + protected Properties config; + + protected void loadData() { + users = new TreeMap<>(); + groups = new TreeMap<>(); + } + + protected User getUser(final String uid) { + return users.get(uid); + } + + protected Group getGroup(final String gid) { + return groups.get(gid); + } + + @Override + public void init() throws ServletException { + super.init(); + final InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("config.properties"); //$NON-NLS-1$ + config = new Properties(); + try { + config.load(inputStream); + ldapSession = new LDAPSession(config.getProperty("provider.url"), config.getProperty("security.principal"), config.getProperty("security.password")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } catch (IOException | LDAPSessionException e) { + LOG.severe(e.getMessage()); + throw new ServletException(e); + } + } + + @Override + public void destroy() { + super.destroy(); + try { + ldapSession.close(); + } catch (NamingException e) { + LOG.severe(e.getMessage()); + } + } + + protected void throwServletException(final HttpSession session, final Exception e) throws ServletException + { + session.setAttribute("servletexception", e); //$NON-NLS-1$ + LOG.severe(e.getMessage()); + throw new ServletException(e); + } + + protected void cleanSession(final HttpSession httpSession) { + httpSession.removeAttribute("errormessage"); //$NON-NLS-1$ + httpSession.removeAttribute("successmessage"); //$NON-NLS-1$ + httpSession.removeAttribute("servletexception"); //$NON-NLS-1$ + } + +} diff --git a/src/main/java/de/jalin/ldapadmin/web/GroupServlet.java b/src/main/java/de/jalin/ldapadmin/web/GroupServlet.java new file mode 100644 index 0000000..35a54f0 --- /dev/null +++ b/src/main/java/de/jalin/ldapadmin/web/GroupServlet.java @@ -0,0 +1,148 @@ +package de.jalin.ldapadmin.web; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.SortedMap; + +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import de.jalin.ldapadmin.beans.Group; +import de.jalin.ldapadmin.beans.User; +import de.jalin.ldapadmin.ldap.AlreadyBoundException; +import de.jalin.ldapadmin.ldap.GroupsDAO; +import de.jalin.ldapadmin.ldap.LDAPSessionException; +import de.jalin.ldapadmin.ldap.NoGroupMembersException; +import de.jalin.ldapadmin.ldap.UsersDAO; + +@WebServlet(name="LdapGroup",urlPatterns={"/group/*"}) +public class GroupServlet extends AbstractLDAPServlet { + + private static final long serialVersionUID = 1L; + + @Override + protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) + throws ServletException, IOException { + final String pathInfo = req.getPathInfo(); + String groupDN = ""; //$NON-NLS-1$ + if (pathInfo != null && pathInfo.length() > 1 && pathInfo.charAt(0) == '/') { + groupDN = pathInfo.substring(1); + } + final HttpSession httpSession = req.getSession(); + cleanSession(httpSession); + String operation = req.getParameter("op"); //$NON-NLS-1$ + if (operation == null || operation.isEmpty()) { + operation = "view"; //$NON-NLS-1$ + } + httpSession.setAttribute("operation", operation); //$NON-NLS-1$ + httpSession.setAttribute("formdisabled", "view".equals(operation) || "delete".equals(operation) ? "disabled" : ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ + httpSession.setAttribute("iddisabled", "create".equals(operation) ? "" : "disabled"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ + + + final GroupsDAO groupsDAO = new GroupsDAO(ldapSession); + final UsersDAO usersDAO = new UsersDAO(ldapSession); + try { + final SortedMap users = usersDAO.loadUsers(); + httpSession.setAttribute("users", users); //$NON-NLS-1$ + if (groupDN != null && groupDN.length() > 9) { + httpSession.setAttribute("group", groupsDAO.readGroup(groupDN, users)); //$NON-NLS-1$ + } else { + httpSession.setAttribute("group", new Group()); //$NON-NLS-1$ + } + } catch (LDAPSessionException e) { + throwServletException(httpSession, e); + } + req.getRequestDispatcher("/WEB-INF/group.jsp").forward(req, resp); //$NON-NLS-1$ + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + final Messages messages = new Messages(req.getLocale()); + final String pathInfo = req.getPathInfo(); + String dn = ""; //$NON-NLS-1$ + if (pathInfo != null && pathInfo.length() > 1 && pathInfo.charAt(0) == '/') { + dn = pathInfo.substring(1); + } + String operation = req.getParameter("op"); //$NON-NLS-1$ + if (operation == null || operation.isEmpty()) { + operation = "none"; //$NON-NLS-1$ + } + final GroupsDAO groupsDAO = new GroupsDAO(ldapSession); + final Group grp = new Group(); + final List members = new ArrayList(); + final HttpSession httpSession = req.getSession(); + cleanSession(httpSession); + @SuppressWarnings("unchecked") final SortedMap usersHash = (SortedMap) httpSession.getAttribute("users"); //$NON-NLS-1$ + final Iterator userDNIterator = usersHash.keySet().iterator(); + while (userDNIterator.hasNext()) { + final String userDN = userDNIterator.next(); + final User usr = usersHash.get(userDN); + final String isChecked = req.getParameter("check_user_" + usr.getLogin()); //$NON-NLS-1$ + if (isChecked != null && !isChecked.isEmpty()) { + members.add(usr.getDn()); + } + } + grp.setMembers(members); + try { + if ("edit".equals(operation) && !dn.isEmpty()) { //$NON-NLS-1$ + final Group oldValue = groupsDAO.readGroup(dn, usersHash); + grp.setDn(dn); + grp.setName(oldValue.getName()); + if (grp.getMembers().size() == 0) { + httpSession.setAttribute("group", grp); //$NON-NLS-1$ + httpSession.setAttribute("errormessage", messages.getString("GroupServlet.no_empty_group")); //$NON-NLS-1$ //$NON-NLS-2$ + req.getRequestDispatcher("/WEB-INF/group.jsp").forward(req, resp); //$NON-NLS-1$ + return; + } + try { + groupsDAO.update(grp); + } catch (NoGroupMembersException e) { + httpSession.setAttribute("group", grp); //$NON-NLS-1$ + httpSession.setAttribute("errormessage", messages.getString("GroupServlet.no_empty_group")); //$NON-NLS-1$ //$NON-NLS-2$ + req.getRequestDispatcher("/WEB-INF/group.jsp").forward(req, resp); //$NON-NLS-1$ + return; + } + } + if ("delete".equals(operation) && !dn.isEmpty()) { //$NON-NLS-1$ + final Group oldValue = groupsDAO.readGroup(dn, usersHash); + grp.setDn(dn); + grp.setName(oldValue.getName()); + groupsDAO.delete(grp); + } + if ("create".equals(operation)) { //$NON-NLS-1$ + final String grpName = req.getParameter("name"); //$NON-NLS-1$ + grp.setName(grpName); + if (grp.getMembers().size() == 0) { + httpSession.setAttribute("group", grp); //$NON-NLS-1$ + httpSession.setAttribute("errormessage", messages.getString("GroupServlet.no_empty_group")); //$NON-NLS-1$ //$NON-NLS-2$ + req.getRequestDispatcher("/WEB-INF/group.jsp").forward(req, resp); //$NON-NLS-1$ + return; + } + try { + groupsDAO.create(grp); + } catch (AlreadyBoundException e) { + httpSession.setAttribute("group", grp); //$NON-NLS-1$ + httpSession.setAttribute("errormessage", messages.getString("GroupServlet.group_exists")); //$NON-NLS-1$ //$NON-NLS-2$ + req.getRequestDispatcher("/WEB-INF/group.jsp").forward(req, resp); //$NON-NLS-1$ + return; + } + resp.sendRedirect(req.getContextPath() + req.getServletPath() + "/" + grp.getDn()); //$NON-NLS-1$ + } else { + if ("delete".equals(operation)) { //$NON-NLS-1$ + resp.sendRedirect(req.getContextPath() + "/groups"); //$NON-NLS-1$ + } else { + resp.sendRedirect(req.getContextPath() + req.getServletPath() + pathInfo); + } + } + } catch (LDAPSessionException e) { + throwServletException(httpSession, e); + } + } + +} diff --git a/src/main/java/de/jalin/ldapadmin/web/GroupsServlet.java b/src/main/java/de/jalin/ldapadmin/web/GroupsServlet.java new file mode 100644 index 0000000..3ecdc55 --- /dev/null +++ b/src/main/java/de/jalin/ldapadmin/web/GroupsServlet.java @@ -0,0 +1,40 @@ +package de.jalin.ldapadmin.web; + +import java.io.IOException; +import java.util.SortedMap; + +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import de.jalin.ldapadmin.beans.User; +import de.jalin.ldapadmin.ldap.GroupsDAO; +import de.jalin.ldapadmin.ldap.LDAPSessionException; +import de.jalin.ldapadmin.ldap.UsersDAO; + +@WebServlet(name="LdapGroups",urlPatterns={"/groups"}) +public class GroupsServlet extends AbstractLDAPServlet { + + private static final long serialVersionUID = 1L; + + @Override + protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) + throws ServletException, IOException + { + final HttpSession httpSession = req.getSession(); + cleanSession(httpSession); + final UsersDAO usersDAO = new UsersDAO(ldapSession); + final GroupsDAO groupsDAO = new GroupsDAO(ldapSession); + try { + final SortedMap loadedUsers = usersDAO.loadUsers(); + httpSession.setAttribute("users", loadedUsers); //$NON-NLS-1$ + httpSession.setAttribute("groups", groupsDAO.loadGroups(loadedUsers)); //$NON-NLS-1$ + } catch (LDAPSessionException e) { + throwServletException(httpSession, e); + } + req.getRequestDispatcher("/WEB-INF/groups.jsp").forward(req, resp); //$NON-NLS-1$ + } + +} diff --git a/src/main/java/de/jalin/ldapadmin/web/LogoutServlet.java b/src/main/java/de/jalin/ldapadmin/web/LogoutServlet.java new file mode 100644 index 0000000..8273c98 --- /dev/null +++ b/src/main/java/de/jalin/ldapadmin/web/LogoutServlet.java @@ -0,0 +1,25 @@ +package de.jalin.ldapadmin.web; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +@WebServlet(name="Logout",urlPatterns={"/logout"}) +public class LogoutServlet extends AbstractLDAPServlet { + + private static final long serialVersionUID = 1L; + + @Override + protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) + throws ServletException, IOException { + final HttpSession httpSession = req.getSession(); + cleanSession(httpSession); + httpSession.invalidate(); + resp.sendRedirect(httpSession.getServletContext().getContextPath() + "/"); //$NON-NLS-1$ + } + +} diff --git a/src/main/java/de/jalin/ldapadmin/web/Messages.java b/src/main/java/de/jalin/ldapadmin/web/Messages.java new file mode 100644 index 0000000..4ba3bb5 --- /dev/null +++ b/src/main/java/de/jalin/ldapadmin/web/Messages.java @@ -0,0 +1,24 @@ +package de.jalin.ldapadmin.web; + +import java.util.Locale; +import java.util.MissingResourceException; +import java.util.ResourceBundle; + +public class Messages { + + private static final String BUNDLE_NAME = "de.jalin.ldapadmin.admin.web.messages"; //$NON-NLS-1$ + + private final ResourceBundle resourceBundle; + + public Messages(final Locale requestLocale) { + resourceBundle = ResourceBundle.getBundle(BUNDLE_NAME, requestLocale); + } + + public String getString(final String key) { + try { + return resourceBundle.getString(key); + } catch (MissingResourceException e) { + return '!' + key + '!'; + } + } +} diff --git a/src/main/java/de/jalin/ldapadmin/web/NaiveTrustManager.java b/src/main/java/de/jalin/ldapadmin/web/NaiveTrustManager.java new file mode 100644 index 0000000..1ae4675 --- /dev/null +++ b/src/main/java/de/jalin/ldapadmin/web/NaiveTrustManager.java @@ -0,0 +1,52 @@ +package de.jalin.ldapadmin.web; + +import java.io.IOException; +import java.net.Socket; +import java.net.UnknownHostException; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; + +public class NaiveTrustManager implements X509TrustManager { + + private static SSLContext SSL_CONTEXT; + + static { + try { + SSL_CONTEXT = SSLContext.getInstance("TLSv1.2"); + SSL_CONTEXT.init(null, new TrustManager[] { new NaiveTrustManager() }, null); + SSLContext.setDefault(SSL_CONTEXT); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("Unable to initialise SSL context", e); + } catch (KeyManagementException e) { + throw new RuntimeException("Unable to initialise SSL context", e); + } + } + + public Socket createSocket(String host, int port) throws IOException, UnknownHostException { + return SSL_CONTEXT.getSocketFactory().createSocket(host, port); + } + + @Override + public void checkClientTrusted(X509Certificate[] arg0, String arg1) + throws CertificateException { + + } + + @Override + public void checkServerTrusted(X509Certificate[] arg0, String arg1) + throws CertificateException { + + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return null; + } + +} diff --git a/src/main/java/de/jalin/ldapadmin/web/ProfileServlet.java b/src/main/java/de/jalin/ldapadmin/web/ProfileServlet.java new file mode 100644 index 0000000..8399208 --- /dev/null +++ b/src/main/java/de/jalin/ldapadmin/web/ProfileServlet.java @@ -0,0 +1,119 @@ +package de.jalin.ldapadmin.web; + +import java.io.IOException; +import java.util.List; +import java.util.SortedMap; + +import javax.naming.directory.SearchResult; +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import de.jalin.ldapadmin.beans.User; +import de.jalin.ldapadmin.beans.ValidationException; +import de.jalin.ldapadmin.ldap.GroupsDAO; +import de.jalin.ldapadmin.ldap.LDAPSessionException; +import de.jalin.ldapadmin.ldap.SimplePasswordException; +import de.jalin.ldapadmin.ldap.UsersDAO; + +@WebServlet(name="LdapProfile",urlPatterns={"/profile", "/profile/*"}) +public class ProfileServlet extends AbstractLDAPServlet { + + private static final long serialVersionUID = 1L; + + @Override + protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) + throws ServletException, IOException { + final HttpSession httpSession = req.getSession(); + String userDN = ""; //$NON-NLS-1$ + try { + final String remoteUID = req.getRemoteUser(); + final List list = ldapSession.search("ou=users", "uid", remoteUID); //$NON-NLS-1$ //$NON-NLS-2$ + if (list != null && list.size() > 0) { + final SearchResult first = list.get(0); + userDN = first.getNameInNamespace(); + } + cleanSession(httpSession); + String operation = req.getParameter("op"); //$NON-NLS-1$ + if (operation == null || operation.isEmpty()) { + operation = "profile"; //$NON-NLS-1$ + } + httpSession.setAttribute("operation", operation); //$NON-NLS-1$ + httpSession.setAttribute("formdisabled", "profile".equals(operation) ? "disabled" : ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ + httpSession.setAttribute("iddisabled", "disabled"); //$NON-NLS-1$ //$NON-NLS-2$ + final UsersDAO usersDAO = new UsersDAO(ldapSession); + final GroupsDAO groupsDAO = new GroupsDAO(ldapSession); + SortedMap users = usersDAO.loadUsers(); + httpSession.setAttribute("users", users); //$NON-NLS-1$ + httpSession.setAttribute("groups", groupsDAO.loadGroups(users)); //$NON-NLS-1$ + httpSession.setAttribute("user", users.get(userDN)); //$NON-NLS-1$ + } catch (LDAPSessionException e) { + throwServletException(httpSession, e); + } + req.getRequestDispatcher("/WEB-INF/user.jsp").forward(req, resp); //$NON-NLS-1$ + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + final Messages messages = new Messages(req.getLocale()); + final HttpSession httpSession = req.getSession(); + cleanSession(httpSession); + final String pathInfo = req.getPathInfo(); + String dn = ""; //$NON-NLS-1$ + if (pathInfo != null && pathInfo.length() > 1 && pathInfo.charAt(0) == '/') { + dn = pathInfo.substring(1); + } + String operation = req.getParameter("op"); //$NON-NLS-1$ + if (operation == null || operation.isEmpty()) { + operation = "none"; //$NON-NLS-1$ + } + final UsersDAO usersDAO = new UsersDAO(ldapSession); + final String password = req.getParameter("password"); //$NON-NLS-1$ + final String password2 = req.getParameter("password2"); //$NON-NLS-1$ + final User usr = (User) httpSession.getAttribute("user"); //$NON-NLS-1$ + try { + if (password != null && !password.isEmpty()) { + if (password2 == null || !password2.equals(password)) { + throw new ValidationException("password2", messages.getString("ProfileServlet.passwords_donot_match")); //$NON-NLS-1$ //$NON-NLS-2$ + } else { + usr.setAndValidatePassword(password); + } + } + } catch (SimplePasswordException e) { + httpSession.setAttribute("user", usr); //$NON-NLS-1$ + httpSession.setAttribute("errormessage", messages.getString("ResetPasswordServlet.simple_password")); //$NON-NLS-1$ //$NON-NLS-2$ + req.getRequestDispatcher("/WEB-INF/user.jsp").forward(req, resp); //$NON-NLS-1$ + return; + } catch (ValidationException e) { + httpSession.setAttribute("user", usr); //$NON-NLS-1$ + httpSession.setAttribute("errormessage", messages.getString("ProfileServlet.inputfield") + e.getFieldname() + " " + e.getCondition()); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + req.getRequestDispatcher("/WEB-INF/user.jsp").forward(req, resp); //$NON-NLS-1$ + return; + } + try { + if ("password".equals(operation)) { //$NON-NLS-1$ + final User oldValue = usersDAO.read(dn); + usr.setLogin(oldValue.getLogin()); + usr.setDn(dn); + usersDAO.update(usr); + httpSession.setAttribute("successmessage", messages.getString("ProfileServlet.password_changed")); //$NON-NLS-1$ //$NON-NLS-2$ + httpSession.setAttribute("operation", "profile"); //$NON-NLS-1$ //$NON-NLS-2$ + httpSession.setAttribute("user", usr); //$NON-NLS-1$ + req.getRequestDispatcher("/WEB-INF/user.jsp").forward(req, resp); + } + } catch (LDAPSessionException e) { + final String excMessage = e.getMessage(); + if (excMessage != null && excMessage.contains("invalid reuse of password")) { //$NON-NLS-1$ + httpSession.setAttribute("user", usr); //$NON-NLS-1$ + httpSession.setAttribute("errormessage", messages.getString("ProfileServlet.invalid_reuse")); //$NON-NLS-1$ //$NON-NLS-2$ + req.getRequestDispatcher("/WEB-INF/user.jsp").forward(req, resp); //$NON-NLS-1$ + return; + } + throwServletException(httpSession, e); + } + } + +} diff --git a/src/main/java/de/jalin/ldapadmin/web/ResetPasswordServlet.java b/src/main/java/de/jalin/ldapadmin/web/ResetPasswordServlet.java new file mode 100644 index 0000000..c4397af --- /dev/null +++ b/src/main/java/de/jalin/ldapadmin/web/ResetPasswordServlet.java @@ -0,0 +1,196 @@ +package de.jalin.ldapadmin.web; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.io.Writer; +import java.net.InetAddress; +import java.util.Collection; +import java.util.SortedMap; + +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.apache.commons.net.smtp.SMTPClient; +import org.apache.commons.net.smtp.SMTPReply; +import org.apache.commons.net.smtp.SimpleSMTPHeader; + +import de.jalin.ldapadmin.beans.User; +import de.jalin.ldapadmin.ldap.LDAPSessionException; +import de.jalin.ldapadmin.ldap.SimplePasswordException; +import de.jalin.ldapadmin.ldap.UsersDAO; + +@WebServlet(name="ResetPassword",urlPatterns={"/passwordreset"}) +public class ResetPasswordServlet extends AbstractLDAPServlet { + + + private static final long serialVersionUID = 1L; + + private String smtpHost; + private String smtpPort; + private String smtpFrom; + + @Override + public void init() throws ServletException { + super.init(); + smtpHost = config.getProperty("smtp.host", "localhost"); //$NON-NLS-1$ //$NON-NLS-2$ + smtpPort = config.getProperty("smtp.port", "25"); //$NON-NLS-1$ //$NON-NLS-2$ + smtpFrom = config.getProperty("smtp.from", "nobody@localhost"); //$NON-NLS-1$ //$NON-NLS-2$ + } + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + final HttpSession httpSession = req.getSession(); + cleanSession(httpSession); + final String token = req.getParameter("token"); //$NON-NLS-1$ + if (token != null && !token.isEmpty()) { + final File passwdResetFile = new File("/tmp/passwd" + token + ".tmp"); //$NON-NLS-1$ //$NON-NLS-2$ + if (passwdResetFile.exists() && passwdResetFile.canRead()) { + try (final BufferedReader reader = new BufferedReader(new FileReader(passwdResetFile))) { + final String[] uidAndEMail = reader.readLine().split(":"); //$NON-NLS-1$ + final UsersDAO usrDAO = new UsersDAO(ldapSession); + final User usr = usrDAO.read("uid=" + uidAndEMail[0] + ",ou=users,"); //$NON-NLS-1$ //$NON-NLS-2$ + httpSession.setAttribute("user", usr); //$NON-NLS-1$ + req.getRequestDispatcher("/WEB-INF/new-password.jsp").forward(req, resp); //$NON-NLS-1$ + return; + } catch (LDAPSessionException e) { + LOG.warning("no valid password reset request"); + httpSession.setAttribute("errormessage", new Messages(req.getLocale()).getString("ResetPasswordServlet.no_valid_passwordreset_request")); //$NON-NLS-1$ //$NON-NLS-2$ + req.getRequestDispatcher("/WEB-INF/reset-password.jsp").forward(req, resp); //$NON-NLS-1$ + return; + } + } + } + req.getRequestDispatcher("/WEB-INF/reset-password.jsp").forward(req, resp); //$NON-NLS-1$ + } + + @Override + protected void doPost(final HttpServletRequest req, final HttpServletResponse resp) + throws ServletException, IOException { + final Messages messages = new Messages(req.getLocale()); + final HttpSession httpSession = req.getSession(); + cleanSession(httpSession); + final UsersDAO usrDAO = new UsersDAO(ldapSession); + final String loginParam = req.getParameter("login"); //$NON-NLS-1$ + final User sessUsr = (User) httpSession.getAttribute("user"); //$NON-NLS-1$ + if (loginParam != null && sessUsr != null && loginParam.equals(sessUsr.getLogin())) { + final String password1 = req.getParameter("password"); //$NON-NLS-1$ + final String password2 = req.getParameter("password2"); //$NON-NLS-1$ + if (password1 != null && !password1.isEmpty()) { + if (password2 == null || !password2.equals(password1)) { + httpSession.setAttribute("errormessage", messages.getString("ResetPasswordServlet.passwords_donot_match")); //$NON-NLS-1$ //$NON-NLS-2$ + req.getRequestDispatcher("/WEB-INF/new-password.jsp").forward(req, resp); //$NON-NLS-1$ + return; + } else { + try { + sessUsr.setAndValidatePassword(password1); + usrDAO.update(sessUsr); + httpSession.setAttribute("successmessage", messages.getString("ResetPasswordServlet.password_changed")); //$NON-NLS-1$ //$NON-NLS-2$ + req.getRequestDispatcher("/WEB-INF/new-password.jsp").forward(req, resp); //$NON-NLS-1$ + return; + } catch (SimplePasswordException e) { + httpSession.setAttribute("user", sessUsr); //$NON-NLS-1$ + httpSession.setAttribute("errormessage", messages.getString("ResetPasswordServlet.simple_password")); //$NON-NLS-1$ //$NON-NLS-2$ + req.getRequestDispatcher("/WEB-INF/new-password.jsp").forward(req, resp); //$NON-NLS-1$ + return; + } catch (LDAPSessionException e) { + final String excMessage = e.getMessage(); + if (excMessage != null && excMessage.contains("invalid reuse of password")) { //$NON-NLS-1$ + httpSession.setAttribute("user", sessUsr); //$NON-NLS-1$ + httpSession.setAttribute("errormessage", messages.getString("ResetPasswordServlet.invalid_password_reuse")); //$NON-NLS-1$ //$NON-NLS-2$ + req.getRequestDispatcher("/WEB-INF/new-password.jsp").forward(req, resp); //$NON-NLS-1$ + return; + } + throwServletException(httpSession, e); + } + } + } + } + final String loginOrEMail = req.getParameter("loginoremail"); //$NON-NLS-1$ + final File tempFile = File.createTempFile("passwd", ".tmp", new File("/tmp")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + try (final PrintStream printStream = new PrintStream(tempFile)) { + String email = ""; //$NON-NLS-1$ + String login = ""; //$NON-NLS-1$ + String salutation = ""; //$NON-NLS-1$ + if (loginOrEMail != null) { + if (loginOrEMail.contains("@")) { //$NON-NLS-1$ + final SortedMap usersMap = usrDAO.loadUsers(); + final Collection allUsers = usersMap.values(); + for (User usr : allUsers) { + if (usr.getEmail() != null && usr.getEmail().equalsIgnoreCase(loginOrEMail)) { + login = usr.getLogin(); + email = usr.getEmail(); + salutation = usr.getFirstname() + " " + usr.getLastname(); //$NON-NLS-1$ + } + } + } else { + final User usr = usrDAO.read("uid=" + loginOrEMail + ",ou=users,"); //$NON-NLS-1$ //$NON-NLS-2$ + if (usr != null) { + login = usr.getLogin(); + email = usr.getEmail(); + salutation = usr.getFirstname() + " " + usr.getLastname(); //$NON-NLS-1$ + } + } + if (login.isEmpty() || email.isEmpty()) { + httpSession.setAttribute("errormessage", messages.getString("ResetPasswordServlet.error_sending_password_reset")); //$NON-NLS-1$ //$NON-NLS-2$ + req.getRequestDispatcher("/WEB-INF/reset-password.jsp").forward(req, resp); //$NON-NLS-1$ + return; + } + printStream.println(login + ":" + email); //$NON-NLS-1$ + } + final StringBuffer messageText = new StringBuffer(messages.getString("ResetPasswordServlet.email_greeting")); //$NON-NLS-1$ + messageText.append(salutation); + messageText.append(messages.getString("ResetPasswordServlet.email_content")); //$NON-NLS-1$ + messageText.append(req.getRequestURL().toString().replaceAll("^http\\:", "https://")); + messageText.append("?token="); //$NON-NLS-1$ + final String filename = tempFile.getName(); + messageText.append(filename.substring(6,filename.length()-4)); + messageText.append(messages.getString("ResetPasswordServlet.email_signature")); //$NON-NLS-1$ + smtpSend(smtpHost, smtpPort, messages, smtpFrom, email, messages.getString("ResetPasswordServlet.email_subject"), messageText.toString()); //$NON-NLS-1$ //$NON-NLS-2$ + } catch (LDAPSessionException | IOException e) { + LOG.severe("smtp problem"); + httpSession.setAttribute("errormessage", messages.getString("ResetPasswordServlet.error_sending_password_reset")); //$NON-NLS-1$ //$NON-NLS-2$ + req.getRequestDispatcher("/WEB-INF/reset-password.jsp").forward(req, resp); //$NON-NLS-1$ + return; + } + httpSession.invalidate(); + resp.sendRedirect(httpSession.getServletContext().getContextPath() + "/"); //$NON-NLS-1$ + } + + private static void smtpSend(final String smtpHost, final String smtpPort, final Messages messages, final String fromAddress, final String toAddress, final String subject, final String text) throws IOException { + final SMTPClient client = new SMTPClient(); + final String canonicalHostName = InetAddress.getLocalHost().getHostName(); + client.connect(smtpHost, Integer.parseInt(smtpPort)); + int reply = client.getReplyCode(); + if (!SMTPReply.isPositiveCompletion(reply)) { + throw new IOException(messages.getString("ResetPasswordServlet.error_sending_email_server")); //$NON-NLS-1$ + } + client.login(canonicalHostName); + client.setSender(fromAddress.trim()); + client.addRecipient(toAddress.trim()); + final Writer sendMessageData = client.sendMessageData(); + if (sendMessageData == null) { + throw new IOException(messages.getString("ResetPasswordServlet.error_sending_email_relay")); //$NON-NLS-1$ + } + try (PrintWriter wr = new PrintWriter(sendMessageData)) { + final SimpleSMTPHeader header = new SimpleSMTPHeader(fromAddress, toAddress, subject); + header.addHeaderField("Content-Type", "text/plain; charset=ISO-8859-15"); + header.addHeaderField("Content-Transfer-Encoding", "8bit"); + wr.write(header.toString()); + wr.write(text); + } + if (!client.completePendingCommand()) { + throw new IOException(messages.getString("ResetPasswordServlet.error_sending_email")); //$NON-NLS-1$ + } + client.logout(); + client.disconnect(); + } + +} diff --git a/src/main/java/de/jalin/ldapadmin/web/UserServlet.java b/src/main/java/de/jalin/ldapadmin/web/UserServlet.java new file mode 100644 index 0000000..c1999d5 --- /dev/null +++ b/src/main/java/de/jalin/ldapadmin/web/UserServlet.java @@ -0,0 +1,217 @@ +package de.jalin.ldapadmin.web; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.SortedMap; + +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import de.jalin.ldapadmin.beans.Group; +import de.jalin.ldapadmin.beans.User; +import de.jalin.ldapadmin.beans.ValidationException; +import de.jalin.ldapadmin.ldap.AlreadyBoundException; +import de.jalin.ldapadmin.ldap.GroupsDAO; +import de.jalin.ldapadmin.ldap.LDAPSessionException; +import de.jalin.ldapadmin.ldap.NoGroupMembersException; +import de.jalin.ldapadmin.ldap.RequiredAttributeException; +import de.jalin.ldapadmin.ldap.SimplePasswordException; +import de.jalin.ldapadmin.ldap.UsersDAO; + +@WebServlet(name="LdapUser",urlPatterns={"/user/*"}) +public class UserServlet extends AbstractLDAPServlet { + + private static final long serialVersionUID = 1L; + + @Override + protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) + throws ServletException, IOException { + final String pathInfo = req.getPathInfo(); + String userDN = ""; //$NON-NLS-1$ + if (pathInfo != null && pathInfo.length() > 1 && pathInfo.charAt(0) == '/') { + userDN = pathInfo.substring(1); + } + final HttpSession httpSession = req.getSession(); + cleanSession(httpSession); + String operation = req.getParameter("op"); //$NON-NLS-1$ + if (operation == null || operation.isEmpty()) { + operation = "view"; //$NON-NLS-1$ + } + httpSession.setAttribute("operation", operation); //$NON-NLS-1$ + httpSession.setAttribute("formdisabled", "view".equals(operation) || "delete".equals(operation) ? "disabled" : ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ + httpSession.setAttribute("iddisabled", "create".equals(operation) ? "" : "disabled"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ + final UsersDAO usersDAO = new UsersDAO(ldapSession); + final GroupsDAO groupsDAO = new GroupsDAO(ldapSession); + try { + SortedMap users = usersDAO.loadUsers(); + httpSession.setAttribute("users", users); //$NON-NLS-1$ + httpSession.setAttribute("groups", groupsDAO.loadGroups(users)); //$NON-NLS-1$ + if ("create".equals(operation)) { //$NON-NLS-1$ + httpSession.setAttribute("user", new User()); //$NON-NLS-1$ + } else { + httpSession.setAttribute("user", users.get(userDN)); //$NON-NLS-1$ + } + } catch (LDAPSessionException e) { + throwServletException(httpSession, e); + } + req.getRequestDispatcher("/WEB-INF/user.jsp").forward(req, resp); //$NON-NLS-1$ + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + final Messages messages = new Messages(req.getLocale()); + final HttpSession httpSession = req.getSession(); + cleanSession(httpSession); + final String pathInfo = req.getPathInfo(); + String dn = ""; //$NON-NLS-1$ + if (pathInfo != null && pathInfo.length() > 1 && pathInfo.charAt(0) == '/') { + dn = pathInfo.substring(1); + } + String operation = req.getParameter("op"); //$NON-NLS-1$ + if (operation == null || operation.isEmpty()) { + operation = "none"; //$NON-NLS-1$ + } + final UsersDAO usersDAO = new UsersDAO(ldapSession); + final String login = req.getParameter("login"); //$NON-NLS-1$ + final String firstname = req.getParameter("firstname"); //$NON-NLS-1$ + final String lastname = req.getParameter("lastname"); //$NON-NLS-1$ + final String email = req.getParameter("email"); //$NON-NLS-1$ + final String phone = req.getParameter("phone"); //$NON-NLS-1$ + final String mobile = req.getParameter("mobile"); //$NON-NLS-1$ + final String password = req.getParameter("password"); //$NON-NLS-1$ + final String password2 = req.getParameter("password2"); //$NON-NLS-1$ + final User usr = new User(); + if (!dn.isEmpty()) { + usr.setDn(dn); + } + usr.setLogin(login); + usr.setFirstname(firstname); + usr.setLastname(lastname); + usr.setDisplayname(lastname + ", " + firstname); //$NON-NLS-1$ + usr.setEmail(email); + usr.setPhone(phone); + usr.setMobile(mobile); + final List memberships = new ArrayList(); + @SuppressWarnings("unchecked") final SortedMap groupsHash = (SortedMap) httpSession.getAttribute("groups"); //$NON-NLS-1$ + final Iterator groupDNIterator = groupsHash.keySet().iterator(); + while (groupDNIterator.hasNext()) { + final String groupDN = groupDNIterator.next(); + final Group grp = groupsHash.get(groupDN); + final String isChecked = req.getParameter("check_group_" + grp.getName()); //$NON-NLS-1$ + if (isChecked != null && !isChecked.isEmpty()) { + memberships.add(grp.getDn()); + } + } + usr.setGroups(memberships); + try { + validatePhone(messages, "phone", phone); //$NON-NLS-1$ + validatePhone(messages, "mobile", mobile); //$NON-NLS-1$ + validateEMail(messages, email); + validateLastName(messages, lastname); + if (password != null && !password.isEmpty()) { + if (password2 == null || !password2.equals(password)) { + throw new ValidationException("password2", messages.getString("UserServlet.passwords_donot_match")); //$NON-NLS-1$ //$NON-NLS-2$ + } else { + usr.setAndValidatePassword(password); + } + } + } catch (SimplePasswordException e) { + httpSession.setAttribute("user", usr); //$NON-NLS-1$ + httpSession.setAttribute("errormessage", messages.getString("ResetPasswordServlet.simple_password")); //$NON-NLS-1$ //$NON-NLS-2$ + req.getRequestDispatcher("/WEB-INF/user.jsp").forward(req, resp); //$NON-NLS-1$ + return; + } catch (ValidationException e) { + httpSession.setAttribute("user", usr); //$NON-NLS-1$ + httpSession.setAttribute("errormessage", messages.getString("UserServlet.input_field") + " \"" + e.getFieldname() + "\" " + e.getCondition()); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + req.getRequestDispatcher("/WEB-INF/user.jsp").forward(req, resp); //$NON-NLS-1$ + return; + } + final GroupsDAO groupsDAO = new GroupsDAO(ldapSession); + try { + if ("edit".equals(operation)) { //$NON-NLS-1$ + final User oldValue = usersDAO.read(dn); + usr.setLogin(oldValue.getLogin()); + usr.setDn(dn); + usersDAO.update(usr); + groupsDAO.updateMemberships(usr); + } + if ("delete".equals(operation)) { //$NON-NLS-1$ + final User oldValue = usersDAO.read(dn); + usr.setLogin(oldValue.getLogin()); + usr.setDn(dn); + usr.getGroups().clear(); + groupsDAO.updateMemberships(usr); + usersDAO.delete(usr); + } + if ("create".equals(operation)) { //$NON-NLS-1$ + usr.setLogin(login); + usersDAO.create(usr); + groupsDAO.updateMemberships(usr); + resp.sendRedirect(req.getContextPath() + req.getServletPath() + "/" + usr.getDn()); //$NON-NLS-1$ + } else { + if ("delete".equals(operation)) { //$NON-NLS-1$ + resp.sendRedirect(req.getContextPath() + "/users"); //$NON-NLS-1$ + } else { + resp.sendRedirect(req.getContextPath() + req.getServletPath() + pathInfo); + } + } + } catch (LDAPSessionException e) { + final String excMessage = e.getMessage(); + if (excMessage != null && excMessage.contains("invalid reuse of password")) { //$NON-NLS-1$ + httpSession.setAttribute("user", usr); //$NON-NLS-1$ + httpSession.setAttribute("errormessage", messages.getString("UserServlet.invalid_password_reuse")); //$NON-NLS-1$ //$NON-NLS-2$ + req.getRequestDispatcher("/WEB-INF/user.jsp").forward(req, resp); //$NON-NLS-1$ + return; + } + throwServletException(httpSession, e); + } catch (NoGroupMembersException e) { + httpSession.setAttribute("user", usr); //$NON-NLS-1$ + httpSession.setAttribute("errormessage", messages.getString("UserServlet.group_last_member")); //$NON-NLS-1$ //$NON-NLS-2$ + req.getRequestDispatcher("/WEB-INF/user.jsp").forward(req, resp); //$NON-NLS-1$ + return; + } catch (RequiredAttributeException e) { + httpSession.setAttribute("user", usr); //$NON-NLS-1$ + httpSession.setAttribute("errormessage", messages.getString("UserServlet.the_input_field") + " " + e.getFieldname() + " " + messages.getString("UserServlet.is_required")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + req.getRequestDispatcher("/WEB-INF/user.jsp").forward(req, resp); //$NON-NLS-1$ + return; + } catch (AlreadyBoundException e) { + httpSession.setAttribute("user", usr); //$NON-NLS-1$ + httpSession.setAttribute("errormessage", messages.getString("UserServlet.user_exists")); //$NON-NLS-1$ //$NON-NLS-2$ + req.getRequestDispatcher("/WEB-INF/user.jsp").forward(req, resp); //$NON-NLS-1$ + return; + } + } + + private void validatePhone(final Messages messages, final String field, final String phone) throws ValidationException { + if (phone == null || phone.isEmpty()) { + return; + } + final String valid = phone.trim(); + if (!valid.matches("[0-9\\-\\ \\(\\)]*")) { //$NON-NLS-1$ + throw new ValidationException(field, messages.getString("UserServlet.phone_not_valid")); //$NON-NLS-1$ + } + } + + private void validateEMail(final Messages messages, final String email) throws ValidationException { + if (email == null || email.isEmpty()) { + return; + } + final String valid = email.trim(); + if (!valid.matches("[A-Za-z0-9_+\\.\\-]*@[a-z0-9\\.\\-üöäß]*")) { //$NON-NLS-1$ + throw new ValidationException("email", messages.getString("UserServlet.email_not_valid")); //$NON-NLS-1$ //$NON-NLS-2$ + } + } + + private void validateLastName(final Messages messages, final String lastname) throws ValidationException { + if (lastname == null || lastname.isEmpty()) { + throw new ValidationException("lastname", messages.getString("UserServlet.is_required")); //$NON-NLS-1$ //$NON-NLS-2$ + } + } + +} diff --git a/src/main/java/de/jalin/ldapadmin/web/UsersServlet.java b/src/main/java/de/jalin/ldapadmin/web/UsersServlet.java new file mode 100644 index 0000000..34ea954 --- /dev/null +++ b/src/main/java/de/jalin/ldapadmin/web/UsersServlet.java @@ -0,0 +1,41 @@ +package de.jalin.ldapadmin.web; + +import java.io.IOException; +import java.util.SortedMap; + +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import de.jalin.ldapadmin.beans.Group; +import de.jalin.ldapadmin.beans.User; +import de.jalin.ldapadmin.ldap.GroupsDAO; +import de.jalin.ldapadmin.ldap.LDAPSessionException; +import de.jalin.ldapadmin.ldap.UsersDAO; + +@WebServlet(name="LdapUsers",urlPatterns={"/users"}) +public class UsersServlet extends AbstractLDAPServlet { + + private static final long serialVersionUID = 1L; + + @Override + protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) + throws ServletException, IOException { + final HttpSession httpSession = req.getSession(); + cleanSession(httpSession); + final UsersDAO usersDAO = new UsersDAO(ldapSession); + final GroupsDAO groupsDAO = new GroupsDAO(ldapSession); + try { + final SortedMap users = usersDAO.loadUsers(); + final SortedMap groups = groupsDAO.loadGroups(users); + httpSession.setAttribute("users", users); //$NON-NLS-1$ + httpSession.setAttribute("groups", groups); //$NON-NLS-1$ + } catch (LDAPSessionException e) { + throwServletException(httpSession, e); + } + req.getRequestDispatcher("/WEB-INF/users.jsp").forward(req, resp); //$NON-NLS-1$ + } + +} diff --git a/src/main/resources/config.properties b/src/main/resources/config.properties new file mode 100644 index 0000000..0f38366 --- /dev/null +++ b/src/main/resources/config.properties @@ -0,0 +1,4 @@ +provider.url=ldap://localhost:10389/dc=example,dc=com +security.principal=uid=admin,ou=system +security.password=secret +smtp.host=localhost diff --git a/src/main/resources/de/jalin/ldapadmin/accessdenied.properties b/src/main/resources/de/jalin/ldapadmin/accessdenied.properties new file mode 100644 index 0000000..f77298f --- /dev/null +++ b/src/main/resources/de/jalin/ldapadmin/accessdenied.properties @@ -0,0 +1,3 @@ +access.denied.title=Access denied +access.denied.text=You don't have access rights to the pages for user and group administration. +access.denied.logout=logout user {0} \ No newline at end of file diff --git a/src/main/resources/de/jalin/ldapadmin/accessdenied_de.properties b/src/main/resources/de/jalin/ldapadmin/accessdenied_de.properties new file mode 100644 index 0000000..cd7209f --- /dev/null +++ b/src/main/resources/de/jalin/ldapadmin/accessdenied_de.properties @@ -0,0 +1,3 @@ +access.denied.title=Zugriff verweigert +access.denied.text=Sie haben keine Zugriffsrechte auf die Seiten zur Benutzer- und Gruppenverwaltung. +access.denied.logout=als Benutzer {0} abmelden \ No newline at end of file diff --git a/src/main/resources/de/jalin/ldapadmin/accessdenied_en.properties b/src/main/resources/de/jalin/ldapadmin/accessdenied_en.properties new file mode 100644 index 0000000..f77298f --- /dev/null +++ b/src/main/resources/de/jalin/ldapadmin/accessdenied_en.properties @@ -0,0 +1,3 @@ +access.denied.title=Access denied +access.denied.text=You don't have access rights to the pages for user and group administration. +access.denied.logout=logout user {0} \ No newline at end of file diff --git a/src/main/resources/de/jalin/ldapadmin/contact.properties b/src/main/resources/de/jalin/ldapadmin/contact.properties new file mode 100644 index 0000000..10b3a3f --- /dev/null +++ b/src/main/resources/de/jalin/ldapadmin/contact.properties @@ -0,0 +1,2 @@ +contact.title=LDAP Administration +contact.text=We 'll answer your questions gladly. \ No newline at end of file diff --git a/src/main/resources/de/jalin/ldapadmin/contact_de.properties b/src/main/resources/de/jalin/ldapadmin/contact_de.properties new file mode 100644 index 0000000..9c73e3b --- /dev/null +++ b/src/main/resources/de/jalin/ldapadmin/contact_de.properties @@ -0,0 +1,2 @@ +contact.title=LDAP Administration +contact.text=Ihre Fragen beantworten wir Ihnen gern. \ No newline at end of file diff --git a/src/main/resources/de/jalin/ldapadmin/contact_en.properties b/src/main/resources/de/jalin/ldapadmin/contact_en.properties new file mode 100644 index 0000000..10b3a3f --- /dev/null +++ b/src/main/resources/de/jalin/ldapadmin/contact_en.properties @@ -0,0 +1,2 @@ +contact.title=LDAP Administration +contact.text=We 'll answer your questions gladly. \ No newline at end of file diff --git a/src/main/resources/de/jalin/ldapadmin/exception.properties b/src/main/resources/de/jalin/ldapadmin/exception.properties new file mode 100644 index 0000000..a9ad16f --- /dev/null +++ b/src/main/resources/de/jalin/ldapadmin/exception.properties @@ -0,0 +1 @@ +exception.title=Unexpected error \ No newline at end of file diff --git a/src/main/resources/de/jalin/ldapadmin/exception_de.properties b/src/main/resources/de/jalin/ldapadmin/exception_de.properties new file mode 100644 index 0000000..0f71590 --- /dev/null +++ b/src/main/resources/de/jalin/ldapadmin/exception_de.properties @@ -0,0 +1 @@ +exception.title=Nicht erwarteter Fehler \ No newline at end of file diff --git a/src/main/resources/de/jalin/ldapadmin/exception_en.properties b/src/main/resources/de/jalin/ldapadmin/exception_en.properties new file mode 100644 index 0000000..a9ad16f --- /dev/null +++ b/src/main/resources/de/jalin/ldapadmin/exception_en.properties @@ -0,0 +1 @@ +exception.title=Unexpected error \ No newline at end of file diff --git a/src/main/resources/de/jalin/ldapadmin/groups.properties b/src/main/resources/de/jalin/ldapadmin/groups.properties new file mode 100644 index 0000000..e68b6fa --- /dev/null +++ b/src/main/resources/de/jalin/ldapadmin/groups.properties @@ -0,0 +1,14 @@ +groups.title=Groups +groups.group.title=Group +groups.create=create new group +groups.column.name=Group name +groups.column.members=Members +groups.column.operations=Operations +groups.operations.edit=update +groups.operations.update=edit group members +groups.operations.submit=submit +groups.operations.delete=delete +groups.error.title=Error! +groups.confirm.delete=Should this group be deleted? +groups.label.name=Group name +groups.label.members=Members \ No newline at end of file diff --git a/src/main/resources/de/jalin/ldapadmin/groups_de.properties b/src/main/resources/de/jalin/ldapadmin/groups_de.properties new file mode 100644 index 0000000..da65cf7 --- /dev/null +++ b/src/main/resources/de/jalin/ldapadmin/groups_de.properties @@ -0,0 +1,14 @@ +groups.title=Gruppen +groups.group.title=Gruppe +groups.create=neue Gruppe +groups.column.name=Gruppen-Kennung +groups.column.members=Mitglieder +groups.column.operations= +groups.operations.edit=ändern +groups.operations.update=Mitglieder zuordnen +groups.operations.submit=speichern +groups.operations.delete=löschen +groups.error.title=Fehler! +groups.confirm.delete=Soll diese Gruppe gelöscht werden? +groups.label.name=Gruppen-Kennung +groups.label.members=Mitglieder \ No newline at end of file diff --git a/src/main/resources/de/jalin/ldapadmin/groups_en.properties b/src/main/resources/de/jalin/ldapadmin/groups_en.properties new file mode 100644 index 0000000..e68b6fa --- /dev/null +++ b/src/main/resources/de/jalin/ldapadmin/groups_en.properties @@ -0,0 +1,14 @@ +groups.title=Groups +groups.group.title=Group +groups.create=create new group +groups.column.name=Group name +groups.column.members=Members +groups.column.operations=Operations +groups.operations.edit=update +groups.operations.update=edit group members +groups.operations.submit=submit +groups.operations.delete=delete +groups.error.title=Error! +groups.confirm.delete=Should this group be deleted? +groups.label.name=Group name +groups.label.members=Members \ No newline at end of file diff --git a/src/main/resources/de/jalin/ldapadmin/login.properties b/src/main/resources/de/jalin/ldapadmin/login.properties new file mode 100644 index 0000000..0e00c89 --- /dev/null +++ b/src/main/resources/de/jalin/ldapadmin/login.properties @@ -0,0 +1,13 @@ +login.title=LDAP Admin +login.username=Login name +login.password=Password +login.password.repeat=Repeat password +login.reset.password=Reset password +login.error.title=Error! +login.error.message=Login failed. Check your credentials. +login.submit=login +login.password.change=Change password +password.reset.title=Restore password +password.reset.loginoremail=Login or EMail +password.reset.submit=Send password link +password.error.title=Error \ No newline at end of file diff --git a/src/main/resources/de/jalin/ldapadmin/login_de.properties b/src/main/resources/de/jalin/ldapadmin/login_de.properties new file mode 100644 index 0000000..764251b --- /dev/null +++ b/src/main/resources/de/jalin/ldapadmin/login_de.properties @@ -0,0 +1,13 @@ +login.title=LDAP Administration Anmeldung +login.username=Anmelde-Kennung +login.password=Passwort +login.password.repeat=Passwort-Wiederholung +login.reset.password=Passwort zur\u00fccksetzen +login.error.title=Fehler! +login.error.message=Anmeldung fehlgeschlagen. Bitte pr\u00fcfen Sie Ihre Anmelde-Daten. +login.submit=Anmelden +login.password.change=Passwort \u00e4ndern +password.reset.title=Passwort zur\u00fccksetzen +password.reset.loginoremail=Anmelde-Kennung oder E-Mail +password.reset.submit=Sende Passwort-Link +password.error.title=Fehler \ No newline at end of file diff --git a/src/main/resources/de/jalin/ldapadmin/login_en.properties b/src/main/resources/de/jalin/ldapadmin/login_en.properties new file mode 100644 index 0000000..d8a4c1b --- /dev/null +++ b/src/main/resources/de/jalin/ldapadmin/login_en.properties @@ -0,0 +1,13 @@ +login.title=LDAO Administration Login +login.username=Login name +login.password=Password +login.password.repeat=Repeat password +login.reset.password=Reset password +login.error.title=Error! +login.error.message=Login failed. Check your credentials. +login.submit=login +login.password.change=Change password +password.reset.title=Restore password +password.reset.loginoremail=Login or EMail +password.reset.submit=Send password link +password.error.title=Error \ No newline at end of file diff --git a/src/main/resources/de/jalin/ldapadmin/menu.properties b/src/main/resources/de/jalin/ldapadmin/menu.properties new file mode 100644 index 0000000..149855f --- /dev/null +++ b/src/main/resources/de/jalin/ldapadmin/menu.properties @@ -0,0 +1,2 @@ +menu.title=LDAP Admin +menu.text=You can reach your applications via the following references \ No newline at end of file diff --git a/src/main/resources/de/jalin/ldapadmin/menu_de.properties b/src/main/resources/de/jalin/ldapadmin/menu_de.properties new file mode 100644 index 0000000..4414420 --- /dev/null +++ b/src/main/resources/de/jalin/ldapadmin/menu_de.properties @@ -0,0 +1,2 @@ +menu.title=LDAP Admin +menu.text=Sie finden Ihre Anwendungen \u00fcber die folgenden Links \ No newline at end of file diff --git a/src/main/resources/de/jalin/ldapadmin/menu_en.properties b/src/main/resources/de/jalin/ldapadmin/menu_en.properties new file mode 100644 index 0000000..149855f --- /dev/null +++ b/src/main/resources/de/jalin/ldapadmin/menu_en.properties @@ -0,0 +1,2 @@ +menu.title=LDAP Admin +menu.text=You can reach your applications via the following references \ No newline at end of file diff --git a/src/main/resources/de/jalin/ldapadmin/navbar.properties b/src/main/resources/de/jalin/ldapadmin/navbar.properties new file mode 100644 index 0000000..4dd80de --- /dev/null +++ b/src/main/resources/de/jalin/ldapadmin/navbar.properties @@ -0,0 +1,6 @@ +navbar.title=LDAP Admin +navbar.toggle=Toggle navigation +navbar.item.groups=Groups +navbar.item.users=Users +navbar.item.profile=Profile +navbar.item.contact=Contact \ No newline at end of file diff --git a/src/main/resources/de/jalin/ldapadmin/navbar_de.properties b/src/main/resources/de/jalin/ldapadmin/navbar_de.properties new file mode 100644 index 0000000..de962f6 --- /dev/null +++ b/src/main/resources/de/jalin/ldapadmin/navbar_de.properties @@ -0,0 +1,6 @@ +navbar.title=LDAP Admin +navbar.toggle=Menue umschalten +navbar.item.groups=Gruppen +navbar.item.users=Benutzer +navbar.item.profile=mein Profil +navbar.item.contact=Kontakt \ No newline at end of file diff --git a/src/main/resources/de/jalin/ldapadmin/navbar_en.properties b/src/main/resources/de/jalin/ldapadmin/navbar_en.properties new file mode 100644 index 0000000..4dd80de --- /dev/null +++ b/src/main/resources/de/jalin/ldapadmin/navbar_en.properties @@ -0,0 +1,6 @@ +navbar.title=LDAP Admin +navbar.toggle=Toggle navigation +navbar.item.groups=Groups +navbar.item.users=Users +navbar.item.profile=Profile +navbar.item.contact=Contact \ No newline at end of file diff --git a/src/main/resources/de/jalin/ldapadmin/users.properties b/src/main/resources/de/jalin/ldapadmin/users.properties new file mode 100644 index 0000000..c6b22a7 --- /dev/null +++ b/src/main/resources/de/jalin/ldapadmin/users.properties @@ -0,0 +1,23 @@ +users.title=Users +users.create=create new user +users.column.login=Login +users.column.firstname=First name +users.column.lastname=Last name +users.column.groups=Groups +users.column.operations=Operations +users.label.login=Login name +users.label.firstname=First name +users.label.lastname=Last name +users.label.email=EMail address +users.label.phone=Phone +users.label.mobile=Mobile +users.label.password=Password +users.label.password.repeat=Repeat password +users.label.memberof=Member of +users.operations.edit=edit +users.operations.delete=delete +users.operations.update=submit +users.operations.changepassword=change password +users.confirm.delete=Should this user be deleted? +users.title.new=New user +users.error.title=Error! \ No newline at end of file diff --git a/src/main/resources/de/jalin/ldapadmin/users_de.properties b/src/main/resources/de/jalin/ldapadmin/users_de.properties new file mode 100644 index 0000000..6f249f8 --- /dev/null +++ b/src/main/resources/de/jalin/ldapadmin/users_de.properties @@ -0,0 +1,23 @@ +users.title=Benutzer +users.create=neuer Benutzer +users.column.login=Anmelde-Kennung +users.column.firstname=Vorname +users.column.lastname=Nachname +users.column.groups=Gruppen +users.column.operations= +users.label.login=Anmelde-Kennung +users.label.firstname=Vorname +users.label.lastname=Nachname +users.label.email=E-Mail Adresse +users.label.phone=Telefon +users.label.mobile=Handy +users.label.password=Passwort +users.label.password.repeat=Passwort-Wiederholung +users.label.memberof=Gruppen-Mitgliedschaft +users.operations.edit=bearbeiten +users.operations.delete=löschen +users.operations.update=speichern +users.operations.changepassword=Passwort ändern +users.confirm.delete=Soll dieser Benutzer gelöscht werden? +users.title.new=Neuer Benutzer +users.error.title=Fehler! \ No newline at end of file diff --git a/src/main/resources/de/jalin/ldapadmin/users_en.properties b/src/main/resources/de/jalin/ldapadmin/users_en.properties new file mode 100644 index 0000000..c6b22a7 --- /dev/null +++ b/src/main/resources/de/jalin/ldapadmin/users_en.properties @@ -0,0 +1,23 @@ +users.title=Users +users.create=create new user +users.column.login=Login +users.column.firstname=First name +users.column.lastname=Last name +users.column.groups=Groups +users.column.operations=Operations +users.label.login=Login name +users.label.firstname=First name +users.label.lastname=Last name +users.label.email=EMail address +users.label.phone=Phone +users.label.mobile=Mobile +users.label.password=Password +users.label.password.repeat=Repeat password +users.label.memberof=Member of +users.operations.edit=edit +users.operations.delete=delete +users.operations.update=submit +users.operations.changepassword=change password +users.confirm.delete=Should this user be deleted? +users.title.new=New user +users.error.title=Error! \ No newline at end of file diff --git a/src/main/resources/de/jalin/ldapadmin/web/messages.properties b/src/main/resources/de/jalin/ldapadmin/web/messages.properties new file mode 100644 index 0000000..e939323 --- /dev/null +++ b/src/main/resources/de/jalin/ldapadmin/web/messages.properties @@ -0,0 +1,28 @@ +GroupServlet.no_empty_group=A groups is required to have members +GroupServlet.group_exists=This group already exists +ProfileServlet.inputfield=Field +ProfileServlet.invalid_reuse=Invalid reuse of a password known from history +ProfileServlet.password_changed=Password changed +ProfileServlet.passwords_donot_match=Passwords do not match +ResetPasswordServlet.email_content=\!\n\nYou want to reset your password?\n\nUse the follwing link:\n +ResetPasswordServlet.email_greeting=Dear +ResetPasswordServlet.email_signature=\n\nYour LDAP-service\n +ResetPasswordServlet.email_subject=reset LDAP password +ResetPasswordServlet.error_sending_email=Could not sent email +ResetPasswordServlet.error_sending_email_relay=Could not sent email (relay-error) +ResetPasswordServlet.error_sending_email_server=Could not reach email service +ResetPasswordServlet.error_sending_password_reset=Error sending email +ResetPasswordServlet.invalid_password_reuse=Invalid reuse of a password known from history. A new password is required. +ResetPasswordServlet.no_valid_passwordreset_request=Could not find a valid password request. +ResetPasswordServlet.password_changed=Password changed +ResetPasswordServlet.passwords_donot_match=Passwords do not match +ResetPasswordServlet.simple_password=Your password is too simple. It should contain at least one lowercase and uppercase letter and a digit / secial character and a minimum length of 12 characters +UserServlet.email_not_valid=No valid email address given +UserServlet.group_last_member=A group is required to have members +UserServlet.user_exists=This user already exists +UserServlet.input_field=Field +UserServlet.invalid_password_reuse=Invalid reuse of a password known from history. A new password is required. +UserServlet.is_required=is required +UserServlet.passwords_donot_match=Passwords do not match +UserServlet.phone_not_valid=is not valid, valid examples: '(040) 123456-789', '030 1234567' +UserServlet.the_input_field=Field \ No newline at end of file diff --git a/src/main/resources/de/jalin/ldapadmin/web/messages_de.properties b/src/main/resources/de/jalin/ldapadmin/web/messages_de.properties new file mode 100644 index 0000000..0bf263f --- /dev/null +++ b/src/main/resources/de/jalin/ldapadmin/web/messages_de.properties @@ -0,0 +1,28 @@ +GroupServlet.no_empty_group=Eine Gruppe muss mindestens ein Mitglied haben. +GroupServlet.group_exists=Eine Gruppe mit diesem Namen existiert bereits. +ProfileServlet.inputfield=Eingabefeld +ProfileServlet.invalid_reuse=Dieses Passwort haben Sie bereits verwendet. Die Passwort-Richtlinie erfordert es, dass Sie ein neues Passwort ausw\u00e4hlen. +ProfileServlet.password_changed=Neues Passwort gespeichert +ProfileServlet.passwords_donot_match=stimmt nicht mit der ersten Passwort-Eingabe \u00fcberein +ResetPasswordServlet.email_content=\!\n\nSie wollen Ihr Passwort zur\u00fccksetzen?\n\nDann folgen Sie bitte dem folgenden Link:\n +ResetPasswordServlet.email_greeting=Hallo +ResetPasswordServlet.email_signature=\n\nIhr LDAP-Service\n +ResetPasswordServlet.email_subject=LDAP Passwort-Anforderung +ResetPasswordServlet.error_sending_email=Konnte Mail nicht senden +ResetPasswordServlet.error_sending_email_relay=Konnte Mail nicht senden (SMTP-Relay-Problem) +ResetPasswordServlet.error_sending_email_server=Konnte Mail Server nicht erreichen +ResetPasswordServlet.error_sending_password_reset=Fehler beim Versand der E-Mail f\u00fcr das Setzen eines neuen Passworts. +ResetPasswordServlet.invalid_password_reuse=Das Passwort wurde bereits verwendet. Die Passwort-Richtlinie verlangt die Definition eines neuen Passworts. +ResetPasswordServlet.no_valid_passwordreset_request=Es konnte keine g\u00fcltige Anforderung f\u00fcr ein neues Passwort zugeordnet werden. +ResetPasswordServlet.password_changed=Ihr neues Passwort ist gespeichert. +ResetPasswordServlet.passwords_donot_match=Die beiden Passwort-Eingaben stimmen nicht \u00fcberein. +ResetPasswordServlet.simple_password=Ihr Passwort ist zu einfach. Es sollte mindestens einen Klein- und Gro\u00dfbuchstaben und eine Ziffer oder Sonderzeichen enthalten sowie eine Mindestl\u00e4nge von 12 Zeichen aufweisen +UserServlet.email_not_valid=Die E-Mail-Adresse ist nicht g\u00fcltig. +UserServlet.group_last_member=Das letze Mitglied kann nicht aus der Gruppe entfernt werden. Gruppen m\u00fcssen mindestens ein Mitglied haben. +UserServlet.user_exists=Einen Benutzer mit dieser Kennung gibt es bereits. +UserServlet.input_field=Eingabefeld +UserServlet.invalid_password_reuse=Das Passwort wurde bereits verwendet. Die Passwort-Richtlinie erfordert, dass Sie ein neues Passwort vergeben. +UserServlet.is_required=muss gef\u00fcllt werden. +UserServlet.passwords_donot_match=Passwort stimmt nicht mit der ersten Eingabe \u00fcberein +UserServlet.phone_not_valid=Das Eingabeformat f\u00fcr die Telefonnummer ist nicht korrekt, bitte formatieren Sie wie im Beispiel: '(040) 123456-789' oder '030 1234567' +UserServlet.the_input_field=Das Eingabefeld \ No newline at end of file diff --git a/src/main/resources/de/jalin/ldapadmin/web/messages_en.properties b/src/main/resources/de/jalin/ldapadmin/web/messages_en.properties new file mode 100644 index 0000000..e939323 --- /dev/null +++ b/src/main/resources/de/jalin/ldapadmin/web/messages_en.properties @@ -0,0 +1,28 @@ +GroupServlet.no_empty_group=A groups is required to have members +GroupServlet.group_exists=This group already exists +ProfileServlet.inputfield=Field +ProfileServlet.invalid_reuse=Invalid reuse of a password known from history +ProfileServlet.password_changed=Password changed +ProfileServlet.passwords_donot_match=Passwords do not match +ResetPasswordServlet.email_content=\!\n\nYou want to reset your password?\n\nUse the follwing link:\n +ResetPasswordServlet.email_greeting=Dear +ResetPasswordServlet.email_signature=\n\nYour LDAP-service\n +ResetPasswordServlet.email_subject=reset LDAP password +ResetPasswordServlet.error_sending_email=Could not sent email +ResetPasswordServlet.error_sending_email_relay=Could not sent email (relay-error) +ResetPasswordServlet.error_sending_email_server=Could not reach email service +ResetPasswordServlet.error_sending_password_reset=Error sending email +ResetPasswordServlet.invalid_password_reuse=Invalid reuse of a password known from history. A new password is required. +ResetPasswordServlet.no_valid_passwordreset_request=Could not find a valid password request. +ResetPasswordServlet.password_changed=Password changed +ResetPasswordServlet.passwords_donot_match=Passwords do not match +ResetPasswordServlet.simple_password=Your password is too simple. It should contain at least one lowercase and uppercase letter and a digit / secial character and a minimum length of 12 characters +UserServlet.email_not_valid=No valid email address given +UserServlet.group_last_member=A group is required to have members +UserServlet.user_exists=This user already exists +UserServlet.input_field=Field +UserServlet.invalid_password_reuse=Invalid reuse of a password known from history. A new password is required. +UserServlet.is_required=is required +UserServlet.passwords_donot_match=Passwords do not match +UserServlet.phone_not_valid=is not valid, valid examples: '(040) 123456-789', '030 1234567' +UserServlet.the_input_field=Field \ No newline at end of file diff --git a/src/main/resources/de/jalin/ldapadmin/wro/less.min.js b/src/main/resources/de/jalin/ldapadmin/wro/less.min.js new file mode 100644 index 0000000..958b121 --- /dev/null +++ b/src/main/resources/de/jalin/ldapadmin/wro/less.min.js @@ -0,0 +1,16 @@ +/*! + * Less - Leaner CSS v1.7.5 + * http://lesscss.org + * + * Copyright (c) 2009-2014, Alexis Sellier + * Licensed under the Apache v2 License. + * + */ + + /** * @license Apache v2 + */ + +!function(a,b){function c(b){return a.less[b.split("/")[1]]}function d(a,b){"undefined"!=typeof console&&w.logLevel>=b&&console.log("less: "+a)}function e(a){return a.replace(/^[a-z-]+:\/+?[^\/]+/,"").replace(/^\//,"").replace(/\.[a-zA-Z]+$/,"").replace(/[^\.\w-]+/g,"-").replace(/\./g,":")}function f(a,c){var e="{line} {content}",f=a.filename||c,g=[],h=(a.type||"Syntax")+"Error: "+(a.message||"There is an error in your .less file")+" in "+f+" ",i=function(a,c,d){a.extract[c]!==b&&g.push(e.replace(/\{line\}/,(parseInt(a.line,10)||0)+(c-1)).replace(/\{class\}/,d).replace(/\{content\}/,a.extract[c]))};a.extract?(i(a,0,""),i(a,1,"line"),i(a,2,""),h+="on line "+a.line+", column "+(a.column+1)+":\n"+g.join("\n")):a.stack&&(h+=a.stack),d(h,z.errors)}function g(a,b,c){var f=b.href||"",g="less:"+(b.title||e(f)),h=document.getElementById(g),i=!1,j=document.createElement("style");j.setAttribute("type","text/css"),b.media&&j.setAttribute("media",b.media),j.id=g,j.styleSheet||(j.appendChild(document.createTextNode(a)),i=null!==h&&h.childNodes.length>0&&j.childNodes.length>0&&h.firstChild.nodeValue===j.firstChild.nodeValue);var k=document.getElementsByTagName("head")[0];if(null===h||i===!1){var l=b&&b.nextSibling||null;l?l.parentNode.insertBefore(j,l):k.appendChild(j)}if(h&&i===!1&&h.parentNode.removeChild(h),j.styleSheet)try{j.styleSheet.cssText=a}catch(m){throw new Error("Couldn't reassign styleSheet.cssText.")}if(c&&D){d("saving "+f+" to cache.",z.info);try{D.setItem(f,a),D.setItem(f+":timestamp",c)}catch(m){d("failed to save",z.errors)}}}function h(a){return w.postProcessor&&"function"==typeof w.postProcessor&&(a=w.postProcessor.call(a,a)||a),a}function i(a,c){var d,f,h="less-error-message:"+e(c||""),i='
  • {content}
  • ',j=document.createElement("div"),k=[],l=a.filename||c,m=l.match(/([^\/]+(\?.*)?)$/)[1];j.id=h,j.className="less-error-message",f="

    "+(a.type||"Syntax")+"Error: "+(a.message||"There is an error in your .less file")+'

    in '+m+" ";var n=function(a,c,d){a.extract[c]!==b&&k.push(i.replace(/\{line\}/,(parseInt(a.line,10)||0)+(c-1)).replace(/\{class\}/,d).replace(/\{content\}/,a.extract[c]))};a.extract?(n(a,0,""),n(a,1,"line"),n(a,2,""),f+="on line "+a.line+", column "+(a.column+1)+":

      "+k.join("")+"
    "):a.stack&&(f+="
    "+a.stack.split("\n").slice(1).join("
    ")),j.innerHTML=f,g([".less-error-message ul, .less-error-message li {","list-style-type: none;","margin-right: 15px;","padding: 4px 0;","margin: 0;","}",".less-error-message label {","font-size: 12px;","margin-right: 15px;","padding: 4px 0;","color: #cc7777;","}",".less-error-message pre {","color: #dd6666;","padding: 4px 0;","margin: 0;","display: inline-block;","}",".less-error-message pre.line {","color: #ff0000;","}",".less-error-message h3 {","font-size: 20px;","font-weight: bold;","padding: 15px 0 5px 0;","margin: 0;","}",".less-error-message a {","color: #10a","}",".less-error-message .error {","color: red;","font-weight: bold;","padding-bottom: 2px;","border-bottom: 1px dashed red;","}"].join("\n"),{title:"error-message"}),j.style.cssText=["font-family: Arial, sans-serif","border: 1px solid #e00","background-color: #eee","border-radius: 5px","-webkit-border-radius: 5px","-moz-border-radius: 5px","color: #e00","padding: 15px","margin-bottom: 15px"].join(";"),"development"==w.env&&(d=setInterval(function(){document.body&&(document.getElementById(h)?document.body.replaceChild(j,document.getElementById(h)):document.body.insertBefore(j,document.body.firstChild),clearInterval(d))},10))}function j(a,b){w.errorReporting&&"html"!==w.errorReporting?"console"===w.errorReporting?f(a,b):"function"==typeof w.errorReporting&&w.errorReporting("add",a,b):i(a,b)}function k(a){var b=document.getElementById("less-error-message:"+e(a));b&&b.parentNode.removeChild(b)}function l(){}function m(a){w.errorReporting&&"html"!==w.errorReporting?"console"===w.errorReporting?l(a):"function"==typeof w.errorReporting&&w.errorReporting("remove",a):k(a)}function n(a){for(var b,c=document.getElementsByTagName("style"),d=0;d0&&(h.splice(c-1,2),c-=2)}return g.hostPart=f[1],g.directories=h,g.path=f[1]+h.join("/"),g.fileUrl=g.path+(f[4]||""),g.url=g.fileUrl+(f[5]||""),g}function p(a,b){var c,d,e,f,g=o(a),h=o(b),i="";if(g.hostPart!==h.hostPart)return"";for(d=Math.max(h.directories.length,g.directories.length),c=0;d>c&&h.directories[c]===g.directories[c];c++);for(f=h.directories.slice(c),e=g.directories.slice(c),c=0;c=200&&b.status<300?c(b.responseText,b.getResponseHeader("Last-Modified")):"function"==typeof d&&d(b.status,a)}var g=q(),h=y?w.fileAsync:w.async;"function"==typeof g.overrideMimeType&&g.overrideMimeType("text/css"),d("XHR: Getting '"+a+"'",z.debug),g.open("GET",a,h),g.setRequestHeader("Accept",b||"text/x-less, text/css; q=0.9, */*; q=0.5"),g.send(null),y&&!w.fileAsync?0===g.status||g.status>=200&&g.status<300?c(g.responseText):e(g.status,a):h?g.onreadystatechange=function(){4==g.readyState&&f(g,c,e)}:f(g,c,e)}function s(b,c,d,e){c&&c.currentDirectory&&!/^([A-Za-z-]+:)?\//.test(b)&&(b=c.currentDirectory+b);var f=o(b,a.location.href),g=f.url,h={currentDirectory:f.path,filename:g};if(c?(h.entryPath=c.entryPath,h.rootpath=c.rootpath,h.rootFilename=c.rootFilename,h.relativeUrls=c.relativeUrls):(h.entryPath=f.path,h.rootpath=w.rootpath||f.path,h.rootFilename=g,h.relativeUrls=e.relativeUrls),h.relativeUrls&&(h.rootpath=e.rootpath?o(e.rootpath+p(f.path,h.entryPath)).path:f.path),e.useFileCache&&E[g])try{var i=E[g];d(null,i,g,h,{lastModified:new Date})}catch(j){d(j,null,g)}else r(g,e.mime,function(a,b){E[g]=a;try{d(null,a,g,h,{lastModified:b})}catch(c){d(c,null,g)}},function(a,b){d({type:"File",message:"'"+b+"' wasn't found ("+a+")"},null,g)})}function t(a,b,c,d,e){var f=new w.tree.parseEnv(w);f.mime=a.type,(e||w.globalVars)&&(f.useFileCache=!0),s(a.href,null,function(h,i,j,k,l){if(l){l.remaining=d;var n=D&&D.getItem(j),o=D&&D.getItem(j+":timestamp");if(!c&&o&&l.lastModified&&new Date(l.lastModified).valueOf()===new Date(o).valueOf())return g(n,a),l.local=!0,void b(null,null,i,a,l,j)}m(j),i?(f.currentFileInfo=k,new w.Parser(f).parse(i,function(c,d){if(c)return b(c,null,null,a);try{b(c,d,i,a,l,j)}catch(c){b(c,null,null,a)}},{modifyVars:e,globalVars:w.globalVars})):b(h,null,null,a,l,j)},f,e)}function u(a,b,c){for(var d=0;dD&&(C=C.slice(y-D),D=y)}function h(a,b){var c=a.charCodeAt(0|b);return 32>=c&&(32===c||10===c||9===c)}function i(a){var b,c,d=typeof a;return"string"===d?v.charAt(y)!==a?null:(l(1),a):(g(),(b=a.exec(C))?(c=b[0].length,l(c),"string"==typeof b?b:1===b.length?b[0]:b):null)}function j(a){y>D&&(C=C.slice(y-D),D=y);var b=a.exec(C);return b?(l(b[0].length),"string"==typeof b?b:1===b.length?b[0]:b):null}function k(a){return v.charAt(y)!==a?null:(l(1),a)}function l(a){for(var b,c=y,d=z,e=y-D,f=y+C.length-e,g=y+=a,h=v;f>y&&(b=h.charCodeAt(y),!(b>32))&&(32===b||10===b||9===b||13===b);y++);return C=C.slice(a+y-g+e),D=y,!C.length&&z=0&&"\n"!==b.charAt(c);)e++;return"number"==typeof a&&(d=(b.slice(0,a).match(/\n/g)||"").length),{line:d,column:e}}function t(a,b,d){var e=d.currentFileInfo.filename;return"browser"!==w.mode&&"rhino"!==w.mode&&(e=c("path").resolve(e)),{lineNumber:s(a,b).line+1,fileName:e}}function u(a,b){var c=r(a,b),d=s(a.index,c),e=d.line,f=d.column,g=a.call&&s(a.call,c).line,h=c.split("\n");this.type=a.type||"Syntax",this.message=a.message,this.filename=a.filename||b.currentFileInfo.filename,this.index=a.index,this.line="number"==typeof e?e+1:null,this.callLine=g+1,this.callExtract=h[g],this.stack=a.stack,this.column=f,this.extract=[h[e-1],h[e],h[e+1]]}var v,y,z,A,B,C,D,E,F,G=[],H=a&&a.filename;a instanceof x.parseEnv||(a=new x.parseEnv(a));var I=this.imports={paths:a.paths||[],queue:[],files:a.files,contents:a.contents,contentsIgnoredChars:a.contentsIgnoredChars,mime:a.mime,error:null,push:function(b,c,d,e){var f=this;this.queue.push(b);var g=function(a,c,d){f.queue.splice(f.queue.indexOf(b),1);var g=d===H;f.files[d]=c,a&&!f.error&&(f.error=a),e(a,c,g,d)};w.Parser.importer?w.Parser.importer(b,c,g,a):w.Parser.fileLoader(b,c,function(b,e,f,h){if(b)return void g(b);var i=new x.parseEnv(a);i.currentFileInfo=h,i.processImports=!1,i.contents[f]=e,(c.reference||d.reference)&&(h.reference=!0),d.inline?g(null,e,f):new w.Parser(i).parse(e,function(a,b){g(a,b,f)})},a)}},J=j;return u.prototype=new Error,u.prototype.constructor=u,this.env=a=a||{},this.optimization="optimization"in this.env?this.env.optimization:1,E={imports:I,parse:function(d,e,f){var g,h,i,j,k,l=null,m="";if(y=z=D=A=0,j=f&&f.globalVars?w.Parser.serializeVars(f.globalVars)+"\n":"",k=f&&f.modifyVars?"\n"+w.Parser.serializeVars(f.modifyVars):"",(j||f&&f.banner)&&(m=(f&&f.banner?f.banner:"")+j,E.imports.contentsIgnoredChars[a.currentFileInfo.filename]=m.length),d=d.replace(/\r\n/g,"\n"),v=d=m+d.replace(/^\uFEFF/,"")+k,E.imports.contents[a.currentFileInfo.filename]=d,B=function(b){function c(b,c){l=new u({index:c||i,type:"Parse",message:b,filename:a.currentFileInfo.filename},a)}function d(a){var c=i-s;512>c&&!a||!c||(r.push(b.slice(s,i+1)),s=i+1)}var e,f,g,h,i,j,k,m,n,o=b.length,p=0,q=0,r=[],s=0;for(i=0;o>i;i++)if(k=b.charCodeAt(i),!(k>=97&&122>=k||34>k))switch(k){case 40:q++,f=i;continue;case 41:if(--q<0)return c("missing opening `(`");continue;case 59:q||d();continue;case 123:p++,e=i;continue;case 125:if(--p<0)return c("missing opening `{`");p||q||d();continue;case 92:if(o-1>i){i++;continue}return c("unescaped `\\`");case 34:case 39:case 96:for(n=0,j=i,i+=1;o>i;i++)if(m=b.charCodeAt(i),!(m>96)){if(m==k){n=1;break}if(92==m){if(i==o-1)return c("unescaped `\\`");i++}}if(n)continue;return c("unmatched `"+String.fromCharCode(k)+"`",j);case 47:if(q||i==o-1)continue;if(m=b.charCodeAt(i+1),47==m)for(i+=2;o>i&&(m=b.charCodeAt(i),!(13>=m)||10!=m&&13!=m);i++);else if(42==m){for(g=j=i,i+=2;o-1>i&&(m=b.charCodeAt(i),125==m&&(h=i),42!=m||47!=b.charCodeAt(i+1));i++);if(i==o-1)return c("missing closing `*/`",j);i++}continue;case 42:if(o-1>i&&47==b.charCodeAt(i+1))return c("unmatched `/*`");continue}return 0!==p?g>e&&h>g?c("missing closing `}` or `*/`",e):c("missing closing `}`",e):0!==q?c("missing closing `)`",f):(d(!0),r)}(d),l)return e(new u(l,a));C=B[0];try{g=new x.Ruleset(null,this.parsers.primary()),g.root=!0,g.firstRoot=!0}catch(n){return e(new u(n,a))}if(g.toCSS=function(d){return function(e,f){e=e||{};var g,h,i=new x.evalEnv(e);"object"!=typeof f||Array.isArray(f)||(f=Object.keys(f).map(function(a){var b=f[a];return b instanceof x.Value||(b instanceof x.Expression||(b=new x.Expression([b])),b=new x.Value([b])),new x.Rule("@"+a,b,!1,null,0)}),i.frames=[new x.Ruleset(null,f)]);try{var j,k=[],l=[new x.joinSelectorVisitor,new x.processExtendsVisitor,new x.toCSSVisitor({compress:Boolean(e.compress)})],m=this;if(e.plugins)for(j=0;j57||43>b||47===b||44==b))return a=j(/^([+-]?\d*\.?\d+)(%|[a-z]+)?/),a?new x.Dimension(a[1],a[2]):void 0},unicodeDescriptor:function(){var a;return a=j(/^U\+[0-9a-fA-F?]+(\-[0-9a-fA-F?]+)?/),a?new x.UnicodeDescriptor(a[0]):void 0},javascript:function(){var c,d,e=y;return"~"===v.charAt(e)&&(e++,d=!0),"`"===v.charAt(e)?(a.javascriptEnabled===b||a.javascriptEnabled||o("You are using JavaScript, which has been disabled."),d&&k("~"),c=j(/^`([^`]*)`/),c?new x.JavaScript(c[1],y,d):void 0):void 0}},variable:function(){var a;return"@"===v.charAt(y)&&(a=j(/^(@[\w-]+)\s*:/))?a[1]:void 0},rulesetCall:function(){var a;return"@"===v.charAt(y)&&(a=j(/^(@[\w-]+)\s*\(\s*\)\s*;/))?new x.RulesetCall(a[1]):void 0},extend:function(a){var b,c,d,e,f,g=y;if(j(a?/^&:extend\(/:/^:extend\(/)){do{for(d=null,b=null;!(d=j(/^(all)(?=\s*(\)|,))/))&&(c=this.element());)b?b.push(c):b=[c];d=d&&d[1],b||o("Missing target selector for :extend()."),f=new x.Extend(new x.Selector(b),d,g),e?e.push(f):e=[f]}while(k(","));return m(/^\)/),a&&m(/^;/),e}},extendRule:function(){return this.extend(!0)},mixin:{call:function(){var b,c,g,h,i,l,m=v.charAt(y),o=!1,p=y;if("."===m||"#"===m){for(d();;){if(b=y,h=j(/^[#.](?:[\w-]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/),!h)break;g=new x.Element(i,h,b,a.currentFileInfo),c?c.push(g):c=[g],i=k(">")}return c&&(k("(")&&(l=this.args(!0).args,n(")")),F.important()&&(o=!0),F.end())?(f(),new x.mixin.Call(c,l,p,a.currentFileInfo,o)):void e()}},args:function(a){var b,c,g,h,i,l,m=E.parsers,n=m.entities,p={args:null,variadic:!1},q=[],r=[],s=[];for(d();;){if(a)l=m.detachedRuleset()||m.expression();else{if(m.comments(),"."===v.charAt(y)&&j(/^\.{3}/)){p.variadic=!0,k(";")&&!b&&(b=!0),(b?r:s).push({variadic:!0});break}l=n.variable()||n.literal()||n.keyword()}if(!l)break;h=null,l.throwAwayComments&&l.throwAwayComments(),i=l;var t=null;if(a?l.value&&1==l.value.length&&(t=l.value[0]):t=l,t&&t instanceof x.Variable)if(k(":")){if(q.length>0&&(b&&o("Cannot mix ; and , as delimiter types"),c=!0),i=a&&m.detachedRuleset()||m.expression(),!i){if(!a)return e(),p.args=[],p;o("could not understand value for named argument")}h=g=t.name}else{if(!a&&j(/^\.{3}/)){p.variadic=!0,k(";")&&!b&&(b=!0),(b?r:s).push({name:l.name,variadic:!0});break}a||(g=h=t.name,i=null)}i&&q.push(i),s.push({name:h,value:i}),k(",")||(k(";")||b)&&(c&&o("Cannot mix ; and , as delimiter types"),b=!0,q.length>1&&(i=new x.Value(q)),r.push({name:g,value:i}),g=null,q=[],c=!1)}return f(),p.args=b?r:s,p},definition:function(){var a,b,c,g,h=[],i=!1;if(!("."!==v.charAt(y)&&"#"!==v.charAt(y)||p(/^[^{]*\}/)))if(d(),b=j(/^([#.](?:[\w-]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+)\s*\(/)){a=b[1];var l=this.args(!1);if(h=l.args,i=l.variadic,!k(")"))return A=y,void e();if(F.comments(),j(/^when/)&&(g=m(F.conditions,"expected condition")),c=F.block())return f(),new x.mixin.Definition(a,h,c,g,i);e()}else f()}},entity:function(){var a=this.entities;return a.literal()||a.variable()||a.url()||a.call()||a.keyword()||a.javascript()||this.comment()},end:function(){return k(";")||q("}")},alpha:function(){var a;if(j(/^\(opacity=/i))return a=j(/^\d+/)||this.entities.variable(),a?(n(")"),new x.Alpha(a)):void 0},element:function(){var b,c,g,h=y;return c=this.combinator(),b=j(/^(?:\d+\.\d+|\d+)%/)||j(/^(?:[.#]?|:*)(?:[\w-]|[^\x00-\x9f]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/)||k("*")||k("&")||this.attribute()||j(/^\([^()@]+\)/)||j(/^[\.#](?=@)/)||this.entities.variableCurly(),b||(d(),k("(")?(g=this.selector())&&k(")")?(b=new x.Paren(g),f()):e():f()),b?new x.Element(c,b,h,a.currentFileInfo):void 0},combinator:function(){var a=v.charAt(y);if("/"===a){d();var b=j(/^\/[a-z]+\//i);if(b)return f(),new x.Combinator(b);e()}if(">"===a||"+"===a||"~"===a||"|"===a||"^"===a){for(y++,"^"===a&&"^"===v.charAt(y)&&(a="^^",y++);h(v,y);)y++;return new x.Combinator(a)}return new x.Combinator(h(v,y-1)?" ":null)},lessSelector:function(){return this.selector(!0)},selector:function(b){for(var c,d,e,f,g,h,i,j=y,k=J;(b&&(g=this.extend())||b&&(h=k(/^when/))||(f=this.element()))&&(h?i=m(this.conditions,"expected condition"):i?o("CSS guard can only be used at the end of selector"):g?d?d.push(g):d=[g]:(d&&o("Extend can only be used at the end of selector"),e=v.charAt(y),c?c.push(f):c=[f],f=null),"{"!==e&&"}"!==e&&";"!==e&&","!==e&&")"!==e););return c?new x.Selector(c,d,i,j,a.currentFileInfo):void(d&&o("Extend must be used to extend a selector, it cannot be used on its own"))},attribute:function(){if(k("[")){var a,b,c,d=this.entities;return(a=d.variableCurly())||(a=m(/^(?:[_A-Za-z0-9-\*]*\|)?(?:[_A-Za-z0-9-]|\\.)+/)),c=j(/^[|~*$^]?=/),c&&(b=d.quoted()||j(/^[0-9]+%/)||j(/^[\w-]+/)||d.variableCurly()),n("]"),new x.Attribute(a,c,b)}},block:function(){var a;return k("{")&&(a=this.primary())&&k("}")?a:void 0},blockRuleset:function(){var a=this.block();return a&&(a=new x.Ruleset(null,a)),a},detachedRuleset:function(){var a=this.blockRuleset();return a?new x.DetachedRuleset(a):void 0},ruleset:function(){var b,c,g,h;for(d(),a.dumpLineNumbers&&(h=t(y,v,a));;){if(c=this.lessSelector(),!c)break;if(b?b.push(c):b=[c],this.comments(),c.condition&&b.length>1&&o("Guards are only currently allowed on a single selector."),!k(","))break;c.condition&&o("Guards are only currently allowed on a single selector."),this.comments()}if(b&&(g=this.block())){f();var i=new x.Ruleset(b,g,a.strictImports);return a.dumpLineNumbers&&(i.debugInfo=h),i}A=y,e()},rule:function(b){var c,g,h,i,j,k=y,l=v.charAt(k);if("."!==l&&"#"!==l&&"&"!==l)if(d(),c=this.variable()||this.ruleProperty()){if(j="string"==typeof c,j&&(g=this.detachedRuleset()),this.comments(),g||(g=b||!a.compress&&!j?this.anonymousValue()||this.value():this.value()||this.anonymousValue(),h=this.important(),i=!j&&c.pop().value),g&&this.end())return f(),new x.Rule(c,g,h,i,k,a.currentFileInfo);if(A=y,e(),g&&!b)return this.rule(!0)}else f()},anonymousValue:function(){var a;return a=/^([^@+\/'"*`(;{}-]*);/.exec(C),a?(y+=a[0].length-1,new x.Anonymous(a[1])):void 0},"import":function(){var b,c,d=y,e=j(/^@import?\s+/);if(e){var f=(e?this.importOptions():null)||{};if(b=this.entities.quoted()||this.entities.url())return c=this.mediaFeatures(),i(";")||(y=d,o("missing semi-colon or unrecognised media features on import")),c=c&&new x.Value(c),new x.Import(b,c,f,d,a.currentFileInfo);y=d,o("malformed import statement")}},importOptions:function(){var a,b,c,d={};if(!k("("))return null;do if(a=this.importOption()){switch(b=a,c=!0,b){case"css":b="less",c=!1;break;case"once":b="multiple",c=!1}if(d[b]=c,!k(","))break}while(a);return n(")"),d},importOption:function(){var a=j(/^(less|css|multiple|once|inline|reference)/);return a?a[1]:void 0},mediaFeature:function(){var b,c,d=this.entities,e=[];do if(b=d.keyword()||d.variable())e.push(b);else if(k("(")){if(c=this.property(),b=this.value(),!k(")"))return null;if(c&&b)e.push(new x.Paren(new x.Rule(c,b,null,null,y,a.currentFileInfo,!0)));else{if(!b)return null;e.push(new x.Paren(b))}}while(b);return e.length>0?new x.Expression(e):void 0},mediaFeatures:function(){var a,b=this.entities,c=[];do if(a=this.mediaFeature()){if(c.push(a),!k(","))break}else if(a=b.variable(),a&&(c.push(a),!k(",")))break;while(a);return c.length>0?c:null},media:function(){var b,c,d,e;return a.dumpLineNumbers&&(e=t(y,v,a)),j(/^@media/)&&(b=this.mediaFeatures(),c=this.block())?(d=new x.Media(c,b,y,a.currentFileInfo),a.dumpLineNumbers&&(d.debugInfo=e),d):void 0},directive:function(){var b,c,g,h,i,l,m,n=y,p=!0;if("@"===v.charAt(y)){if(c=this["import"]()||this.media())return c;if(d(),b=j(/^@[a-z-]+/)){switch(h=b,"-"==b.charAt(1)&&b.indexOf("-",2)>0&&(h="@"+b.slice(b.indexOf("-",2)+1)),h){case"@charset":i=!0,p=!1;break;case"@namespace":l=!0,p=!1;break;case"@keyframes":i=!0;break;case"@host":case"@page":case"@document":case"@supports":m=!0}return this.comments(),i?(c=this.entity(),c||o("expected "+b+" identifier")):l?(c=this.expression(),c||o("expected "+b+" expression")):m&&(c=(j(/^[^{;]+/)||"").trim(),c&&(c=new x.Anonymous(c))),this.comments(),p&&(g=this.blockRuleset()),g||!p&&c&&k(";")?(f(),new x.Directive(b,c,g,n,a.currentFileInfo,a.dumpLineNumbers?t(n,v,a):null)):void e()}}},value:function(){var a,b=[];do if(a=this.expression(),a&&(b.push(a),!k(",")))break;while(a);return b.length>0?new x.Value(b):void 0},important:function(){return"!"===v.charAt(y)?j(/^! *important/):void 0},sub:function(){var a,b;return k("(")&&(a=this.addition())?(b=new x.Expression([a]),n(")"),b.parens=!0,b):void 0},multiplication:function(){var a,b,c,g,i;if(a=this.operand()){for(i=h(v,y-1);;){if(p(/^\/[*\/]/))break;if(d(),c=k("/")||k("*"),!c){f();break}if(b=this.operand(),!b){e();break}f(),a.parensInOp=!0,b.parensInOp=!0,g=new x.Operation(c,[g||a,b],i),i=h(v,y-1)}return g||a}},addition:function(){var a,b,c,d,e;if(a=this.multiplication()){for(e=h(v,y-1);;){if(c=j(/^[-+]\s+/)||!e&&(k("+")||k("-")),!c)break;if(b=this.multiplication(),!b)break;a.parensInOp=!0,b.parensInOp=!0,d=new x.Operation(c,[d||a,b],e),e=h(v,y-1)}return d||a}},conditions:function(){var a,b,c,d=y;if(a=this.condition()){for(;;){if(!p(/^,\s*(not\s*)?\(/)||!k(","))break;if(b=this.condition(),!b)break;c=new x.Condition("or",c||a,b,d)}return c||a}},condition:function(){var a,b,c,d,e=this.entities,f=y,g=!1;return j(/^not/)&&(g=!0),n("("),a=this.addition()||e.keyword()||e.quoted(),a?(d=j(/^(?:>=|<=|=<|[<=>])/),d?(b=this.addition()||e.keyword()||e.quoted(),b?c=new x.Condition(d,a,b,f,g):o("expected expression")):c=new x.Condition("=",a,new x.Keyword("true"),f,g),n(")"),j(/^and/)?new x.Condition("and",c,this.condition()):c):void 0},operand:function(){var a,b=this.entities,c=v.charAt(y+1);"-"!==v.charAt(y)||"@"!==c&&"("!==c||(a=k("-"));var d=this.sub()||b.dimension()||b.color()||b.variable()||b.call();return a&&(d.parensInOp=!0,d=new x.Negative(d)),d},expression:function(){var a,b,c=[];do a=this.addition()||this.entity(),a&&(c.push(a),p(/^\/[\/*]/)||(b=k("/"),b&&c.push(new x.Anonymous(b))));while(a);return c.length>0?new x.Expression(c):void 0},property:function(){var a=j(/^(\*?-?[_a-zA-Z0-9-]+)\s*:/);return a?a[1]:void 0},ruleProperty:function(){function b(a){var b=a.exec(f);return b?(h.push(y+i),i+=b[0].length,f=f.slice(b[1].length),g.push(b[1])):void 0}function c(){var a=/^\s*\/\*(?:[^*]|\*+[^\/*])*\*+\//.exec(f);return a?(i+=a[0].length,f=f.slice(a[0].length),!0):!1}var d,e,f=C,g=[],h=[],i=0;for(b(/^(\*?)/);b(/^((?:[\w-]+)|(?:@\{[\w-]+\}))/););for(;c(););if(g.length>1&&b(/^\s*((?:\+_|\+)?)\s*:/)){for(l(i),""===g[0]&&(g.shift(),h.shift()),e=0;el;l++)e=b.rgb[l]/255,f=c.rgb[l]/255,h=a(e,f),g&&(h=(j*f+i*(e-j*(e+f-h)))/g),k[l]=255*h;return new d.Color(k,g)}function g(){var a,b=d.functions;for(a in l)l.hasOwnProperty(a)&&(b[a]=e.bind(null,Math[a],l[a]));for(a in m)m.hasOwnProperty(a)&&(b[a]=f.bind(null,m[a]));a=d.defaultFunc,b["default"]=a.eval.bind(a)}function h(a){return d.functions.hsla(a.h,a.s,a.l,a.a)}function i(a,b){return a instanceof d.Dimension&&a.unit.is("%")?parseFloat(a.value*b/100):j(a)}function j(a){if(a instanceof d.Dimension)return parseFloat(a.unit.is("%")?a.value/100:a.value);if("number"==typeof a)return a;throw{error:"RuntimeError",message:"color functions take numbers as parameters"}}function k(a){return Math.min(1,Math.max(0,a))}d.functions={rgb:function(a,b,c){return this.rgba(a,b,c,1)},rgba:function(a,b,c,e){var f=[a,b,c].map(function(a){return i(a,255)});return e=j(e),new d.Color(f,e)},hsl:function(a,b,c){return this.hsla(a,b,c,1)},hsla:function(a,b,c,d){function e(a){return a=0>a?a+1:a>1?a-1:a,1>6*a?g+(f-g)*a*6:1>2*a?f:2>3*a?g+(f-g)*(2/3-a)*6:g}a=j(a)%360/360,b=k(j(b)),c=k(j(c)),d=k(j(d));var f=.5>=c?c*(b+1):c+b-c*b,g=2*c-f;return this.rgba(255*e(a+1/3),255*e(a),255*e(a-1/3),d)},hsv:function(a,b,c){return this.hsva(a,b,c,1)},hsva:function(a,b,c,d){a=j(a)%360/360*360,b=j(b),c=j(c),d=j(d);var e,f;e=Math.floor(a/60%6),f=a/60-e;var g=[c,c*(1-b),c*(1-f*b),c*(1-(1-f)*b)],h=[[0,3,1],[2,0,1],[1,0,3],[1,2,0],[3,1,0],[0,1,2]];return this.rgba(255*g[h[e][0]],255*g[h[e][1]],255*g[h[e][2]],d)},hue:function(a){return new d.Dimension(a.toHSL().h)},saturation:function(a){return new d.Dimension(100*a.toHSL().s,"%")},lightness:function(a){return new d.Dimension(100*a.toHSL().l,"%")},hsvhue:function(a){return new d.Dimension(a.toHSV().h)},hsvsaturation:function(a){return new d.Dimension(100*a.toHSV().s,"%")},hsvvalue:function(a){return new d.Dimension(100*a.toHSV().v,"%")},red:function(a){return new d.Dimension(a.rgb[0])},green:function(a){return new d.Dimension(a.rgb[1])},blue:function(a){return new d.Dimension(a.rgb[2])},alpha:function(a){return new d.Dimension(a.toHSL().a)},luma:function(a){return new d.Dimension(a.luma()*a.alpha*100,"%")},luminance:function(a){var b=.2126*a.rgb[0]/255+.7152*a.rgb[1]/255+.0722*a.rgb[2]/255;return new d.Dimension(b*a.alpha*100,"%")},saturate:function(a,b){if(!a.rgb)return null;var c=a.toHSL();return c.s+=b.value/100,c.s=k(c.s),h(c)},desaturate:function(a,b){var c=a.toHSL();return c.s-=b.value/100,c.s=k(c.s),h(c)},lighten:function(a,b){var c=a.toHSL();return c.l+=b.value/100,c.l=k(c.l),h(c)},darken:function(a,b){var c=a.toHSL();return c.l-=b.value/100,c.l=k(c.l),h(c)},fadein:function(a,b){var c=a.toHSL();return c.a+=b.value/100,c.a=k(c.a),h(c)},fadeout:function(a,b){var c=a.toHSL();return c.a-=b.value/100,c.a=k(c.a),h(c)},fade:function(a,b){var c=a.toHSL();return c.a=b.value/100,c.a=k(c.a),h(c)},spin:function(a,b){var c=a.toHSL(),d=(c.h+b.value)%360;return c.h=0>d?360+d:d,h(c)},mix:function(a,b,c){c||(c=new d.Dimension(50));var e=c.value/100,f=2*e-1,g=a.toHSL().a-b.toHSL().a,h=((f*g==-1?f:(f+g)/(1+f*g))+1)/2,i=1-h,j=[a.rgb[0]*h+b.rgb[0]*i,a.rgb[1]*h+b.rgb[1]*i,a.rgb[2]*h+b.rgb[2]*i],k=a.alpha*e+b.alpha*(1-e);return new d.Color(j,k)},greyscale:function(a){return this.desaturate(a,new d.Dimension(100))},contrast:function(a,b,c,d){if(!a.rgb)return null;if("undefined"==typeof c&&(c=this.rgba(255,255,255,1)),"undefined"==typeof b&&(b=this.rgba(0,0,0,1)),b.luma()>c.luma()){var e=c;c=b,b=e}return d="undefined"==typeof d?.43:j(d),a.luma()i.value)&&(m[f]=g);else{if(k!==b&&j!==k)throw{type:"Argument",message:"incompatible types"};n[j]=m.length,m.push(g)}else Array.isArray(c[e].value)&&Array.prototype.push.apply(c,Array.prototype.slice.call(c[e].value));return 1==m.length?m[0]:(c=m.map(function(a){return a.toCSS(this.env)}).join(this.env.compress?",":", "),new d.Anonymous((a?"min":"max")+"("+c+")"))},min:function(){return this._minmax(!0,arguments)},max:function(){return this._minmax(!1,arguments)},"get-unit":function(a){return new d.Anonymous(a.unit)},argb:function(a){return new d.Anonymous(a.toARGB())},percentage:function(a){return new d.Dimension(100*a.value,"%")},color:function(a){if(a instanceof d.Quoted){var b,c=a.value;if(b=d.Color.fromKeyword(c))return b;if(/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})/.test(c))return new d.Color(c.slice(1));throw{type:"Argument",message:"argument must be a color keyword or 3/6 digit hex e.g. #FFF"}}throw{type:"Argument",message:"argument must be a string"}},iscolor:function(a){return this._isa(a,d.Color)},isnumber:function(a){return this._isa(a,d.Dimension)},isstring:function(a){return this._isa(a,d.Quoted)},iskeyword:function(a){return this._isa(a,d.Keyword)},isurl:function(a){return this._isa(a,d.URL)},ispixel:function(a){return this.isunit(a,"px")},ispercentage:function(a){return this.isunit(a,"%")},isem:function(a){return this.isunit(a,"em")},isunit:function(a,b){return a instanceof d.Dimension&&a.unit.is(b.value||b)?d.True:d.False},_isa:function(a,b){return a instanceof b?d.True:d.False},tint:function(a,b){return this.mix(this.rgb(255,255,255),a,b)},shade:function(a,b){return this.mix(this.rgb(0,0,0),a,b)},extract:function(a,b){return b=b.value-1,Array.isArray(a.value)?a.value[b]:Array(a)[b]},length:function(a){var b=Array.isArray(a.value)?a.value.length:1;return new d.Dimension(b)},"data-uri":function(b,e){if("undefined"!=typeof a)return new d.URL(e||b,this.currentFileInfo).eval(this.env);var f=b.value,g=e&&e.value,h=c("./fs"),i=c("path"),j=!1;arguments.length<2&&(g=f);var k=g.indexOf("#"),l="";if(-1!==k&&(l=g.slice(k),g=g.slice(0,k)),this.env.isPathRelative(g)&&(g=this.currentFileInfo.relativeUrls?i.join(this.currentFileInfo.currentDirectory,g):i.join(this.currentFileInfo.entryPath,g)),arguments.length<2){var m;try{m=c("mime")}catch(n){m=d._mime}f=m.lookup(g);var o=m.charsets.lookup(f);j=["US-ASCII","UTF-8"].indexOf(o)<0,j&&(f+=";base64")}else j=/;base64$/.test(f);var p=h.readFileSync(g),q=32,r=parseInt(p.length/1024,10);if(r>=q&&this.env.ieCompat!==!1)return this.env.silent||console.warn("Skipped data-uri embedding of %s because its size (%dKB) exceeds IE8-safe %dKB!",g,r,q),new d.URL(e||b,this.currentFileInfo).eval(this.env);p=j?p.toString("base64"):encodeURIComponent(p);var s='"data:'+f+","+p+l+'"';return new d.URL(new d.Anonymous(s))},"svg-gradient":function(a){function e(){throw{type:"Argument",message:"svg-gradient expects direction, start_color [start_position], [color position,]..., end_color [end_position]"}}arguments.length<3&&e();var f,g,h,i,j,k,l,m=Array.prototype.slice.call(arguments,1),n="linear",o='x="0" y="0" width="1" height="1"',p=!0,q={compress:!1},r=a.toCSS(q);switch(r){case"to bottom":f='x1="0%" y1="0%" x2="0%" y2="100%"';break;case"to right":f='x1="0%" y1="0%" x2="100%" y2="0%"';break;case"to bottom right":f='x1="0%" y1="0%" x2="100%" y2="100%"';break;case"to top right":f='x1="0%" y1="100%" x2="100%" y2="0%"';break;case"ellipse":case"ellipse at center":n="radial",f='cx="50%" cy="50%" r="75%"',o='x="-50" y="-50" width="101" height="101"';break;default:throw{type:"Argument",message:"svg-gradient direction must be 'to bottom', 'to right', 'to bottom right', 'to top right' or 'ellipse at center'"}}for(g='<'+n+'Gradient id="gradient" gradientUnits="userSpaceOnUse" '+f+">",h=0;hl?' stop-opacity="'+l+'"':"")+"/>";if(g+="',p)try{g=c("./encoder").encodeBase64(g)}catch(s){p=!1}return g="'data:image/svg+xml"+(p?";base64":"")+","+g+"'",new d.URL(new d.Anonymous(g))}},d._mime={_types:{".htm":"text/html",".html":"text/html",".gif":"image/gif",".jpg":"image/jpeg",".jpeg":"image/jpeg",".png":"image/png"},lookup:function(a){var e=c("path").extname(a),f=d._mime._types[e];if(f===b)throw new Error('Optional dependency "mime" is required for '+e);return f},charsets:{lookup:function(a){return a&&/^text\//.test(a)?"UTF-8":""}}};var l={ceil:null,floor:null,sqrt:null,abs:null,tan:"",sin:"",cos:"",atan:"rad",asin:"rad",acos:"rad"},m={multiply:function(a,b){return a*b},screen:function(a,b){return a+b-a*b},overlay:function(a,b){return a*=2,1>=a?m.multiply(a,b):m.screen(a-1,b)},softlight:function(a,b){var c=1,d=a;return b>.5&&(d=1,c=a>.25?Math.sqrt(a):((16*a-12)*a+4)*a),a-(1-2*b)*d*(c-a)},hardlight:function(a,b){return m.overlay(b,a)},difference:function(a,b){return Math.abs(a-b)},exclusion:function(a,b){return a+b-2*a*b},average:function(a,b){return(a+b)/2},negation:function(a,b){return 1-Math.abs(a+b-1)}};d.defaultFunc={eval:function(){var a=this.value_,b=this.error_;if(b)throw b;return null!=a?a?d.True:d.False:void 0},value:function(a){this.value_=a},error:function(a){this.error_=a},reset:function(){this.value_=this.error_=null}},g(),d.fround=function(a,b){var c=a&&a.numPrecision;return null==c?b:Number((b+2e-16).toFixed(c))},d.functionCall=function(a,b){this.env=a,this.currentFileInfo=b},d.functionCall.prototype=d.functions}(c("./tree")),function(a){a.colors={aliceblue:"#f0f8ff",antiquewhite:"#faebd7",aqua:"#00ffff",aquamarine:"#7fffd4",azure:"#f0ffff",beige:"#f5f5dc",bisque:"#ffe4c4",black:"#000000",blanchedalmond:"#ffebcd",blue:"#0000ff",blueviolet:"#8a2be2",brown:"#a52a2a",burlywood:"#deb887",cadetblue:"#5f9ea0",chartreuse:"#7fff00",chocolate:"#d2691e",coral:"#ff7f50",cornflowerblue:"#6495ed",cornsilk:"#fff8dc",crimson:"#dc143c",cyan:"#00ffff",darkblue:"#00008b",darkcyan:"#008b8b",darkgoldenrod:"#b8860b",darkgray:"#a9a9a9",darkgrey:"#a9a9a9",darkgreen:"#006400",darkkhaki:"#bdb76b",darkmagenta:"#8b008b",darkolivegreen:"#556b2f",darkorange:"#ff8c00",darkorchid:"#9932cc",darkred:"#8b0000",darksalmon:"#e9967a",darkseagreen:"#8fbc8f",darkslateblue:"#483d8b",darkslategray:"#2f4f4f",darkslategrey:"#2f4f4f",darkturquoise:"#00ced1",darkviolet:"#9400d3",deeppink:"#ff1493",deepskyblue:"#00bfff",dimgray:"#696969",dimgrey:"#696969",dodgerblue:"#1e90ff",firebrick:"#b22222",floralwhite:"#fffaf0",forestgreen:"#228b22",fuchsia:"#ff00ff",gainsboro:"#dcdcdc",ghostwhite:"#f8f8ff",gold:"#ffd700",goldenrod:"#daa520",gray:"#808080",grey:"#808080",green:"#008000",greenyellow:"#adff2f",honeydew:"#f0fff0",hotpink:"#ff69b4",indianred:"#cd5c5c",indigo:"#4b0082",ivory:"#fffff0",khaki:"#f0e68c",lavender:"#e6e6fa",lavenderblush:"#fff0f5",lawngreen:"#7cfc00",lemonchiffon:"#fffacd",lightblue:"#add8e6",lightcoral:"#f08080",lightcyan:"#e0ffff",lightgoldenrodyellow:"#fafad2",lightgray:"#d3d3d3",lightgrey:"#d3d3d3",lightgreen:"#90ee90",lightpink:"#ffb6c1",lightsalmon:"#ffa07a",lightseagreen:"#20b2aa",lightskyblue:"#87cefa",lightslategray:"#778899",lightslategrey:"#778899",lightsteelblue:"#b0c4de",lightyellow:"#ffffe0",lime:"#00ff00",limegreen:"#32cd32",linen:"#faf0e6",magenta:"#ff00ff",maroon:"#800000",mediumaquamarine:"#66cdaa",mediumblue:"#0000cd",mediumorchid:"#ba55d3",mediumpurple:"#9370d8",mediumseagreen:"#3cb371",mediumslateblue:"#7b68ee",mediumspringgreen:"#00fa9a",mediumturquoise:"#48d1cc",mediumvioletred:"#c71585",midnightblue:"#191970",mintcream:"#f5fffa",mistyrose:"#ffe4e1",moccasin:"#ffe4b5",navajowhite:"#ffdead",navy:"#000080",oldlace:"#fdf5e6",olive:"#808000",olivedrab:"#6b8e23",orange:"#ffa500",orangered:"#ff4500",orchid:"#da70d6",palegoldenrod:"#eee8aa",palegreen:"#98fb98",paleturquoise:"#afeeee",palevioletred:"#d87093",papayawhip:"#ffefd5",peachpuff:"#ffdab9",peru:"#cd853f",pink:"#ffc0cb",plum:"#dda0dd",powderblue:"#b0e0e6",purple:"#800080",red:"#ff0000",rosybrown:"#bc8f8f",royalblue:"#4169e1",saddlebrown:"#8b4513",salmon:"#fa8072",sandybrown:"#f4a460",seagreen:"#2e8b57",seashell:"#fff5ee",sienna:"#a0522d",silver:"#c0c0c0",skyblue:"#87ceeb",slateblue:"#6a5acd",slategray:"#708090",slategrey:"#708090",snow:"#fffafa",springgreen:"#00ff7f",steelblue:"#4682b4",tan:"#d2b48c",teal:"#008080",thistle:"#d8bfd8",tomato:"#ff6347",turquoise:"#40e0d0",violet:"#ee82ee",wheat:"#f5deb3",white:"#ffffff",whitesmoke:"#f5f5f5",yellow:"#ffff00",yellowgreen:"#9acd32"}}(c("./tree")),function(a){a.debugInfo=function(b,c,d){var e="";if(b.dumpLineNumbers&&!b.compress)switch(b.dumpLineNumbers){case"comments":e=a.debugInfo.asComment(c);break;case"mediaquery":e=a.debugInfo.asMediaQuery(c);break;case"all":e=a.debugInfo.asComment(c)+(d||"")+a.debugInfo.asMediaQuery(c)}return e},a.debugInfo.asComment=function(a){return"/* line "+a.debugInfo.lineNumber+", "+a.debugInfo.fileName+" */\n"},a.debugInfo.asMediaQuery=function(a){return"@media -sass-debug-info{filename{font-family:"+("file://"+a.debugInfo.fileName).replace(/([.:\/\\])/g,function(a){return"\\"==a&&(a="/"),"\\"+a})+"}line{font-family:\\00003"+a.debugInfo.lineNumber+"}}\n"},a.find=function(a,b){for(var c,d=0;d1?"["+a.value.map(function(a){return a.toCSS()}).join(", ")+"]":a.toCSS()},a.toCSS=function(a){var b=[];return this.genCSS(a,{add:function(a){b.push(a)},isEmpty:function(){return 0===b.length}}),b.join("")},a.outputRuleset=function(a,b,c){var d,e=c.length;if(a.tabLevel=(0|a.tabLevel)+1,a.compress){for(b.add("{"),d=0;e>d;d++)c[d].genCSS(a,b);return b.add("}"),void a.tabLevel--}var f="\n"+Array(a.tabLevel).join(" "),g=f+" ";if(e){for(b.add(" {"+g),c[0].genCSS(a,b),d=1;e>d;d++)b.add(g),c[d].genCSS(a,b);b.add(f+"}")}else b.add(" {"+f+"}");a.tabLevel--}}(c("./tree")),function(a){a.Alpha=function(a){this.value=a},a.Alpha.prototype={type:"Alpha",accept:function(a){this.value=a.visit(this.value)},eval:function(b){return this.value.eval?new a.Alpha(this.value.eval(b)):this},genCSS:function(a,b){b.add("alpha(opacity="),this.value.genCSS?this.value.genCSS(a,b):b.add(this.value),b.add(")")},toCSS:a.toCSS}}(c("../tree")),function(a){a.Anonymous=function(a,b,c,d,e){this.value=a,this.index=b,this.mapLines=d,this.currentFileInfo=c,this.rulesetLike="undefined"==typeof e?!1:e},a.Anonymous.prototype={type:"Anonymous",eval:function(){return new a.Anonymous(this.value,this.index,this.currentFileInfo,this.mapLines,this.rulesetLike)},compare:function(a){if(!a.toCSS)return-1;var b=this.toCSS(),c=a.toCSS();return b===c?0:c>b?-1:1},isRulesetLike:function(){return this.rulesetLike},genCSS:function(a,b){b.add(this.value,this.currentFileInfo,this.index,this.mapLines)},toCSS:a.toCSS}}(c("../tree")),function(a){a.Assignment=function(a,b){this.key=a,this.value=b},a.Assignment.prototype={type:"Assignment",accept:function(a){this.value=a.visit(this.value)},eval:function(b){return this.value.eval?new a.Assignment(this.key,this.value.eval(b)):this},genCSS:function(a,b){b.add(this.key+"="),this.value.genCSS?this.value.genCSS(a,b):b.add(this.value)},toCSS:a.toCSS}}(c("../tree")),function(a){a.Call=function(a,b,c,d){this.name=a,this.args=b,this.index=c,this.currentFileInfo=d},a.Call.prototype={type:"Call",accept:function(a){this.args&&(this.args=a.visitArray(this.args))},eval:function(b){var c,d,e=this.args.map(function(a){return a.eval(b)}),f=this.name.toLowerCase();if(f in a.functions)try{if(d=new a.functionCall(b,this.currentFileInfo),c=d[f].apply(d,e),null!=c)return c}catch(g){throw{type:g.type||"Runtime",message:"error evaluating function `"+this.name+"`"+(g.message?": "+g.message:""),index:this.index,filename:this.currentFileInfo.filename}}return new a.Call(this.name,e,this.index,this.currentFileInfo)},genCSS:function(a,b){b.add(this.name+"(",this.currentFileInfo,this.index);for(var c=0;ca?"0":"")+a.toString(16)}).join("")}function c(a,b){return Math.min(Math.max(a,0),b)}a.Color=function(a,b){this.rgb=Array.isArray(a)?a:6==a.length?a.match(/.{2}/g).map(function(a){return parseInt(a,16)}):a.split("").map(function(a){return parseInt(a+a,16)}),this.alpha="number"==typeof b?b:1};var d="transparent";a.Color.prototype={type:"Color",eval:function(){return this},luma:function(){var a=this.rgb[0]/255,b=this.rgb[1]/255,c=this.rgb[2]/255;return a=.03928>=a?a/12.92:Math.pow((a+.055)/1.055,2.4),b=.03928>=b?b/12.92:Math.pow((b+.055)/1.055,2.4),c=.03928>=c?c/12.92:Math.pow((c+.055)/1.055,2.4),.2126*a+.7152*b+.0722*c},genCSS:function(a,b){b.add(this.toCSS(a))},toCSS:function(b,e){var f=b&&b.compress&&!e,g=a.fround(b,this.alpha);if(1>g)return 0===g&&this.isTransparentKeyword?d:"rgba("+this.rgb.map(function(a){return c(Math.round(a),255)}).concat(c(g,1)).join(","+(f?"":" "))+")";var h=this.toRGB();if(f){var i=h.split("");i[1]===i[2]&&i[3]===i[4]&&i[5]===i[6]&&(h="#"+i[1]+i[3]+i[5])}return h},operate:function(b,c,d){for(var e=[],f=this.alpha*(1-d.alpha)+d.alpha,g=0;3>g;g++)e[g]=a.operate(b,c,this.rgb[g],d.rgb[g]);return new a.Color(e,f)},toRGB:function(){return b(this.rgb)},toHSL:function(){var a,b,c=this.rgb[0]/255,d=this.rgb[1]/255,e=this.rgb[2]/255,f=this.alpha,g=Math.max(c,d,e),h=Math.min(c,d,e),i=(g+h)/2,j=g-h;if(g===h)a=b=0;else{switch(b=i>.5?j/(2-g-h):j/(g+h),g){case c:a=(d-e)/j+(e>d?6:0);break;case d:a=(e-c)/j+2;break;case e:a=(c-d)/j+4}a/=6}return{h:360*a,s:b,l:i,a:f}},toHSV:function(){var a,b,c=this.rgb[0]/255,d=this.rgb[1]/255,e=this.rgb[2]/255,f=this.alpha,g=Math.max(c,d,e),h=Math.min(c,d,e),i=g,j=g-h;if(b=0===g?0:j/g,g===h)a=0;else{switch(g){case c:a=(d-e)/j+(e>d?6:0);break;case d:a=(e-c)/j+2;break;case e:a=(c-d)/j+4}a/=6}return{h:360*a,s:b,v:i,a:f}},toARGB:function(){return b([255*this.alpha].concat(this.rgb))},compare:function(a){return a.rgb&&a.rgb[0]===this.rgb[0]&&a.rgb[1]===this.rgb[1]&&a.rgb[2]===this.rgb[2]&&a.alpha===this.alpha?0:-1}},a.Color.fromKeyword=function(b){if(b=b.toLowerCase(),a.colors.hasOwnProperty(b))return new a.Color(a.colors[b].slice(1));if(b===d){var c=new a.Color([0,0,0],0);return c.isTransparentKeyword=!0,c}}}(c("../tree")),function(a){a.Comment=function(a,b,c,d){this.value=a,this.silent=!!b,this.currentFileInfo=d},a.Comment.prototype={type:"Comment",genCSS:function(b,c){this.debugInfo&&c.add(a.debugInfo(b,this),this.currentFileInfo,this.index),c.add(this.value.trim())},toCSS:a.toCSS,isSilent:function(a){var b=this.currentFileInfo&&this.currentFileInfo.reference&&!this.isReferenced,c=a.compress&&!this.value.match(/^\/\*!/);return this.silent||b||c},eval:function(){return this},markReferenced:function(){this.isReferenced=!0}}}(c("../tree")),function(a){a.Condition=function(a,b,c,d,e){this.op=a.trim(),this.lvalue=b,this.rvalue=c,this.index=d,this.negate=e},a.Condition.prototype={type:"Condition",accept:function(a){this.lvalue=a.visit(this.lvalue),this.rvalue=a.visit(this.rvalue)},eval:function(a){var b,c=this.lvalue.eval(a),d=this.rvalue.eval(a),e=this.index;return b=function(a){switch(a){case"and":return c&&d;case"or":return c||d;default:if(c.compare)b=c.compare(d);else{if(!d.compare)throw{type:"Type",message:"Unable to perform comparison",index:e};b=d.compare(c)}switch(b){case-1:return"<"===a||"=<"===a||"<="===a;case 0:return"="===a||">="===a||"=<"===a||"<="===a;case 1:return">"===a||">="===a}}}(this.op),this.negate?!b:b}}}(c("../tree")),function(a){a.DetachedRuleset=function(a,b){this.ruleset=a,this.frames=b},a.DetachedRuleset.prototype={type:"DetachedRuleset",accept:function(a){this.ruleset=a.visit(this.ruleset)},eval:function(b){var c=this.frames||b.frames.slice(0);return new a.DetachedRuleset(this.ruleset,c)},callEval:function(b){return this.ruleset.eval(this.frames?new a.evalEnv(b,this.frames.concat(b.frames)):b)}}}(c("../tree")),function(a){a.Dimension=function(c,d){this.value=parseFloat(c),this.unit=d&&d instanceof a.Unit?d:new a.Unit(d?[d]:b)},a.Dimension.prototype={type:"Dimension",accept:function(a){this.unit=a.visit(this.unit)},eval:function(){return this},toColor:function(){return new a.Color([this.value,this.value,this.value])},genCSS:function(b,c){if(b&&b.strictUnits&&!this.unit.isSingular())throw new Error("Multiple units in dimension. Correct the units or use the unit function. Bad unit: "+this.unit.toString());var d=a.fround(b,this.value),e=String(d);if(0!==d&&1e-6>d&&d>-1e-6&&(e=d.toFixed(20).replace(/0+$/,"")),b&&b.compress){if(0===d&&this.unit.isLength())return void c.add(e);d>0&&1>d&&(e=e.substr(1))}c.add(e),this.unit.genCSS(b,c)},toCSS:a.toCSS,operate:function(b,c,d){var e=a.operate(b,c,this.value,d.value),f=this.unit.clone();if("+"===c||"-"===c)if(0===f.numerator.length&&0===f.denominator.length)f.numerator=d.unit.numerator.slice(0),f.denominator=d.unit.denominator.slice(0);else if(0===d.unit.numerator.length&&0===f.denominator.length);else{if(d=d.convertTo(this.unit.usedUnits()),b.strictUnits&&d.unit.toString()!==f.toString())throw new Error("Incompatible units. Change the units or use the unit function. Bad units: '"+f.toString()+"' and '"+d.unit.toString()+"'.");e=a.operate(b,c,this.value,d.value)}else"*"===c?(f.numerator=f.numerator.concat(d.unit.numerator).sort(),f.denominator=f.denominator.concat(d.unit.denominator).sort(),f.cancel()):"/"===c&&(f.numerator=f.numerator.concat(d.unit.denominator).sort(),f.denominator=f.denominator.concat(d.unit.numerator).sort(),f.cancel());return new a.Dimension(e,f)},compare:function(b){if(b instanceof a.Dimension){var c,d,e,f;if(this.unit.isEmpty()||b.unit.isEmpty())c=this,d=b;else if(c=this.unify(),d=b.unify(),0!==c.unit.compare(d.unit))return-1;return e=c.value,f=d.value,f>e?-1:e>f?1:0}return-1},unify:function(){return this.convertTo({length:"px",duration:"s",angle:"rad"})},convertTo:function(b){var c,d,e,f,g,h=this.value,i=this.unit.clone(),j={};if("string"==typeof b){for(c in a.UnitConversions)a.UnitConversions[c].hasOwnProperty(b)&&(j={},j[c]=b);b=j}g=function(a,b){return e.hasOwnProperty(a)?(b?h/=e[a]/e[f]:h*=e[a]/e[f],f):a};for(d in b)b.hasOwnProperty(d)&&(f=b[d],e=a.UnitConversions[d],i.map(g));return i.cancel(),new a.Dimension(h,i)}},a.UnitConversions={length:{m:1,cm:.01,mm:.001,"in":.0254,px:.0254/96,pt:.0254/72,pc:.0254/72*12},duration:{s:1,ms:.001},angle:{rad:1/(2*Math.PI),deg:1/360,grad:.0025,turn:1}},a.Unit=function(a,b,c){this.numerator=a?a.slice(0).sort():[],this.denominator=b?b.slice(0).sort():[],this.backupUnit=c},a.Unit.prototype={type:"Unit",clone:function(){return new a.Unit(this.numerator.slice(0),this.denominator.slice(0),this.backupUnit)},genCSS:function(a,b){this.numerator.length>=1?b.add(this.numerator[0]):this.denominator.length>=1?b.add(this.denominator[0]):a&&a.strictUnits||!this.backupUnit||b.add(this.backupUnit)},toCSS:a.toCSS,toString:function(){var a,b=this.numerator.join("*");for(a=0;a0)for(b=0;e>b;b++)this.numerator.push(a);else if(0>e)for(b=0;-e>b;b++)this.denominator.push(a)}0===this.numerator.length&&0===this.denominator.length&&c&&(this.backupUnit=c),this.numerator.sort(),this.denominator.sort()}}}(c("../tree")),function(a){a.Directive=function(a,b,c,d,e,f){this.name=a,this.value=b,c&&(this.rules=c,this.rules.allowImports=!0),this.index=d,this.currentFileInfo=e,this.debugInfo=f},a.Directive.prototype={type:"Directive",accept:function(a){var b=this.value,c=this.rules;c&&(c=a.visit(c)),b&&(b=a.visit(b))},isRulesetLike:function(){return!this.isCharset()},isCharset:function(){return"@charset"===this.name},genCSS:function(b,c){var d=this.value,e=this.rules;c.add(this.name,this.currentFileInfo,this.index),d&&(c.add(" "),d.genCSS(b,c)),e?a.outputRuleset(b,c,[e]):c.add(";")},toCSS:a.toCSS,eval:function(b){var c=this.value,d=this.rules;return c&&(c=c.eval(b)),d&&(d=d.eval(b),d.root=!0),new a.Directive(this.name,c,d,this.index,this.currentFileInfo,this.debugInfo)},variable:function(b){return this.rules?a.Ruleset.prototype.variable.call(this.rules,b):void 0},find:function(){return this.rules?a.Ruleset.prototype.find.apply(this.rules,arguments):void 0},rulesets:function(){return this.rules?a.Ruleset.prototype.rulesets.apply(this.rules):void 0},markReferenced:function(){var a,b;if(this.isReferenced=!0,this.rules)for(b=this.rules.rules,a=0;a1?c=new a.Expression(this.value.map(function(a){return a.eval(b)})):1===this.value.length?(this.value[0].parens&&!this.value[0].parensInOp&&(e=!0),c=this.value[0].eval(b)):c=this,d&&b.outOfParenthesis(),this.parens&&this.parensInOp&&!b.isMathOn()&&!e&&(c=new a.Paren(c)),c},genCSS:function(a,b){for(var c=0;c0&&c.length&&""===c[0].combinator.value&&(c[0].combinator.value=" "),d=d.concat(a[b].elements);this.selfSelectors=[{elements:d}]}}}(c("../tree")),function(a){a.Import=function(a,c,d,e,f){if(this.options=d,this.index=e,this.path=a,this.features=c,this.currentFileInfo=f,this.options.less!==b||this.options.inline)this.css=!this.options.less||this.options.inline;else{var g=this.getPath();g&&/css([\?;].*)?$/.test(g)&&(this.css=!0)}},a.Import.prototype={type:"Import",accept:function(a){this.features&&(this.features=a.visit(this.features)),this.path=a.visit(this.path),!this.options.inline&&this.root&&(this.root=a.visit(this.root))},genCSS:function(a,b){this.css&&(b.add("@import ",this.currentFileInfo,this.index),this.path.genCSS(a,b),this.features&&(b.add(" "),this.features.genCSS(a,b)),b.add(";"))},toCSS:a.toCSS,getPath:function(){if(this.path instanceof a.Quoted){var c=this.path.value;return this.css!==b||/(\.[a-z]*$)|([\?;].*)$/.test(c)?c:c+".less"}return this.path instanceof a.URL?this.path.value.value:null},evalForImport:function(b){return new a.Import(this.path.eval(b),this.features,this.options,this.index,this.currentFileInfo)},evalPath:function(b){var c=this.path.eval(b),d=this.currentFileInfo&&this.currentFileInfo.rootpath;if(!(c instanceof a.URL)){if(d){var e=c.value;e&&b.isPathRelative(e)&&(c.value=d+e)}c.value=b.normalizePath(c.value)}return c},eval:function(b){var c,d=this.features&&this.features.eval(b);if(this.skip&&("function"==typeof this.skip&&(this.skip=this.skip()),this.skip))return[];if(this.options.inline){var e=new a.Anonymous(this.root,0,{filename:this.importedFilename},!0,!0);return this.features?new a.Media([e],this.features.value):[e]}if(this.css){var f=new a.Import(this.evalPath(b),d,this.options,this.index);if(!f.css&&this.error)throw this.error;return f}return c=new a.Ruleset(null,this.root.rules.slice(0)),c.evalImports(b),this.features?new a.Media(c.rules,this.features.value):c.rules}}}(c("../tree")),function(a){a.JavaScript=function(a,b,c){this.escaped=c,this.expression=a,this.index=b},a.JavaScript.prototype={type:"JavaScript",eval:function(b){var c,d=this,e={},f=this.expression.replace(/@\{([\w-]+)\}/g,function(c,e){return a.jsify(new a.Variable("@"+e,d.index).eval(b))});try{f=new Function("return ("+f+")")}catch(g){throw{message:"JavaScript evaluation error: "+g.message+" from `"+f+"`",index:this.index}}var h=b.frames[0].variables();for(var i in h)h.hasOwnProperty(i)&&(e[i.slice(1)]={value:h[i].value,toJS:function(){return this.value.eval(b).toCSS()}});try{c=f.call(e)}catch(g){throw{message:"JavaScript evaluation error: '"+g.name+": "+g.message.replace(/["]/g,"'")+"'",index:this.index}}return"number"==typeof c?new a.Dimension(c):"string"==typeof c?new a.Quoted('"'+c+'"',c,this.escaped,this.index):new a.Anonymous(Array.isArray(c)?c.join(", "):c)}}}(c("../tree")),function(a){a.Keyword=function(a){this.value=a},a.Keyword.prototype={type:"Keyword",eval:function(){return this},genCSS:function(a,b){if("%"===this.value)throw{type:"Syntax",message:"Invalid % without number"};b.add(this.value)},toCSS:a.toCSS,compare:function(b){return b instanceof a.Keyword?b.value===this.value?0:1:-1}},a.True=new a.Keyword("true"),a.False=new a.Keyword("false")}(c("../tree")),function(a){a.Media=function(b,c,d,e){this.index=d,this.currentFileInfo=e;var f=this.emptySelectors();this.features=new a.Value(c),this.rules=[new a.Ruleset(f,b)],this.rules[0].allowImports=!0},a.Media.prototype={type:"Media",accept:function(a){this.features&&(this.features=a.visit(this.features)),this.rules&&(this.rules=a.visitArray(this.rules))},genCSS:function(b,c){c.add("@media ",this.currentFileInfo,this.index),this.features.genCSS(b,c),a.outputRuleset(b,c,this.rules)},toCSS:a.toCSS,eval:function(b){b.mediaBlocks||(b.mediaBlocks=[],b.mediaPath=[]);var c=new a.Media(null,[],this.index,this.currentFileInfo);this.debugInfo&&(this.rules[0].debugInfo=this.debugInfo,c.debugInfo=this.debugInfo);var d=!1;b.strictMath||(d=!0,b.strictMath=!0);try{c.features=this.features.eval(b)}finally{d&&(b.strictMath=!1)}return b.mediaPath.push(c),b.mediaBlocks.push(c),b.frames.unshift(this.rules[0]),c.rules=[this.rules[0].eval(b)],b.frames.shift(),b.mediaPath.pop(),0===b.mediaPath.length?c.evalTop(b):c.evalNested(b)},variable:function(b){return a.Ruleset.prototype.variable.call(this.rules[0],b)},find:function(){return a.Ruleset.prototype.find.apply(this.rules[0],arguments)},rulesets:function(){return a.Ruleset.prototype.rulesets.apply(this.rules[0])},emptySelectors:function(){var b=new a.Element("","&",this.index,this.currentFileInfo),c=[new a.Selector([b],null,null,this.index,this.currentFileInfo)];return c[0].mediaEmpty=!0,c},markReferenced:function(){var a,b=this.rules[0].rules;for(this.rules[0].markReferenced(),this.isReferenced=!0,a=0;a1){var d=this.emptySelectors();c=new a.Ruleset(d,b.mediaBlocks),c.multiMedia=!0}return delete b.mediaBlocks,delete b.mediaPath,c},evalNested:function(b){var c,d,e=b.mediaPath.concat([this]);for(c=0;c0;c--)b.splice(c,0,new a.Anonymous("and"));return new a.Expression(b)})),new a.Ruleset([],[])},permute:function(a){if(0===a.length)return[]; +if(1===a.length)return a[0];for(var b=[],c=this.permute(a.slice(1)),d=0;d0){for(j=!0,g=0;gh;h++)t.value(h),s[h]=d.matchCondition(e,b);(s[0]||s[1])&&(s[0]!=s[1]&&(l.group=s[1]?v:w),r.push(l))}else r.push(l);q=!0}}for(t.reset(),n=[0,0,0],g=0;g0)m=w;else if(m=v,n[v]+n[w]>1)throw{type:"Runtime",message:"Ambiguous use of `default()` found when matching for `"+this.format(e)+"`",index:this.index,filename:this.currentFileInfo.filename};for(g=0;gh;h++)if(g=d[h],k=g&&g.name){for(l=!1,i=0;ii;i++)f.push(d[i].value.eval(b));n.prependRule(new a.Rule(k,new a.Expression(f).eval(b)))}else{if(j=g&&g.value)j=j.eval(b);else{if(!o[h].value)throw{type:"Runtime",message:"wrong number of arguments for "+this.name+" ("+p+" for "+this.arity+")"};j=o[h].value.eval(c),n.resetCache()}n.prependRule(new a.Rule(k,j)),e[h]=j}if(o[h].variadic&&d)for(i=m;p>i;i++)e[i]=d[i].value.eval(b);m++}return n},eval:function(b){return new a.mixin.Definition(this.name,this.params,this.rules,this.condition,this.variadic,this.frames||b.frames.slice(0))},evalCall:function(b,c,d){var e,f,g=[],h=this.frames?this.frames.concat(b.frames):b.frames,i=this.evalParams(b,new a.evalEnv(b,h),c,g);return i.prependRule(new a.Rule("@arguments",new a.Expression(g).eval(b))),e=this.rules.slice(0),f=new a.Ruleset(null,e),f.originalRuleset=this,f=f.eval(new a.evalEnv(b,[this,i].concat(h))),d&&(f=this.parent.makeImportant.apply(f)),f},matchCondition:function(b,c){return this.condition&&!this.condition.eval(new a.evalEnv(c,[this.evalParams(c,new a.evalEnv(c,this.frames?this.frames.concat(c.frames):c.frames),b,[])].concat(this.frames).concat(c.frames)))?!1:!0},matchArgs:function(a,b){var c,d=a&&a.length||0;if(this.variadic){if(dthis.params.length)return!1}c=Math.min(d,this.arity);for(var e=0;c>e;e++)if(!this.params[e].name&&!this.params[e].variadic&&a[e].value.eval(b).toCSS()!=this.params[e].value.eval(b).toCSS())return!1;return!0}}}(c("../tree")),function(a){a.Negative=function(a){this.value=a},a.Negative.prototype={type:"Negative",accept:function(a){this.value=a.visit(this.value)},genCSS:function(a,b){b.add("-"),this.value.genCSS(a,b)},toCSS:a.toCSS,eval:function(b){return b.isMathOn()?new a.Operation("*",[new a.Dimension(-1),this.value]).eval(b):new a.Negative(this.value.eval(b))}}}(c("../tree")),function(a){a.Operation=function(a,b,c){this.op=a.trim(),this.operands=b,this.isSpaced=c},a.Operation.prototype={type:"Operation",accept:function(a){this.operands=a.visit(this.operands)},eval:function(b){var c=this.operands[0].eval(b),d=this.operands[1].eval(b);if(b.isMathOn()){if(c instanceof a.Dimension&&d instanceof a.Color&&(c=c.toColor()),d instanceof a.Dimension&&c instanceof a.Color&&(d=d.toColor()),!c.operate)throw{type:"Operation",message:"Operation on an invalid type"};return c.operate(b,this.op,d)}return new a.Operation(this.op,[c,d],this.isSpaced)},genCSS:function(a,b){this.operands[0].genCSS(a,b),this.isSpaced&&b.add(" "),b.add(this.op),this.isSpaced&&b.add(" "),this.operands[1].genCSS(a,b)},toCSS:a.toCSS},a.operate=function(a,b,c,d){switch(b){case"+":return c+d;case"-":return c-d;case"*":return c*d;case"/":return c/d}}}(c("../tree")),function(a){a.Paren=function(a){this.value=a},a.Paren.prototype={type:"Paren",accept:function(a){this.value=a.visit(this.value)},genCSS:function(a,b){b.add("("),this.value.genCSS(a,b),b.add(")")},toCSS:a.toCSS,eval:function(b){return new a.Paren(this.value.eval(b))}}}(c("../tree")),function(a){a.Quoted=function(a,b,c,d,e){this.escaped=c,this.value=b||"",this.quote=a.charAt(0),this.index=d,this.currentFileInfo=e},a.Quoted.prototype={type:"Quoted",genCSS:function(a,b){this.escaped||b.add(this.quote,this.currentFileInfo,this.index),b.add(this.value),this.escaped||b.add(this.quote)},toCSS:a.toCSS,eval:function(b){var c=this,d=this.value.replace(/`([^`]+)`/g,function(d,e){return new a.JavaScript(e,c.index,!0).eval(b).value}).replace(/@\{([\w-]+)\}/g,function(d,e){var f=new a.Variable("@"+e,c.index,c.currentFileInfo).eval(b,!0);return f instanceof a.Quoted?f.value:f.toCSS()});return new a.Quoted(this.quote+d+this.quote,d,this.escaped,this.index,this.currentFileInfo)},compare:function(a){if(!a.toCSS)return-1;var b,c;return"Quoted"!==a.type||this.escaped||a.escaped?(b=this.toCSS(),c=a.toCSS()):(b=a.value,c=this.value),b===c?0:c>b?-1:1}}}(c("../tree")),function(a){function c(a,b){var c,d="",e=b.length,f={add:function(a){d+=a}};for(c=0;e>c;c++)b[c].eval(a).genCSS(a,f);return d}a.Rule=function(c,d,e,f,g,h,i,j){this.name=c,this.value=d instanceof a.Value||d instanceof a.Ruleset?d:new a.Value([d]),this.important=e?" "+e.trim():"",this.merge=f,this.index=g,this.currentFileInfo=h,this.inline=i||!1,this.variable=j!==b?j:c.charAt&&"@"===c.charAt(0)},a.Rule.prototype={type:"Rule",accept:function(a){this.value=a.visit(this.value)},genCSS:function(a,b){b.add(this.name+(a.compress?":":": "),this.currentFileInfo,this.index);try{this.value.genCSS(a,b)}catch(c){throw c.index=this.index,c.filename=this.currentFileInfo.filename,c}b.add(this.important+(this.inline||a.lastRule&&a.compress?"":";"),this.currentFileInfo,this.index)},toCSS:a.toCSS,eval:function(b){var d,e=!1,f=this.name,g=this.variable;"string"!=typeof f&&(f=1===f.length&&f[0]instanceof a.Keyword?f[0].value:c(b,f),g=!1),"font"!==f||b.strictMath||(e=!0,b.strictMath=!0);try{if(d=this.value.eval(b),!this.variable&&"DetachedRuleset"===d.type)throw{message:"Rulesets cannot be evaluated on a property.",index:this.index,filename:this.currentFileInfo.filename};return new a.Rule(f,d,this.important,this.merge,this.index,this.currentFileInfo,this.inline,g)}catch(h){throw"number"!=typeof h.index&&(h.index=this.index,h.filename=this.currentFileInfo.filename),h}finally{e&&(b.strictMath=!1)}},makeImportant:function(){return new a.Rule(this.name,this.value,"!important",this.merge,this.index,this.currentFileInfo,this.inline)}}}(c("../tree")),function(a){a.RulesetCall=function(a){this.variable=a},a.RulesetCall.prototype={type:"RulesetCall",accept:function(){},eval:function(b){var c=new a.Variable(this.variable).eval(b);return c.callEval(b)}}}(c("../tree")),function(a){a.Ruleset=function(a,b,c){this.selectors=a,this.rules=b,this._lookups={},this.strictImports=c},a.Ruleset.prototype={type:"Ruleset",accept:function(a){this.paths?a.visitArray(this.paths,!0):this.selectors&&(this.selectors=a.visitArray(this.selectors)),this.rules&&this.rules.length&&(this.rules=a.visitArray(this.rules))},eval:function(b){var c,d,e,f,g=this.selectors,h=a.defaultFunc,i=!1;if(g&&(d=g.length)){for(c=[],h.error({type:"Syntax",message:"it is currently only allowed in parametric mixin guards,"}),f=0;d>f;f++)e=g[f].eval(b),c.push(e),e.evaldCondition&&(i=!0);h.reset()}else i=!0;var j,k,l=this.rules?this.rules.slice(0):null,m=new a.Ruleset(c,l,this.strictImports);m.originalRuleset=this,m.root=this.root,m.firstRoot=this.firstRoot,m.allowImports=this.allowImports,this.debugInfo&&(m.debugInfo=this.debugInfo),i||(l.length=0);var n=b.frames;n.unshift(m);var o=b.selectors;o||(b.selectors=o=[]),o.unshift(this.selectors),(m.root||m.allowImports||!m.strictImports)&&m.evalImports(b);var p=m.rules,q=p?p.length:0;for(f=0;q>f;f++)(p[f]instanceof a.mixin.Definition||p[f]instanceof a.DetachedRuleset)&&(p[f]=p[f].eval(b));var r=b.mediaBlocks&&b.mediaBlocks.length||0;for(f=0;q>f;f++)p[f]instanceof a.mixin.Call?(l=p[f].eval(b).filter(function(b){return b instanceof a.Rule&&b.variable?!m.variable(b.name):!0}),p.splice.apply(p,[f,1].concat(l)),q+=l.length-1,f+=l.length-1,m.resetCache()):p[f]instanceof a.RulesetCall&&(l=p[f].eval(b).rules.filter(function(b){return b instanceof a.Rule&&b.variable?!1:!0}),p.splice.apply(p,[f,1].concat(l)),q+=l.length-1,f+=l.length-1,m.resetCache());for(f=0;fb;b++)c=g[b],(c instanceof d||c instanceof e)&&f.push(c);return f},prependRule:function(a){var b=this.rules;b?b.unshift(a):this.rules=[a]},find:function(b,c){c=c||this;var d,e=[],f=b.toCSS();return f in this._lookups?this._lookups[f]:(this.rulesets().forEach(function(f){if(f!==c)for(var g=0;gd?Array.prototype.push.apply(e,f.find(new a.Selector(b.elements.slice(d)),c)):e.push(f);break}}),this._lookups[f]=e,e)},genCSS:function(b,c){function d(b,c){return b.rules?!0:b instanceof a.Media||c&&b instanceof a.Comment?!0:b instanceof a.Directive||b instanceof a.Anonymous?b.isRulesetLike():!1}var e,f,g,h,i,j,k=[],l=[],m=[];b.tabLevel=b.tabLevel||0,this.root||b.tabLevel++;var n,o=b.compress?"":Array(b.tabLevel+1).join(" "),p=b.compress?"":Array(b.tabLevel).join(" ");for(e=0;ee;e++)if(j=r[e],q=j.length)for(e>0&&c.add(n),b.firstSelector=!0,j[0].genCSS(b,c),b.firstSelector=!1,f=1;q>f;f++)j[f].genCSS(b,c);c.add((b.compress?"{":" {\n")+o)}for(e=0;ee;e++)n&&c.add(n),m[e].genCSS(b,c);c.isEmpty()||b.compress||!this.firstRoot||c.add("\n")},toCSS:a.toCSS,markReferenced:function(){if(this.selectors)for(var a=0;a0&&this.mergeElementsOnToSelectors(r,i),f=0;f0&&(k[0].elements=k[0].elements.slice(0),k[0].elements.push(new a.Element(j.combinator,"",j.index,j.currentFileInfo))),s.push(k);else for(g=0;g0?(m=k.slice(0),q=m.pop(),o=d.createDerived(q.elements.slice(0)),p=!1):o=d.createDerived([]),l.length>1&&(n=n.concat(l.slice(1))),l.length>0&&(p=!1,o.elements.push(new a.Element(j.combinator,l[0].elements[0].value,j.index,j.currentFileInfo)),o.elements=o.elements.concat(l[0].elements.slice(1))),p||m.push(o),m=m.concat(n),s.push(m);i=s,r=[]}for(r.length>0&&this.mergeElementsOnToSelectors(r,i),e=0;e0&&b.push(i[e])}else if(c.length>0)for(e=0;e0?e[e.length-1]=e[e.length-1].createDerived(e[e.length-1].elements.concat(b)):e.push(new a.Selector(b))}}}(c("../tree")),function(a){a.Selector=function(a,b,c,d,e,f){this.elements=a,this.extendList=b,this.condition=c,this.currentFileInfo=e||{},this.isReferenced=f,c||(this.evaldCondition=!0)},a.Selector.prototype={type:"Selector",accept:function(a){this.elements&&(this.elements=a.visitArray(this.elements)),this.extendList&&(this.extendList=a.visitArray(this.extendList)),this.condition&&(this.condition=a.visit(this.condition))},createDerived:function(b,c,d){d=null!=d?d:this.evaldCondition;var e=new a.Selector(b,c||this.extendList,null,this.index,this.currentFileInfo,this.isReferenced);return e.evaldCondition=d,e.mediaEmpty=this.mediaEmpty,e},match:function(a){var b,c,d=this.elements,e=d.length;if(a.CacheElements(),b=a._elements.length,0===b||b>e)return 0;for(c=0;b>c;c++)if(d[c].value!==a._elements[c])return 0;return b},CacheElements:function(){var a,b,c,d="";if(!this._elements){for(a=this.elements.length,c=0;a>c;c++)if(b=this.elements[c],d+=b.combinator.value,b.value.value){if("string"!=typeof b.value.value){d="";break}d+=b.value.value}else d+=b.value;this._elements=d.match(/[,&#\*\.\w-]([\w-]|(\\.))*/g),this._elements?"&"===this._elements[0]&&this._elements.shift():this._elements=[]}},isJustParentSelector:function(){return!this.mediaEmpty&&1===this.elements.length&&"&"===this.elements[0].value&&(" "===this.elements[0].combinator.value||""===this.elements[0].combinator.value)},eval:function(a){var b=this.condition&&this.condition.eval(a),c=this.elements,d=this.extendList;return c=c&&c.map(function(b){return b.eval(a)}),d=d&&d.map(function(b){return b.eval(a)}),this.createDerived(c,d,b)},genCSS:function(a,b){var c,d;if(a&&a.firstSelector||""!==this.elements[0].combinator.value||b.add(" ",this.currentFileInfo,this.index),!this._css)for(c=0;cc;c++)this.visit(a[c]);return a}var e=[];for(c=0;d>c;c++){var f=this.visit(a[c]);f.splice?f.length&&this.flatten(f,e):e.push(f)}return e},flatten:function(a,b){b||(b=[]);var c,d,e,f,g,h;for(d=0,c=a.length;c>d;d++)if(e=a[d],e.splice)for(g=0,f=e.length;f>g;g++)h=e[g],h.splice?h.length&&this.flatten(h,b):b.push(h);else b.push(e);return b}}}(c("./tree")),function(a){a.importVisitor=function(b,c,d,e,f){if(this._visitor=new a.visitor(this),this._importer=b,this._finish=c,this.env=d||new a.evalEnv,this.importCount=0,this.onceFileDetectionMap=e||{},this.recursionDetector={},f)for(var g in f)f.hasOwnProperty(g)&&(this.recursionDetector[g]=!0)},a.importVisitor.prototype={isReplacing:!0,run:function(a){var b;try{this._visitor.visit(a)}catch(c){b=c}this.isFinished=!0,0===this.importCount&&this._finish(b)},visitImport:function(b,c){var d,e=this,f=b.options.inline;if(!b.css||f){try{d=b.evalForImport(this.env)}catch(g){g.filename||(g.index=b.index,g.filename=b.currentFileInfo.filename),b.css=!0,b.error=g}if(d&&(!d.css||f)){b=d,this.importCount++;var h=new a.evalEnv(this.env,this.env.frames.slice(0));b.options.multiple&&(h.importMultiple=!0),this._importer.push(b.getPath(),b.currentFileInfo,b.options,function(c,d,g,i){c&&!c.filename&&(c.index=b.index,c.filename=b.currentFileInfo.filename);var j=g||i in e.recursionDetector;h.importMultiple||(b.skip=j?!0:function(){return i in e.onceFileDetectionMap?!0:(e.onceFileDetectionMap[i]=!0,!1)});var k=function(a){e.importCount--,0===e.importCount&&e.isFinished&&e._finish(a)};return!d||(b.root=d,b.importedFilename=i,f||!h.importMultiple&&j)?void k():(e.recursionDetector[i]=!0,void new a.importVisitor(e._importer,k,h,e.onceFileDetectionMap,e.recursionDetector).run(d))})}}return c.visitDeeper=!1,b},visitRule:function(a,b){return b.visitDeeper=!1,a},visitDirective:function(a){return this.env.frames.unshift(a),a},visitDirectiveOut:function(){this.env.frames.shift()},visitMixinDefinition:function(a){return this.env.frames.unshift(a),a},visitMixinDefinitionOut:function(){this.env.frames.shift()},visitRuleset:function(a){return this.env.frames.unshift(a),a},visitRulesetOut:function(){this.env.frames.shift()},visitMedia:function(a){return this.env.frames.unshift(a.rules[0]),a},visitMediaOut:function(){this.env.frames.shift()}}}(c("./tree")),function(a){a.joinSelectorVisitor=function(){this.contexts=[[]],this._visitor=new a.visitor(this)},a.joinSelectorVisitor.prototype={run:function(a){return this._visitor.visit(a)},visitRule:function(a,b){b.visitDeeper=!1},visitMixinDefinition:function(a,b){b.visitDeeper=!1},visitRuleset:function(a){var b,c=this.contexts[this.contexts.length-1],d=[];this.contexts.push(d),a.root||(b=a.selectors,b&&(b=b.filter(function(a){return a.getIsOutput()}),a.selectors=b.length?b:b=null,b&&a.joinSelectors(d,c,b)),b||(a.rules=null),a.paths=d)},visitRulesetOut:function(){this.contexts.length=this.contexts.length-1},visitMedia:function(a){var b=this.contexts[this.contexts.length-1];a.rules[0].root=0===b.length||b[0].multiMedia}}}(c("./tree")),function(a){a.toCSSVisitor=function(b){this._visitor=new a.visitor(this),this._env=b},a.toCSSVisitor.prototype={isReplacing:!0,run:function(a){return this._visitor.visit(a)},visitRule:function(a){return a.variable?[]:a},visitMixinDefinition:function(a){return a.frames=[],[]},visitExtend:function(){return[]},visitComment:function(a){return a.isSilent(this._env)?[]:a},visitMedia:function(a,b){return a.accept(this._visitor),b.visitDeeper=!1,a.rules.length?a:[]},visitDirective:function(b){if(b.currentFileInfo.reference&&!b.isReferenced)return[];if("@charset"===b.name){if(this.charset){if(b.debugInfo){var c=new a.Comment("/* "+b.toCSS(this._env).replace(/\n/g,"")+" */\n");return c.debugInfo=b.debugInfo,this._visitor.visit(c)}return[]}this.charset=!0}return b.rules&&b.rules.rules&&this._mergeRules(b.rules.rules),b},checkPropertiesInRoot:function(b){for(var c,d=0;d0)&&e.splice(0,0,b);else{b.paths&&(b.paths=b.paths.filter(function(b){var c;for(" "===b[0].elements[0].combinator.value&&(b[0].elements[0].combinator=new a.Combinator("")),c=0;ch;)d=f[h],d&&d.rules?(e.push(this._visitor.visit(d)),f.splice(h,1),g--):h++;g>0?b.accept(this._visitor):b.rules=null,c.visitDeeper=!1,f=b.rules,f&&(this._mergeRules(f),f=b.rules),f&&(this._removeDuplicateRules(f),f=b.rules),f&&f.length>0&&b.paths.length>0&&e.splice(0,0,b)}return 1===e.length?e[0]:e},_removeDuplicateRules:function(b){if(b){var c,d,e,f={};for(e=b.length-1;e>=0;e--)if(d=b[e],d instanceof a.Rule)if(f[d.name]){c=f[d.name],c instanceof a.Rule&&(c=f[d.name]=[f[d.name].toCSS(this._env)]);var g=d.toCSS(this._env);-1!==c.indexOf(g)?b.splice(e,1):c.push(g)}else f[d.name]=d}},_mergeRules:function(b){if(b){for(var c,d,e,f={},g=0;g1){d=c[0];var h=[],i=[];c.map(function(a){"+"===a.merge&&(i.length>0&&h.push(e(i)),i=[]),i.push(a)}),h.push(e(i)),d.value=g(h)}})}}}}(c("./tree")),function(a){a.extendFinderVisitor=function(){this._visitor=new a.visitor(this),this.contexts=[],this.allExtendsStack=[[]]},a.extendFinderVisitor.prototype={run:function(a){return a=this._visitor.visit(a),a.allExtends=this.allExtendsStack[0],a},visitRule:function(a,b){b.visitDeeper=!1},visitMixinDefinition:function(a,b){b.visitDeeper=!1},visitRuleset:function(b){if(!b.root){var c,d,e,f,g=[],h=b.rules,i=h?h.length:0;for(c=0;i>c;c++)b.rules[c]instanceof a.Extend&&(g.push(h[c]),b.extendOnEveryPath=!0);var j=b.paths;for(c=0;c=0||(i=[k.selfSelectors[0]],g=n.findMatch(j,i),g.length&&j.selfSelectors.forEach(function(b){h=n.extendSelector(g,i,b),l=new a.Extend(k.selector,k.option,0),l.selfSelectors=h,h[h.length-1].extendList=[l],m.push(l),l.ruleset=k.ruleset,l.parent_ids=l.parent_ids.concat(k.parent_ids,j.parent_ids),k.firstExtendOnThisSelectorPath&&(l.firstExtendOnThisSelectorPath=!0,k.ruleset.paths.push(h))}));if(m.length){if(this.extendChainCount++,d>100){var o="{unable to calculate}",p="{unable to calculate}";try{o=m[0].selfSelectors[0].toCSS(),p=m[0].selector.toCSS()}catch(q){}throw{message:"extend circular reference detected. One of the circular extends is currently:"+o+":extend("+p+")"}}return m.concat(n.doExtendChaining(m,c,d+1))}return m},visitRule:function(a,b){b.visitDeeper=!1},visitMixinDefinition:function(a,b){b.visitDeeper=!1},visitSelector:function(a,b){b.visitDeeper=!1},visitRuleset:function(a){if(!a.root){var b,c,d,e,f=this.allExtendsStack[this.allExtendsStack.length-1],g=[],h=this;for(d=0;d0&&k[i.matched].combinator.value!==g?i=null:i.matched++,i&&(i.finished=i.matched===k.length,i.finished&&!a.allowAfter&&(e+1j&&k>0&&(l[l.length-1].elements=l[l.length-1].elements.concat(c[j].elements.slice(k)),k=0,j++),i=f.elements.slice(k,h.index).concat([g]).concat(d.elements.slice(1)),j===h.pathIndex&&e>0?l[l.length-1].elements=l[l.length-1].elements.concat(i):(l=l.concat(c.slice(j,h.pathIndex)),l.push(new a.Selector(i))),j=h.endPathIndex,k=h.endPathElementIndex,k>=c[j].elements.length&&(k=0,j++);return j0&&(l[l.length-1].elements=l[l.length-1].elements.concat(c[j].elements.slice(k)),j++),l=l.concat(c.slice(j,c.length))},visitRulesetOut:function(){},visitMedia:function(a){var b=a.allExtends.concat(this.allExtendsStack[this.allExtendsStack.length-1]);b=b.concat(this.doExtendChaining(b,a.allExtends)),this.allExtendsStack.push(b)},visitMediaOut:function(){this.allExtendsStack.length=this.allExtendsStack.length-1},visitDirective:function(a){var b=a.allExtends.concat(this.allExtendsStack[this.allExtendsStack.length-1]);b=b.concat(this.doExtendChaining(b,a.allExtends)),this.allExtendsStack.push(b)},visitDirectiveOut:function(){this.allExtendsStack.length=this.allExtendsStack.length-1}}}(c("./tree")),function(a){a.sourceMapOutput=function(a){this._css=[],this._rootNode=a.rootNode,this._writeSourceMap=a.writeSourceMap,this._contentsMap=a.contentsMap,this._contentsIgnoredCharsMap=a.contentsIgnoredCharsMap,this._sourceMapFilename=a.sourceMapFilename,this._outputFilename=a.outputFilename,this._sourceMapURL=a.sourceMapURL,a.sourceMapBasepath&&(this._sourceMapBasepath=a.sourceMapBasepath.replace(/\\/g,"/")),this._sourceMapRootpath=a.sourceMapRootpath,this._outputSourceFiles=a.outputSourceFiles,this._sourceMapGeneratorConstructor=a.sourceMapGenerator||c("source-map").SourceMapGenerator,this._sourceMapRootpath&&"/"!==this._sourceMapRootpath.charAt(this._sourceMapRootpath.length-1)&&(this._sourceMapRootpath+="/"),this._lineNumber=0,this._column=0},a.sourceMapOutput.prototype.normalizeFilename=function(a){return a=a.replace(/\\/g,"/"),this._sourceMapBasepath&&0===a.indexOf(this._sourceMapBasepath)&&(a=a.substring(this._sourceMapBasepath.length),("\\"===a.charAt(0)||"/"===a.charAt(0))&&(a=a.substring(1))),(this._sourceMapRootpath||"")+a},a.sourceMapOutput.prototype.add=function(a,b,c,d){if(a){var e,f,g,h,i;if(b){var j=this._contentsMap[b.filename];this._contentsIgnoredCharsMap[b.filename]&&(c-=this._contentsIgnoredCharsMap[b.filename],0>c&&(c=0),j=j.slice(this._contentsIgnoredCharsMap[b.filename])),j=j.substring(0,c),f=j.split("\n"),h=f[f.length-1]}if(e=a.split("\n"),g=e[e.length-1],b)if(d)for(i=0;i0){var e,f=JSON.stringify(this._sourceMapGenerator.toJSON());this._sourceMapURL?e=this._sourceMapURL:this._sourceMapFilename&&(e=this.normalizeFilename(this._sourceMapFilename)),this._writeSourceMap?this._writeSourceMap(f):e="data:application/json;base64,"+c("./encoder.js").encodeBase64(f),e&&this._css.push("/*# sourceMappingURL="+e+" */")}return this._css.join("")}}(c("./tree"));var y=/^(file|chrome(-extension)?|resource|qrc|app):/.test(location.protocol);w.env=w.env||("127.0.0.1"==location.hostname||"0.0.0.0"==location.hostname||"localhost"==location.hostname||location.port&&location.port.length>0||y?"development":"production");var z={debug:3,info:2,errors:1,none:0};if(w.logLevel="undefined"!=typeof w.logLevel?w.logLevel:"development"===w.env?z.debug:z.errors,w.async=w.async||!1,w.fileAsync=w.fileAsync||!1,w.poll=w.poll||(y?1e3:1500),w.functions)for(var A in w.functions)w.functions.hasOwnProperty(A)&&(w.tree.functions[A]=w.functions[A]);var B=/!dumpLineNumbers:(comments|mediaquery|all)/.exec(location.hash);B&&(w.dumpLineNumbers=B[1]);var C=/^text\/(x-)?less$/,D=null,E={};if(w.watch=function(){return w.watchMode||(w.env="development",v()),this.watchMode=!0,!0},w.unwatch=function(){return clearInterval(w.watchTimer),this.watchMode=!1,!1},/!watch/.test(location.hash)&&w.watch(),"development"!=w.env)try{D="undefined"==typeof a.localStorage?null:a.localStorage}catch(F){}var G=document.getElementsByTagName("link");w.sheets=[];for(var H=0;H + diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000..56ce9b8 --- /dev/null +++ b/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,93 @@ + + + + + COOKIE + + + + default + /css/* + /webjars/* + *.css + *.js + + + + /contact.jsp + + + + 403 + /access-denied.jsp + + + 500 + /servlet-exception.jsp + + + 503 + /servlet-exception.jsp + + + + + Public access + *.css + *.js + /css/* + /webjars/* + /logout + /access-denied.jsp + /contact.jsp + /passwordreset + /servlet-exception.jsp + + + + + + Profile Area + /profile + /profile/* + / + + + admins + login + + + + + + Administrative Area + /users + /users/* + /groups + /groups/* + + + admins + + + + + FORM + Administration Area + + /login.jsp + /loginfail.jsp + + + + + admins + + + + login + + + \ No newline at end of file diff --git a/src/main/webapp/access-denied.jsp b/src/main/webapp/access-denied.jsp new file mode 100644 index 0000000..9d46b2b --- /dev/null +++ b/src/main/webapp/access-denied.jsp @@ -0,0 +1,20 @@ +<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> + + + + + + + + + +
    +

    +

    + +
    + + + diff --git a/src/main/webapp/contact.jsp b/src/main/webapp/contact.jsp new file mode 100644 index 0000000..29fdcad --- /dev/null +++ b/src/main/webapp/contact.jsp @@ -0,0 +1,19 @@ +<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> + + + + + + + + + + + + diff --git a/src/main/webapp/css/style.css b/src/main/webapp/css/style.css new file mode 100644 index 0000000..c2e2eab --- /dev/null +++ b/src/main/webapp/css/style.css @@ -0,0 +1,13 @@ +body { padding-top: 70px; } +.additional-space-top { margin-top:12px; } +.additional-space-bottom { margin-bottom:12px; } +.alert { + padding: 8px 35px 8px 14px; + margin-bottom: 18px; + text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); + background-color: #fcf8e3; + border: 1px solid #fbeed5; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; +} \ No newline at end of file diff --git a/src/main/webapp/group.jsp b/src/main/webapp/group.jsp new file mode 100644 index 0000000..215b3b6 --- /dev/null +++ b/src/main/webapp/group.jsp @@ -0,0 +1,73 @@ +<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> + + + + + + + +
    +

    ${group.name}

    + +
    + × + ${errormessage} +
    +
    + +
    +
    + + +
    +
    +
    + +
    +
    +

    +
    +
    +
    + +
     
    +
    +
    +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    + + + + +
    +
    +
    +
    + + + +
    + + + +
    +
    +
    + + + diff --git a/src/main/webapp/groups.jsp b/src/main/webapp/groups.jsp new file mode 100644 index 0000000..c3af4fa --- /dev/null +++ b/src/main/webapp/groups.jsp @@ -0,0 +1,38 @@ +<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> + + + + + + +
    +

    +
    + + +
    + + + + + + + + + + + +
    ${groupentry.value.name} + +
    +
    + + + diff --git a/src/main/webapp/login.jsp b/src/main/webapp/login.jsp new file mode 100644 index 0000000..2e1ab7e --- /dev/null +++ b/src/main/webapp/login.jsp @@ -0,0 +1,44 @@ +<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> + + + + + + + + + +
    +

    +
    +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    + +
    + +
    +
    +
    +
    +
    + + + diff --git a/src/main/webapp/loginfail.jsp b/src/main/webapp/loginfail.jsp new file mode 100644 index 0000000..43f6182 --- /dev/null +++ b/src/main/webapp/loginfail.jsp @@ -0,0 +1,48 @@ +<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> + + + + + + + + + +
    +

    +
    + × + +
    +
    +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    + +
    + +
    +
    +
    +
    +
    + + + diff --git a/src/main/webapp/new-password.jsp b/src/main/webapp/new-password.jsp new file mode 100644 index 0000000..cd5ae91 --- /dev/null +++ b/src/main/webapp/new-password.jsp @@ -0,0 +1,63 @@ +<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> + + + + + + + +
    +

    ${user.firstname} ${user.lastname}

    + +
    + × + ${errormessage} +
    +
    + +
    + × + ${successmessage} +
    +
    +
     
    +
    +
    +
    +
    + +
    + + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    + +
    +
    +
    +
    + + + diff --git a/src/main/webapp/reset-password.jsp b/src/main/webapp/reset-password.jsp new file mode 100644 index 0000000..5b03ccf --- /dev/null +++ b/src/main/webapp/reset-password.jsp @@ -0,0 +1,37 @@ +<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> + + + + + + + +
    +

    + +
    + × + ${errormessage} +
    +
    +
    +
    +
    +
    + +
    + +
    +
    +
    + +
    +
    +
    + + + diff --git a/src/main/webapp/servlet-exception.jsp b/src/main/webapp/servlet-exception.jsp new file mode 100644 index 0000000..86828d2 --- /dev/null +++ b/src/main/webapp/servlet-exception.jsp @@ -0,0 +1,24 @@ +<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> + + + + + + + + + +
    +

    +

    ${servletexception.message}

    +

    + +
    +
    +

    +
    + + + diff --git a/src/main/webapp/template/empty-navbar.jsp b/src/main/webapp/template/empty-navbar.jsp new file mode 100644 index 0000000..0d00a03 --- /dev/null +++ b/src/main/webapp/template/empty-navbar.jsp @@ -0,0 +1,27 @@ +<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> + + diff --git a/src/main/webapp/template/footer.jsp b/src/main/webapp/template/footer.jsp new file mode 100644 index 0000000..2e40a81 --- /dev/null +++ b/src/main/webapp/template/footer.jsp @@ -0,0 +1,4 @@ +<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> + + diff --git a/src/main/webapp/template/header.jsp b/src/main/webapp/template/header.jsp new file mode 100644 index 0000000..9fbd730 --- /dev/null +++ b/src/main/webapp/template/header.jsp @@ -0,0 +1,15 @@ +<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> + + + + + + + + <fmt:message key="navbar.title"/> + + + + diff --git a/src/main/webapp/template/navbar.jsp b/src/main/webapp/template/navbar.jsp new file mode 100644 index 0000000..1194ab0 --- /dev/null +++ b/src/main/webapp/template/navbar.jsp @@ -0,0 +1,33 @@ +<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> + + diff --git a/src/main/webapp/user.jsp b/src/main/webapp/user.jsp new file mode 100644 index 0000000..4e51cf8 --- /dev/null +++ b/src/main/webapp/user.jsp @@ -0,0 +1,152 @@ +<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> + + + + + + + + + + +
    +

    ${user.firstname} ${user.lastname}

    + +
    + × + ${errormessage} +
    +
    + +
    + × + ${successmessage} +
    +
    + +
    +
    + + +
    +
    +
    + +
    +
    + + +
    +
    +
    + +
    +
    +

    +
    +
    +
    + +
     
    +
    +
    +
    +
    +
    + +
    + +
    +
    + +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    + +
    + + + + +
    +
    +
    +
    +
    + + + +
    + + + +
    +
    +
    + + + diff --git a/src/main/webapp/users.jsp b/src/main/webapp/users.jsp new file mode 100644 index 0000000..e26a86e --- /dev/null +++ b/src/main/webapp/users.jsp @@ -0,0 +1,40 @@ +<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> + + + + + + +
    +

    +
    + + +
    + + + + + + + + + + + + + +
    ${userentry.value.login}${userentry.value.firstname}${userentry.value.lastname} + +
    +
    + + + diff --git a/src/test/java/de/jalin/ldapadmin/ldap/DirectoryServiceRunner.java b/src/test/java/de/jalin/ldapadmin/ldap/DirectoryServiceRunner.java new file mode 100644 index 0000000..835fa36 --- /dev/null +++ b/src/test/java/de/jalin/ldapadmin/ldap/DirectoryServiceRunner.java @@ -0,0 +1,126 @@ +package de.jalin.ldapadmin.ldap; + +import java.io.File; +import java.io.FilenameFilter; +import java.util.ArrayList; +import java.util.List; + +import net.sf.ehcache.Cache; + +import org.apache.directory.api.ldap.model.entry.Entry; +import org.apache.directory.api.ldap.model.exception.LdapInvalidDnException; +import org.apache.directory.api.ldap.model.name.Dn; +import org.apache.directory.api.ldap.model.schema.SchemaManager; +import org.apache.directory.server.core.api.CacheService; +import org.apache.directory.server.core.api.DirectoryService; +import org.apache.directory.server.core.api.partition.Partition; +import org.apache.directory.server.core.factory.DefaultDirectoryServiceFactory; +import org.apache.directory.server.core.factory.JdbmPartitionFactory; +import org.apache.directory.server.core.shared.DefaultDnFactory; +import org.apache.directory.server.ldap.LdapServer; +import org.apache.directory.server.protocol.shared.store.LdifFileLoader; +import org.apache.directory.server.protocol.shared.store.LdifLoadFilter; +import org.apache.directory.server.protocol.shared.transport.TcpTransport; + +public class DirectoryServiceRunner +{ + private static DirectoryServiceRunner serviceRunner = null; + + private final DirectoryService service; + + public DirectoryServiceRunner(final String dnString, final String ip, final String port) throws Exception + { + service = initService(); + addPartition("ou=config", "config"); + addPartition(dnString, "example"); + service.startup(); + loadData(); + startServer(ip, port); + } + + private DirectoryService initService() throws Exception { + final DefaultDirectoryServiceFactory factory = new DefaultDirectoryServiceFactory(); + factory.init("example"); + final DirectoryService directoryService = factory.getDirectoryService(); + directoryService.setShutdownHookEnabled(true); + directoryService.getChangeLog().setEnabled(false); + directoryService.setAccessControlEnabled(true); + directoryService.setAllowAnonymousAccess(false); + directoryService.setPasswordHidden(true); + return directoryService; + } + + private void addPartition(final String dnString, final String partitionId) throws LdapInvalidDnException, Exception { + final JdbmPartitionFactory partitionFactory = new JdbmPartitionFactory(); + final SchemaManager schemaManager = service.getSchemaManager(); + final CacheService cacheService = service.getCacheService(); + final Cache cache = cacheService.getCache("dnCache"); + final DefaultDnFactory defaultDnFactory = new DefaultDnFactory(schemaManager, cache); + final Partition partition = partitionFactory.createPartition(schemaManager, defaultDnFactory, partitionId, dnString, 400, new File("ldap-data." + Double.valueOf(Math.random()).hashCode())); + service.addPartition(partition); + } + + private void startServer(final String ip, final String port) throws Exception { + final LdapServer ldapServer = new LdapServer(); + ldapServer.setTransports(new TcpTransport(ip, Integer.parseInt(port))); + ldapServer.setDirectoryService(service); + ldapServer.start(); + } + + private void loadData() { + final File ldifDirectory = new File("ldif"); + if (ldifDirectory.exists() && ldifDirectory.isDirectory()) { + final File[] ldifFiles = ldifDirectory.listFiles(new FilenameFilter() { + @Override + public boolean accept(final File dir, final String name) { + return name.endsWith(".ldif"); + } + }); + for (final File ldifFile : ldifFiles) { + final LdifFileLoader ldifFileLoader = new LdifFileLoader(service.getAdminSession(), ldifFile, (List) new ArrayList()); + ldifFileLoader.execute(); + System.out.println(ldifFile.getName() + " loaded"); + } + } + } + + public static void assureServiceRunning(final String name) throws DirectoryServiceException + { + if (serviceRunner == null) { + final String dnName = "dc=" + name + ",dc=example,dc=com"; + try { + serviceRunner = new DirectoryServiceRunner(dnName, "127.0.0.1", "10389"); + } catch (Exception e) { + throw new DirectoryServiceException(e); + } + } + } + + public static void main(final String[] args) + { + final String dnString = "dc=" + args[0] + ",dc=example,dc=com"; + final String ip = args[1]; + final String port = args[2]; + try + { + final DirectoryServiceRunner ads = new DirectoryServiceRunner(dnString, ip, port); + final Entry result = ads.service.getAdminSession().lookup(new Dn(dnString)); + System.out.println( "Found entry : " + result ); + } + catch ( Exception e ) + { + System.err.println(e.getMessage()); + } + } + + static class DirectoryServiceException extends Exception { + + private static final long serialVersionUID = 1L; + + public DirectoryServiceException(final Throwable exc) { + super(exc); + } + + } + +} diff --git a/src/test/java/de/jalin/ldapadmin/ldap/TestCreateGroup.java b/src/test/java/de/jalin/ldapadmin/ldap/TestCreateGroup.java new file mode 100644 index 0000000..b7e32bb --- /dev/null +++ b/src/test/java/de/jalin/ldapadmin/ldap/TestCreateGroup.java @@ -0,0 +1,80 @@ +package de.jalin.ldapadmin.ldap; + +import de.jalin.ldapadmin.ldap.LDAPSession; +import de.jalin.ldapadmin.ldap.AlreadyBoundException; +import de.jalin.ldapadmin.ldap.RequiredAttributeException; +import de.jalin.ldapadmin.ldap.UsersDAO; +import de.jalin.ldapadmin.ldap.LDAPSessionException; +import de.jalin.ldapadmin.ldap.GroupsDAO; +import static org.junit.Assert.*; + +import java.util.Arrays; + +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import de.jalin.ldapadmin.beans.Group; +import de.jalin.ldapadmin.beans.User; + +public class TestCreateGroup { + + private static final String USERS_DN = "uid=${uid},ou=users,dc=saastest,dc=example,dc=com"; + + private LDAPSession session; + + @BeforeClass + public static void setupClass() throws Exception { + DirectoryServiceRunner.assureServiceRunning("saastest"); + } + + @Before + public void setUp() throws Exception { + session = new LDAPSession("ldap://localhost:10389/dc=saastest,dc=example,dc=com", "uid=admin,ou=system", "streng-geheim"); + } + + @After + public void tearDown() throws Exception { + session.close(); + session = null; + } + + @Test + public void test() { + final UsersDAO udao = new UsersDAO(session); + final GroupsDAO gdao = new GroupsDAO(session); + try { + final User newUser1 = newUsersInstance("Jan", "Janssen"); + udao.create(newUser1); + final User newUser2 = newUsersInstance("Jens", "Jenssen"); + udao.create(newUser2); + Group login = new Group(); + login.setName("login"); + login.setMembers(Arrays.asList(new String[] { newUser1.getDn(), newUser2.getDn() })); + gdao.create(login); + Group admins = new Group(); + admins.setName("admins"); + admins.setMembers(Arrays.asList(new String[] { newUser1.getDn(), newUser2.getDn() })); + gdao.create(admins); + } catch (LDAPSessionException | RequiredAttributeException | AlreadyBoundException e) { + fail(e.getMessage()); + } + } + + private User newUsersInstance(final String fn, final String ln) { + final String uid = fn.substring(0, 3).toLowerCase(); + final User newUser = new User(); + newUser.setDn(USERS_DN.replace("${uid}", uid)); + newUser.setDisplayname(fn + " " + ln); + newUser.setEmail(fn.toLowerCase() + "." + ln.toLowerCase() + "@example.com"); + newUser.setFirstname(fn); + newUser.setLastname(ln); + newUser.setLogin(uid); + newUser.setMobile("0163 1234567"); + newUser.setPhone("030 12345678"); + newUser.setPassword("geheim"); + return newUser; + } + +} diff --git a/src/test/java/de/jalin/ldapadmin/ldap/TestCreateUser.java b/src/test/java/de/jalin/ldapadmin/ldap/TestCreateUser.java new file mode 100644 index 0000000..2208ad9 --- /dev/null +++ b/src/test/java/de/jalin/ldapadmin/ldap/TestCreateUser.java @@ -0,0 +1,63 @@ +package de.jalin.ldapadmin.ldap; + +import de.jalin.ldapadmin.ldap.LDAPSession; +import de.jalin.ldapadmin.ldap.AlreadyBoundException; +import de.jalin.ldapadmin.ldap.RequiredAttributeException; +import de.jalin.ldapadmin.ldap.UsersDAO; +import de.jalin.ldapadmin.ldap.LDAPSessionException; +import static org.junit.Assert.*; + +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import de.jalin.ldapadmin.beans.User; + +public class TestCreateUser { + + private static final String USERS_DN = "uid=pet,ou=users,dc=saastest,dc=example,dc=com"; + + private LDAPSession session; + + @BeforeClass + public static void setupClass() throws Exception { + DirectoryServiceRunner.assureServiceRunning("saastest"); + } + + @Before + public void setUp() throws Exception { + session = new LDAPSession("ldap://localhost:10389/dc=saastest,dc=example,dc=com", "uid=admin,ou=system", "streng-geheim"); + } + + @After + public void tearDown() throws Exception { + session.close(); + session = null; + } + + @Test + public void test() { + final UsersDAO dao = new UsersDAO(session); + try { + final User existingUser = dao.loadUsers().get(USERS_DN); + assertNull("user already exists", existingUser); + final User newUser = new User(); + newUser.setDn(USERS_DN); + newUser.setDisplayname("Peter Petersen"); + newUser.setEmail("peter.petersen@example.com"); + newUser.setFirstname("Peter"); + newUser.setLastname("Petersen"); + newUser.setLogin("pet"); + newUser.setMobile("0163 1234567"); + newUser.setPhone("030 12345678"); + newUser.setPassword("geheim"); + dao.create(newUser); + final User createdUser = dao.loadUsers().get(USERS_DN); + assertNotNull("should exist", createdUser); + } catch (LDAPSessionException | RequiredAttributeException | AlreadyBoundException e) { + fail(e.getMessage()); + } + } + +} diff --git a/src/test/java/de/jalin/ldapadmin/ldap/TestDeleteUser.java b/src/test/java/de/jalin/ldapadmin/ldap/TestDeleteUser.java new file mode 100644 index 0000000..ce0046b --- /dev/null +++ b/src/test/java/de/jalin/ldapadmin/ldap/TestDeleteUser.java @@ -0,0 +1,66 @@ +package de.jalin.ldapadmin.ldap; + +import de.jalin.ldapadmin.ldap.LDAPSession; +import de.jalin.ldapadmin.ldap.AlreadyBoundException; +import de.jalin.ldapadmin.ldap.RequiredAttributeException; +import de.jalin.ldapadmin.ldap.UsersDAO; +import de.jalin.ldapadmin.ldap.LDAPSessionException; +import static org.junit.Assert.*; + +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import de.jalin.ldapadmin.beans.User; + +public class TestDeleteUser { + + private static final String USERS_DN = "uid=hei,ou=users,dc=saastest,dc=example,dc=com"; + + private LDAPSession session; + + @BeforeClass + public static void setupClass() throws Exception { + DirectoryServiceRunner.assureServiceRunning("saastest"); + } + + @Before + public void setUp() throws Exception { + session = new LDAPSession("ldap://localhost:10389/dc=saastest,dc=example,dc=com", "uid=admin,ou=system", "streng-geheim"); + } + + @After + public void tearDown() throws Exception { + session.close(); + session = null; + } + + @Test + public void test() { + final UsersDAO dao = new UsersDAO(session); + try { + final User existingUser = dao.loadUsers().get(USERS_DN); + assertNull("user already exists", existingUser); + final User newUser = new User(); + newUser.setDn(USERS_DN); + newUser.setDisplayname("Hein Hanssen"); + newUser.setEmail("hein.hanssen@example.com"); + newUser.setFirstname("Hein"); + newUser.setLastname("Hanssen"); + newUser.setLogin("hei"); + newUser.setMobile("0163 4434567"); + newUser.setPhone("030 44345678"); + newUser.setPassword("geheim"); + dao.create(newUser); + final User createdUser = dao.loadUsers().get(USERS_DN); + assertNotNull("should exist", createdUser); + dao.delete(createdUser); + final User deletedUser = dao.loadUsers().get(USERS_DN); + assertNull("should be removed", deletedUser); + } catch (LDAPSessionException | RequiredAttributeException | AlreadyBoundException e) { + fail(e.getMessage()); + } + } + +} diff --git a/src/test/java/de/jalin/ldapadmin/ldap/TestReadUser.java b/src/test/java/de/jalin/ldapadmin/ldap/TestReadUser.java new file mode 100644 index 0000000..7e0df36 --- /dev/null +++ b/src/test/java/de/jalin/ldapadmin/ldap/TestReadUser.java @@ -0,0 +1,66 @@ +package de.jalin.ldapadmin.ldap; + +import de.jalin.ldapadmin.ldap.LDAPSession; +import de.jalin.ldapadmin.ldap.AlreadyBoundException; +import de.jalin.ldapadmin.ldap.RequiredAttributeException; +import de.jalin.ldapadmin.ldap.UsersDAO; +import de.jalin.ldapadmin.ldap.LDAPSessionException; +import static org.junit.Assert.*; + +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import de.jalin.ldapadmin.beans.User; + +public class TestReadUser { + + private static final String USERS_DN = "uid=chr,ou=users,dc=saastest,dc=example,dc=com"; + + private LDAPSession session; + + @BeforeClass + public static void setupClass() throws Exception { + DirectoryServiceRunner.assureServiceRunning("saastest"); + } + + @Before + public void setUp() throws Exception { + session = new LDAPSession("ldap://localhost:10389/dc=saastest,dc=example,dc=com", "uid=admin,ou=system", "streng-geheim"); + } + + @After + public void tearDown() throws Exception { + session.close(); + session = null; + } + + @Test + public void test() { + final UsersDAO dao = new UsersDAO(session); + try { + final User existingUser = dao.loadUsers().get(USERS_DN); + assertNull("user already exists", existingUser); + final User newUser = new User(); + newUser.setDn(USERS_DN); + newUser.setDisplayname("Chris Christansen"); + newUser.setEmail("chris.christansen@example.com"); + newUser.setFirstname("Chris"); + newUser.setLastname("Christansen"); + newUser.setLogin("chr"); + newUser.setMobile("0163 8834567"); + newUser.setPhone("030 88345678"); + newUser.setPassword("geheim"); + dao.create(newUser); + final User createdUser = dao.loadUsers().get(USERS_DN); + assertNotNull("should exist", createdUser); + final User readUser = dao.read(USERS_DN); + assertNotNull(readUser); + assertTrue("chris.christansen@example.com".equals(readUser.getEmail())); + } catch (LDAPSessionException | RequiredAttributeException | AlreadyBoundException e) { + fail(e.getMessage()); + } + } + +} diff --git a/src/test/java/de/jalin/ldapadmin/ldap/TestUpdateAsBindUser.java b/src/test/java/de/jalin/ldapadmin/ldap/TestUpdateAsBindUser.java new file mode 100644 index 0000000..cc0e973 --- /dev/null +++ b/src/test/java/de/jalin/ldapadmin/ldap/TestUpdateAsBindUser.java @@ -0,0 +1,76 @@ +package de.jalin.ldapadmin.ldap; + +import de.jalin.ldapadmin.ldap.LDAPSession; +import de.jalin.ldapadmin.ldap.AlreadyBoundException; +import de.jalin.ldapadmin.ldap.RequiredAttributeException; +import de.jalin.ldapadmin.ldap.UsersDAO; +import de.jalin.ldapadmin.ldap.LDAPSessionException; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import de.jalin.ldapadmin.beans.User; + +public class TestUpdateAsBindUser { + + private static final String USERS_DN = "uid=pau,ou=users,dc=saastest,dc=example,dc=com"; + + private LDAPSession session; + + @BeforeClass + public static void setupClass() throws Exception { + DirectoryServiceRunner.assureServiceRunning("saastest"); + } + + @Before + public void setUp() throws Exception { + session = new LDAPSession("ldap://localhost:10389/dc=saastest,dc=example,dc=com", "uid=application,ou=bind,dc=saastest,dc=example,dc=com", "app-secret"); + } + + @After + public void tearDown() throws Exception { + session.close(); + session = null; + } + + @AfterClass + public static void tearDownClass() throws Exception { + } + + @Test + public void test() { + final UsersDAO dao = new UsersDAO(session); + try { + final User existingUser = dao.loadUsers().get(USERS_DN); + assertNull("user already exists", existingUser); + final User newUser = new User(); + newUser.setDn(USERS_DN); + newUser.setDisplayname("Paul Paulsen"); + newUser.setEmail("paul.paulsen@example.com"); + newUser.setFirstname("Paul"); + newUser.setLastname("Pausen"); + newUser.setLogin("pau"); + newUser.setMobile("0163 2234567"); + newUser.setPhone("030 22345678"); + newUser.setPassword("geheim"); + dao.create(newUser); + final User createdUser = dao.loadUsers().get(USERS_DN); + assertNotNull("should exist", createdUser); + createdUser.setLastname("Paulsen"); + createdUser.setPassword("strenggeheim"); + dao.update(createdUser); + final User updatedUser = dao.loadUsers().get(USERS_DN); + assertTrue("should be updated", "Paulsen".equals(updatedUser.getLastname())); + } catch (LDAPSessionException | RequiredAttributeException | AlreadyBoundException e) { + fail(e.getMessage()); + } + } + +} diff --git a/src/test/java/de/jalin/ldapadmin/ldap/TestUpdateAsSimpleUser.java b/src/test/java/de/jalin/ldapadmin/ldap/TestUpdateAsSimpleUser.java new file mode 100644 index 0000000..a7e108e --- /dev/null +++ b/src/test/java/de/jalin/ldapadmin/ldap/TestUpdateAsSimpleUser.java @@ -0,0 +1,89 @@ +package de.jalin.ldapadmin.ldap; + +import de.jalin.ldapadmin.ldap.LDAPSession; +import de.jalin.ldapadmin.ldap.AlreadyBoundException; +import de.jalin.ldapadmin.ldap.RequiredAttributeException; +import de.jalin.ldapadmin.ldap.UsersDAO; +import de.jalin.ldapadmin.ldap.LDAPSessionException; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import javax.naming.NamingException; +import javax.naming.NoPermissionException; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import de.jalin.ldapadmin.beans.User; + +public class TestUpdateAsSimpleUser { + + private static final String USERS_DN = "uid=mic,ou=users,dc=saastest,dc=example,dc=com"; + + @BeforeClass + public static void setupClass() throws Exception { + DirectoryServiceRunner.assureServiceRunning("saastest"); + } + + @Before + public void setUp() throws Exception { + final LDAPSession bindUserSession = new LDAPSession("ldap://localhost:10389/dc=saastest,dc=example,dc=com", "uid=application,ou=bind,dc=saastest,dc=example,dc=com", "app-secret"); + final UsersDAO dao = new UsersDAO(bindUserSession); + final User newUser = new User(); + newUser.setDn(USERS_DN); + newUser.setDisplayname("Paul Petersen"); + newUser.setEmail("paul.petersen@example.com"); + newUser.setFirstname("Paul"); + newUser.setLastname("Petersen"); + newUser.setLogin("plp"); + newUser.setMobile("0163 1234567"); + newUser.setPhone("030 12345678"); + newUser.setPassword("geheim"); + dao.create(newUser); + bindUserSession.close(); + } + + @After + public void tearDown() throws Exception { + } + + @AfterClass + public static void tearDownClass() throws Exception { +// Thread.sleep(10 * 60000L); + } + + @Test + public void test() { + try { + final LDAPSession simpleUserSession = new LDAPSession("ldap://localhost:10389/dc=saastest,dc=example,dc=com", "uid=plp,ou=users,dc=saastest,dc=example,dc=com", "geheim"); + final UsersDAO dao = new UsersDAO(simpleUserSession); + final User existingUser = dao.loadUsers().get(USERS_DN); + assertNull("user already exists", existingUser); + final User newUser = new User(); + newUser.setDn(USERS_DN); + newUser.setDisplayname("Micha Michaelsen"); + newUser.setEmail("micha.michaelsen@example.com"); + newUser.setFirstname("Michael"); + newUser.setLastname("Michaelsen"); + newUser.setLogin("mic"); + newUser.setMobile("0163 2234567"); + newUser.setPhone("030 22345678"); + newUser.setPassword("geheim"); + try { + dao.create(newUser); + dao.loadUsers().get(USERS_DN); + fail("should not exist"); + } catch (LDAPSessionException e) { + assertTrue(e.getCause() instanceof NoPermissionException); + } + simpleUserSession.close(); + } catch (LDAPSessionException | RequiredAttributeException | AlreadyBoundException | NamingException e) { + fail(e.getMessage()); + } + } + +} diff --git a/src/test/java/de/jalin/ldapadmin/ldap/TestUpdateUser.java b/src/test/java/de/jalin/ldapadmin/ldap/TestUpdateUser.java new file mode 100644 index 0000000..1e137e2 --- /dev/null +++ b/src/test/java/de/jalin/ldapadmin/ldap/TestUpdateUser.java @@ -0,0 +1,71 @@ +package de.jalin.ldapadmin.ldap; + +import de.jalin.ldapadmin.ldap.LDAPSession; +import de.jalin.ldapadmin.ldap.AlreadyBoundException; +import de.jalin.ldapadmin.ldap.RequiredAttributeException; +import de.jalin.ldapadmin.ldap.UsersDAO; +import de.jalin.ldapadmin.ldap.LDAPSessionException; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import de.jalin.ldapadmin.beans.User; + +public class TestUpdateUser { + + private static final String USERS_DN = "uid=kla,ou=users,dc=saastest,dc=example,dc=com"; + + private LDAPSession session; + + @BeforeClass + public static void setupClass() throws Exception { + DirectoryServiceRunner.assureServiceRunning("saastest"); + } + + @Before + public void setUp() throws Exception { + session = new LDAPSession("ldap://localhost:10389/dc=saastest,dc=example,dc=com", "uid=admin,ou=system", "streng-geheim"); + } + + @After + public void tearDown() throws Exception { + session.close(); + session = null; + } + + @Test + public void test() { + final UsersDAO dao = new UsersDAO(session); + try { + final User existingUser = dao.loadUsers().get(USERS_DN); + assertNull("user already exists", existingUser); + final User newUser = new User(); + newUser.setDn(USERS_DN); + newUser.setDisplayname("Klaas Clahsen"); + newUser.setEmail("klaas.clahsen@example.com"); + newUser.setFirstname("Klaas"); + newUser.setLastname("Klahsen"); + newUser.setLogin("kla"); + newUser.setMobile("0163 2234567"); + newUser.setPhone("030 22345678"); + newUser.setPassword("geheim"); + dao.create(newUser); + final User createdUser = dao.loadUsers().get(USERS_DN); + assertNotNull("should exist", createdUser); + createdUser.setLastname("Clahsen"); + createdUser.setPassword("strenggeheim"); + dao.update(createdUser); + final User updatedUser = dao.loadUsers().get(USERS_DN); + assertTrue("should be updated", "Clahsen".equals(updatedUser.getLastname())); + } catch (LDAPSessionException | RequiredAttributeException | AlreadyBoundException e) { + fail(e.getMessage()); + } + } + +}