diff --git a/ch.eitchnet.privilege/LICENSE b/ch.eitchnet.privilege/LICENSE new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/ch.eitchnet.privilege/LICENSE @@ -0,0 +1,202 @@ + + 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/ch.eitchnet.privilege/README.md b/ch.eitchnet.privilege/README.md new file mode 100644 index 000000000..e5eeeb487 --- /dev/null +++ b/ch.eitchnet.privilege/README.md @@ -0,0 +1,95 @@ +ch.eitchnet.privilege +================== + +[![Build Status](http://jenkins.eitchnet.ch/buildStatus/icon?job=ch.eitchnet.privilege)](http://jenkins.eitchnet.ch/view/ch.eitchnet/job/ch.eitchnet.privilege/) + +Overview +======================================================================= + +Privilege is a light weight library to secure access or grant privileges to +users in an application. Privilege allows a developer to secure the application +in different levels of the application providing API's for different +contexts. + +Privilege is implemented in the Java language and is light weight in that it has +no external dependencies other than a Java runtime environment version 6. Since +the JRE 6 has an LDAP implementation it is possible to store Privilege data in +a LDAP repository with only the Privilege JAR. + +Privilege is distributed under the GNU Lesser General Public License on +Github.com and can be downloaded at + + https://github.com/eitchnet/ch.eitchnet.privilege + +The main developer is Robert von Burg who also maintains the +Github repository. He is available for all questions regarding Privilege + +Motivation +======================================================================= +In some cases a developer might want to restrict access to an application +depending on the role which an authenticated user has. In other cases the +developer would need a more finely grained control by restricting access to a +certain object, or a certain method call. + +We were looking for an API which would allows us to restrict access to a given +object in different ways. For instance it was our intention to not simply +restrict access to a specific object type, or instance, but to restrict access +to an instance of the object if it had fields set to a specific value. + +Evaluations on existing libraries which implement access restriction did not +provide an API which suited our needs or which were not easily implemented, thus +leading to the design of Privilege. + +Design Goals +======================================================================= +When a developer needs to implement access restriction an application there are +different questions which the developer will ask: +- Does the user have a specific role? +- Does the user have a specific privilege i.e. is the user allowed to perform a +specific action? +- Is a user allowed to access a specific type of object? +- Is a user allowed to access a specific instance of a type? +- Is a user allowed to access a field on a specific object? + +Privilege's design goals are to allow the developer to answer these questions +with an API which does not mean implementing a lot of additional project +specific code. + +Further in Privilege it should be possible to perform the normal CRUD functions: +- Create users, roles, privileges, etc. +- Read existing users, roles, privileges, etc. +- Update users, roles, privileges, etc. +- Delete users, roles, privileges, etc. + +It should be possible to store Privilege's data in different databases, +depending on the application. For example it should be able to store the data in +XML files, in a LDAP directory and so forth. + +Documentation +======================================================================= + +The current documentation, though a bit outdated, can be found in the docs/ +directory of the Repository + +Compiling +======================================================================= + +Privilege is a Maven3 project and can be built by simply performing the +following command: + +$ mvn compile + +Using +======================================================================= + +To use Privilege see the ch.eitchnet.privilege.test.PrivilegeTest.java class +which contains a few test cases including showing how to load Privilege. + +This documentation is still in need of more work, but for any questions please +don't hesitate to write an e-mail to the developer and we'll find a solution. + + Switzerland, the 29. July 2012 + Robert von Burg + + + diff --git a/ch.eitchnet.privilege/config/PrivilegeConfig.xml b/ch.eitchnet.privilege/config/PrivilegeConfig.xml new file mode 100644 index 000000000..744933dcf --- /dev/null +++ b/ch.eitchnet.privilege/config/PrivilegeConfig.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ch.eitchnet.privilege/config/PrivilegeConfigMerge.xml b/ch.eitchnet.privilege/config/PrivilegeConfigMerge.xml new file mode 100644 index 000000000..403652054 --- /dev/null +++ b/ch.eitchnet.privilege/config/PrivilegeConfigMerge.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ch.eitchnet.privilege/config/PrivilegeRoles.xml b/ch.eitchnet.privilege/config/PrivilegeRoles.xml new file mode 100644 index 000000000..95ca00403 --- /dev/null +++ b/ch.eitchnet.privilege/config/PrivilegeRoles.xml @@ -0,0 +1,90 @@ + + + + + + Persist + Reload + GetPolicies + + + + true + + + true + + + true + + + true + + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + ENABLED + DISABLED + SYSTEM + + + true + + + + + + true + + + + + + true + + + + + + true + + + + + + ch.eitchnet.privilege.test.model.TestSystemUserAction + ch.eitchnet.privilege.test.model.TestSystemUserActionDeny + + + true + + + + + + hello + goodbye + + + + \ No newline at end of file diff --git a/ch.eitchnet.privilege/config/PrivilegeRolesMerge.xml b/ch.eitchnet.privilege/config/PrivilegeRolesMerge.xml new file mode 100644 index 000000000..02e9ea037 --- /dev/null +++ b/ch.eitchnet.privilege/config/PrivilegeRolesMerge.xml @@ -0,0 +1,28 @@ + + + + + + allow1 + + + + + true + + + + + + allow1 + deny1 + + + + + allow2 + deny2 + + + + diff --git a/ch.eitchnet.privilege/config/PrivilegeUsers.xml b/ch.eitchnet.privilege/config/PrivilegeUsers.xml new file mode 100644 index 000000000..46b0cdf42 --- /dev/null +++ b/ch.eitchnet.privilege/config/PrivilegeUsers.xml @@ -0,0 +1,39 @@ + + + + + Application + Administrator + ENABLED + en_GB + + PrivilegeAdmin + AppUser + + + + + + + + + System User + Administrator + SYSTEM + en_GB + + system_admin_privileges + + + + + System User + Administrator + SYSTEM + en_GB + + system_admin_privileges + + + + \ No newline at end of file diff --git a/ch.eitchnet.privilege/config/PrivilegeUsersMerge.xml b/ch.eitchnet.privilege/config/PrivilegeUsersMerge.xml new file mode 100644 index 000000000..978464173 --- /dev/null +++ b/ch.eitchnet.privilege/config/PrivilegeUsersMerge.xml @@ -0,0 +1,26 @@ + + + + + System User + Administrator + ENABLED + en_GB + + RoleA1 + RoleA2 + + + + + System User + Administrator + ENABLED + en_GB + + RoleB1 + RoleB2 + + + + \ No newline at end of file diff --git a/ch.eitchnet.privilege/docs/PrivilegeAuthentication.dia b/ch.eitchnet.privilege/docs/PrivilegeAuthentication.dia new file mode 100644 index 000000000..fe9ed43c9 Binary files /dev/null and b/ch.eitchnet.privilege/docs/PrivilegeAuthentication.dia differ diff --git a/ch.eitchnet.privilege/docs/PrivilegeHandlers.dia b/ch.eitchnet.privilege/docs/PrivilegeHandlers.dia new file mode 100644 index 000000000..cf4b84050 Binary files /dev/null and b/ch.eitchnet.privilege/docs/PrivilegeHandlers.dia differ diff --git a/ch.eitchnet.privilege/docs/PrivilegeModelPrivilege.dia b/ch.eitchnet.privilege/docs/PrivilegeModelPrivilege.dia new file mode 100644 index 000000000..8842f4ea4 Binary files /dev/null and b/ch.eitchnet.privilege/docs/PrivilegeModelPrivilege.dia differ diff --git a/ch.eitchnet.privilege/docs/PrivilegeModelUser.dia b/ch.eitchnet.privilege/docs/PrivilegeModelUser.dia new file mode 100644 index 000000000..fca31db1d Binary files /dev/null and b/ch.eitchnet.privilege/docs/PrivilegeModelUser.dia differ diff --git a/ch.eitchnet.privilege/docs/TODO b/ch.eitchnet.privilege/docs/TODO new file mode 100644 index 000000000..e6c36ace6 --- /dev/null +++ b/ch.eitchnet.privilege/docs/TODO @@ -0,0 +1,11 @@ +A list of TODOs for Privilege +============================================ + +- Write up a proper explanation on the idea on how Privilege, PrivilegePolicy, + Restrictable and Roles fit together to grant privileges to Users + +- i18n for any messages and exceptions! + +- Finish the JavaDoc + +- Set up a website =) diff --git a/ch.eitchnet.privilege/pom.xml b/ch.eitchnet.privilege/pom.xml new file mode 100644 index 000000000..51d2ba92f --- /dev/null +++ b/ch.eitchnet.privilege/pom.xml @@ -0,0 +1,70 @@ + + + 4.0.0 + + + ch.eitchnet + ch.eitchnet.parent + 1.1.0-SNAPSHOT + ../ch.eitchnet.parent/pom.xml + + + ch.eitchnet.privilege + jar + ch.eitchnet.privilege + https://github.com/eitchnet/ch.eitchnet.privilege + 2011 + + + 1.1.0-SNAPSHOT + + + + Github Issues + https://github.com/eitchnet/ch.eitchnet.privilege/issues + + + + scm:git:https://github.com/eitchnet/ch.eitchnet.privilege.git + scm:git:git@github.com:eitchnet/ch.eitchnet.privilege.git + https://github.com/eitchnet/ch.eitchnet.privilege + HEAD + + + + + ch.eitchnet + ch.eitchnet.utils + ${eitchnet.utils.version} + + + + + + + org.apache.maven.plugins + maven-eclipse-plugin + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + org.apache.maven.plugins + maven-source-plugin + + + + org.apache.maven.plugins + maven-jar-plugin + + + + org.apache.maven.plugins + maven-site-plugin + + + + diff --git a/ch.eitchnet.privilege/privilege.jardesc b/ch.eitchnet.privilege/privilege.jardesc new file mode 100644 index 000000000..d58ef8d02 --- /dev/null +++ b/ch.eitchnet.privilege/privilege.jardesc @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/base/AccessDeniedException.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/base/AccessDeniedException.java new file mode 100644 index 000000000..2a14841d7 --- /dev/null +++ b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/base/AccessDeniedException.java @@ -0,0 +1,34 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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. + */ +package ch.eitchnet.privilege.base; + +/** + * Exception thrown if access is denied during login, or if a certain privilege is not granted + * + * @author Robert von Burg + */ +public class AccessDeniedException extends PrivilegeException { + + private static final long serialVersionUID = 1L; + + /** + * @param msg + * detail on why and where access was denied + */ + public AccessDeniedException(String msg) { + super(msg); + } +} diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/base/InvalidCredentialsException.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/base/InvalidCredentialsException.java new file mode 100644 index 000000000..013e9dce8 --- /dev/null +++ b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/base/InvalidCredentialsException.java @@ -0,0 +1,19 @@ +package ch.eitchnet.privilege.base; + +/** + * Exception thrown if the given credentials are invalid + * + * @author Robert von Burg + */ +public class InvalidCredentialsException extends AccessDeniedException { + + private static final long serialVersionUID = 1L; + + /** + * @param msg + * the message to accompany the exception + */ + public InvalidCredentialsException(String msg) { + super(msg); + } +} diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/base/PrivilegeConflictResolution.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/base/PrivilegeConflictResolution.java new file mode 100644 index 000000000..0d60fb28d --- /dev/null +++ b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/base/PrivilegeConflictResolution.java @@ -0,0 +1,52 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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. + */ +package ch.eitchnet.privilege.base; + +import ch.eitchnet.privilege.model.internal.Role; +import ch.eitchnet.privilege.model.internal.User; + +/** + * The {@link PrivilegeConflictResolution} defines what should be done if a {@link User} has {@link Role Roles} which + * have Privileges with conflicting names. + * + * @author Robert von Burg + */ +public enum PrivilegeConflictResolution { + + /** + * STRICT requires that a User may not have conflicting Privileges throug multiple Roles. In this case an Exception + * is thrown. + */ + STRICT { + @Override + public boolean isStrict() { + return true; + } + }, + + /** + * MERGE defines that if conflicting privileges are encountered then a merge is to take place. A merge means that if + * all is allowed, then that wins. Otherwise any existing allow and deny lists are merged + */ + MERGE { + @Override + public boolean isStrict() { + return false; + } + }; + + public abstract boolean isStrict(); +} diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/base/PrivilegeException.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/base/PrivilegeException.java new file mode 100644 index 000000000..9d34cd7ed --- /dev/null +++ b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/base/PrivilegeException.java @@ -0,0 +1,48 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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. + */ +package ch.eitchnet.privilege.base; + +/** + * Main {@link RuntimeException} thrown if something goes wrong in Privilege + * + * @author Robert von Burg + */ +public class PrivilegeException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + /** + * Default constructor + * + * @param string + * message to go with the exception + */ + public PrivilegeException(String string) { + super(string); + } + + /** + * Constructor with underlying exception + * + * @param string + * message to go with the exception + * @param t + * throwable to wrap with this exception which is the underlying exception of this exception + */ + public PrivilegeException(String string, Throwable t) { + super(string, t); + } +} diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java new file mode 100644 index 000000000..5c083dbbe --- /dev/null +++ b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java @@ -0,0 +1,119 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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. + */ +package ch.eitchnet.privilege.handler; + +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.text.MessageFormat; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ch.eitchnet.privilege.base.PrivilegeException; +import ch.eitchnet.privilege.helper.XmlConstants; +import ch.eitchnet.utils.helper.StringHelper; + +/** + *

+ * This default {@link EncryptionHandler} creates tokens using a {@link SecureRandom} object. Hashing is done by using + * {@link MessageDigest} and the configured algorithm which is passed in the parameters + *

+ * + * Required parameters: + *
    + *
  • {@link XmlConstants#XML_PARAM_HASH_ALGORITHM}
  • + *
+ * + * @author Robert von Burg + */ +public class DefaultEncryptionHandler implements EncryptionHandler { + + /** + * The log4j logger used in this instance + */ + private static final Logger logger = LoggerFactory.getLogger(DefaultEncryptionHandler.class); + + /** + * The {@link SecureRandom} which is used to create new tokens + */ + private SecureRandom secureRandom; + + /** + * The configured hash algorithm for this instance + */ + private String hashAlgorithm; + + @Override + public String convertToHash(String string) { + return convertToHash(string.getBytes()); + } + + @Override + public String convertToHash(byte[] bytes) { + try { + + return StringHelper.hashAsHex(this.hashAlgorithm, bytes); + + } catch (RuntimeException e) { + if (e.getCause() == null) + throw e; + if (e.getCause().getClass().equals(NoSuchAlgorithmException.class)) + throw new PrivilegeException( + MessageFormat.format("Algorithm {0} was not found!", this.hashAlgorithm), e.getCause()); //$NON-NLS-1$ + if (e.getCause().getClass().equals(UnsupportedEncodingException.class)) + throw new PrivilegeException("Charset ASCII is not supported!", e.getCause()); //$NON-NLS-1$ + + throw e; + } + } + + @Override + public String nextToken() { + byte[] bytes = new byte[16]; + this.secureRandom.nextBytes(bytes); + String randomString = new String(bytes); + return randomString; + } + + @Override + public void initialize(Map parameterMap) { + + this.secureRandom = new SecureRandom(); + + // get hash algorithm parameters + this.hashAlgorithm = parameterMap.get(XmlConstants.XML_PARAM_HASH_ALGORITHM); + if (this.hashAlgorithm == null || this.hashAlgorithm.isEmpty()) { + String msg = "[{0}] Defined parameter {1} is invalid"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, EncryptionHandler.class.getName(), XmlConstants.XML_PARAM_HASH_ALGORITHM); + throw new PrivilegeException(msg); + } + + // test hash algorithm + try { + convertToHash("test"); //$NON-NLS-1$ + DefaultEncryptionHandler.logger.info(MessageFormat + .format("Using hashing algorithm {0}", this.hashAlgorithm)); //$NON-NLS-1$ + } catch (Exception e) { + String msg = "[{0}] Defined parameter {1} is invalid because of underlying exception: {2}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, EncryptionHandler.class.getName(), XmlConstants.XML_PARAM_HASH_ALGORITHM, + e.getLocalizedMessage()); + throw new PrivilegeException(msg, e); + } + } +} diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java new file mode 100644 index 000000000..91e800a2e --- /dev/null +++ b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -0,0 +1,1770 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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. + */ +package ch.eitchnet.privilege.handler; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javax.crypto.SecretKey; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ch.eitchnet.privilege.base.AccessDeniedException; +import ch.eitchnet.privilege.base.InvalidCredentialsException; +import ch.eitchnet.privilege.base.PrivilegeConflictResolution; +import ch.eitchnet.privilege.base.PrivilegeException; +import ch.eitchnet.privilege.model.Certificate; +import ch.eitchnet.privilege.model.IPrivilege; +import ch.eitchnet.privilege.model.PrivilegeContext; +import ch.eitchnet.privilege.model.PrivilegeRep; +import ch.eitchnet.privilege.model.RoleRep; +import ch.eitchnet.privilege.model.SimpleRestrictable; +import ch.eitchnet.privilege.model.UserRep; +import ch.eitchnet.privilege.model.UserState; +import ch.eitchnet.privilege.model.internal.PrivilegeImpl; +import ch.eitchnet.privilege.model.internal.Role; +import ch.eitchnet.privilege.model.internal.User; +import ch.eitchnet.privilege.policy.PrivilegePolicy; +import ch.eitchnet.privilege.xml.CertificateStubsDomWriter; +import ch.eitchnet.privilege.xml.CertificateStubsSaxReader; +import ch.eitchnet.privilege.xml.CertificateStubsSaxReader.CertificateStub; +import ch.eitchnet.utils.collections.Tuple; +import ch.eitchnet.utils.helper.AesCryptoHelper; +import ch.eitchnet.utils.helper.StringHelper; + +/** + *

+ * This is default implementation of the {@link PrivilegeHandler} + *

+ * + * The following list describes implementation details: + *
    + *
  • any methods which change the model are first validated by checking if the certificate is for an admin user by + * calling {@link #assertIsPrivilegeAdmin(Certificate)}
  • + *
  • all model requests are delegated to the configured {@link PrivilegeHandler}, except for the session id to + * {@link Certificate} map, no model data is kept in this implementation. This also means that to return the + * representation objects, for every new model query, a new representation object is created
  • + *
  • when creating new users, or editing users then a null password is understood as no password set
  • + *
  • Password requirements are simple: Non null and non empty/length 0
  • + *
+ * + * @author Robert von Burg + */ +public class DefaultPrivilegeHandler implements PrivilegeHandler { + + /** + * slf4j logger + */ + protected static final Logger logger = LoggerFactory.getLogger(DefaultPrivilegeHandler.class); + + /** + * Map keeping a reference to all active sessions + */ + private Map privilegeContextMap; + + /** + * Map of {@link PrivilegePolicy} classes + */ + private Map> policyMap; + + /** + * The persistence handler is used for getting objects and saving changes + */ + private PersistenceHandler persistenceHandler; + + /** + * The encryption handler is used for generating hashes and tokens + */ + private EncryptionHandler encryptionHandler; + + /** + * flag to define if already initialized + */ + private boolean initialized; + + /** + * flag to define if a persist should be performed after a user changes their own data + */ + private boolean autoPersistOnUserChangesData; + + /** + * flag to define if sessions should be persisted + */ + private boolean persistSessions; + + /** + * Path to sessions file for persistence + */ + private File persistSessionsPath; + + /** + * Secret key + */ + private SecretKey secretKey; + + private PrivilegeConflictResolution privilegeConflictResolution; + + @Override + public EncryptionHandler getEncryptionHandler() throws PrivilegeException { + return this.encryptionHandler; + } + + @Override + public RoleRep getRole(Certificate certificate, String roleName) { + + // validate user actually has this type of privilege + PrivilegeContext prvCtx = getPrivilegeContext(certificate); + prvCtx.assertHasPrivilege(PRIVILEGE_GET_ROLE); + + Role role = this.persistenceHandler.getRole(roleName); + if (role == null) + return null; + + prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_GET_ROLE, new Tuple(null, role))); + + return role.asRoleRep(); + } + + @Override + public UserRep getUser(Certificate certificate, String username) { + + // validate user actually has this type of privilege + PrivilegeContext prvCtx = getPrivilegeContext(certificate); + prvCtx.assertHasPrivilege(PRIVILEGE_GET_USER); + + User user = this.persistenceHandler.getUser(username); + if (user == null) + return null; + + prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_GET_USER, new Tuple(null, user))); + return user.asUserRep(); + } + + @Override + public Map getPolicyDefs(Certificate certificate) { + + // validate user actually has this type of privilege + PrivilegeContext prvCtx = getPrivilegeContext(certificate); + prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_ACTION, PRIVILEGE_ACTION_GET_POLICIES)); + + Map policyDef = new HashMap<>(this.policyMap.size()); + for (Entry> entry : this.policyMap.entrySet()) { + policyDef.put(entry.getKey(), entry.getValue().getName()); + } + return policyDef; + } + + @Override + public List getCertificates(Certificate certificate) { + + // validate user actually has this type of privilege + PrivilegeContext prvCtx = getPrivilegeContext(certificate); + prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_ACTION, PRIVILEGE_ACTION_GET_CERTIFICATES)); + + return this.privilegeContextMap.values().stream().map(p -> p.getCertificate()).collect(Collectors.toList()); + } + + @Override + public List getRoles(Certificate certificate) { + + // validate user actually has this type of privilege + PrivilegeContext prvCtx = getPrivilegeContext(certificate); + prvCtx.assertHasPrivilege(PRIVILEGE_GET_ROLE); + + Stream rolesStream = this.persistenceHandler.getAllRoles().stream(); + + // validate access to each role + // TODO throwing and catching exception ain't cool + rolesStream = rolesStream.filter(role -> { + try { + prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_GET_ROLE, new Tuple(null, role))); + return true; + } catch (AccessDeniedException e) { + return false; + } + }); + + List roles = rolesStream.map(r -> r.asRoleRep()).collect(Collectors.toList()); + return roles; + } + + @Override + public List getUsers(Certificate certificate) { + + // validate user actually has this type of privilege + PrivilegeContext prvCtx = getPrivilegeContext(certificate); + prvCtx.assertHasPrivilege(PRIVILEGE_GET_USER); + + Stream usersStream = this.persistenceHandler.getAllUsers().stream(); + + // validate access to each user + // TODO throwing and catching exception ain't cool + usersStream = usersStream.filter(user -> { + try { + prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_GET_USER, new Tuple(null, user))); + return true; + } catch (AccessDeniedException e) { + return false; + } + }); + + List users = usersStream.map(u -> u.asUserRep()).collect(Collectors.toList()); + return users; + } + + @Override + public List queryUsers(Certificate certificate, UserRep selectorRep) { + + // validate user actually has this type of privilege + PrivilegeContext prvCtx = getPrivilegeContext(certificate); + prvCtx.assertHasPrivilege(PRIVILEGE_GET_USER); + + String selUserId = selectorRep.getUserId(); + String selUsername = selectorRep.getUsername(); + String selFirstname = selectorRep.getFirstname(); + String selLastname = selectorRep.getLastname(); + UserState selUserState = selectorRep.getUserState(); + Locale selLocale = selectorRep.getLocale(); + Set selRoles = selectorRep.getRoles(); + Map selPropertyMap = selectorRep.getPropertyMap(); + + List result = new ArrayList<>(); + List allUsers = this.persistenceHandler.getAllUsers(); + for (User user : allUsers) { + + // TODO throwing and catching exception ain't cool + try { + prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_GET_USER, new Tuple(null, user))); + } catch (AccessDeniedException e) { + continue; + } + + // selections + boolean userIdSelected; + boolean usernameSelected; + boolean firstnameSelected; + boolean lastnameSelected; + boolean userStateSelected; + boolean localeSelected; + boolean roleSelected; + boolean propertySelected; + + // userId + if (selUserId == null) + userIdSelected = true; + else if (selUserId.equals(user.getUserId())) + userIdSelected = true; + else + userIdSelected = false; + + // username + if (selUsername == null) + usernameSelected = true; + else if (selUsername.equals(user.getUsername())) + usernameSelected = true; + else + usernameSelected = false; + + // firstname + if (selFirstname == null) + firstnameSelected = true; + else if (selFirstname.equals(user.getFirstname())) + firstnameSelected = true; + else + firstnameSelected = false; + + // lastname + if (selLastname == null) + lastnameSelected = true; + else if (selLastname.equals(user.getLastname())) + lastnameSelected = true; + else + lastnameSelected = false; + + // user state + if (selUserState == null) + userStateSelected = true; + else if (selUserState.equals(user.getUserState())) + userStateSelected = true; + else + userStateSelected = false; + + // locale + if (selLocale == null) + localeSelected = true; + else if (selLocale.equals(user.getLocale())) + localeSelected = true; + else + localeSelected = false; + + // roles + roleSelected = isSelectedByRole(selRoles, user.getRoles()); + + // properties + propertySelected = isSelectedByProperty(selPropertyMap, user.getProperties()); + + boolean selected = userIdSelected && usernameSelected && firstnameSelected && lastnameSelected + && userStateSelected && localeSelected && roleSelected && propertySelected; + + if (selected) + result.add(user.asUserRep()); + } + + return result; + } + + /** + * Checks if the given properties contains values which are contained in the selectionMap. If the selectionMap is + * null or empty, then true is returned. If a key/value pair from the selectionMap is not in the properties, then + * false is returned + * + * @param selectionMap + * the map defining the expected properties + * @param properties + * the properties which must be a sub set of selectionMap to have this method return true + * + * @return If the selectionMap is null or empty, then true is returned. If a key/value pair from the selectionMap is + * not in the properties, then false is returned + */ + private boolean isSelectedByProperty(Map selectionMap, Map properties) { + + if (selectionMap == null) + return true; + + if (selectionMap.isEmpty() && properties.isEmpty()) + return true; + + for (String selKey : selectionMap.keySet()) { + + String value = properties.get(selKey); + if (value == null || !value.equals(selectionMap.get(selKey))) + return false; + } + + return true; + } + + /** + * Checks if the given roles contains the given selectionRoles, if this is the case, or selectionRoles is null or + * empty, then true is returned, otherwise false + * + * @param selectionRoles + * the required roles + * @param roles + * the roles to check if they contain the selectionRoles + * + * @return Checks if the given roles contains the given selectionRoles, if this is the case, or selectionRoles is + * null or empty, then true is returned, otherwise false + */ + private boolean isSelectedByRole(Set selectionRoles, Set roles) { + + if (selectionRoles == null) + return true; + + return roles.containsAll(selectionRoles); + } + + @Override + public UserRep addUser(Certificate certificate, UserRep userRep, byte[] password) { + try { + + // validate user actually has this type of privilege + PrivilegeContext prvCtx = getPrivilegeContext(certificate); + prvCtx.assertHasPrivilege(PRIVILEGE_ADD_USER); + + // make sure userId is not set + if (StringHelper.isNotEmpty(userRep.getUserId())) { + String msg = "UserId can not be set when adding a new user!"; + throw new PrivilegeException(MessageFormat.format(msg, userRep.getUsername())); + } + + // set userId + userRep.setUserId(StringHelper.getUniqueId()); + + // first validate user + userRep.validate(); + + validateRolesExist(userRep); + + // validate user does not already exist + if (this.persistenceHandler.getUser(userRep.getUsername()) != null) { + String msg = "User {0} can not be added as it already exists!"; + throw new PrivilegeException(MessageFormat.format(msg, userRep.getUsername())); + } + + String passwordHash = null; + if (password != null) { + + // validate password meets basic requirements + validatePassword(password); + + // hash password + passwordHash = this.encryptionHandler.convertToHash(password); + } + + // create new user + User newUser = createUser(userRep, passwordHash); + + // detect privilege conflicts + assertNoPrivilegeConflict(newUser); + + // validate this user may create such a user + prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_ADD_USER, new Tuple(null, newUser))); + + // delegate to persistence handler + this.persistenceHandler.addUser(newUser); + + return newUser.asUserRep(); + + } finally { + clearPassword(password); + } + } + + @Override + public UserRep replaceUser(Certificate certificate, UserRep userRep, byte[] password) { + try { + + // validate user actually has this type of privilege + PrivilegeContext prvCtx = getPrivilegeContext(certificate); + prvCtx.assertHasPrivilege(PRIVILEGE_MODIFY_USER); + + // first validate user + userRep.validate(); + + validateRolesExist(userRep); + + // validate user exists + User existingUser = this.persistenceHandler.getUser(userRep.getUsername()); + if (existingUser == null) { + String msg = "User {0} can not be replaced as it does not exist!"; + throw new PrivilegeException(MessageFormat.format(msg, userRep.getUsername())); + } + + // validate same userId + if (!existingUser.getUserId().equals(userRep.getUserId())) { + String msg = "UserId of existing user {0} does not match userRep {1}"; + msg = MessageFormat.format(msg, existingUser.getUserId(), userRep.getUserId()); + throw new PrivilegeException(MessageFormat.format(msg, userRep.getUsername())); + } + + String passwordHash = null; + if (password != null) { + + // validate password meets basic requirements + validatePassword(password); + + // hash password + passwordHash = this.encryptionHandler.convertToHash(password); + } + + User newUser = createUser(userRep, passwordHash); + + // detect privilege conflicts + assertNoPrivilegeConflict(newUser); + + // validate this user may modify this user + prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_MODIFY_USER, new Tuple(existingUser, newUser))); + + // delegate to persistence handler + this.persistenceHandler.replaceUser(newUser); + + return newUser.asUserRep(); + + } finally { + clearPassword(password); + } + } + + private void validateRolesExist(UserRep userRep) { + // validate all roles exist + for (String role : userRep.getRoles()) { + if (this.persistenceHandler.getRole(role) == null) { + String msg = "Can not add user {0} as role {1} does not exist!"; + msg = MessageFormat.format(msg, userRep.getUsername(), role); + throw new PrivilegeException(msg); + } + } + } + + private User createUser(UserRep userRep, String passwordHash) { + User user = new User(userRep.getUserId(), userRep.getUsername(), passwordHash, userRep.getFirstname(), + userRep.getLastname(), userRep.getUserState(), userRep.getRoles(), userRep.getLocale(), + userRep.getPropertyMap()); + return user; + } + + @Override + public UserRep updateUser(Certificate certificate, UserRep userRep) + throws AccessDeniedException, PrivilegeException { + + // validate user actually has this type of privilege + PrivilegeContext prvCtx = getPrivilegeContext(certificate); + prvCtx.assertHasPrivilege(PRIVILEGE_MODIFY_USER); + + // get existing user + User existingUser = this.persistenceHandler.getUser(userRep.getUsername()); + if (existingUser == null) { + throw new PrivilegeException(MessageFormat.format("User {0} does not exist!", userRep.getUsername())); //$NON-NLS-1$ + } + + // if nothing to do, then stop + if (StringHelper.isEmpty(userRep.getFirstname()) && StringHelper.isEmpty(userRep.getLastname()) + && userRep.getLocale() == null + && (userRep.getProperties() == null || userRep.getProperties().isEmpty())) { + throw new PrivilegeException(MessageFormat.format("All updateable fields are empty for update of user {0}", //$NON-NLS-1$ + userRep.getUsername())); + } + + String userId = existingUser.getUserId(); + String username = existingUser.getUsername(); + String password = existingUser.getPassword(); + String firstname = existingUser.getFirstname(); + String lastname = existingUser.getLastname(); + UserState userState = existingUser.getUserState(); + Set roles = existingUser.getRoles(); + Locale locale = existingUser.getLocale(); + Map propertyMap = existingUser.getProperties(); + + // get updated fields + if (StringHelper.isNotEmpty(userRep.getFirstname())) + firstname = userRep.getFirstname(); + if (StringHelper.isNotEmpty(userRep.getLastname())) + lastname = userRep.getLastname(); + if (userRep.getLocale() != null) + locale = userRep.getLocale(); + if (userRep.getProperties() != null && !userRep.getProperties().isEmpty()) + propertyMap = userRep.getPropertyMap(); + + // create new user + User newUser = new User(userId, username, password, firstname, lastname, userState, roles, locale, propertyMap); + + // detect privilege conflicts + assertNoPrivilegeConflict(newUser); + + // validate this user may modify this user + prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_MODIFY_USER, new Tuple(existingUser, newUser))); + + // delegate to persistence handler + this.persistenceHandler.replaceUser(newUser); + + return newUser.asUserRep(); + } + + @Override + public UserRep removeUser(Certificate certificate, String username) { + + // validate user actually has this type of privilege + PrivilegeContext prvCtx = getPrivilegeContext(certificate); + prvCtx.assertHasPrivilege(PRIVILEGE_REMOVE_USER); + + // validate user exists + User existingUser = this.persistenceHandler.getUser(username); + if (existingUser == null) { + String msg = "Can not remove User {0} because user does not exist!"; + throw new PrivilegeException(MessageFormat.format(msg, username)); + } + + // validate this user may remove this user + prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_REMOVE_USER, new Tuple(null, existingUser))); + + // delegate user removal to persistence handler + this.persistenceHandler.removeUser(username); + + return existingUser.asUserRep(); + } + + @Override + public UserRep addRoleToUser(Certificate certificate, String username, String roleName) { + + // validate user actually has this type of privilege + PrivilegeContext prvCtx = getPrivilegeContext(certificate); + prvCtx.assertHasPrivilege(PRIVILEGE_ADD_ROLE_TO_USER); + + // get user + User existingUser = this.persistenceHandler.getUser(username); + if (existingUser == null) { + throw new PrivilegeException(MessageFormat.format("User {0} does not exist!", username)); //$NON-NLS-1$ + } + + // validate that this user may add this role to this user + prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_ADD_ROLE_TO_USER, new Tuple(existingUser, roleName))); + + // check that user not already has role + Set currentRoles = existingUser.getRoles(); + if (currentRoles.contains(roleName)) { + String msg = MessageFormat.format("User {0} already has role {1}", username, roleName); //$NON-NLS-1$ + throw new PrivilegeException(msg); + } + + // validate that the role exists + if (this.persistenceHandler.getRole(roleName) == null) { + String msg = MessageFormat.format("Role {0} does not exist!", roleName); //$NON-NLS-1$ + throw new PrivilegeException(msg); + } + + // create new user + Set newRoles = new HashSet<>(currentRoles); + newRoles.add(roleName); + + User newUser = new User(existingUser.getUserId(), existingUser.getUsername(), existingUser.getPassword(), + existingUser.getFirstname(), existingUser.getLastname(), existingUser.getUserState(), newRoles, + existingUser.getLocale(), existingUser.getProperties()); + + // detect privilege conflicts + assertNoPrivilegeConflict(newUser); + + // delegate user replacement to persistence handler + this.persistenceHandler.replaceUser(newUser); + + return newUser.asUserRep(); + } + + @Override + public UserRep removeRoleFromUser(Certificate certificate, String username, String roleName) { + + // validate user actually has this type of privilege + PrivilegeContext prvCtx = getPrivilegeContext(certificate); + prvCtx.assertHasPrivilege(PRIVILEGE_REMOVE_ROLE_FROM_USER); + + // get User + User existingUser = this.persistenceHandler.getUser(username); + if (existingUser == null) { + throw new PrivilegeException(MessageFormat.format("User {0} does not exist!", username)); //$NON-NLS-1$ + } + + // validate that this user may remove this role from this user + prvCtx.validateAction( + new SimpleRestrictable(PRIVILEGE_REMOVE_ROLE_FROM_USER, new Tuple(existingUser, roleName))); + + // ignore if user does not have role + Set currentRoles = existingUser.getRoles(); + if (!currentRoles.contains(roleName)) { + String msg = MessageFormat.format("User {0} does not have role {1}", existingUser.getUsername(), roleName); //$NON-NLS-1$ + throw new PrivilegeException(msg); + } + + // create new user + Set newRoles = new HashSet<>(currentRoles); + newRoles.remove(roleName); + User newUser = new User(existingUser.getUserId(), existingUser.getUsername(), existingUser.getPassword(), + existingUser.getFirstname(), existingUser.getLastname(), existingUser.getUserState(), newRoles, + existingUser.getLocale(), existingUser.getProperties()); + + // delegate user replacement to persistence handler + this.persistenceHandler.replaceUser(newUser); + + return newUser.asUserRep(); + } + + @Override + public UserRep setUserLocale(Certificate certificate, String username, Locale locale) { + + // validate user actually has this type of privilege + PrivilegeContext prvCtx = getPrivilegeContext(certificate); + prvCtx.assertHasPrivilege(PRIVILEGE_SET_USER_LOCALE); + + // get User + User existingUser = this.persistenceHandler.getUser(username); + if (existingUser == null) { + throw new PrivilegeException(MessageFormat.format("User {0} does not exist!", username)); //$NON-NLS-1$ + } + + // create new user + User newUser = new User(existingUser.getUserId(), existingUser.getUsername(), existingUser.getPassword(), + existingUser.getFirstname(), existingUser.getLastname(), existingUser.getUserState(), + existingUser.getRoles(), locale, existingUser.getProperties()); + + // if the user is not setting their own locale, then make sure this user may set this user's locale + if (!certificate.getUsername().equals(username)) { + prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_SET_USER_LOCALE, new Tuple(existingUser, newUser))); + } + + // delegate user replacement to persistence handler + this.persistenceHandler.replaceUser(newUser); + + // perform automatic persisting, if enabled + if (this.autoPersistOnUserChangesData) { + this.persistenceHandler.persist(); + } + + return newUser.asUserRep(); + } + + @Override + public void setUserPassword(Certificate certificate, String username, byte[] password) { + try { + + // validate user actually has this type of privilege + PrivilegeContext prvCtx = getPrivilegeContext(certificate); + prvCtx.assertHasPrivilege(PRIVILEGE_SET_USER_PASSWORD); + + // get User + User existingUser = this.persistenceHandler.getUser(username); + if (existingUser == null) { + throw new PrivilegeException(MessageFormat.format("User {0} does not exist!", username)); //$NON-NLS-1$ + } + + String passwordHash = null; + if (password != null) { + + // validate password meets basic requirements + validatePassword(password); + + // hash password + passwordHash = this.encryptionHandler.convertToHash(password); + } + + // create new user + User newUser = new User(existingUser.getUserId(), existingUser.getUsername(), passwordHash, + existingUser.getFirstname(), existingUser.getLastname(), existingUser.getUserState(), + existingUser.getRoles(), existingUser.getLocale(), existingUser.getProperties()); + + // if the user is not setting their own password, then make sure this user may set this user's password + if (!certificate.getUsername().equals(username)) { + prvCtx.validateAction( + new SimpleRestrictable(PRIVILEGE_SET_USER_PASSWORD, new Tuple(existingUser, newUser))); + } + + // delegate user replacement to persistence handler + this.persistenceHandler.replaceUser(newUser); + + // perform automatic persisting, if enabled + if (this.autoPersistOnUserChangesData) { + this.persistenceHandler.persist(); + } + + } finally { + clearPassword(password); + } + } + + @Override + public UserRep setUserState(Certificate certificate, String username, UserState state) { + + // validate user actually has this type of privilege + PrivilegeContext prvCtx = getPrivilegeContext(certificate); + prvCtx.assertHasPrivilege(PRIVILEGE_SET_USER_STATE); + + // get User + User existingUser = this.persistenceHandler.getUser(username); + if (existingUser == null) { + throw new PrivilegeException(MessageFormat.format("User {0} does not exist!", username)); //$NON-NLS-1$ + } + + // create new user + User newUser = new User(existingUser.getUserId(), existingUser.getUsername(), existingUser.getPassword(), + existingUser.getFirstname(), existingUser.getLastname(), state, existingUser.getRoles(), + existingUser.getLocale(), existingUser.getProperties()); + + // validate that this user may modify this user's state + prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_SET_USER_STATE, new Tuple(existingUser, newUser))); + + // delegate user replacement to persistence handler + this.persistenceHandler.replaceUser(newUser); + + return newUser.asUserRep(); + } + + @Override + public RoleRep addRole(Certificate certificate, RoleRep roleRep) { + + // validate user actually has this type of privilege + PrivilegeContext prvCtx = getPrivilegeContext(certificate); + prvCtx.assertHasPrivilege(PRIVILEGE_ADD_ROLE); + + // first validate role + roleRep.validate(); + + // validate role does not exist + if (this.persistenceHandler.getRole(roleRep.getName()) != null) { + String msg = MessageFormat.format("Can not add role {0} as it already exists!", roleRep.getName()); + throw new PrivilegeException(msg); + } + + // create new role from RoleRep + Role newRole = new Role(roleRep); + + // validate that this user may add this new role + prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_ADD_ROLE, new Tuple(null, newRole))); + + // validate policy if not null + validatePolicies(newRole); + + // delegate to persistence handler + this.persistenceHandler.addRole(newRole); + + return newRole.asRoleRep(); + } + + @Override + public RoleRep replaceRole(Certificate certificate, RoleRep roleRep) { + + // validate user actually has this type of privilege + PrivilegeContext prvCtx = getPrivilegeContext(certificate); + prvCtx.assertHasPrivilege(PRIVILEGE_MODIFY_ROLE); + + // first validate role + roleRep.validate(); + + // validate role does exist + Role existingRole = this.persistenceHandler.getRole(roleRep.getName()); + if (existingRole == null) { + String msg = MessageFormat.format("Can not replace role {0} as it does not exist!", roleRep.getName()); + throw new PrivilegeException(msg); + } + + // create new role from RoleRep + Role newRole = new Role(roleRep); + + // detect privilege conflicts + assertNoPrivilegeConflict(newRole); + + // validate that this user may modify this role + prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_MODIFY_ROLE, new Tuple(existingRole, newRole))); + + // validate policy if not null + validatePolicies(newRole); + + // delegate to persistence handler + this.persistenceHandler.replaceRole(newRole); + + return newRole.asRoleRep(); + } + + @Override + public RoleRep removeRole(Certificate certificate, String roleName) { + + // validate user actually has this type of privilege + PrivilegeContext prvCtx = getPrivilegeContext(certificate); + prvCtx.assertHasPrivilege(PRIVILEGE_REMOVE_ROLE); + + // validate no user is using this role + Set roles = new HashSet<>(Arrays.asList(roleName)); + UserRep selector = new UserRep(null, null, null, null, null, roles, null, null); + List usersWithRole = queryUsers(certificate, selector); + if (!usersWithRole.isEmpty()) { + String usersS = usersWithRole.stream().map(UserRep::getUsername).collect(Collectors.joining(", ")); + String msg = "The role {0} can not be removed as the following {1} user have the role assigned: {2}"; + msg = MessageFormat.format(msg, roleName, usersWithRole.size(), usersS); + throw new PrivilegeException(msg); + } + + // validate role exists + Role existingRole = this.persistenceHandler.getRole(roleName); + if (existingRole == null) { + String msg = "Can not remove Role {0} because role does not exist!"; + throw new PrivilegeException(MessageFormat.format(msg, roleName)); + } + + // validate that this user may remove this role + prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_REMOVE_ROLE, new Tuple(null, existingRole))); + + // delegate role removal to persistence handler + this.persistenceHandler.removeRole(roleName); + + return existingRole.asRoleRep(); + } + + @Override + public RoleRep addOrReplacePrivilegeOnRole(Certificate certificate, String roleName, PrivilegeRep privilegeRep) { + + // validate user actually has this type of privilege + PrivilegeContext prvCtx = getPrivilegeContext(certificate); + prvCtx.assertHasPrivilege(PRIVILEGE_MODIFY_ROLE); + + // validate PrivilegeRep + privilegeRep.validate(); + + // get role + Role existingRole = this.persistenceHandler.getRole(roleName); + if (existingRole == null) { + String msg = MessageFormat.format("Role {0} does not exist!", roleName); //$NON-NLS-1$ + throw new PrivilegeException(msg); + } + + // validate that policy exists if needed + String policy = privilegeRep.getPolicy(); + if (policy != null && !this.policyMap.containsKey(policy)) { + String msg = "Policy {0} for Privilege {1} does not exist"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, policy, privilegeRep.getName()); + throw new PrivilegeException(msg); + } + + // create new role with the additional privilege + IPrivilege newPrivilege = new PrivilegeImpl(privilegeRep); + + // copy existing privileges + Set existingPrivilegeNames = existingRole.getPrivilegeNames(); + Map privilegeMap = new HashMap<>(existingPrivilegeNames.size() + 1); + for (String name : existingPrivilegeNames) { + IPrivilege privilege = existingRole.getPrivilege(name); + privilegeMap.put(name, privilege); + } + + // add new one + privilegeMap.put(newPrivilege.getName(), newPrivilege); + + // create new role + Role newRole = new Role(existingRole.getName(), privilegeMap); + + // detect privilege conflicts + assertNoPrivilegeConflict(newRole); + + // validate that this user may modify this role + prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_MODIFY_ROLE, new Tuple(existingRole, newRole))); + + // delegate role replacement to persistence handler + this.persistenceHandler.replaceRole(newRole); + + return newRole.asRoleRep(); + } + + @Override + public RoleRep removePrivilegeFromRole(Certificate certificate, String roleName, String privilegeName) { + + // validate user actually has this type of privilege + PrivilegeContext prvCtx = getPrivilegeContext(certificate); + prvCtx.assertHasPrivilege(PRIVILEGE_MODIFY_ROLE); + + // get role + Role existingRole = this.persistenceHandler.getRole(roleName); + if (existingRole == null) { + throw new PrivilegeException(MessageFormat.format("Role {0} does not exist!", roleName)); //$NON-NLS-1$ + } + + // ignore if role does not have privilege + if (!existingRole.hasPrivilege(privilegeName)) { + String msg = MessageFormat.format("Role {0} does not have Privilege {1}", roleName, privilegeName); //$NON-NLS-1$ + throw new PrivilegeException(msg); + } + + // create new set of privileges with out the to removed privilege + Set privilegeNames = existingRole.getPrivilegeNames(); + Map newPrivileges = new HashMap<>(privilegeNames.size() - 1); + for (String name : privilegeNames) { + IPrivilege privilege = existingRole.getPrivilege(name); + if (!privilege.getName().equals(privilegeName)) + newPrivileges.put(privilege.getName(), privilege); + } + + // create new role + Role newRole = new Role(existingRole.getName(), newPrivileges); + + // validate that this user may modify this role + prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_MODIFY_ROLE, new Tuple(existingRole, newRole))); + + // delegate user replacement to persistence handler + this.persistenceHandler.replaceRole(newRole); + + return newRole.asRoleRep(); + } + + @Override + public Certificate authenticate(String username, byte[] password) { + + try { + // username must be at least 2 characters in length + if (username == null || username.length() < 2) { + String msg = MessageFormat.format("The given username ''{0}'' is shorter than 2 characters", username); //$NON-NLS-1$ + throw new PrivilegeException(msg); + } + + // check the password + User user = checkCredentialsAndUserState(username, password); + + // validate user has at least one role + Set userRoles = user.getRoles(); + if (userRoles.isEmpty()) { + throw new PrivilegeException( + MessageFormat.format("User {0} does not have any roles defined!", username)); //$NON-NLS-1$ + } + + // get 2 auth tokens + String authToken = this.encryptionHandler.convertToHash(this.encryptionHandler.nextToken()); + + // get next session id + String sessionId = UUID.randomUUID().toString(); + + // create a new certificate, with details of the user + Certificate certificate = new Certificate(sessionId, username, user.getFirstname(), user.getLastname(), + user.getUserState(), authToken, new Date(), user.getLocale(), userRoles, + new HashMap<>(user.getProperties())); + certificate.setLastAccess(new Date()); + + PrivilegeContext privilegeContext = buildPrivilegeContext(certificate, user); + this.privilegeContextMap.put(sessionId, privilegeContext); + + persistSessions(); + + // log + DefaultPrivilegeHandler.logger + .info(MessageFormat.format("User {0} authenticated: {1}", username, certificate)); //$NON-NLS-1$ + + // return the certificate + return certificate; + + } catch (RuntimeException e) { + String msg = "User {0} Failed to authenticate: {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, username, e.getMessage()); + DefaultPrivilegeHandler.logger.error(msg); + throw e; + } finally { + clearPassword(password); + } + } + + private boolean persistSessions() { + if (!this.persistSessions) + return false; + + List sessions = this.privilegeContextMap.values().stream().map(p -> p.getCertificate()) + .filter(c -> !c.getUserState().isSystem()).collect(Collectors.toList()); + + try (FileOutputStream fout = new FileOutputStream(this.persistSessionsPath); + OutputStream outputStream = AesCryptoHelper.wrapEncrypt(this.secretKey, fout)) { + + CertificateStubsDomWriter writer = new CertificateStubsDomWriter(sessions, outputStream); + writer.write(); + outputStream.flush(); + + } catch (Exception e) { + throw new PrivilegeException("Failed to persist sessions!", e); + } + + return true; + } + + private boolean loadSessions() { + if (!this.persistSessions) { + logger.info("Persisteding of sessions not enabled, so not loading!."); + return false; + } + + if (!this.persistSessionsPath.exists()) { + logger.info("No persisted sessions exist to be loaded."); + return false; + } + + if (!this.persistSessionsPath.isFile()) + throw new PrivilegeException( + "Sessions data file is not a file but exists at " + this.persistSessionsPath.getAbsolutePath()); + + List certificateStubs; + try (FileInputStream fin = new FileInputStream(this.persistSessionsPath); + InputStream inputStream = AesCryptoHelper.wrapDecrypt(this.secretKey, fin)) { + + CertificateStubsSaxReader reader = new CertificateStubsSaxReader(inputStream); + certificateStubs = reader.read(); + + } catch (Exception e) { + logger.error("Failed to load sessions!", e); + this.persistSessionsPath.delete(); + return false; + } + + if (certificateStubs.isEmpty()) { + logger.info("No persisted sessions exist to be loaded."); + return false; + } + + for (CertificateStub certificateStub : certificateStubs) { + String username = certificateStub.getUsername(); + String sessionId = certificateStub.getSessionId(); + String authToken = certificateStub.getAuthToken(); + User user = this.persistenceHandler.getUser(username); + if (user == null) { + logger.error("Ignoring session data for missing user " + username); + continue; + } + + Set userRoles = user.getRoles(); + if (userRoles.isEmpty()) { + logger.error("Ignoring session data for user " + username + " which has not roles defined!"); + continue; + } + + // create a new certificate, with details of the user + Certificate certificate = new Certificate(sessionId, username, user.getFirstname(), user.getLastname(), + user.getUserState(), authToken, certificateStub.getLoginTime(), certificateStub.getLocale(), + userRoles, new HashMap<>(user.getProperties())); + certificate.setLastAccess(certificateStub.getLastAccess()); + + PrivilegeContext privilegeContext = buildPrivilegeContext(certificate, user); + this.privilegeContextMap.put(sessionId, privilegeContext); + } + + logger.info("Loaded " + this.privilegeContextMap.size() + " sessions."); + return true; + } + + /** + * Checks the credentials and validates that the user may log in. + * + * @param username + * the username of the {@link User} to check against + * @param password + * the password of this user + * + * @return the {@link User} if the credentials are valid and the user may login + * + * @throws AccessDeniedException + * if anything is wrong with the credentials or the user state + * @throws InvalidCredentialsException + * if the given credentials are invalid, the user does not exist, or has no password set + */ + private User checkCredentialsAndUserState(String username, byte[] password) + throws InvalidCredentialsException, AccessDeniedException { + + // and validate the password + validatePassword(password); + + // we only work with hashed passwords + String passwordHash = this.encryptionHandler.convertToHash(password); + + // get user object + User user = this.persistenceHandler.getUser(username); + // no user means no authentication + if (user == null) { + String msg = MessageFormat.format("There is no user defined with the username {0}", username); //$NON-NLS-1$ + throw new InvalidCredentialsException(msg); + } + + // make sure not a system user - they may not login in + if (user.getUserState() == UserState.SYSTEM) { + String msg = "User {0} is a system user and may not login!"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, username); + throw new InvalidCredentialsException(msg); + } + + // validate password + String pwHash = user.getPassword(); + if (pwHash == null) + throw new AccessDeniedException( + MessageFormat.format("User {0} has no password and may not login!", username)); //$NON-NLS-1$ + if (!pwHash.equals(passwordHash)) + throw new InvalidCredentialsException(MessageFormat.format("Password is incorrect for {0}", username)); //$NON-NLS-1$ + + // validate if user is allowed to login + // this also capture the trying to login of SYSTEM user + if (user.getUserState() != UserState.ENABLED) { + String msg = "User {0} does not have state {1} and can not login!"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, username, UserState.ENABLED); + throw new AccessDeniedException(msg); + } + + return user; + } + + /** + * Builds a {@link PrivilegeContext} for the given {@link User} and its {@link Certificate} + * + * @param certificate + * @param user + * + * @return + */ + private PrivilegeContext buildPrivilegeContext(Certificate certificate, User user) { + + Set userRoles = user.getRoles(); + Map privileges = new HashMap<>(); + Map policies = new HashMap<>(); + + // get a cache of the privileges and policies for this user + for (String roleName : userRoles) { + Role role = this.persistenceHandler.getRole(roleName); + Set privilegeNames = role.getPrivilegeNames(); + for (String privilegeName : privilegeNames) { + + IPrivilege privilege = role.getPrivilege(privilegeName); + if (privilege == null) { + String msg = "The Privilege {0} does not exist for role {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, privilegeName, roleName); + throw new PrivilegeException(msg); + } + + // cache the privilege + if (privileges.containsKey(privilegeName)) { + if (this.privilegeConflictResolution.isStrict()) { + String msg = "User has conflicts for privilege {0} with role {1}"; + msg = MessageFormat.format(msg, privilegeName, roleName); + throw new PrivilegeException(msg); + } + + IPrivilege priv = privileges.get(privilegeName); + boolean allAllowed = priv.isAllAllowed() || privilege.isAllAllowed(); + Set allowList; + Set denyList; + if (allAllowed) { + allowList = Collections.emptySet(); + denyList = Collections.emptySet(); + } else { + allowList = new HashSet<>(priv.getAllowList()); + allowList.addAll(privilege.getAllowList()); + denyList = new HashSet<>(priv.getDenyList()); + denyList.addAll(privilege.getDenyList()); + } + priv = new PrivilegeImpl(priv.getName(), priv.getPolicy(), allAllowed, denyList, allowList); + + privileges.put(privilegeName, priv); + continue; + } + + privileges.put(privilegeName, privilege); + + // cache the policy for the privilege + String policyName = privilege.getPolicy(); + if (policies.containsKey(policyName)) + continue; + + PrivilegePolicy policy = getPolicy(policyName); + if (policy == null) { + String msg = "The Policy {0} does not exist for Privilege {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, policyName, privilegeName); + throw new PrivilegeException(msg); + } + policies.put(policyName, policy); + } + } + + UserRep userRep = user.asUserRep(); + PrivilegeContext privilegeContext = new PrivilegeContext(userRep, certificate, privileges, policies); + return privilegeContext; + } + + @Override + public boolean invalidateSession(Certificate certificate) { + + // first validate certificate + isCertificateValid(certificate); + + // remove registration + PrivilegeContext privilegeContext = this.privilegeContextMap.remove(certificate.getSessionId()); + + // persist sessions + persistSessions(); + + // return true if object was really removed + boolean loggedOut = privilegeContext != null; + if (loggedOut) + DefaultPrivilegeHandler.logger + .info(MessageFormat.format("User {0} logged out.", certificate.getUsername())); //$NON-NLS-1$ + else + DefaultPrivilegeHandler.logger.warn("User already logged out!"); //$NON-NLS-1$ + return loggedOut; + } + + @Override + public void isCertificateValid(Certificate certificate) { + + // certificate must not be null + if (certificate == null) + throw new PrivilegeException("Certificate may not be null!"); //$NON-NLS-1$ + + // first see if a session exists for this certificate + PrivilegeContext privilegeContext = this.privilegeContextMap.get(certificate.getSessionId()); + if (privilegeContext == null) { + String msg = MessageFormat.format("There is no session information for {0}", certificate); //$NON-NLS-1$ + throw new AccessDeniedException(msg); + } + + // validate certificate has not been tampered with + Certificate sessionCertificate = privilegeContext.getCertificate(); + if (!sessionCertificate.equals(certificate)) { + String msg = "Received illegal certificate for session id {0}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, certificate.getSessionId()); + throw new PrivilegeException(msg); + } + + // get user object + User user = this.persistenceHandler.getUser(privilegeContext.getUsername()); + + // if user exists, then certificate is valid + if (user == null) { + String msg = "Oh boy, how did this happen: No User in user map although the certificate is valid!"; //$NON-NLS-1$ + throw new PrivilegeException(msg); + } + + // everything is ok + } + + @Override + public void checkPassword(Certificate certificate, byte[] password) throws PrivilegeException { + try { + isCertificateValid(certificate); + checkCredentialsAndUserState(certificate.getUsername(), password); + } finally { + clearPassword(password); + } + } + + @Override + public PrivilegeContext getPrivilegeContext(Certificate certificate) throws PrivilegeException { + + // first validate certificate + isCertificateValid(certificate); + + return this.privilegeContextMap.get(certificate.getSessionId()); + } + + /** + * This simple implementation validates that the password is not null, and that the password string is not empty + * + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#validatePassword(byte[]) + */ + @Override + public void validatePassword(byte[] password) throws PrivilegeException { + + if (password == null || password.length == 0) { + throw new PrivilegeException("A password may not be empty!"); //$NON-NLS-1$ + } + + if (password.length < 3) { + throw new PrivilegeException("The given password is shorter than 3 characters"); //$NON-NLS-1$ + } + } + + @Override + public boolean persist(Certificate certificate) { + + // validate who is doing this + PrivilegeContext prvCtx = getPrivilegeContext(certificate); + prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_ACTION, PRIVILEGE_ACTION_PERSIST)); + + return this.persistenceHandler.persist(); + } + + @Override + public boolean persistSessions(Certificate certificate) { + + // validate who is doing this + PrivilegeContext prvCtx = getPrivilegeContext(certificate); + prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_ACTION, PRIVILEGE_ACTION_PERSIST_SESSIONS)); + + return persistSessions(); + } + + @Override + public boolean reload(Certificate certificate) { + + // validate who is doing this + PrivilegeContext prvCtx = getPrivilegeContext(certificate); + prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_ACTION, PRIVILEGE_ACTION_RELOAD)); + + return this.persistenceHandler.reload(); + } + + /** + * Initializes the concrete {@link EncryptionHandler}. The passed parameter map contains any configuration this + * {@link PrivilegeHandler} might need. This method may only be called once and this must be enforced by the + * concrete implementation + * + * @param parameterMap + * a map containing configuration properties + * @param encryptionHandler + * the {@link EncryptionHandler} instance for this {@link PrivilegeHandler} + * @param persistenceHandler + * the {@link PersistenceHandler} instance for this {@link PrivilegeHandler} + * @param policyMap + * map of {@link PrivilegePolicy} classes + * + * @throws PrivilegeException + * if the this method is called multiple times or an initialization exception occurs + */ + public synchronized void initialize(Map parameterMap, EncryptionHandler encryptionHandler, + PersistenceHandler persistenceHandler, Map> policyMap) { + + if (this.initialized) + throw new PrivilegeException("Already initialized!"); //$NON-NLS-1$ + + this.policyMap = policyMap; + this.encryptionHandler = encryptionHandler; + this.persistenceHandler = persistenceHandler; + + handleAutoPersistOnUserDataChange(parameterMap); + handlePersistSessionsParam(parameterMap); + handleConflictResolutionParam(parameterMap); + handleSecretParams(parameterMap); + + // validate policies on privileges of Roles + for (Role role : persistenceHandler.getAllRoles()) { + validatePolicies(role); + } + + // validate privilege conflicts + validatePrivilegeConflicts(); + + this.privilegeContextMap = Collections.synchronizedMap(new HashMap()); + + loadSessions(); + + this.initialized = true; + } + + private void handleAutoPersistOnUserDataChange(Map parameterMap) { + String autoPersistS = parameterMap.get(PARAM_AUTO_PERSIST_ON_USER_CHANGES_DATA); + if (StringHelper.isEmpty(autoPersistS) || autoPersistS.equals(Boolean.FALSE.toString())) { + this.autoPersistOnUserChangesData = false; + } else if (autoPersistS.equals(Boolean.TRUE.toString())) { + this.autoPersistOnUserChangesData = true; + logger.info("Enabling automatic persistence when user changes their data."); //$NON-NLS-1$ + } else { + String msg = "Parameter {0} has illegal value {1}. Overriding with {2}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, PARAM_AUTO_PERSIST_ON_USER_CHANGES_DATA, autoPersistS, Boolean.FALSE); + logger.error(msg); + this.autoPersistOnUserChangesData = false; + } + } + + private void handlePersistSessionsParam(Map parameterMap) { + String persistSessionsS = parameterMap.get(PARAM_PERSIST_SESSIONS); + if (StringHelper.isEmpty(persistSessionsS) || persistSessionsS.equals(Boolean.FALSE.toString())) { + this.persistSessions = false; + } else if (persistSessionsS.equals(Boolean.TRUE.toString())) { + this.persistSessions = true; + + String persistSessionsPathS = parameterMap.get(PARAM_PERSIST_SESSIONS_PATH); + if (StringHelper.isEmpty(persistSessionsPathS)) { + String msg = "Parameter {0} has illegal value {1}."; //$NON-NLS-1$ + msg = MessageFormat.format(msg, PARAM_PERSIST_SESSIONS_PATH, persistSessionsPathS); + throw new PrivilegeException(msg); + } + + File persistSessionsPath = new File(persistSessionsPathS); + if (!persistSessionsPath.getParentFile().isDirectory()) { + String msg = "Path for param {0} is invalid as parent does not exist or is not a directory. Value: {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, PARAM_PERSIST_SESSIONS_PATH, persistSessionsPath.getAbsolutePath()); + throw new PrivilegeException(msg); + } + + if (persistSessionsPath.exists() && (!persistSessionsPath.isFile() || !persistSessionsPath.canWrite())) { + String msg = "Path for param {0} is invalid as file exists but is not a file or not writeable. Value: {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, PARAM_PERSIST_SESSIONS_PATH, persistSessionsPath.getAbsolutePath()); + throw new PrivilegeException(msg); + } + + this.persistSessionsPath = persistSessionsPath; + logger.info(MessageFormat.format("Enabling persistence of sessions to {0}", //$NON-NLS-1$ + this.persistSessionsPath.getAbsolutePath())); + } else { + String msg = "Parameter {0} has illegal value {1}. Overriding with {2}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, PARAM_PERSIST_SESSIONS, persistSessionsS, Boolean.FALSE); + logger.error(msg); + this.persistSessions = false; + } + } + + private void handleConflictResolutionParam(Map parameterMap) { + String privilegeConflictResolutionS = parameterMap.get(PARAM_PRIVILEGE_CONFLICT_RESOLUTION); + if (privilegeConflictResolutionS == null) { + this.privilegeConflictResolution = PrivilegeConflictResolution.STRICT; + String msg = "No {0} parameter defined. Using {1}"; + msg = MessageFormat.format(msg, PARAM_PRIVILEGE_CONFLICT_RESOLUTION, this.privilegeConflictResolution); + logger.info(msg); + } else { + try { + this.privilegeConflictResolution = PrivilegeConflictResolution.valueOf(privilegeConflictResolutionS); + } catch (Exception e) { + String msg = "Parameter {0} has illegal value {1}."; //$NON-NLS-1$ + msg = MessageFormat.format(msg, PARAM_PRIVILEGE_CONFLICT_RESOLUTION, privilegeConflictResolutionS); + throw new PrivilegeException(msg); + } + } + logger.info("Privilege conflict resolution set to " + this.privilegeConflictResolution); //$NON-NLS-1$ + } + + private void handleSecretParams(Map parameterMap) { + + if (!this.persistSessions) + return; + + String secretKeyS = parameterMap.get(PARAM_SECRET_KEY); + if (StringHelper.isEmpty(secretKeyS)) { + String msg = "Parameter {0} may not be empty if parameter {1} is enabled."; //$NON-NLS-1$ + msg = MessageFormat.format(msg, PARAM_SECRET_KEY, PARAM_PRIVILEGE_CONFLICT_RESOLUTION); + throw new PrivilegeException(msg); + } + + String secretSaltS = parameterMap.get(PARAM_SECRET_SALT); + if (StringHelper.isEmpty(secretSaltS)) { + String msg = "Parameter {0} may not be empty if parameter {1} is enabled."; //$NON-NLS-1$ + msg = MessageFormat.format(msg, PARAM_SECRET_SALT, PARAM_PRIVILEGE_CONFLICT_RESOLUTION); + throw new PrivilegeException(msg); + } + + this.secretKey = AesCryptoHelper.buildSecret(secretKeyS.toCharArray(), secretSaltS.getBytes()); + } + + private void validatePrivilegeConflicts() { + if (!this.privilegeConflictResolution.isStrict()) { + return; + } + + List conflicts = new ArrayList<>(); + List users = this.persistenceHandler.getAllUsers(); + for (User user : users) { + Map privilegeNames = new HashMap<>(); + conflicts.addAll(detectPrivilegeConflicts(privilegeNames, user)); + } + + if (!conflicts.isEmpty()) { + for (String conflict : conflicts) { + logger.error(conflict); + } + throw new PrivilegeException("There are " + conflicts.size() + " privilege conflicts!"); + } + } + + private void assertNoPrivilegeConflict(User user) { + if (this.privilegeConflictResolution.isStrict()) { + Map privilegeNames = new HashMap<>(); + List conflicts = detectPrivilegeConflicts(privilegeNames, user); + if (!conflicts.isEmpty()) { + String msg = conflicts.stream().collect(Collectors.joining("\n")); + throw new PrivilegeException(msg); + } + } + } + + private void assertNoPrivilegeConflict(Role role) { + if (!this.privilegeConflictResolution.isStrict()) + return; + + Map privilegeNames = new HashMap<>(); + for (String privilegeName : role.getPrivilegeNames()) { + privilegeNames.put(privilegeName, role.getName()); + } + + List conflicts = new ArrayList<>(); + List users = this.persistenceHandler.getAllUsers(); + for (User user : users) { + if (user.hasRole(role.getName())) + conflicts.addAll(detectPrivilegeConflicts(privilegeNames, user)); + } + + if (!conflicts.isEmpty()) { + String msg = conflicts.stream().collect(Collectors.joining("\n")); + throw new PrivilegeException(msg); + } + } + + private List detectPrivilegeConflicts(Map privilegeNames, User user) { + List conflicts = new ArrayList<>(); + + Set userRoles = user.getRoles(); + for (String roleName : userRoles) { + Role role = this.persistenceHandler.getRole(roleName); + for (String privilegeName : role.getPrivilegeNames()) { + if (!privilegeNames.containsKey(privilegeName)) { + privilegeNames.put(privilegeName, roleName); + } else { + String roleOrigin = privilegeNames.get(privilegeName); + String msg = "User {0} has conflicts for privilege {1} on roles {2} and {3}"; + msg = MessageFormat.format(msg, user.getUsername(), privilegeName, roleOrigin, roleName); + conflicts.add(msg); + } + } + } + + return conflicts; + } + + /** + * Validates that the policies which are not null on the privileges of the role exist + * + * @param role + * the role for which the policies are to be checked + */ + private void validatePolicies(Role role) { + for (String privilegeName : role.getPrivilegeNames()) { + IPrivilege privilege = role.getPrivilege(privilegeName); + String policy = privilege.getPolicy(); + if (policy != null && !this.policyMap.containsKey(policy)) { + String msg = "Policy {0} for Privilege {1} does not exist on role {2}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, policy, privilege.getName(), role); + throw new PrivilegeException(msg); + } + } + } + + /** + * Passwords should not be kept as strings, as string are immutable, this method thus clears the byte array so that + * the password is not in memory anymore + * + * @param password + * the byte array containing the passwort which is to be set to zeroes + */ + private void clearPassword(byte[] password) { + if (password != null) { + for (int i = 0; i < password.length; i++) { + password[i] = 0; + } + } + } + + @Override + public T runAsSystem(String systemUsername, T action) throws PrivilegeException { + + if (systemUsername == null) + throw new PrivilegeException("systemUsername may not be null!"); //$NON-NLS-1$ + if (action == null) + throw new PrivilegeException("action may not be null!"); //$NON-NLS-1$ + + // get the system user + User systemUser = this.persistenceHandler.getUser(systemUsername); + if (systemUser == null) + throw new PrivilegeException(MessageFormat.format("System user {0} does not exist!", systemUsername)); //$NON-NLS-1$ + + // validate this is a system user + if (systemUser.getUserState() != UserState.SYSTEM) + throw new PrivilegeException(MessageFormat.format("User {0} is not a System user!", systemUsername)); //$NON-NLS-1$ + + // get privilegeContext for this system user + PrivilegeContext systemUserPrivilegeContext = getSystemUserPrivilegeContext(systemUsername); + + // validate this system user may perform the given action + systemUserPrivilegeContext.validateAction(action); + + String sessionId = systemUserPrivilegeContext.getCertificate().getSessionId(); + this.privilegeContextMap.put(sessionId, systemUserPrivilegeContext); + try { + // perform the action + action.execute(systemUserPrivilegeContext); + } finally { + this.privilegeContextMap.remove(sessionId); + } + + return action; + } + + /** + * Returns the {@link Certificate} for the given system username. If it does not yet exist, then it is created by + * authenticating the system user + * + * @param systemUsername + * the name of the system user + * + * @return the {@link Certificate} for this system user + */ + private PrivilegeContext getSystemUserPrivilegeContext(String systemUsername) { + + // get user object + User user = this.persistenceHandler.getUser(systemUsername); + // no user means no authentication + if (user == null) { + String msg = MessageFormat.format("The system user with username {0} does not exist!", systemUsername); //$NON-NLS-1$ + throw new AccessDeniedException(msg); + } + + // validate password + String pwHash = user.getPassword(); + if (pwHash != null) { + String msg = MessageFormat.format("System users must not have a password: {0}", systemUsername); //$NON-NLS-1$ + throw new AccessDeniedException(msg); + } + + // validate user state is system + if (user.getUserState() != UserState.SYSTEM) { + String msg = "The system {0} user does not have expected user state {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, systemUsername, UserState.SYSTEM); + throw new PrivilegeException(msg); + } + + // validate user has at least one role + if (user.getRoles().isEmpty()) { + String msg = MessageFormat.format("The system user {0} does not have any roles defined!", systemUsername); //$NON-NLS-1$ + throw new PrivilegeException(msg); + } + + // get 2 auth tokens + String authToken = this.encryptionHandler.nextToken(); + + // get next session id + String sessionId = UUID.randomUUID().toString(); + + // create a new certificate, with details of the user + Certificate systemUserCertificate = new Certificate(sessionId, systemUsername, user.getFirstname(), + user.getLastname(), user.getUserState(), authToken, new Date(), user.getLocale(), user.getRoles(), + new HashMap<>(user.getProperties())); + systemUserCertificate.setLastAccess(new Date()); + + // create and save a new privilege context + PrivilegeContext privilegeContext = buildPrivilegeContext(systemUserCertificate, user); + + // log + String msg = "The system user ''{0}'' is logged in with session {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, systemUsername, systemUserCertificate.getSessionId()); + DefaultPrivilegeHandler.logger.info(msg); + + return privilegeContext; + } + + /** + *

+ * This method instantiates a {@link PrivilegePolicy} object from the given policyName. The {@link PrivilegePolicy} + * is not stored in a database. The privilege name is a class name and is then used to instantiate a new + * {@link PrivilegePolicy} object + *

+ * + * @param policyName + * the class name of the {@link PrivilegePolicy} object to return + * + * @return the {@link PrivilegePolicy} object + * + * @throws PrivilegeException + * if the {@link PrivilegePolicy} object for the given policy name could not be instantiated + */ + private PrivilegePolicy getPolicy(String policyName) { + + // get the policies class + Class policyClazz = this.policyMap.get(policyName); + if (policyClazz == null) { + return null; + } + + // instantiate the policy + PrivilegePolicy policy; + try { + + policy = policyClazz.newInstance(); + } catch (Exception e) { + String msg = "The class for the policy with the name {0} does not exist!{1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, policyName, policyName); + throw new PrivilegeException(msg, e); + } + + return policy; + } +} diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/handler/EncryptionHandler.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/handler/EncryptionHandler.java new file mode 100644 index 000000000..59eddcfbe --- /dev/null +++ b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/handler/EncryptionHandler.java @@ -0,0 +1,61 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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. + */ +package ch.eitchnet.privilege.handler; + +import java.util.Map; + +/** + * The {@link EncryptionHandler} exposes API which is used to handle encrypting of strings, or returning secure tokens + * for certificates and so forth + * + * @author Robert von Burg + */ +public interface EncryptionHandler { + + /** + * Calculates or generates a token which can be used to identify certificates and so forth + * + * @return the secure token + */ + public String nextToken(); + + /** + * Converts a given string, e.g. a password to a hash which is defined by the concrete implementation + * + * @param string + * the string to convert + * @return the hash of the string after converting + */ + public String convertToHash(String string); + + /** + * Converts a given byte array, e.g. a password to a hash which is defined by the concrete implementation + * + * @param bytes + * the bytes to convert + * @return the hash of the string after converting + */ + public String convertToHash(byte[] bytes); + + /** + * Initialize the concrete {@link EncryptionHandler}. The passed parameter map contains any configuration the + * concrete {@link EncryptionHandler} might need + * + * @param parameterMap + * a map containing configuration properties + */ + public void initialize(Map parameterMap); +} diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java new file mode 100644 index 000000000..c3fd0fdc6 --- /dev/null +++ b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java @@ -0,0 +1,151 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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. + */ +package ch.eitchnet.privilege.handler; + +import java.util.List; +import java.util.Map; + +import ch.eitchnet.privilege.model.IPrivilege; +import ch.eitchnet.privilege.model.Restrictable; +import ch.eitchnet.privilege.model.internal.Role; +import ch.eitchnet.privilege.model.internal.User; +import ch.eitchnet.privilege.policy.PrivilegePolicy; + +/** + *

+ * The {@link PersistenceHandler} takes care of retrieving and persisting model objects to the underlying database. This + * database can be simple XML files, or an LDAP and so forth + *

+ * + *

+ * The {@link PersistenceHandler} also serves the special {@link PrivilegePolicy} objects. These policies are special + * objects which implement an algorithm to define if an action is allowed on a {@link Restrictable} by a {@link Role} + * and {@link IPrivilege} + *

+ * + * @author Robert von Burg + */ +public interface PersistenceHandler { + + /** + * Returns all currently known {@link User}s + * + * @return all currently known {@link User}s + */ + public List getAllUsers(); + + /** + * Returns all currently known {@link Role}s + * + * @return all currently known {@link Role}s + */ + public List getAllRoles(); + + /** + * Returns a {@link User} object from the underlying database + * + * @param username + * the name/id of the {@link User} object to return + * + * @return the {@link User} object, or null if it was not found + */ + public User getUser(String username); + + /** + * Returns a {@link Role} object from the underlying database + * + * @param roleName + * the name/id of the {@link Role} object to return + * + * @return the {@link Role} object, or null if it was not found + */ + public Role getRole(String roleName); + + /** + * Removes a {@link User} with the given name and returns the removed object if it existed + * + * @param username + * the name of the {@link User} to remove + * + * @return the {@link User} removed, or null if it did not exist + */ + public User removeUser(String username); + + /** + * Removes a {@link Role} with the given name and returns the removed object if it existed + * + * @param roleName + * the name of the {@link Role} to remove + * + * @return the {@link Role} removed, or null if it did not exist + */ + public Role removeRole(String roleName); + + /** + * Adds a {@link User} object to the underlying database + * + * @param user + * the {@link User} object to add + */ + public void addUser(User user); + + /** + * Replaces the existing {@link User} object in the underlying database + * + * @param user + * the {@link User} object to add + */ + public void replaceUser(User user); + + /** + * Adds a {@link Role} object to the underlying database + * + * @param role + * the {@link User} object to add + */ + public void addRole(Role role); + + /** + * Replaces the {@link Role} object in the underlying database + * + * @param role + * the {@link User} object to add + */ + public void replaceRole(Role role); + + /** + * Informs this {@link PersistenceHandler} to persist any changes which need to be saved + * + * @return true if changes were persisted successfully, false if nothing needed to be persisted + */ + public boolean persist(); + + /** + * Informs this {@link PersistenceHandler} to reload the data from the backend + * + * @return true if the reload was successful, false if something went wrong + */ + public boolean reload(); + + /** + * Initialize the concrete {@link PersistenceHandler}. The passed parameter map contains any configuration the + * concrete {@link PersistenceHandler} might need + * + * @param parameterMap + * a map containing configuration properties + */ + public void initialize(Map parameterMap); +} diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java new file mode 100644 index 000000000..cb612278d --- /dev/null +++ b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java @@ -0,0 +1,689 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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. + */ +package ch.eitchnet.privilege.handler; + +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import ch.eitchnet.privilege.base.AccessDeniedException; +import ch.eitchnet.privilege.base.PrivilegeConflictResolution; +import ch.eitchnet.privilege.base.PrivilegeException; +import ch.eitchnet.privilege.model.Certificate; +import ch.eitchnet.privilege.model.IPrivilege; +import ch.eitchnet.privilege.model.PrivilegeContext; +import ch.eitchnet.privilege.model.PrivilegeRep; +import ch.eitchnet.privilege.model.RoleRep; +import ch.eitchnet.privilege.model.UserRep; +import ch.eitchnet.privilege.model.UserState; +import ch.eitchnet.privilege.model.internal.Role; +import ch.eitchnet.privilege.model.internal.User; +import ch.eitchnet.privilege.policy.PrivilegePolicy; + +/** + * The {@link PrivilegeHandler} is the centrally exposed API for accessing the privilege library. It exposes all needed + * methods to access Privilege data model objects, modify them and validate if users or roles have privileges to perform + * an action + * + * @author Robert von Burg + */ +public interface PrivilegeHandler { + + /// + + /** + * Privilege "PrivilegeAction" which is used for privileges which are not further categorized e.g. s + * {@link #PRIVILEGE_ACTION_PERSIST} and {@link #PRIVILEGE_ACTION_GET_POLICIES} + */ + public static final String PRIVILEGE_ACTION = "PrivilegeAction"; + + /** + * For Privilege "PrivilegeAction" value required to be able to persist changes if not exempted by auto persist or + * allAllowed + */ + public static final String PRIVILEGE_ACTION_PERSIST = "Persist"; + /** + * For Privilege "PrivilegeAction" value required to be able to persist session if not exempted by + * allAllowed + */ + public static final String PRIVILEGE_ACTION_PERSIST_SESSIONS = "PersistSessions"; + /** + * For Privilege "PrivilegeAction" value required to be able to reload changes if not exempted by + * allAllowed + */ + public static final String PRIVILEGE_ACTION_RELOAD = "Reload"; + /** + * For Privilege "PrivilegeAction" value required to get currently configured policies if not + * allAllowed + */ + public static final String PRIVILEGE_ACTION_GET_POLICIES = "GetPolicies"; + /** + * For Privilege "PrivilegeAction" value required to get a certificate if not allAllowed + */ + public static final String PRIVILEGE_ACTION_GET_CERTIFICATE = "GetCertificate"; + /** + * For Privilege "PrivilegeAction" value required to get all certificates if not allAllowed + */ + public static final String PRIVILEGE_ACTION_GET_CERTIFICATES = "GetCertificates"; + + /// + + /** + * Privilege "PrivilegeGetRole" which is used to validate that a user can get a specific role + */ + public static final String PRIVILEGE_GET_ROLE = "PrivilegeGetRole"; + /** + * Privilege "PrivilegeAddRole" which is used to validate that a user can add a specific role + */ + public static final String PRIVILEGE_ADD_ROLE = "PrivilegeAddRole"; + /** + * Privilege "PrivilegeRemoveRole" which is used to validate that a user can remove a specific role + */ + public static final String PRIVILEGE_REMOVE_ROLE = "PrivilegeRemoveRole"; + /** + * Privilege "PrivilegeModifyRole" which is used to validate that a user can modify a specific role. Note: + * This includes modifying of the privileges on the role + */ + public static final String PRIVILEGE_MODIFY_ROLE = "PrivilegeModifyRole"; + + /// + + /** + * Privilege "PrivilegeGetUser" which is used to validate that a user can get a specific user + */ + public static final String PRIVILEGE_GET_USER = "PrivilegeGetUser"; + /** + * Privilege "PrivilegeAddUser" which is used to validate that a user can add a specific user + */ + public static final String PRIVILEGE_ADD_USER = "PrivilegeAddUser"; + /** + * Privilege "PrivilegeRemoveUser" which is used to validate that a user can remove a specific user + */ + public static final String PRIVILEGE_REMOVE_USER = "PrivilegeRemoveUser"; + /** + * Privilege "PrivilegeModifyUser" which is used to validate that a user can modify a specific user + */ + public static final String PRIVILEGE_MODIFY_USER = "PrivilegeModifyUser"; + /** + * Privilege "PrivilegeAddRoleToUser" which is used to validate that a user can add a specific role to a specific + * user + */ + public static final String PRIVILEGE_ADD_ROLE_TO_USER = "PrivilegeAddRoleToUser"; + /** + * Privilege "PrivilegeRemoveRoleFromUser" which is used to validate that a user can remove a specific role from a + * specific user + */ + public static final String PRIVILEGE_REMOVE_ROLE_FROM_USER = "PrivilegeRemoveRoleFromUser"; + /** + * Privilege "PRIVILEGE_SET_USER_LOCALE" which is used to validate that a user can set the locale of a user, or + * their own + */ + public static final String PRIVILEGE_SET_USER_LOCALE = "PrivilegeSetUserLocale"; + /** + * Privilege "PRIVILEGE_SET_USER_STATE" which is used to validate that a user can set the state of a user + */ + public static final String PRIVILEGE_SET_USER_STATE = "PrivilegeSetUserState"; + /** + * Privilege "PRIVILEGE_SET_USER_PASSWORD" which is used to validate that a user can set the password of a user, or + * their own + */ + public static final String PRIVILEGE_SET_USER_PASSWORD = "PrivilegeSetUserPassword"; + + /// + + /** + * configuration parameter to define a secret_key + */ + public static final String PARAM_SECRET_KEY = "secretKey"; //$NON-NLS-1$ + + /** + * configuration parameter to define a secret salt + */ + public static final String PARAM_SECRET_SALT = "secretSalt"; //$NON-NLS-1$ + + /** + * configuration parameter to define automatic persisting on password change + */ + public static final String PARAM_AUTO_PERSIST_ON_USER_CHANGES_DATA = "autoPersistOnUserChangesData"; //$NON-NLS-1$ + + /** + * configuration parameter to define if sessions should be persisted + */ + public static final String PARAM_PERSIST_SESSIONS = "persistSessions"; //$NON-NLS-1$ + + /** + * configuration parameter to define where sessions are to be persisted + */ + public static final String PARAM_PERSIST_SESSIONS_PATH = "persistSessionsPath"; //$NON-NLS-1$ + + /** + * configuration parameter to define {@link PrivilegeConflictResolution} + */ + public static final String PARAM_PRIVILEGE_CONFLICT_RESOLUTION = "privilegeConflictResolution"; + + /** + * Returns a {@link UserRep} for the given username + * + * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action + * @param username + * the name of the {@link UserRep} to return + * + * @return the {@link UserRep} for the given username, or null if it was not found + */ + public UserRep getUser(Certificate certificate, String username); + + /** + * Returns a {@link RoleRep} for the given roleName + * + * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action + * @param roleName + * the name of the {@link RoleRep} to return + * + * @return the {@link RoleRep} for the given roleName, or null if it was not found + */ + public RoleRep getRole(Certificate certificate, String roleName); + + /** + * Returns the map of {@link PrivilegePolicy} definitions + * + * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action + * + * @return the map of {@link PrivilegePolicy} definitions + */ + public Map getPolicyDefs(Certificate certificate); + + /** + * Returns the list of {@link Certificate Certificates} + * + * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action + * + * @return the list of {@link Certificate Certificates} + */ + public List getCertificates(Certificate certificate); + + /** + * Returns all {@link RoleRep RoleReps} + * + * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action + * + * @return the list of {@link RoleRep RoleReps} + */ + public List getRoles(Certificate certificate); + + /** + * Returns all {@link UserRep UserReps} + * + * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action + * + * @return the list of {@link UserRep UserReps} + */ + public List getUsers(Certificate certificate); + + /** + * Method to query {@link UserRep} which meet the criteria set in the given {@link UserRep}. Null fields mean the + * fields are irrelevant. + * + * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action + * @param selectorRep + * the {@link UserRep} to use as criteria selection + * + * @return a list of {@link UserRep}s which fit the given criteria + */ + public List queryUsers(Certificate certificate, UserRep selectorRep); + + /** + * Removes the user with the given username + * + * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action + * @param username + * the username of the user to remove + * + * @return the {@link UserRep} of the user removed, or null if the user did not exist + * + * @throws AccessDeniedException + * if the user for this certificate may not perform the action + * @throws PrivilegeException + * if there is anything wrong with this certificate + */ + public UserRep removeUser(Certificate certificate, String username) + throws AccessDeniedException, PrivilegeException; + + /** + * Removes the role with the given roleName from the user with the given username + * + * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action + * @param username + * the username of the user from which the role is to be removed + * @param roleName + * the roleName of the role to remove from the user + * + * @throws AccessDeniedException + * if the user for this certificate may not perform the action + * @throws PrivilegeException + * if there is anything wrong with this certificate + */ + public UserRep removeRoleFromUser(Certificate certificate, String username, String roleName) + throws AccessDeniedException, PrivilegeException; + + /** + * Removes the role with the given roleName + * + * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action + * @param roleName + * the roleName of the role to remove + * + * @return the {@link RoleRep} of the role removed, or null if the role did not exist + * + * @throws AccessDeniedException + * if the user for this certificate may not perform the action + * @throws PrivilegeException + * if there is anything wrong with this certificate or the role is still in use by a user + */ + public RoleRep removeRole(Certificate certificate, String roleName) + throws AccessDeniedException, PrivilegeException; + + /** + * Removes the privilege with the given privilegeName from the role with the given roleName + * + * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action + * @param roleName + * the roleName of the role from which the privilege is to be removed + * @param privilegeName + * the privilegeName of the privilege to remove from the role + * + * @throws AccessDeniedException + * if the user for this certificate may not perform the action + * @throws PrivilegeException + * if there is anything wrong with this certificate + */ + public RoleRep removePrivilegeFromRole(Certificate certificate, String roleName, String privilegeName) + throws AccessDeniedException, PrivilegeException; + + /** + *

+ * Adds a new user with the information from this {@link UserRep} + *

+ * + *

+ * If the password given is null, then the user is created, but can not not login! Otherwise the password must meet + * the requirements of the implementation under {@link PrivilegeHandler#validatePassword(byte[])} + *

+ * + * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action + * @param userRep + * the {@link UserRep} containing the information to create the new {@link User} + * @param password + * the password of the new user. If the password is null, then this is accepted but the user can not + * login, otherwise the password must be validated against + * {@link PrivilegeHandler#validatePassword(byte[])} + * + * @throws AccessDeniedException + * if the user for this certificate may not perform the action + * @throws PrivilegeException + * if there is anything wrong with this certificate or the user already exists + */ + public UserRep addUser(Certificate certificate, UserRep userRep, byte[] password) + throws AccessDeniedException, PrivilegeException; + + /** + *

+ * Updates the fields for the user with the given user. All fields on the given {@link UserRep} which are non-null + * will be updated on the existing user. The username on the given {@link UserRep} must be set and correspond to an + * existing user. + *

+ * + * The following fields are considered updateable: + *
    + *
  • {@link UserRep#getFirstname()}
  • + *
  • {@link UserRep#getLastname()}
  • + *
  • {@link UserRep#getLocale()}
  • + *
  • {@link UserRep#getProperties()} - the existing properties will be replaced with the given properties
  • + *
+ * + *

+ * Any other fields will be ignored + *

+ * + * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action + * @param userRep + * the {@link UserRep} with the fields set to their new values + * + * @throws AccessDeniedException + * if the user for this certificate may not perform the action + * @throws PrivilegeException + * if there is anything wrong with this certificate or if the user does not exist + */ + public UserRep updateUser(Certificate certificate, UserRep userRep) + throws AccessDeniedException, PrivilegeException; + + /** + *

+ * Replaces the existing user with the information from this {@link UserRep} if the user already exists + *

+ * + *

+ * If the password given is null, then the user is created, but can not not login! Otherwise the password must meet + * the requirements of the implementation under {@link PrivilegeHandler#validatePassword(byte[])} + *

+ * + * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action + * @param userRep + * the {@link UserRep} containing the information to replace the existing {@link User} + * @param password + * the password of the new user. If the password is null, then this is accepted but the user can not + * login, otherwise the password must be validated against + * {@link PrivilegeHandler#validatePassword(byte[])} + * + * @throws AccessDeniedException + * if the user for this certificate may not perform the action + * @throws PrivilegeException + * if there is anything wrong with this certificate or if the user does not exist + */ + public UserRep replaceUser(Certificate certificate, UserRep userRep, byte[] password) + throws AccessDeniedException, PrivilegeException; + + /** + * Adds a new role with the information from this {@link RoleRep} + * + * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action + * @param roleRep + * the {@link RoleRep} containing the information to create the new {@link Role} + * + * @throws AccessDeniedException + * if the user for this certificate may not perform the action + * @throws PrivilegeException + * if there is anything wrong with this certificate or if the role already exists + */ + public RoleRep addRole(Certificate certificate, RoleRep roleRep) throws AccessDeniedException, PrivilegeException; + + /** + * Replaces the existing role with the information from this {@link RoleRep} + * + * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action + * @param roleRep + * the {@link RoleRep} containing the information to replace the existing {@link Role} + * + * @throws AccessDeniedException + * if the user for this certificate may not perform the action + * @throws PrivilegeException + * if there is anything wrong with this certificate or if the role does not exist + */ + public RoleRep replaceRole(Certificate certificate, RoleRep roleRep) + throws AccessDeniedException, PrivilegeException; + + /** + * Adds the role with the given roleName to the {@link User} with the given username + * + * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action + * @param username + * the username of the {@link User} to which the role should be added + * @param roleName + * the roleName of the {@link Role} which should be added to the {@link User} + * + * @throws AccessDeniedException + * if the user for this certificate may not perform the action + * @throws PrivilegeException + * if there is anything wrong with this certificate or if the role does not exist + */ + public UserRep addRoleToUser(Certificate certificate, String username, String roleName) + throws AccessDeniedException, PrivilegeException; + + /** + * Adds the {@link PrivilegeRep} to the {@link Role} with the given roleName or replaces it, if it already exists + * + * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action + * @param roleName + * the roleName of the {@link Role} to which the privilege should be added + * @param privilegeRep + * the representation of the {@link IPrivilege} which should be added or replaced on the {@link Role} + * + * @throws AccessDeniedException + * if the user for this certificate may not perform the action + * @throws PrivilegeException + * if there is anything wrong with this certificate or the role does not exist + */ + public RoleRep addOrReplacePrivilegeOnRole(Certificate certificate, String roleName, PrivilegeRep privilegeRep) + throws AccessDeniedException, PrivilegeException; + + /** + *

+ * Changes the password for the {@link User} with the given username. If the password is null, then the {@link User} + * can not login anymore. Otherwise the password must meet the requirements of the implementation under + * {@link PrivilegeHandler#validatePassword(byte[])} + *

+ * + *

+ * It should be possible for a user to change their own password + *

+ * + * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action + * @param username + * the username of the {@link User} for which the password is to be changed + * @param password + * the new password for this user. If the password is null, then the {@link User} can not login anymore. + * Otherwise the password must meet the requirements of the implementation under + * {@link PrivilegeHandler#validatePassword(byte[])} + * + * @throws AccessDeniedException + * if the user for this certificate may not perform the action + * @throws PrivilegeException + * if there is anything wrong with this certificate + */ + public void setUserPassword(Certificate certificate, String username, byte[] password) + throws AccessDeniedException, PrivilegeException; + + /** + * Changes the {@link UserState} of the user + * + * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action + * @param username + * the username of the {@link User} for which the {@link UserState} is to be changed + * @param state + * the new state for the user + * + * @throws AccessDeniedException + * if the user for this certificate may not perform the action + * @throws PrivilegeException + * if there is anything wrong with this certificate + */ + public UserRep setUserState(Certificate certificate, String username, UserState state) + throws AccessDeniedException, PrivilegeException; + + /** + * Changes the {@link Locale} of the user + * + * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action + * @param username + * the username of the {@link User} for which the {@link Locale} is to be changed + * @param locale + * the new {@link Locale} for the user + * + * @throws AccessDeniedException + * if the user for this certificate may not perform the action + * @throws PrivilegeException + * if there is anything wrong with this certificate + */ + public UserRep setUserLocale(Certificate certificate, String username, Locale locale) + throws AccessDeniedException, PrivilegeException; + + /** + * Authenticates a user by validating that a {@link User} for the given username and password exist and then returns + * a {@link Certificate} with which this user may then perform actions + * + * @param username + * the username of the {@link User} which is registered in the {@link PersistenceHandler} + * @param password + * the password with which this user is to be authenticated. Null passwords are not accepted and they + * must meet the requirements of the {@link #validatePassword(byte[])}-method + * + * @return a {@link Certificate} with which this user may then perform actions + * + * @throws AccessDeniedException + * if the user credentials are not valid + */ + public Certificate authenticate(String username, byte[] password) throws AccessDeniedException; + + /** + * Invalidates the session for the given {@link Certificate}, effectively logging out the user who was authenticated + * with the credentials associated to the given {@link Certificate} + * + * @param certificate + * the {@link Certificate} for which the session is to be invalidated + * @return true if the session was still valid and is now invalidated, false otherwise + */ + public boolean invalidateSession(Certificate certificate); + + /** + * Checks if the given {@link Certificate} is valid. This means that the certificate is for a valid session and that + * the user exists for the certificate. This method checks if the {@link Certificate} has been tampered with + * + * @param certificate + * the {@link Certificate} to check + * + * @throws PrivilegeException + * if there is anything wrong with this certificate + */ + public void isCertificateValid(Certificate certificate) throws PrivilegeException; + + /** + * Checks that the given password belongs to the given {@link Certificate}. If it doesn't, then a + * {@link PrivilegeException} is thrown + * + * @param certificate + * the certificate for which to check the password + * @param password + * the password to check against the user from the certificate + * + * @throws PrivilegeException + * if the certificate is invalid or the password does not match + */ + public void checkPassword(Certificate certificate, byte[] password) throws PrivilegeException; + + /** + * Returns the {@link PrivilegeContext} for the given {@link Certificate}. The {@link PrivilegeContext} is an + * encapsulated state of a user's privileges so that for the duration of a user's call, the user can perform their + * actions and do not need to access the {@link PrivilegeHandler} anymore + * + * @param certificate + * a valid {@link Certificate} for which a {@link PrivilegeContext} is to be returned + * @return the {@link PrivilegeContext} for the given {@link Certificate} + * + * @throws PrivilegeException + * if there is a configuration error or the {@link Certificate} is invalid + */ + public PrivilegeContext getPrivilegeContext(Certificate certificate) throws PrivilegeException; + + /** + * Validate that the given password meets certain requirements. What these requirements are is a decision made by + * the concrete implementation + * + * @param password + * the password to be validated to meet certain requirements + * + * @throws PrivilegeException + * if the password does not implement the requirement of the concrete implementation + */ + public void validatePassword(byte[] password) throws PrivilegeException; + + /** + *

+ * Informs this {@link PersistenceHandler} to reload the data from the backend + *

+ * + * Note: It depends on the underlying {@link PersistenceHandler} implementation if data really is read + * + * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action + * + * @return true if the reload was successful, false if something went wrong + * + * @throws AccessDeniedException + * if the users of the given certificate does not have the privilege to perform this action + */ + public boolean reload(Certificate certificate); + + /** + * Persists any changes to the privilege data model. Changes are thus not persisted immediately, but must be + * actively performed + * + * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action + * + * @return true if changes were persisted, false if no changes were persisted + * + * @throws AccessDeniedException + * if the users of the given certificate does not have the privilege to perform this action + */ + public boolean persist(Certificate certificate) throws AccessDeniedException; + + /** + * Persists all currently active sessions + * + * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action + * + * @return true if changes were persisted, false if not (i.e. not enabled) + * + * @throws AccessDeniedException + * if the users of the given certificate does not have the privilege to perform this action + */ + public boolean persistSessions(Certificate certificate) throws AccessDeniedException; + + /** + * Special method to perform work as a System user, meaning the given systemUsername corresponds to an account which + * has the state {@link UserState#SYSTEM} and this user must have privilege to perform the concrete implementation + * of the given {@link SystemUserAction} instance + * + * + * @param systemUsername + * the username of the system user to perform the action as + * @param action + * the action to be performed as the system user + * + * @return the action + * + * @throws PrivilegeException + */ + public T runAsSystem(String systemUsername, T action) throws PrivilegeException; + + /** + * Returns the {@link EncryptionHandler} instance + * + * @return the {@link EncryptionHandler} instance + */ + public EncryptionHandler getEncryptionHandler() throws PrivilegeException; +} diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/handler/SystemUserAction.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/handler/SystemUserAction.java new file mode 100644 index 000000000..082bc2ca3 --- /dev/null +++ b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/handler/SystemUserAction.java @@ -0,0 +1,52 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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. + */ +package ch.eitchnet.privilege.handler; + +import ch.eitchnet.privilege.model.Certificate; +import ch.eitchnet.privilege.model.PrivilegeContext; +import ch.eitchnet.privilege.model.Restrictable; + +/** + * With this interface system actions, which are to be performed in an automated fashion, i.e. by cron jobs, can be + * implemented and then the authorized execution can be delegated to + * {@link PrivilegeHandler#runAsSystem(String, SystemUserAction)} + * + * @author Robert von Burg + */ +public abstract class SystemUserAction implements Restrictable { + + @Override + public String getPrivilegeName() { + return SystemUserAction.class.getName(); + } + + @Override + public Object getPrivilegeValue() { + return this.getClass().getName(); + } + + /** + * This method will be called by the {@link PrivilegeHandler} when an authorized {@link Certificate} has been + * generated to allow this action to properly validate its execution + * + * TODO: I'm not really happy with this... we might want to pass the certificate and then force the action to + * validate it to get the {@link PrivilegeContext} - we don't want the {@link PrivilegeContext} to live forever... + * + * @param privilegeContext + * the {@link PrivilegeContext} which was generated for a valid system user + */ + public abstract void execute(PrivilegeContext privilegeContext); +} diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java new file mode 100644 index 000000000..2ad3462b4 --- /dev/null +++ b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java @@ -0,0 +1,314 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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. + */ +package ch.eitchnet.privilege.handler; + +import java.io.File; +import java.text.MessageFormat; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ch.eitchnet.privilege.base.PrivilegeException; +import ch.eitchnet.privilege.helper.XmlConstants; +import ch.eitchnet.privilege.model.internal.Role; +import ch.eitchnet.privilege.model.internal.User; +import ch.eitchnet.privilege.xml.PrivilegeRolesDomWriter; +import ch.eitchnet.privilege.xml.PrivilegeRolesSaxReader; +import ch.eitchnet.privilege.xml.PrivilegeUsersDomWriter; +import ch.eitchnet.privilege.xml.PrivilegeUsersSaxReader; +import ch.eitchnet.utils.helper.StringHelper; +import ch.eitchnet.utils.helper.XmlHelper; + +/** + * {@link PersistenceHandler} implementation which reads the configuration from XML files. These configuration is passed + * in {@link #initialize(Map)} + * + * @author Robert von Burg + */ +public class XmlPersistenceHandler implements PersistenceHandler { + + protected static final Logger logger = LoggerFactory.getLogger(XmlPersistenceHandler.class); + + private Map userMap; + private Map roleMap; + + private boolean userMapDirty; + private boolean roleMapDirty; + + private Map parameterMap; + + private long usersFileDate; + private long rolesFileDate; + private File usersPath; + private File rolesPath; + + @Override + public List getAllUsers() { + synchronized (this.userMap) { + return new LinkedList<>(this.userMap.values()); + } + } + + @Override + public List getAllRoles() { + synchronized (this.roleMap) { + return new LinkedList<>(this.roleMap.values()); + } + } + + @Override + public User getUser(String username) { + return this.userMap.get(username); + } + + @Override + public Role getRole(String roleName) { + return this.roleMap.get(roleName); + } + + @Override + public User removeUser(String username) { + User user = this.userMap.remove(username); + this.userMapDirty = user != null; + return user; + } + + @Override + public Role removeRole(String roleName) { + Role role = this.roleMap.remove(roleName); + this.roleMapDirty = role != null; + return role; + } + + @Override + public void addUser(User user) { + if (this.userMap.containsKey(user.getUsername())) + throw new IllegalStateException(MessageFormat.format("The user {0} already exists!", user.getUsername())); + this.userMap.put(user.getUsername(), user); + this.userMapDirty = true; + } + + @Override + public void replaceUser(User user) { + if (!this.userMap.containsKey(user.getUsername())) + throw new IllegalStateException(MessageFormat + .format("The user {0} can not be replaced as it does not exiset!", user.getUsername())); + this.userMap.put(user.getUsername(), user); + this.userMapDirty = true; + } + + @Override + public void addRole(Role role) { + if (this.roleMap.containsKey(role.getName())) + throw new IllegalStateException(MessageFormat.format("The role {0} already exists!", role.getName())); + this.roleMap.put(role.getName(), role); + this.roleMapDirty = true; + } + + @Override + public void replaceRole(Role role) { + if (!this.roleMap.containsKey(role.getName())) + throw new IllegalStateException( + MessageFormat.format("The role {0} can not be replaced as it does not exist!", role.getName())); + this.roleMap.put(role.getName(), role); + this.roleMapDirty = true; + } + + /** + * Initializes this {@link XmlPersistenceHandler} by reading the following parameters: + *
    + *
  • {@link XmlConstants#XML_PARAM_BASE_PATH}
  • + *
  • {@link XmlConstants#XML_PARAM_MODEL_FILE}
  • + *
+ */ + @Override + public void initialize(Map paramsMap) { + + // copy parameter map + this.parameterMap = Collections.unmodifiableMap(new HashMap<>(paramsMap)); + + // get and validate base bath + String basePath = this.parameterMap.get(XmlConstants.XML_PARAM_BASE_PATH); + File basePathF = new File(basePath); + if (!basePathF.exists() && !basePathF.isDirectory()) { + String msg = "[{0}] Defined parameter {1} does not point to a valid path at {2}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, PersistenceHandler.class.getName(), XmlConstants.XML_PARAM_BASE_PATH, + basePathF.getAbsolutePath()); + throw new PrivilegeException(msg); + } + + // get users file name + String usersFileName = this.parameterMap.get(XmlConstants.XML_PARAM_USERS_FILE); + if (StringHelper.isEmpty(usersFileName)) { + String msg = "[{0}] Defined parameter {1} is not valid as it is empty!"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, PersistenceHandler.class.getName(), XmlConstants.XML_PARAM_USERS_FILE); + throw new PrivilegeException(msg); + } + + // get roles file name + String rolesFileName = this.parameterMap.get(XmlConstants.XML_PARAM_ROLES_FILE); + if (StringHelper.isEmpty(rolesFileName)) { + String msg = "[{0}] Defined parameter {1} is not valid as it is empty!"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, PersistenceHandler.class.getName(), XmlConstants.XML_PARAM_ROLES_FILE); + throw new PrivilegeException(msg); + } + + // validate users file exists + String usersPathS = basePath + "/" + usersFileName; //$NON-NLS-1$ + File usersPath = new File(usersPathS); + if (!usersPath.exists()) { + String msg = "[{0}] Defined parameter {1} is invalid as users file does not exist at path {2}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, PersistenceHandler.class.getName(), XmlConstants.XML_PARAM_USERS_FILE, + usersPath.getAbsolutePath()); + throw new PrivilegeException(msg); + } + + // validate roles file exists + String rolesPathS = basePath + "/" + rolesFileName; //$NON-NLS-1$ + File rolesPath = new File(rolesPathS); + if (!rolesPath.exists()) { + String msg = "[{0}] Defined parameter {1} is invalid as roles file does not exist at path {2}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, PersistenceHandler.class.getName(), XmlConstants.XML_PARAM_ROLES_FILE, + rolesPath.getAbsolutePath()); + throw new PrivilegeException(msg); + } + + // save path to model + this.usersPath = usersPath; + this.rolesPath = rolesPath; + + if (reload()) + logger.info("Privilege Data loaded."); //$NON-NLS-1$ + } + + /** + * Reads the XML configuration files which contain the model. Which configuration files are parsed was defined in + * the while calling {@link #initialize(Map)} + * + * @see #initialize(Map) + */ + @Override + public boolean reload() { + + this.roleMap = Collections.synchronizedMap(new HashMap()); + this.userMap = Collections.synchronizedMap(new HashMap()); + + // parse models xml file to XML document + PrivilegeUsersSaxReader usersXmlHandler = new PrivilegeUsersSaxReader(); + XmlHelper.parseDocument(this.usersPath, usersXmlHandler); + + PrivilegeRolesSaxReader rolesXmlHandler = new PrivilegeRolesSaxReader(); + XmlHelper.parseDocument(this.rolesPath, rolesXmlHandler); + + this.usersFileDate = this.usersPath.lastModified(); + this.rolesFileDate = this.rolesPath.lastModified(); + + // ROLES + List roles = rolesXmlHandler.getRoles(); + for (Role role : roles) { + this.roleMap.put(role.getName(), role); + } + + // USERS + List users = usersXmlHandler.getUsers(); + for (User user : users) { + this.userMap.put(user.getUsername(), user); + } + + this.userMapDirty = false; + this.roleMapDirty = false; + + logger.info(MessageFormat.format("Read {0} Users", this.userMap.size())); //$NON-NLS-1$ + logger.info(MessageFormat.format("Read {0} Roles", this.roleMap.size())); //$NON-NLS-1$ + + // validate referenced roles exist + for (User user : users) { + for (String roleName : user.getRoles()) { + + // validate that role exists + if (getRole(roleName) == null) { + String msg = "Role {0} does not exist referenced by user {1}"; + msg = MessageFormat.format(msg, roleName, user.getUsername()); + throw new PrivilegeException(msg); + } + } + } + + return true; + } + + /** + * Writes the model to the XML files. Where the files are written to was defined in the {@link #initialize(Map)} + */ + @Override + public boolean persist() { + + // get users file name + String usersFileName = this.parameterMap.get(XmlConstants.XML_PARAM_USERS_FILE); + if (usersFileName == null || usersFileName.isEmpty()) { + String msg = "[{0}] Defined parameter {1} is invalid"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, PersistenceHandler.class.getName(), XmlConstants.XML_PARAM_USERS_FILE); + throw new PrivilegeException(msg); + } + + // get roles file name + String rolesFileName = this.parameterMap.get(XmlConstants.XML_PARAM_ROLES_FILE); + if (rolesFileName == null || rolesFileName.isEmpty()) { + String msg = "[{0}] Defined parameter {1} is invalid"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, PersistenceHandler.class.getName(), XmlConstants.XML_PARAM_ROLES_FILE); + throw new PrivilegeException(msg); + } + + boolean saved = false; + + // get users file + boolean usersFileUnchanged = this.usersPath.exists() && this.usersPath.lastModified() == this.usersFileDate; + if (usersFileUnchanged && !this.userMapDirty) { + logger.warn("Not persisting of users as current file is unchanged and users data is not dirty"); //$NON-NLS-1$ + } else { + + // delegate writing + PrivilegeUsersDomWriter modelWriter = new PrivilegeUsersDomWriter(getAllUsers(), this.usersPath); + modelWriter.write(); + + this.userMapDirty = false; + saved = true; + } + + // get roles file + boolean rolesFileUnchanged = this.rolesPath.exists() && this.rolesPath.lastModified() == this.rolesFileDate; + if (rolesFileUnchanged && !this.roleMapDirty) { + logger.warn("Not persisting of roles as current file is unchanged and roles data is not dirty"); //$NON-NLS-1$ + } else { + + // delegate writing + PrivilegeRolesDomWriter modelWriter = new PrivilegeRolesDomWriter(getAllRoles(), this.rolesPath); + modelWriter.write(); + + this.roleMapDirty = false; + saved = true; + } + + // reset dirty states + + return saved; + } +} diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java new file mode 100644 index 000000000..6504e12d4 --- /dev/null +++ b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java @@ -0,0 +1,105 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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. + */ +package ch.eitchnet.privilege.helper; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; + +import ch.eitchnet.privilege.handler.PrivilegeHandler; +import ch.eitchnet.privilege.model.internal.PrivilegeContainerModel; +import ch.eitchnet.privilege.xml.PrivilegeConfigDomWriter; + +/** + *

+ * This class is a simple application which can be used to bootstrap a new configuration for the + * {@link PrivilegeHandler} + *

+ * + *

+ * Simple execute the application and it will ask a few questions and then write a new set of configuration files which + * can be used to run the {@link PrivilegeHandler} + *

+ * + *

+ * Note:This class is not yet implemented! + *

+ * + * @author Robert von Burg + */ +@SuppressWarnings("nls") +public class BootstrapConfigurationHelper { + + // private static final Logger logger = Loggerdoc.getLogger(BootstrapConfigurationHelper.class); + + private static String path; + + private static String defaultPrivilegeContainerXmlFile = "Privilege.xml"; + + //private static String basePath = ""; + //private static String modelFileName = "PrivilegeUsers.xml"; + //private static String hashAlgorithm = "SHA-256"; + + private static String defaultPersistenceHandler = "ch.eitchnet.privilege.handler.DefaultPersistenceHandler"; + private static String defaultEncryptionHandler = "ch.eitchnet.privilege.handler.DefaultEncryptionHandler"; + + /** + * @param args + * the args from the command line + */ + public static void main(String[] args) { + + // get current directory + BootstrapConfigurationHelper.path = System.getProperty("user.dir") + "/newConfig"; + + // TODO ask user where to save configuration, default is pwd/newConfig/.... + + // see if path already exists + File pathF = new File(BootstrapConfigurationHelper.path); + if (pathF.exists()) { + throw new RuntimeException("Path already exists: " + pathF.getAbsolutePath()); + } + + if (!pathF.mkdirs()) { + throw new RuntimeException("Could not create path " + pathF.getAbsolutePath()); + } + + Map parameterMap = new HashMap<>(); + Map encryptionHandlerParameterMap = new HashMap<>(); + Map persistenceHandlerParameterMap = new HashMap<>(); + + // TODO ask other questions... + parameterMap.put("autoPersistOnPasswordChange", "true"); + encryptionHandlerParameterMap.put("hashAlgorithm", "SHA-256"); + persistenceHandlerParameterMap.put("basePath", "./target/test"); + persistenceHandlerParameterMap.put("modelXmlFile", "PrivilegeModel.xml"); + + PrivilegeContainerModel containerModel = new PrivilegeContainerModel(); + containerModel.setParameterMap(parameterMap); + containerModel.setEncryptionHandlerClassName(defaultEncryptionHandler); + containerModel.setEncryptionHandlerParameterMap(encryptionHandlerParameterMap); + containerModel.setPersistenceHandlerClassName(defaultPersistenceHandler); + containerModel.setPersistenceHandlerParameterMap(persistenceHandlerParameterMap); + + containerModel.addPolicy("DefaultPrivilege", "ch.eitchnet.privilege.policy.DefaultPrivilege"); + + // now perform work: + File configFile = new File(BootstrapConfigurationHelper.path + "/" + + BootstrapConfigurationHelper.defaultPrivilegeContainerXmlFile); + PrivilegeConfigDomWriter configSaxWriter = new PrivilegeConfigDomWriter(containerModel, configFile); + configSaxWriter.write(); + } +} diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/helper/PasswordCreaterUI.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/helper/PasswordCreaterUI.java new file mode 100644 index 000000000..62eab7fa8 --- /dev/null +++ b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/helper/PasswordCreaterUI.java @@ -0,0 +1,123 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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. + */ +package ch.eitchnet.privilege.helper; + +import java.awt.Dimension; +import java.awt.GridLayout; +import java.awt.Toolkit; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; + +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPasswordField; +import javax.swing.JTextField; +import javax.swing.SwingConstants; + +import ch.eitchnet.utils.helper.StringHelper; + +/** + * Simple Swing UI to create passwords + * + * @author Robert von Burg + */ +@SuppressWarnings("nls") +public class PasswordCreaterUI { + + /** + * Launches the UI + * + * @param args + * not used + */ + public static void main(String[] args) { + + JFrame.setDefaultLookAndFeelDecorated(true); + + JFrame frame = new JFrame(); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + frame.setTitle("Password creator"); + frame.setLayout(new GridLayout(4, 2)); + + JLabel digest = new JLabel("Digest:", SwingConstants.RIGHT); + JLabel password = new JLabel("Password:", SwingConstants.RIGHT); + JLabel hash = new JLabel("Hash:", SwingConstants.RIGHT); + + String[] digests = new String[] { "MD2", "MD5", "SHA-1", "SHA-256", "SHA-384", "SHA-512" }; + final JComboBox digestCombo = new JComboBox<>(digests); + digestCombo.setSelectedIndex(3); + final JPasswordField passwordField = new JPasswordField(); + final JTextField hashField = new JTextField(150); + + JButton digestBtn = new JButton("Digest"); + + passwordField.addKeyListener(new KeyListener() { + + @Override + public void keyTyped(KeyEvent e) { + // + } + + @Override + public void keyReleased(KeyEvent e) { + // + } + + @Override + public void keyPressed(KeyEvent e) { + hashField.setText(""); + } + }); + digestBtn.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(ActionEvent e) { + + try { + String digest = (String) digestCombo.getSelectedItem(); + char[] passwordChar = passwordField.getPassword(); + String password = new String(passwordChar); + String hash = StringHelper.hashAsHex(digest, password); + hashField.setText(hash); + } catch (Exception e1) { + e1.printStackTrace(); + hashField.setText("Failed: " + e1.getLocalizedMessage()); + } + } + }); + + frame.add(digest); + frame.add(digestCombo); + frame.add(password); + frame.add(passwordField); + frame.add(hash); + frame.add(hashField); + frame.add(new JLabel()); + frame.add(digestBtn); + + Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); + int width = 500; + int height = 160; + frame.setSize(width, height); + frame.setLocation(screenSize.width / 2 - width, screenSize.height / 2 - height); + + frame.setVisible(true); + } +} \ No newline at end of file diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/helper/PasswordCreator.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/helper/PasswordCreator.java new file mode 100644 index 000000000..81e3bf567 --- /dev/null +++ b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/helper/PasswordCreator.java @@ -0,0 +1,72 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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. + */ +package ch.eitchnet.privilege.helper; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.security.MessageDigest; + +import ch.eitchnet.utils.helper.StringHelper; + +/** + *

+ * Simple main class which can be used to create a hash from a password which the user must type in at the command line + *

+ * + *

+ * TODO: Note: currently the password input is echoed which is a security risk + *

+ * + * @author Robert von Burg + */ +public class PasswordCreator { + + /** + * @param args + * the args from the command line, NOT USED + * @throws Exception + * thrown if anything goes wrong + */ + @SuppressWarnings("nls") + public static void main(String[] args) throws Exception { + + BufferedReader r = new BufferedReader(new InputStreamReader(System.in)); + + String hashAlgorithm = null; + while (hashAlgorithm == null) { + System.out.print("Hash Algorithm [SHA-256]: "); + String readLine = r.readLine().trim(); + + if (readLine.isEmpty()) { + hashAlgorithm = "SHA-256"; + } else { + + try { + MessageDigest.getInstance(readLine); + hashAlgorithm = readLine; + } catch (Exception e) { + System.out.println(e.getLocalizedMessage()); + hashAlgorithm = null; + } + } + } + + System.out.print("Password: "); + String password = r.readLine().trim(); + System.out.print("Hash is: " + StringHelper.hashAsHex(hashAlgorithm, password)); + } + +} diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/helper/PrivilegeInitializationHelper.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/helper/PrivilegeInitializationHelper.java new file mode 100644 index 000000000..5cdf1b556 --- /dev/null +++ b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/helper/PrivilegeInitializationHelper.java @@ -0,0 +1,140 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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. + */ +package ch.eitchnet.privilege.helper; + +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.text.MessageFormat; +import java.util.Map; + +import ch.eitchnet.privilege.base.PrivilegeException; +import ch.eitchnet.privilege.handler.DefaultPrivilegeHandler; +import ch.eitchnet.privilege.handler.EncryptionHandler; +import ch.eitchnet.privilege.handler.PersistenceHandler; +import ch.eitchnet.privilege.handler.PrivilegeHandler; +import ch.eitchnet.privilege.model.internal.PrivilegeContainerModel; +import ch.eitchnet.privilege.policy.PrivilegePolicy; +import ch.eitchnet.privilege.xml.PrivilegeConfigSaxReader; +import ch.eitchnet.utils.helper.ClassHelper; +import ch.eitchnet.utils.helper.XmlHelper; + +/** + * This class implements the initializing of the {@link PrivilegeHandler} by loading an XML file containing the + * configuration + * + * @author Robert von Burg + */ +public class PrivilegeInitializationHelper { + + /** + * Initializes the {@link DefaultPrivilegeHandler} from the configuration file + * + * @param privilegeXmlFile + * a {@link File} reference to the XML file containing the configuration for Privilege + * + * @return the initialized {@link PrivilegeHandler} where the {@link EncryptionHandler} and + * {@link PersistenceHandler} are set and initialized as well + */ + public static PrivilegeHandler initializeFromXml(File privilegeXmlFile) { + + // make sure file exists + if (!privilegeXmlFile.exists()) { + String msg = "Privilege file does not exist at path {0}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, privilegeXmlFile.getAbsolutePath()); + throw new PrivilegeException(msg); + } + + // delegate using input stream + try (FileInputStream fin = new FileInputStream(privilegeXmlFile)) { + return initializeFromXml(fin); + } catch (Exception e) { + String msg = "Failed to load configuration from {0}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, privilegeXmlFile.getAbsolutePath()); + throw new PrivilegeException(msg, e); + } + } + + /** + * Initializes the {@link PrivilegeHandler} by loading from the given input stream. This stream must be a valid XML + * source + * + * @param privilegeConfigInputStream + * the XML stream containing the privilege configuration + * + * @return the initialized {@link PrivilegeHandler} where the {@link EncryptionHandler} and + * {@link PersistenceHandler} are set and initialized as well + */ + public static PrivilegeHandler initializeFromXml(InputStream privilegeConfigInputStream) { + + // parse configuration file + PrivilegeContainerModel containerModel = new PrivilegeContainerModel(); + PrivilegeConfigSaxReader xmlHandler = new PrivilegeConfigSaxReader(containerModel); + XmlHelper.parseDocument(privilegeConfigInputStream, xmlHandler); + + return initializeFromXml(containerModel); + } + + /** + * Initializes the {@link PrivilegeHandler} by initializing from the given {@link PrivilegeContainerModel} + * + * @param containerModel + * the configuration for the {@link PrivilegeHandler} + * + * @return the initialized {@link PrivilegeHandler} where the {@link EncryptionHandler} and + * {@link PersistenceHandler} are set and initialized as well + */ + public static PrivilegeHandler initializeFromXml(PrivilegeContainerModel containerModel) { + + // initialize encryption handler + String encryptionHandlerClassName = containerModel.getEncryptionHandlerClassName(); + EncryptionHandler encryptionHandler = ClassHelper.instantiateClass(encryptionHandlerClassName); + Map parameterMap = containerModel.getEncryptionHandlerParameterMap(); + try { + encryptionHandler.initialize(parameterMap); + } catch (Exception e) { + String msg = "EncryptionHandler {0} could not be initialized"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, encryptionHandlerClassName); + throw new PrivilegeException(msg, e); + } + + // initialize persistence handler + String persistenceHandlerClassName = containerModel.getPersistenceHandlerClassName(); + PersistenceHandler persistenceHandler = ClassHelper.instantiateClass(persistenceHandlerClassName); + parameterMap = containerModel.getPersistenceHandlerParameterMap(); + try { + persistenceHandler.initialize(parameterMap); + } catch (Exception e) { + String msg = "PersistenceHandler {0} could not be initialized"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, persistenceHandlerClassName); + throw new PrivilegeException(msg, e); + } + + // initialize privilege handler + DefaultPrivilegeHandler privilegeHandler = new DefaultPrivilegeHandler(); + parameterMap = containerModel.getParameterMap(); + Map> policyMap = containerModel.getPolicies(); + try { + privilegeHandler.initialize(parameterMap, encryptionHandler, persistenceHandler, policyMap); + } catch (Exception e) { + String msg = "PrivilegeHandler {0} could not be initialized"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, privilegeHandler.getClass().getName()); + throw new PrivilegeException(msg, e); + } + + return privilegeHandler; + } +} diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/helper/XmlConstants.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/helper/XmlConstants.java new file mode 100644 index 000000000..041ce2f5f --- /dev/null +++ b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/helper/XmlConstants.java @@ -0,0 +1,245 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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. + */ +package ch.eitchnet.privilege.helper; + +/** + * The constants used in parsing XML documents which contain the configuration for Privilege + * + * @author Robert von Burg + */ +@SuppressWarnings("nls") +public class XmlConstants { + + /** + * XML_ROOT_PRIVILEGE_CONTAINER = "PrivilegeContainer" : + */ + public static final String XML_ROOT_PRIVILEGE = "Privilege"; + + /** + * XML_CONTAINER = "Container" : + */ + public static final String XML_CONTAINER = "Container"; + + /** + * XML_POLICIES = "Policies" : + */ + public static final String XML_POLICIES = "Policies"; + + /** + * XML_PRIVILEGES = "Privileges" : + */ + public static final String XML_PRIVILEGES = "Privileges"; + + /** + * XML_ROOT_PRIVILEGE_USERS_AND_ROLES = "UsersAndRoles" : + */ + public static final String XML_ROOT_PRIVILEGE_USERS_AND_ROLES = "UsersAndRoles"; + + /** + * XML_ROOT_CERTIFICATES = "Certificates" : + */ + public static final String XML_ROOT_CERTIFICATES = "Certificates"; + + /** + * XML_HANDLER_PERSISTENCE = "PersistenceHandler" : + */ + public static final String XML_HANDLER_PERSISTENCE = "PersistenceHandler"; + + /** + * XML_HANDLER_ENCRYPTION = "EncryptionHandler" : + */ + public static final String XML_HANDLER_ENCRYPTION = "EncryptionHandler"; + + /** + * XML_HANDLER_PRIVILEGE = "PrivilegeHandler" : + */ + public static final String XML_HANDLER_PRIVILEGE = "PrivilegeHandler"; + + /** + * XML_ROLES = "Roles" : + */ + public static final String XML_ROLES = "Roles"; + + /** + * XML_ROLE = "Role" : + */ + public static final String XML_ROLE = "Role"; + + /** + * XML_USERS = "Users" : + */ + public static final String XML_USERS = "Users"; + + /** + * XML_CERTIFICATE = "Certificate" : + */ + public static final String XML_CERTIFICATE = "Certificate"; + + /** + * XML_SESSION_DATA = "SessionData" : + */ + public static final String XML_SESSION_DATA = "SessionData"; + + /** + * XML_USER = "User" + */ + public static final String XML_USER = "User"; + + /** + * XML_PRIVILEGE = "Privilege" : + */ + public static final String XML_PRIVILEGE = "Privilege"; + + /** + * XML_POLICY = "Policy" : + */ + public static final String XML_POLICY = "Policy"; + + /** + * XML_PARAMETERS = "Parameters" : + */ + public static final String XML_PARAMETERS = "Parameters"; + + /** + * XML_PARAMETER = "Parameter" : + */ + public static final String XML_PARAMETER = "Parameter"; + + /** + * XML_PROPERTIES = "Properties" : + */ + public static final String XML_PROPERTIES = "Properties"; + + /** + * XML_PROPERTY = "Property" : + */ + public static final String XML_PROPERTY = "Property"; + + /** + * XML_ALL_ALLOWED = "AllAllowed" : + */ + public static final String XML_ALL_ALLOWED = "AllAllowed"; + + /** + * XML_DENY = "Deny" : + */ + public static final String XML_DENY = "Deny"; + + /** + * XML_ALLOW = "Allow" : + */ + public static final String XML_ALLOW = "Allow"; + + /** + * XML_FIRSTNAME = "Firstname" : + */ + public static final String XML_FIRSTNAME = "Firstname"; + + /** + * XML_LASTNAME = "Lastname" : + */ + public static final String XML_LASTNAME = "Lastname"; + + /** + * XML_STATE = "State" : + */ + public static final String XML_STATE = "State"; + + /** + * XML_LOCALE = "Locale" : + */ + public static final String XML_LOCALE = "Locale"; + + /** + * XML_ATTR_CLASS = "class" : + */ + public static final String XML_ATTR_CLASS = "class"; + + /** + * XML_ATTR_LOGIN_TIME = "loginTime" : + */ + public static final String XML_ATTR_LOGIN_TIME = "loginTime"; + + /** + * XML_ATTR_LAST_ACCESS = "lastAccess" : + */ + public static final String XML_ATTR_LAST_ACCESS = "lastAccess"; + + /** + * XML_ATTR_NAME = "name" : + */ + public static final String XML_ATTR_NAME = "name"; + + /** + * XML_ATTR_VALUE = "value" : + */ + public static final String XML_ATTR_VALUE = "value"; + + /** + * XML_ATTR_POLICY = "policy" : + */ + public static final String XML_ATTR_POLICY = "policy"; + + /** + * XML_ATTR_USER_ID = "userId" : + */ + public static final String XML_ATTR_USER_ID = "userId"; + + /** + * XML_ATTR_SESSION_ID = "sessionId" : + */ + public static final String XML_ATTR_SESSION_ID = "sessionId"; + + /** + * XML_ATTR_USERNAME = "username" : + */ + public static final String XML_ATTR_USERNAME = "username"; + + /** + * XML_ATTR_AUTH_TOKEN = "authToken" : + */ + public static final String XML_ATTR_AUTH_TOKEN = "authToken"; + + /** + * XML_ATTR_LOCALE = "locale" : + */ + public static final String XML_ATTR_LOCALE = "locale"; + + /** + * XML_ATTR_PASSWORD = "password" : + */ + public static final String XML_ATTR_PASSWORD = "password"; + + /** + * XML_PARAM_HASH_ALGORITHM = "hashAlgorithm" : + */ + public static final String XML_PARAM_HASH_ALGORITHM = "hashAlgorithm"; + + /** + * XML_PARAM_USERS_FILE = "usersXmlFile" : + */ + public static final String XML_PARAM_USERS_FILE = "usersXmlFile"; + + /** + * XML_PARAM_ROLES_FILE = "rolesXmlFile" : + */ + public static final String XML_PARAM_ROLES_FILE = "rolesXmlFile"; + + /** + * XML_PARAM_BASE_PATH = "basePath" : + */ + public static final String XML_PARAM_BASE_PATH = "basePath"; +} diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/i18n/PrivilegeMessages.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/i18n/PrivilegeMessages.java new file mode 100644 index 000000000..5b39df545 --- /dev/null +++ b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/i18n/PrivilegeMessages.java @@ -0,0 +1,40 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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. + */ +package ch.eitchnet.privilege.i18n; + +import java.util.MissingResourceException; +import java.util.ResourceBundle; + +/** + * @author Robert von Burg + * + */ +public class PrivilegeMessages { + private static final String BUNDLE_NAME = "PrivilegeMessages"; //$NON-NLS-1$ + + private static final ResourceBundle RESOURCE_BUNDLE = ResourceBundle.getBundle(BUNDLE_NAME); + + private PrivilegeMessages() { + } + + public static String getString(String key) { + try { + return RESOURCE_BUNDLE.getString(key); + } catch (MissingResourceException e) { + return '!' + key + '!'; + } + } +} diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/Certificate.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/Certificate.java new file mode 100644 index 000000000..d170ab45b --- /dev/null +++ b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/Certificate.java @@ -0,0 +1,316 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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. + */ +package ch.eitchnet.privilege.model; + +import java.io.Serializable; +import java.util.Collections; +import java.util.Date; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +import ch.eitchnet.privilege.base.PrivilegeException; +import ch.eitchnet.privilege.handler.PrivilegeHandler; +import ch.eitchnet.privilege.model.internal.User; +import ch.eitchnet.utils.helper.StringHelper; + +/** + * The {@link Certificate} is the object a client keeps when accessing a Privilege enabled system. This object is the + * instance which is always used when performing an access and is returned when a user performs a login through + * {@link PrivilegeHandler#authenticate(String, byte[])} + * + * @author Robert von Burg + */ +public final class Certificate implements Serializable { + + private static final long serialVersionUID = 1L; + + private final String sessionId; + private final String username; + private final String firstname; + private final String lastname; + private final UserState userState; + private final String authToken; + private final Date loginTime; + + private final Set userRoles; + private final Map propertyMap; + + private Locale locale; + private Date lastAccess; + + /** + * Default constructor initializing with all information needed for this certificate + * + *

+ * Note, both the authentication token and password are private fields which are generated on login and only known + * by the {@link PrivilegeHandler} + *

+ * + * @param sessionId + * the users session id + * @param username + * the users login name + * @param firstname + * the users first name + * @param lastname + * the users last name + * @param authToken + * the authentication token defining the users unique session and is a private field of this certificate. + * @param locale + * the users {@link Locale} + * @param userRoles + * the user's roles + * @param propertyMap + * a {@link Map} containing string value pairs of properties for the logged in user. These properties can + * be edited and can be used for the user to change settings of this session + */ + public Certificate(String sessionId, String username, String firstname, String lastname, UserState userState, + String authToken, Date loginTime, Locale locale, Set userRoles, Map propertyMap) { + + // validate arguments are not null + if (StringHelper.isEmpty(sessionId)) { + throw new PrivilegeException("sessionId is null!"); //$NON-NLS-1$ + } + if (StringHelper.isEmpty(username)) { + throw new PrivilegeException("username is null!"); //$NON-NLS-1$ + } + if (StringHelper.isEmpty(authToken)) { + throw new PrivilegeException("authToken is null!"); //$NON-NLS-1$ + } + if (userState == null) { + throw new PrivilegeException("userState is null!"); //$NON-NLS-1$ + } + + this.sessionId = sessionId; + this.username = username; + this.firstname = firstname; + this.lastname = lastname; + this.userState = userState; + this.authToken = authToken; + this.loginTime = loginTime; + + // if no locale is given, set default + if (locale == null) + this.locale = Locale.getDefault(); + else + this.locale = locale; + + if (propertyMap == null) + this.propertyMap = Collections.emptyMap(); + else + this.propertyMap = Collections.unmodifiableMap(propertyMap); + + this.userRoles = Collections.unmodifiableSet(userRoles); + } + + /** + * Returns the set or roles this user has + * + * @return the user's roles + */ + public Set getUserRoles() { + return this.userRoles; + } + + /** + * Returns true if the user of this certificate has the given role + * + * @param role + * the role to check for + * + * @return true if the user of this certificate has the given role + */ + public boolean hasRole(String role) { + return this.userRoles.contains(role); + } + + /** + * Returns the {@link User User's} property map. The map is immutable + * + * @return the propertyMap + */ + public Map getPropertyMap() { + return this.propertyMap; + } + + /** + * Returns the property with the given key + * + * @param key + * the key for which the property is to be returned + * + * @return the value of the property with the given key, or null if it does not exist + */ + public String getProperty(String key) { + return this.propertyMap.get(key); + } + + /** + * @return the locale + */ + public Locale getLocale() { + return this.locale; + } + + /** + * @param locale + * the locale to set + */ + public void setLocale(Locale locale) { + this.locale = locale; + } + + /** + * @return the sessionId + */ + public String getSessionId() { + return this.sessionId; + } + + /** + * @return the username + */ + public String getUsername() { + return this.username; + } + + /** + * @return the firstname + */ + public String getFirstname() { + return this.firstname; + } + + /** + * @return the lastname + */ + public String getLastname() { + return this.lastname; + } + + /** + * @return the userState + */ + public UserState getUserState() { + return userState; + } + + /** + * @return the loginTime + */ + public Date getLoginTime() { + return this.loginTime; + } + + /** + * Returns the authToken if the given authPassword is correct, null otherwise + * + * @return the authToken if the given authPassword is correct, null otherwise + */ + public String getAuthToken() { + return this.authToken; + } + + /** + * @return the lastAccess + */ + public Date getLastAccess() { + return this.lastAccess; + } + + /** + * @param lastAccess + * the lastAccess to set + */ + public void setLastAccess(Date lastAccess) { + this.lastAccess = lastAccess; + } + + /** + * Returns a string representation of this object displaying its concrete type and its values + * + * @see java.lang.Object#toString() + */ + @SuppressWarnings("nls") + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("Certificate [sessionId="); + builder.append(this.sessionId); + builder.append(", username="); + builder.append(this.username); + + if (StringHelper.isNotEmpty(this.firstname)) { + builder.append(", firstname="); + builder.append(this.firstname); + } + + if (StringHelper.isNotEmpty(this.lastname)) { + builder.append(", lastname="); + builder.append(this.lastname); + } + + builder.append(", locale="); + builder.append(this.locale); + + builder.append("]"); + return builder.toString(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((this.authToken == null) ? 0 : this.authToken.hashCode()); + result = prime * result + ((this.locale == null) ? 0 : this.locale.hashCode()); + result = prime * result + ((this.sessionId == null) ? 0 : this.sessionId.hashCode()); + result = prime * result + ((this.username == null) ? 0 : this.username.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (!(obj instanceof Certificate)) + return false; + Certificate other = (Certificate) obj; + if (this.authToken == null) { + if (other.authToken != null) + return false; + } else if (!this.authToken.equals(other.authToken)) + return false; + if (this.locale == null) { + if (other.locale != null) + return false; + } else if (!this.locale.equals(other.locale)) + return false; + if (this.sessionId == null) { + if (other.sessionId != null) + return false; + } else if (!this.sessionId.equals(other.sessionId)) + return false; + if (this.username == null) { + if (other.username != null) + return false; + } else if (!this.username.equals(other.username)) + return false; + return true; + } +} diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/IPrivilege.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/IPrivilege.java new file mode 100644 index 000000000..3d22e7b34 --- /dev/null +++ b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/IPrivilege.java @@ -0,0 +1,86 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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. + */ +package ch.eitchnet.privilege.model; + +import java.util.Set; + +import ch.eitchnet.privilege.model.internal.Role; +import ch.eitchnet.privilege.policy.PrivilegePolicy; + +/** + *

+ * {@link IPrivilege} is the main model object for Privilege. A {@link Role} has a set of Privileges assigned to it + * which defines the privileges a logged in user with that role has. If the {@link IPrivilege} has a + * {@link PrivilegePolicy} defined, then that policy will be used for finer granularity and with the deny and allow + * lists configured which is used to evaluate if privilege is granted to a {@link Restrictable} + *

+ * + * @author Robert von Burg + * + */ +public interface IPrivilege { + + /** + * @return a {@link PrivilegeRep} which is a representation of this object used to serialize and view on clients + */ + public abstract PrivilegeRep asPrivilegeRep(); + + /** + * @return the name + */ + public abstract String getName(); + + /** + * @return the policy + */ + public abstract String getPolicy(); + + /** + * @return the allAllowed + */ + public abstract boolean isAllAllowed(); + + /** + * @return the allowList + */ + public abstract Set getAllowList(); + + /** + * @return the denyList + */ + public abstract Set getDenyList(); + + /** + * @return true if there are values in the allow list + */ + public abstract boolean hasAllowed(); + + /** + * @return if the value is in the allow list + */ + public abstract boolean isAllowed(String value); + + /** + * @return true if there are values in the deny list + */ + public abstract boolean hasDenied(); + + /** + * @return true if the value is in the deny list + */ + public abstract boolean isDenied(String value); + +} \ No newline at end of file diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java new file mode 100644 index 000000000..95a7a5efd --- /dev/null +++ b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java @@ -0,0 +1,131 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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. + */ +package ch.eitchnet.privilege.model; + +import java.text.MessageFormat; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import ch.eitchnet.privilege.base.AccessDeniedException; +import ch.eitchnet.privilege.base.PrivilegeException; +import ch.eitchnet.privilege.i18n.PrivilegeMessages; +import ch.eitchnet.privilege.policy.PrivilegePolicy; + +/** + * This context gives access to a logged in user's privilege data e.g. the {@link UserRep}, {@link Certificate} and the + * user's list of {@link PrivilegeRep} + * + *

+ * Note: This is an internal object which is not to be serialized to clients + *

+ * + * @author Robert von Burg + */ +public class PrivilegeContext { + + // + // object state + // + + private UserRep userRep; + private Certificate certificate; + private Map privileges; + private Map policies; + + public PrivilegeContext(UserRep userRep, Certificate certificate, Map privileges, + Map policies) { + this.userRep = userRep; + this.certificate = certificate; + this.privileges = Collections.unmodifiableMap(new HashMap<>(privileges)); + this.policies = Collections.unmodifiableMap(new HashMap<>(policies)); + } + + public UserRep getUserRep() { + return this.userRep; + } + + public Certificate getCertificate() { + return this.certificate; + } + + public String getUsername() { + return this.userRep.getUsername(); + } + + public Set getPrivilegeNames() { + return this.privileges.keySet(); + } + + public void assertHasPrivilege(String privilegeName) { + if (!this.privileges.containsKey(privilegeName)) { + String msg = MessageFormat.format(PrivilegeMessages.getString("Privilege.noprivilege.user"), //$NON-NLS-1$ + userRep.getUsername(), privilegeName); + throw new AccessDeniedException(msg); + } + } + + public IPrivilege getPrivilege(String privilegeName) { + assertHasPrivilege(privilegeName); + return this.privileges.get(privilegeName); + } + + public PrivilegePolicy getPolicy(String policyName) { + PrivilegePolicy policy = this.policies.get(policyName); + if (policy == null) { + String msg = "The PrivilegePolicy {0} does not exist on the PrivilegeContext!"; //$NON-NLS-1$ + throw new PrivilegeException(MessageFormat.format(msg, policyName)); + } + return policy; + } + + // + // business logic + // + + /** + * Validates if the user for this context has the privilege to access to the given {@link Restrictable}. If the user + * has the privilege, then this method returns with no exception and void, if the user does not have the privilege, + * then a {@link AccessDeniedException} is thrown. + * + * @param restrictable + * the {@link Restrictable} which the user wants to access + * + * @throws AccessDeniedException + * if the user does not have access + * @throws PrivilegeException + * if there is an internal error due to wrongly configured privileges or programming errors + */ + public void validateAction(Restrictable restrictable) throws AccessDeniedException, PrivilegeException { + + // the privilege for the restrictable + String privilegeName = restrictable.getPrivilegeName(); + IPrivilege privilege = this.privileges.get(privilegeName); + if (privilege == null) { + String msg = MessageFormat.format(PrivilegeMessages.getString("Privilege.accessdenied.noprivilege"), //$NON-NLS-1$ + getUsername(), privilegeName, restrictable.getClass().getName()); + throw new AccessDeniedException(msg); + } + + // get the policy referenced by the restrictable + String policyName = privilege.getPolicy(); + PrivilegePolicy policy = getPolicy(policyName); + + // delegate to the policy + policy.validateAction(this, privilege, restrictable); + } +} diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java new file mode 100644 index 000000000..66dff555b --- /dev/null +++ b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java @@ -0,0 +1,236 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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. + */ +package ch.eitchnet.privilege.model; + +import java.io.Serializable; +import java.util.HashSet; +import java.util.Set; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + +import ch.eitchnet.privilege.base.PrivilegeException; +import ch.eitchnet.privilege.handler.PrivilegeHandler; +import ch.eitchnet.privilege.model.internal.Role; +import ch.eitchnet.privilege.policy.PrivilegePolicy; +import ch.eitchnet.utils.helper.StringHelper; + +/** + * To keep certain details of the {@link IPrivilege} itself hidden from remote clients and make sure instances are only + * edited by users with the correct privilege, this representational version is allowed to be viewed by remote clients + * and simply wraps all public data from the {@link IPrivilege} + * + * @author Robert von Burg + */ +@XmlRootElement(name = "Privilege") +@XmlAccessorType(XmlAccessType.NONE) +public class PrivilegeRep implements Serializable { + + private static final long serialVersionUID = 1L; + + @XmlAttribute(name = "name") + private String name; + + @XmlAttribute(name = "policy") + private String policy; + + @XmlAttribute(name = "allAllowed") + private boolean allAllowed; + + @XmlElement(name = "denyList") + private Set denyList; + + @XmlElement(name = "allowList") + private Set allowList; + + /** + * Default constructor + * + * @param name + * the name of this privilege, which is unique to all privileges known in the {@link PrivilegeHandler} + * @param policy + * the {@link PrivilegePolicy} configured to evaluate if the privilege is granted + * @param allAllowed + * a boolean defining if a {@link Role} with this {@link IPrivilege} has unrestricted access to a + * {@link Restrictable} + * @param denyList + * a list of deny rules for this {@link IPrivilege} + * @param allowList + * a list of allow rules for this {@link IPrivilege} + */ + public PrivilegeRep(String name, String policy, boolean allAllowed, Set denyList, Set allowList) { + this.name = name; + this.policy = policy; + this.allAllowed = allAllowed; + this.denyList = denyList; + this.allowList = allowList; + } + + /** + * + */ + @SuppressWarnings("unused") + private PrivilegeRep() { + // no-arg constructor for JAXB + } + + /** + * Validates that all required fields are set + */ + public void validate() { + + if (StringHelper.isEmpty(this.name)) { + throw new PrivilegeException("No name defined!"); //$NON-NLS-1$ + } + + if (StringHelper.isEmpty(this.policy)) { + throw new PrivilegeException("policy is null!"); //$NON-NLS-1$ + } + + if (this.denyList == null) { + throw new PrivilegeException("denyList is null"); //$NON-NLS-1$ + } + if (this.allowList == null) { + throw new PrivilegeException("allowList is null"); //$NON-NLS-1$ + } + } + + /** + * @return the name + */ + public String getName() { + return this.name; + } + + /** + * @param name + * the name to set + */ + public void setName(String name) { + this.name = name; + } + + /** + * @return the policy + */ + public String getPolicy() { + return this.policy; + } + + /** + * @param policy + * the policy to set + */ + public void setPolicy(String policy) { + this.policy = policy; + } + + /** + * @return the allAllowed + */ + public boolean isAllAllowed() { + return this.allAllowed; + } + + /** + * @param allAllowed + * the allAllowed to set + */ + public void setAllAllowed(boolean allAllowed) { + this.allAllowed = allAllowed; + } + + /** + * @return the denyList + */ + public Set getDenyList() { + return this.denyList == null ? new HashSet<>() : this.denyList; + } + + /** + * @param denyList + * the denyList to set + */ + public void setDenyList(Set denyList) { + this.denyList = denyList; + } + + /** + * @return the allowList + */ + public Set getAllowList() { + return this.allowList == null ? new HashSet<>() : this.allowList; + } + + /** + * @param allowList + * the allowList to set + */ + public void setAllowList(Set allowList) { + this.allowList = allowList; + } + + /** + * Returns a string representation of this object displaying its concrete type and its values + * + * @see java.lang.Object#toString() + */ + @SuppressWarnings("nls") + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("PrivilegeRep [name="); + builder.append(this.name); + builder.append(", policy="); + builder.append(this.policy); + builder.append(", allAllowed="); + builder.append(this.allAllowed); + builder.append(", denyList="); + builder.append((this.denyList == null ? "null" : this.denyList.size())); + builder.append(", allowList="); + builder.append((this.allowList == null ? "null" : this.allowList.size())); + builder.append("]"); + return builder.toString(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((this.name == null) ? 0 : this.name.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + PrivilegeRep other = (PrivilegeRep) obj; + if (this.name == null) { + if (other.name != null) + return false; + } else if (!this.name.equals(other.name)) + return false; + return true; + } +} diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/Restrictable.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/Restrictable.java new file mode 100644 index 000000000..87f889818 --- /dev/null +++ b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/Restrictable.java @@ -0,0 +1,46 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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. + */ +package ch.eitchnet.privilege.model; + +import ch.eitchnet.privilege.policy.PrivilegePolicy; + +/** + *

+ * Objects implementing this interface are used to grant/restrict privileges to them. A {@link PrivilegePolicy} + * implements the logic on granting/restricting privileges for a {@link Restrictable} and the + * {@link #getPrivilegeName()} is used to find the {@link IPrivilege} which has the associated {@link PrivilegePolicy} + * for evaluating access + *

+ * + * @author Robert von Burg + * + */ +public interface Restrictable { + + /** + * Returns the name of the {@link IPrivilege} which is to be used to validate privileges against + * + * @return the name of the {@link IPrivilege} which is to be used to validate privileges against + */ + public String getPrivilegeName(); + + /** + * Returns the value which defines or describes what privilege is to be granted + * + * @return the value which defines or describes what privilege is to be granted + */ + public Object getPrivilegeValue(); +} diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/RoleRep.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/RoleRep.java new file mode 100644 index 000000000..6e449ff97 --- /dev/null +++ b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/RoleRep.java @@ -0,0 +1,168 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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. + */ +package ch.eitchnet.privilege.model; + +import java.io.Serializable; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.List; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + +import ch.eitchnet.privilege.base.PrivilegeException; +import ch.eitchnet.privilege.model.internal.Role; +import ch.eitchnet.utils.helper.StringHelper; + +/** + * To keep certain details of the {@link Role} itself hidden from remote clients and make sure instances are only edited + * by users with the correct privilege, this representational version is allowed to be viewed by remote clients and + * simply wraps all public data from the {@link Role} + * + * @author Robert von Burg + */ +@XmlRootElement(name = "Role") +@XmlAccessorType(XmlAccessType.NONE) +public class RoleRep implements Serializable { + + private static final long serialVersionUID = 1L; + + @XmlAttribute(name = "name") + private String name; + + @XmlElement(name = "privileges") + private List privileges; + + /** + * Default constructor + * + * @param name + * the name of this role + * @param privileges + * the list of privileges granted to this role + */ + public RoleRep(String name, List privileges) { + this.name = name; + this.privileges = privileges; + } + + /** + * + */ + @SuppressWarnings("unused") + private RoleRep() { + // no-arg constructor for JAXB + } + + /** + * validates that all required fields are set + */ + public void validate() { + if (StringHelper.isEmpty(this.name)) + throw new PrivilegeException("name is null"); //$NON-NLS-1$ + + if (this.privileges != null && !this.privileges.isEmpty()) { + for (PrivilegeRep privilege : this.privileges) { + try { + privilege.validate(); + } catch (Exception e) { + String msg = "Privilege {0} is invalid on role {1}"; + msg = MessageFormat.format(msg, privilege.getName(), this.name); + throw new PrivilegeException(msg, e); + } + } + } + } + + /** + * @return the name + */ + public String getName() { + return this.name; + } + + /** + * @param name + * the name to set + */ + public void setName(String name) { + this.name = name; + } + + /** + * Returns the privileges assigned to this Role as a list + * + * @return the privileges assigned to this Role as a list + */ + public List getPrivileges() { + return this.privileges == null ? new ArrayList<>() : this.privileges; + } + + /** + * Sets the privileges on this from a list + * + * @param privileges + * the list of privileges to assign to this role + */ + public void setPrivileges(List privileges) { + this.privileges = privileges; + } + + /** + * Returns a string representation of this object displaying its concrete type and its values + * + * @see java.lang.Object#toString() + */ + @SuppressWarnings("nls") + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("RoleRep [name="); + builder.append(this.name); + builder.append(", privilegeMap="); + builder.append((this.privileges == null ? "null" : this.privileges)); + builder.append("]"); + return builder.toString(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((this.name == null) ? 0 : this.name.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + RoleRep other = (RoleRep) obj; + if (this.name == null) { + if (other.name != null) + return false; + } else if (!this.name.equals(other.name)) + return false; + return true; + } +} diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/SimpleRestrictable.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/SimpleRestrictable.java new file mode 100644 index 000000000..341bbb37c --- /dev/null +++ b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/SimpleRestrictable.java @@ -0,0 +1,45 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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. + */ +package ch.eitchnet.privilege.model; + +import ch.eitchnet.utils.dbc.DBC; + +public class SimpleRestrictable implements Restrictable { + + private final String name; + private final Object value; + + /** + * @param name + * @param value + */ + public SimpleRestrictable(String name, Object value) { + DBC.PRE.assertNotEmpty("name must not be emepty", name); + DBC.PRE.assertNotNull("value must not be null", value); + this.name = name; + this.value = value; + } + + @Override + public String getPrivilegeName() { + return this.name; + } + + @Override + public Object getPrivilegeValue() { + return this.value; + } +} \ No newline at end of file diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/UserRep.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/UserRep.java new file mode 100644 index 000000000..6300fe96c --- /dev/null +++ b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/UserRep.java @@ -0,0 +1,388 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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. + */ +package ch.eitchnet.privilege.model; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + +import ch.eitchnet.privilege.base.PrivilegeException; +import ch.eitchnet.privilege.model.internal.Role; +import ch.eitchnet.privilege.model.internal.User; +import ch.eitchnet.utils.helper.StringHelper; +import ch.eitchnet.utils.xml.XmlKeyValue; + +/** + * To keep certain details of the {@link User} itself hidden from remote clients and make sure instances are only edited + * by users with the correct privilege, this representational version is allowed to be viewed by remote clients and + * simply wraps all public data from the {@link User} + * + * @author Robert von Burg + */ +@XmlRootElement(name = "User") +@XmlAccessorType(XmlAccessType.NONE) +public class UserRep implements Serializable { + + private static final long serialVersionUID = 1L; + + @XmlAttribute(name = "userId") + private String userId; + + @XmlAttribute(name = "username") + private String username; + + @XmlAttribute(name = "firstname") + private String firstname; + + @XmlAttribute(name = "lastname") + private String lastname; + + @XmlAttribute(name = "userState") + private UserState userState; + + @XmlAttribute(name = "locale") + private Locale locale; + + @XmlElement(name = "roles") + private Set roles; + + @XmlElement(name = "properties") + private List properties; + + /** + * Default constructor + * + * @param userId + * the user's id + * @param username + * the user's login name + * @param firstname + * the user's first name + * @param lastname + * the user's last name + * @param userState + * the user's {@link UserState} + * @param roles + * the set of {@link Role}s assigned to this user + * @param locale + * the user's {@link Locale} + * @param propertyMap + * a {@link Map} containing string value pairs of properties for this user + */ + public UserRep(String userId, String username, String firstname, String lastname, UserState userState, + Set roles, Locale locale, Map propertyMap) { + this.userId = userId; + this.username = username; + this.firstname = firstname; + this.lastname = lastname; + this.userState = userState; + this.roles = roles; + this.locale = locale; + this.properties = propertyMap == null ? new ArrayList<>() : XmlKeyValue.valueOf(propertyMap); + } + + /** + * + */ + @SuppressWarnings("unused") + private UserRep() { + // No arg constructor for JAXB + } + + /** + * Validates that all required fields are set + */ + public void validate() { + + if (StringHelper.isEmpty(this.userId)) + throw new PrivilegeException("userId is null or empty"); //$NON-NLS-1$ + + if (StringHelper.isEmpty(this.username)) + throw new PrivilegeException("username is null or empty"); //$NON-NLS-1$ + + if (this.userState == null) + throw new PrivilegeException("userState is null"); //$NON-NLS-1$ + + if (StringHelper.isEmpty(this.firstname)) + throw new PrivilegeException("firstname is null or empty"); //$NON-NLS-1$ + + if (StringHelper.isEmpty(this.lastname)) + throw new PrivilegeException("lastname is null or empty"); //$NON-NLS-1$ + + if (this.roles == null || this.roles.isEmpty()) + throw new PrivilegeException("roles is null or empty"); //$NON-NLS-1$ + } + + /** + * @return the userId + */ + public String getUserId() { + return this.userId; + } + + /** + * Set the userId + * + * @param userId + * to set + */ + public void setUserId(String userId) { + this.userId = userId; + } + + /** + * @return the username + */ + public String getUsername() { + return this.username; + } + + /** + * @param username + * the username to set + */ + public void setUsername(String username) { + this.username = username; + } + + /** + * @return the firstname + */ + public String getFirstname() { + return this.firstname; + } + + /** + * @param firstname + * the firstname to set + */ + public void setFirstname(String firstname) { + this.firstname = firstname; + } + + /** + * @return the lastname + */ + public String getLastname() { + return this.lastname; + } + + /** + * @param lastname + * the lastname to set + */ + public void setLastname(String lastname) { + this.lastname = lastname; + } + + /** + * @return the userState + */ + public UserState getUserState() { + return this.userState; + } + + /** + * @param userState + * the userState to set + */ + public void setUserState(UserState userState) { + this.userState = userState; + } + + /** + * @return the roles + */ + public Set getRoles() { + return this.roles; + } + + /** + * @param roles + * the roles to set + */ + public void setRoles(Set roles) { + this.roles = roles; + } + + /** + * @return the locale + */ + public Locale getLocale() { + return this.locale; + } + + /** + * @param locale + * the locale to set + */ + public void setLocale(Locale locale) { + this.locale = locale; + } + + /** + * Returns the property with the given key + * + * @param key + * the key for which the property is to be returned + * + * @return the property with the given key, or null if the property is not defined + */ + public String getProperty(String key) { + if (this.properties == null) + return null; + for (XmlKeyValue keyValue : this.properties) { + if (keyValue.getKey().equals(key)) + return keyValue.getValue(); + } + return null; + } + + /** + * Set the property with the key to the value + * + * @param key + * the key of the property to set + * @param value + * the value of the property to set + */ + public void setProperty(String key, String value) { + if (this.properties == null) + this.properties = new ArrayList<>(); + + boolean updated = false; + + for (XmlKeyValue keyValue : this.properties) { + if (keyValue.getKey().equals(key)) { + keyValue.setValue(value); + updated = true; + } + } + + if (!updated) { + this.properties.add(new XmlKeyValue(key, value)); + } + } + + /** + * Returns the {@link Set} of keys of all properties + * + * @return the {@link Set} of keys of all properties + */ + public Set getPropertyKeySet() { + if (this.properties == null) + return new HashSet<>(); + Set keySet = new HashSet<>(this.properties.size()); + for (XmlKeyValue keyValue : this.properties) { + keySet.add(keyValue.getKey()); + } + return keySet; + } + + /** + * Returns the map of properties + * + * @return the map of properties + */ + public Map getPropertyMap() { + if (this.properties == null) + return new HashMap<>(); + return XmlKeyValue.toMap(this.properties); + } + + /** + * Returns the string map properties of this user as a list of {@link XmlKeyValue} elements + * + * @return the string map properties of this user as a list of {@link XmlKeyValue} elements + */ + @XmlElement(name = "properties") + public List getProperties() { + return this.properties == null ? new ArrayList<>() : this.properties; + } + + /** + * Sets the string map properties of this user from the given list of {@link XmlKeyValue} + * + * @param values + * the list of {@link XmlKeyValue} from which to set the properties + */ + public void setProperties(List values) { + this.properties = values; + } + + /** + * Returns a string representation of this object displaying its concrete type and its values + * + * @see java.lang.Object#toString() + */ + @SuppressWarnings("nls") + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("UserRep [userId="); + builder.append(this.userId); + builder.append(", username="); + builder.append(this.username); + builder.append(", firstname="); + builder.append(this.firstname); + builder.append(", lastname="); + builder.append(this.lastname); + builder.append(", userState="); + builder.append(this.userState); + builder.append(", locale="); + builder.append(this.locale); + builder.append(", roles="); + builder.append(this.roles); + builder.append("]"); + return builder.toString(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((this.username == null) ? 0 : this.username.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + UserRep other = (UserRep) obj; + if (this.username == null) { + if (other.username != null) + return false; + } else if (!this.username.equals(other.username)) + return false; + return true; + } +} diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/UserState.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/UserState.java new file mode 100644 index 000000000..b78e492ff --- /dev/null +++ b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/UserState.java @@ -0,0 +1,60 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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. + */ +package ch.eitchnet.privilege.model; + +import ch.eitchnet.privilege.model.internal.User; + +/** + * The {@link UserState} enum defines the different states a {@link User} can have: + *
    + *
  • NEW - the user is new, and cannot login
  • + *
  • ENABLED - the user has been enabled, meaning a password has been set and the user has at least one role assigned + * and may thus login
  • + *
  • DISABLED - the user been disabled by an administrator
  • + *
  • EXPIRED - the user has automatically expired through a predefined time
  • + *
+ * + * @author Robert von Burg + * + */ +public enum UserState { + /** + * the user is new, and cannot login + */ + NEW, + /** + * the user has been enabled, meaning a password has been set and the user has at least one role assigned and may + * thus login + */ + ENABLED, + /** + * the user been disabled by an administrator + */ + DISABLED, + /** + * the user has automatically expired through a predefined time + */ + EXPIRED, + + /** + * This is the System user state which is special and thus exempted from normal uses + */ + SYSTEM; + + public boolean isSystem() { + return this == UserState.SYSTEM; + } +} diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java new file mode 100644 index 000000000..cec81e18d --- /dev/null +++ b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java @@ -0,0 +1,188 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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. + */ +package ch.eitchnet.privilege.model.internal; + +import java.text.MessageFormat; +import java.util.HashMap; +import java.util.Map; + +import ch.eitchnet.privilege.base.PrivilegeException; +import ch.eitchnet.privilege.handler.PrivilegeHandler; +import ch.eitchnet.privilege.policy.PrivilegePolicy; + +/** + * This class is used during XML parsing to hold the model before it is properly validated and made accessible through + * the {@link PrivilegeHandler} + * + *

+ * Note: This is an internal object which is not to be serialized or passed to clients + *

+ * + * @author Robert von Burg + */ +public class PrivilegeContainerModel { + + private String encryptionHandlerClassName; + private Map encryptionHandlerParameterMap; + private String persistenceHandlerClassName; + private Map persistenceHandlerParameterMap; + private Map parameterMap; + private Map> policies; + + /** + * Default constructor + */ + public PrivilegeContainerModel() { + this.policies = new HashMap<>(); + } + + /** + * @return the parameterMap + */ + public Map getParameterMap() { + return this.parameterMap; + } + + /** + * @param parameterMap + * the parameterMap to set + */ + public void setParameterMap(Map parameterMap) { + this.parameterMap = parameterMap; + } + + /** + * @return the encryptionHandlerClassName + */ + public String getEncryptionHandlerClassName() { + return this.encryptionHandlerClassName; + } + + /** + * @param encryptionHandlerClassName + * the encryptionHandlerClassName to set + */ + public void setEncryptionHandlerClassName(String encryptionHandlerClassName) { + this.encryptionHandlerClassName = encryptionHandlerClassName; + } + + /** + * @return the encryptionHandlerParameterMap + */ + public Map getEncryptionHandlerParameterMap() { + return this.encryptionHandlerParameterMap; + } + + /** + * @param encryptionHandlerParameterMap + * the encryptionHandlerParameterMap to set + */ + public void setEncryptionHandlerParameterMap(Map encryptionHandlerParameterMap) { + this.encryptionHandlerParameterMap = encryptionHandlerParameterMap; + } + + /** + * @return the persistenceHandlerClassName + */ + public String getPersistenceHandlerClassName() { + return this.persistenceHandlerClassName; + } + + /** + * @param persistenceHandlerClassName + * the persistenceHandlerClassName to set + */ + public void setPersistenceHandlerClassName(String persistenceHandlerClassName) { + this.persistenceHandlerClassName = persistenceHandlerClassName; + } + + /** + * @return the persistenceHandlerParameterMap + */ + public Map getPersistenceHandlerParameterMap() { + return this.persistenceHandlerParameterMap; + } + + /** + * @param persistenceHandlerParameterMap + * the persistenceHandlerParameterMap to set + */ + public void setPersistenceHandlerParameterMap(Map persistenceHandlerParameterMap) { + this.persistenceHandlerParameterMap = persistenceHandlerParameterMap; + } + + /** + * @param privilegeName + * @param policyClassName + */ + public void addPolicy(String privilegeName, String policyClassName) { + + try { + + // load class and try to create a new instance + @SuppressWarnings("unchecked") + Class clazz = (Class) Class.forName(policyClassName); + clazz.newInstance(); + + this.policies.put(privilegeName, clazz); + + } catch (InstantiationException e) { + String msg = "Configured Privilege Policy {0} with class {1} could not be instantiated."; //$NON-NLS-1$ + msg = MessageFormat.format(msg, privilegeName, policyClassName); + throw new PrivilegeException(msg, e); + } catch (IllegalAccessException e) { + String msg = "Configured Privilege Policy {0} with class {1} can not be accessed."; //$NON-NLS-1$ + msg = MessageFormat.format(msg, privilegeName, policyClassName); + throw new PrivilegeException(msg, e); + } catch (ClassNotFoundException e) { + String msg = "Configured Privilege Policy {0} with class {1} does not exist."; //$NON-NLS-1$ + msg = MessageFormat.format(msg, privilegeName, policyClassName); + throw new PrivilegeException(msg, e); + } + } + + /** + * @return the policies + */ + public Map> getPolicies() { + return this.policies; + } + + /** + * Returns a string representation of this object displaying its concrete type and its values + * + * @see java.lang.Object#toString() + */ + @SuppressWarnings("nls") + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("PrivilegeContainerModel [encryptionHandlerClassName="); + builder.append(this.encryptionHandlerClassName); + builder.append(", encryptionHandlerParameterMap="); + builder.append(this.encryptionHandlerParameterMap.size()); + builder.append(", persistenceHandlerClassName="); + builder.append(this.persistenceHandlerClassName); + builder.append(", persistenceHandlerParameterMap="); + builder.append(this.persistenceHandlerParameterMap.size()); + builder.append(", parameterMap="); + builder.append(this.parameterMap.size()); + builder.append(", policies="); + builder.append(this.policies.size()); + builder.append("]"); + return builder.toString(); + } +} diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeImpl.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeImpl.java new file mode 100644 index 000000000..89b67366a --- /dev/null +++ b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeImpl.java @@ -0,0 +1,229 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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. + */ +package ch.eitchnet.privilege.model.internal; + +import java.text.MessageFormat; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import ch.eitchnet.privilege.base.PrivilegeException; +import ch.eitchnet.privilege.handler.PrivilegeHandler; +import ch.eitchnet.privilege.model.IPrivilege; +import ch.eitchnet.privilege.model.PrivilegeRep; +import ch.eitchnet.privilege.model.Restrictable; +import ch.eitchnet.privilege.policy.PrivilegePolicy; +import ch.eitchnet.utils.helper.StringHelper; + +/** + *

+ * {@link IPrivilege} is the main model object for Privilege. A {@link Role} has a set of Privileges assigned to it + * which defines the privileges a logged in user with that role has. If the {@link IPrivilege} has a + * {@link PrivilegePolicy} defined, then that policy will be used for finer granularity and with the deny and allow + * lists configured which is used to evaluate if privilege is granted to a {@link Restrictable} + *

+ * + *

+ * {@link IPrivilege}s have allow and deny rules which the configured {@link PrivilegeHandler} uses to + *

+ * + *

+ * Note: This is an internal object which is not to be serialized or passed to clients, {@link PrivilegeRep}s are used + * for that + *

+ * + * @author Robert von Burg + */ +public final class PrivilegeImpl implements IPrivilege { + + private final String name; + private final String policy; + private final boolean allAllowed; + private final Set denyList; + private final Set allowList; + + /** + * Default constructor + * + * @param name + * the name of this privilege, which is unique to all privileges known in the {@link PrivilegeHandler} + * @param policy + * the {@link PrivilegePolicy} configured to evaluate if the privilege is granted. If null, then + * privilege is granted + * @param allAllowed + * a boolean defining if a {@link Role} with this {@link PrivilegeImpl} has unrestricted access to a + * {@link Restrictable} in which case the deny and allow lists are ignored and can be null + * @param denyList + * a list of deny rules for this {@link PrivilegeImpl}, can be null if all allowed + * @param allowList + * a list of allow rules for this {@link PrivilegeImpl}, can be null if all allowed + */ + public PrivilegeImpl(String name, String policy, boolean allAllowed, Set denyList, Set allowList) { + + if (StringHelper.isEmpty(name)) { + throw new PrivilegeException("No name defined!"); //$NON-NLS-1$ + } + if (StringHelper.isEmpty(policy)) { + throw new PrivilegeException(MessageFormat.format("Policy may not be empty for Privilege {0}!", name)); //$NON-NLS-1$ + } + if (denyList == null) { + throw new PrivilegeException(MessageFormat.format("denyList is null for Privilege {0}!", name)); //$NON-NLS-1$ + } + if (allowList == null) { + throw new PrivilegeException(MessageFormat.format("allowList is null for Privilege {0}!", name)); //$NON-NLS-1$ + } + + this.name = name; + this.allAllowed = allAllowed; + this.policy = policy; + this.denyList = Collections.unmodifiableSet(denyList); + this.allowList = Collections.unmodifiableSet(allowList); + } + + /** + * Constructs a {@link PrivilegeImpl} from the {@link PrivilegeRep} + * + * @param privilegeRep + * the {@link PrivilegeRep} from which to create the {@link PrivilegeImpl} + */ + public PrivilegeImpl(PrivilegeRep privilegeRep) { + this(privilegeRep.getName(), privilegeRep.getPolicy(), privilegeRep.isAllAllowed(), privilegeRep.getDenyList(), + privilegeRep.getAllowList()); + } + + /** + * @return a {@link PrivilegeRep} which is a representation of this object used to serialize and view on clients + */ + @Override + public PrivilegeRep asPrivilegeRep() { + return new PrivilegeRep(this.name, this.policy, this.allAllowed, new HashSet<>(this.denyList), + new HashSet<>(this.allowList)); + } + + /** + * @return the name + */ + @Override + public String getName() { + return this.name; + } + + /** + * @return the policy + */ + @Override + public String getPolicy() { + return this.policy; + } + + /** + * @return the allAllowed + */ + @Override + public boolean isAllAllowed() { + return this.allAllowed; + } + + /** + * @return the allowList + */ + @Override + public Set getAllowList() { + return this.allowList; + } + + /** + * @return the denyList + */ + @Override + public Set getDenyList() { + return this.denyList; + } + + /** + * @return true if there are values in the allow list + */ + @Override + public boolean hasAllowed() { + return !this.allowList.isEmpty(); + } + + /** + * @return if the value is in the allow list + */ + @Override + public boolean isAllowed(String value) { + return this.allowList.contains(value); + } + + /** + * @return true if there are values in the deny list + */ + @Override + public boolean hasDenied() { + return !this.allowList.isEmpty(); + } + + /** + * @return true if the value is in the deny list + */ + @Override + public boolean isDenied(String value) { + return this.denyList.contains(value); + } + + /** + * Returns a string representation of this object displaying its concrete type and its values + * + * @see java.lang.Object#toString() + */ + @SuppressWarnings("nls") + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("Privilege [name="); + builder.append(this.name); + builder.append(", policy="); + builder.append(this.policy); + builder.append("]"); + return builder.toString(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((this.name == null) ? 0 : this.name.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + PrivilegeImpl other = (PrivilegeImpl) obj; + if (this.name == null) { + if (other.name != null) + return false; + } else if (!this.name.equals(other.name)) + return false; + return true; + } +} diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/internal/Role.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/internal/Role.java new file mode 100644 index 000000000..bd8acdf8f --- /dev/null +++ b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/internal/Role.java @@ -0,0 +1,187 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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. + */ +package ch.eitchnet.privilege.model.internal; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import ch.eitchnet.privilege.base.PrivilegeException; +import ch.eitchnet.privilege.model.IPrivilege; +import ch.eitchnet.privilege.model.PrivilegeRep; +import ch.eitchnet.privilege.model.RoleRep; +import ch.eitchnet.utils.helper.StringHelper; + +/** + *

+ * A {@link User} is assigned a set of roles. These roles have a set of privileges assigned to them by name and they + * define the privileges granted to a user with this role + *

+ * + *

+ * Note: This is an internal object which is not to be serialized or passed to clients, {@link RoleRep}s are used for + * that + *

+ * + * @author Robert von Burg + */ +public final class Role { + + private final String name; + private final Map privilegeMap; + + /** + * Default constructor + * + * @param name + * the name of the role + * @param privilegeMap + * a map of {@link IPrivilege}s granted to this role + */ + public Role(String name, Map privilegeMap) { + + if (StringHelper.isEmpty(name)) { + throw new PrivilegeException("No name defined!"); //$NON-NLS-1$ + } + if (privilegeMap == null) { + throw new PrivilegeException("No privileges defined!"); //$NON-NLS-1$ + } + + this.name = name; + this.privilegeMap = Collections.unmodifiableMap(privilegeMap); + } + + /** + * Construct {@link Role} from its representation {@link RoleRep} + * + * @param roleRep + * the representation from which to create the {@link Role} + */ + public Role(RoleRep roleRep) { + + String name = roleRep.getName(); + if (StringHelper.isEmpty(name)) { + throw new PrivilegeException("No name defined!"); //$NON-NLS-1$ + } + + if (roleRep.getPrivileges() == null) { + throw new PrivilegeException("Privileges may not be null!"); //$NON-NLS-1$ + } + + // build privileges from rep + Map privilegeMap = new HashMap<>(roleRep.getPrivileges().size()); + for (PrivilegeRep privilege : roleRep.getPrivileges()) { + privilegeMap.put(privilege.getName(), new PrivilegeImpl(privilege)); + } + + this.name = name; + this.privilegeMap = Collections.unmodifiableMap(privilegeMap); + } + + /** + * @return the name + */ + public String getName() { + return this.name; + } + + /** + * Returns the {@link Set} of names for the currently stored {@link IPrivilege Privileges} + * + * @return the {@link Set} of names for the currently stored {@link IPrivilege Privileges} + */ + public Set getPrivilegeNames() { + return this.privilegeMap.keySet(); + } + + /** + * Returns the {@link IPrivilege} for the given name, null if it does not exist + * + * @return the {@link IPrivilege} for the given name, null if it does not exist + */ + public IPrivilege getPrivilege(String name) { + return this.privilegeMap.get(name); + } + + /** + * Determines if this {@link Role} has the {@link IPrivilege} with the given name + * + * @param name + * the name of the {@link IPrivilege} + * + * @return true if this {@link Role} has the {@link IPrivilege} with the given name + */ + public boolean hasPrivilege(String name) { + return this.privilegeMap.containsKey(name); + } + + /** + * @return a {@link RoleRep} which is a representation of this object used to serialize and view on clients + */ + public RoleRep asRoleRep() { + List privileges = new ArrayList<>(); + for (Entry entry : this.privilegeMap.entrySet()) { + privileges.add(entry.getValue().asPrivilegeRep()); + } + return new RoleRep(this.name, privileges); + } + + /** + * Returns a string representation of this object displaying its concrete type and its values + * + * @see java.lang.Object#toString() + */ + @SuppressWarnings("nls") + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("Role [name="); + builder.append(this.name); + builder.append(", privileges="); + builder.append(this.privilegeMap.keySet()); + builder.append("]"); + return builder.toString(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((this.name == null) ? 0 : this.name.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Role other = (Role) obj; + if (this.name == null) { + if (other.name != null) + return false; + } else if (!this.name.equals(other.name)) + return false; + return true; + } +} diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/internal/User.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/internal/User.java new file mode 100644 index 000000000..5afe247aa --- /dev/null +++ b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/internal/User.java @@ -0,0 +1,291 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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. + */ +package ch.eitchnet.privilege.model.internal; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +import ch.eitchnet.privilege.base.PrivilegeException; +import ch.eitchnet.privilege.model.UserRep; +import ch.eitchnet.privilege.model.UserState; +import ch.eitchnet.utils.helper.StringHelper; + +/** + * This class defines the actual login information for a given user which can be granted privileges. Every user is + * granted a set of {@link Role}s and has a {@link UserState} including detail information like first name and lastname + * + *

+ * Note: This is an internal object which is not to be serialized or passed to clients, {@link UserRep}s are used for + * that + *

+ * + * @author Robert von Burg + */ +public final class User { + + private final String userId; + + private final String username; + private final String password; + + private final String firstname; + private final String lastname; + + private final UserState userState; + + private final Set roles; + + private final Map propertyMap; + + private final Locale locale; + + /** + * Default constructor + * + * @param userId + * the user's id + * @param username + * the user's login name + * @param password + * the user's password (hashed) + * @param firstname + * the user's first name + * @param lastname + * the user's lastname + * @param userState + * the user's {@link UserState} + * @param roles + * the set of {@link Role}s assigned to this user + * @param locale + * the user's {@link Locale} + * @param propertyMap + * a {@link Map} containing string value pairs of properties for this user + */ + public User(String userId, String username, String password, String firstname, String lastname, + UserState userState, Set roles, Locale locale, Map propertyMap) { + + if (StringHelper.isEmpty(userId)) { + throw new PrivilegeException("No UserId defined!"); //$NON-NLS-1$ + } + if (userState == null) { + throw new PrivilegeException("No userState defined!"); //$NON-NLS-1$ + } + if (StringHelper.isEmpty(username)) { + throw new PrivilegeException("No username defined!"); //$NON-NLS-1$ + } + if (userState != UserState.SYSTEM) { + if (StringHelper.isEmpty(lastname)) { + throw new PrivilegeException("No lastname defined!"); //$NON-NLS-1$ + } + if (StringHelper.isEmpty(firstname)) { + throw new PrivilegeException("No firstname defined!"); //$NON-NLS-1$ + } + } + + // password may be null, meaning not able to login + // roles may be null, meaning not able to login and must be added later + // locale may be null, meaning use system default + // properties may be null, meaning no properties + + this.userId = userId; + + this.username = username; + this.password = StringHelper.isEmpty(password) ? null : password; + this.userState = userState; + + this.firstname = firstname; + this.lastname = lastname; + + if (roles == null) + this.roles = Collections.emptySet(); + else + this.roles = Collections.unmodifiableSet(new HashSet<>(roles)); + + if (locale == null) + this.locale = Locale.getDefault(); + else + this.locale = locale; + + if (propertyMap == null) + this.propertyMap = Collections.emptyMap(); + else + this.propertyMap = Collections.unmodifiableMap(new HashMap<>(propertyMap)); + } + + /** + * @return the userId + */ + public String getUserId() { + return this.userId; + } + + /** + * @return the username + */ + public String getUsername() { + return this.username; + } + + /** + * Returns the hashed password for this {@link User} + * + * @return the hashed password for this {@link User} + */ + public String getPassword() { + return this.password; + } + + /** + * @return the first name + */ + public String getFirstname() { + return this.firstname; + } + + /** + * @return the last name + */ + public String getLastname() { + return this.lastname; + } + + /** + * @return the userState + */ + public UserState getUserState() { + return this.userState; + } + + /** + * @return the roles + */ + public Set getRoles() { + return this.roles; + } + + /** + * Returns true if this user has the specified role + * + * @param role + * the name of the {@link Role} to check for + * + * @return true if the this user has the specified role + */ + public boolean hasRole(String role) { + return this.roles.contains(role); + } + + /** + * @return the locale + */ + public Locale getLocale() { + return this.locale; + } + + /** + * Returns the property with the given key + * + * @param key + * the key for which the property is to be returned + * + * @return the property with the given key, or null if the property is not defined + */ + public String getProperty(String key) { + return this.propertyMap.get(key); + } + + /** + * Returns the {@link Set} of keys of all properties + * + * @return the {@link Set} of keys of all properties + */ + public Set getPropertyKeySet() { + return this.propertyMap.keySet(); + } + + /** + * Returns the map of properties + * + * @return the map of properties + */ + public Map getProperties() { + return this.propertyMap; + } + + /** + * @return a {@link UserRep} which is a representation of this object used to serialize and view on clients + */ + public UserRep asUserRep() { + return new UserRep(this.userId, this.username, this.firstname, this.lastname, this.userState, + new HashSet<>(this.roles), this.locale, new HashMap<>(this.propertyMap)); + } + + /** + * Returns a string representation of this object displaying its concrete type and its values + * + * @see java.lang.Object#toString() + */ + @SuppressWarnings("nls") + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("User [userId="); + builder.append(this.userId); + builder.append(", username="); + builder.append(this.username); + builder.append(", firstname="); + builder.append(this.firstname); + builder.append(", lastname="); + builder.append(this.lastname); + builder.append(", locale="); + builder.append(this.locale); + builder.append(", userState="); + builder.append(this.userState); + builder.append(", roles="); + builder.append(this.roles); + builder.append("]"); + return builder.toString(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((this.userId == null) ? 0 : this.userId.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + User other = (User) obj; + if (this.userId == null) { + if (other.userId != null) + return false; + } else if (!this.userId.equals(other.userId)) + return false; + return true; + } +} diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java new file mode 100644 index 000000000..05494e5c5 --- /dev/null +++ b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java @@ -0,0 +1,63 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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. + */ +package ch.eitchnet.privilege.policy; + +import java.text.MessageFormat; + +import ch.eitchnet.privilege.base.PrivilegeException; +import ch.eitchnet.privilege.i18n.PrivilegeMessages; +import ch.eitchnet.privilege.model.IPrivilege; +import ch.eitchnet.privilege.model.PrivilegeContext; +import ch.eitchnet.privilege.model.Restrictable; +import ch.eitchnet.privilege.model.internal.Role; + +/** + * This is a simple implementation of {@link PrivilegePolicy} which uses the {@link Restrictable#getPrivilegeName()} to + * see if a given {@link Role} has the privilege required by the value from {@link Restrictable#getPrivilegeValue()} + * + * @author Robert von Burg + */ +public class DefaultPrivilege implements PrivilegePolicy { + + /** + * The value of {@link Restrictable#getPrivilegeValue()} is used to check if the {@link Role} has this privilege + * + * @see ch.eitchnet.privilege.policy.PrivilegePolicy#validateAction(IPrivilege, Restrictable) + */ + @Override + public void validateAction(PrivilegeContext ctx, IPrivilege privilege, Restrictable restrictable) { + PrivilegePolicyHelper.preValidate(privilege, restrictable); + + // get the value on which the action is to be performed + Object object = restrictable.getPrivilegeValue(); + + // DefaultPrivilege policy expects the privilege value to be a string + if (!(object instanceof String)) { + String msg = Restrictable.class.getName() + + PrivilegeMessages.getString("Privilege.illegalArgument.nonstring"); //$NON-NLS-1$ + msg = MessageFormat.format(msg, restrictable.getClass().getSimpleName()); + throw new PrivilegeException(msg); + } + + // if everything is allowed, then no need to carry on + if (privilege.isAllAllowed()) + return; + + String privilegeValue = (String) object; + + PrivilegePolicyHelper.checkByAllowDenyValues(ctx, privilege, restrictable, privilegeValue); + } +} diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicy.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicy.java new file mode 100644 index 000000000..ba698b174 --- /dev/null +++ b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicy.java @@ -0,0 +1,54 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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. + */ +package ch.eitchnet.privilege.policy; + +import ch.eitchnet.privilege.base.AccessDeniedException; +import ch.eitchnet.privilege.model.IPrivilege; +import ch.eitchnet.privilege.model.PrivilegeContext; +import ch.eitchnet.privilege.model.Restrictable; +import ch.eitchnet.privilege.model.internal.Role; +import ch.eitchnet.privilege.model.internal.User; + +/** + *

+ * {@link PrivilegePolicy} implements logic to determine if a {@link User} which has the given {@link Role} and the + * given {@link IPrivilege} has access to the given {@link Restrictable} + *

+ * + *

+ * TODO + *

+ * + * @author Robert von Burg + */ +public interface PrivilegePolicy { + + /** + * Checks if the given {@link Role} and the given {@link IPrivilege} has access to the given {@link Restrictable} + * + * @param context + * the privilege context + * @param privilege + * the {@link IPrivilege} containing the permissions + * @param restrictable + * the {@link Restrictable} to which the user wants access + * + * @throws AccessDeniedException + * if action not allowed + */ + public abstract void validateAction(PrivilegeContext context, IPrivilege privilege, Restrictable restrictable) + throws AccessDeniedException; +} diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicyHelper.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicyHelper.java new file mode 100644 index 000000000..b06456453 --- /dev/null +++ b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicyHelper.java @@ -0,0 +1,80 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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. + */ +package ch.eitchnet.privilege.policy; + +import java.text.MessageFormat; + +import ch.eitchnet.privilege.base.AccessDeniedException; +import ch.eitchnet.privilege.base.PrivilegeException; +import ch.eitchnet.privilege.i18n.PrivilegeMessages; +import ch.eitchnet.privilege.model.IPrivilege; +import ch.eitchnet.privilege.model.PrivilegeContext; +import ch.eitchnet.privilege.model.Restrictable; +import ch.eitchnet.utils.helper.StringHelper; + +/** + * @author Robert von Burg + */ +public class PrivilegePolicyHelper { + + public static String preValidate(IPrivilege privilege, Restrictable restrictable) { + if (privilege == null) + throw new PrivilegeException(PrivilegeMessages.getString("Privilege.privilegeNull")); //$NON-NLS-1$ + if (restrictable == null) + throw new PrivilegeException(PrivilegeMessages.getString("Privilege.restrictableNull")); //$NON-NLS-1$ + + // get the PrivilegeName + String privilegeName = restrictable.getPrivilegeName(); + if (StringHelper.isEmpty(privilegeName)) { + String msg = PrivilegeMessages.getString("Privilege.privilegeNameEmpty"); //$NON-NLS-1$ + throw new PrivilegeException(MessageFormat.format(msg, restrictable)); + } + + // we want the privileges names to match + if (!privilege.getName().equals(privilegeName)) { + throw new PrivilegeException(MessageFormat.format( + PrivilegeMessages.getString("Privilege.illegalArgument.privilegeNameMismatch"), //$NON-NLS-1$ + privilege.getName(), privilegeName)); + } + + return privilegeName; + } + + /** + * @param privilege + * @param privilegeValue + */ + public static void checkByAllowDenyValues(PrivilegeContext ctx, IPrivilege privilege, Restrictable restrictable, + String privilegeValue) { + + // first check values not allowed + if (privilege.isDenied(privilegeValue)) { + // then throw access denied + String msg = MessageFormat.format(PrivilegeMessages.getString("Privilege.accessdenied.noprivilege"), //$NON-NLS-1$ + ctx.getUsername(), privilege.getName(), restrictable.getClass().getName()); + throw new AccessDeniedException(msg); + } + + // now check values allowed + if (privilege.isAllowed(privilegeValue)) + return; + + // default is not allowed + String msg = MessageFormat.format(PrivilegeMessages.getString("Privilege.accessdenied.noprivilege"), //$NON-NLS-1$ + ctx.getUsername(), privilege.getName(), restrictable.getClass().getName()); + throw new AccessDeniedException(msg); + } +} diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/RoleAccessPrivilege.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/RoleAccessPrivilege.java new file mode 100644 index 000000000..d82f3576d --- /dev/null +++ b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/RoleAccessPrivilege.java @@ -0,0 +1,117 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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. + */ +package ch.eitchnet.privilege.policy; + +import java.text.MessageFormat; + +import ch.eitchnet.privilege.base.PrivilegeException; +import ch.eitchnet.privilege.handler.PrivilegeHandler; +import ch.eitchnet.privilege.i18n.PrivilegeMessages; +import ch.eitchnet.privilege.model.IPrivilege; +import ch.eitchnet.privilege.model.PrivilegeContext; +import ch.eitchnet.privilege.model.Restrictable; +import ch.eitchnet.privilege.model.internal.Role; +import ch.eitchnet.utils.collections.Tuple; +import ch.eitchnet.utils.dbc.DBC; + +/** + * This {@link PrivilegePolicy} expects a {@link Tuple} as {@link Restrictable#getPrivilegeValue()}. The Tuple must + * contain {@link Role} as first and second value. Then the policy decides depending on the user specific privileges + * (see {@link PrivilegeHandler}), uses the basic Allow and Deny to detect if the username of + * the certificate is allowed + * + * @author Robert von Burg + */ +public class RoleAccessPrivilege implements PrivilegePolicy { + + @Override + public void validateAction(PrivilegeContext ctx, IPrivilege privilege, Restrictable restrictable) { + String privilegeName = PrivilegePolicyHelper.preValidate(privilege, restrictable); + + // get the value on which the action is to be performed + Object object = restrictable.getPrivilegeValue(); + + // if the object is null, then it means the validation is that the privilege must exist + if (object == null) + return; + + // RoleAccessPrivilege policy expects the privilege value to be a role + if (!(object instanceof Tuple)) { + String msg = Restrictable.class.getName() + + PrivilegeMessages.getString("Privilege.illegalArgument.nontuple"); //$NON-NLS-1$ + msg = MessageFormat.format(msg, restrictable.getClass().getSimpleName()); + throw new PrivilegeException(msg); + } + + // if everything is allowed, then no need to carry on + if (privilege.isAllAllowed()) + return; + + Tuple tuple = (Tuple) object; + + // get role name as privilege value + Role oldRole = tuple.getFirst(); + Role newRole = tuple.getSecond(); + + switch (privilegeName) { + case PrivilegeHandler.PRIVILEGE_GET_ROLE: { + DBC.INTERIM.assertNull("For " + privilegeName + " first must be null!", oldRole); + DBC.INTERIM.assertNotNull("For " + privilegeName + " second must not be null!", newRole); + + String privilegeValue = newRole.getName(); + PrivilegePolicyHelper.checkByAllowDenyValues(ctx, privilege, restrictable, privilegeValue); + + break; + } + + case PrivilegeHandler.PRIVILEGE_ADD_ROLE: { + DBC.INTERIM.assertNull("For " + privilegeName + " first must be null!", oldRole); + DBC.INTERIM.assertNotNull("For " + privilegeName + " second must not be null!", newRole); + + String privilegeValue = newRole.getName(); + PrivilegePolicyHelper.checkByAllowDenyValues(ctx, privilege, restrictable, privilegeValue); + + break; + } + case PrivilegeHandler.PRIVILEGE_MODIFY_ROLE: { + DBC.INTERIM.assertNotNull("For " + privilegeName + " first must not be null!", oldRole); + DBC.INTERIM.assertNotNull("For " + privilegeName + " second must not be null!", newRole); + + String privilegeValue = newRole.getName(); + DBC.INTERIM.assertEquals("oldRole and newRole names must be the same", oldRole.getName(), privilegeValue); + + PrivilegePolicyHelper.checkByAllowDenyValues(ctx, privilege, restrictable, privilegeValue); + + break; + } + case PrivilegeHandler.PRIVILEGE_REMOVE_ROLE: { + DBC.INTERIM.assertNull("For " + privilegeName + " first must be null!", oldRole); + DBC.INTERIM.assertNotNull("For " + privilegeName + " second must not be null!", newRole); + + String privilegeValue = newRole.getName(); + PrivilegePolicyHelper.checkByAllowDenyValues(ctx, privilege, restrictable, privilegeValue); + + break; + } + + default: + String msg = Restrictable.class.getName() + + PrivilegeMessages.getString("Privilege.roleAccessPrivilege.unknownPrivilege"); //$NON-NLS-1$ + msg = MessageFormat.format(msg, privilegeName); + throw new PrivilegeException(msg); + } + } +} diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/UserAccessPrivilege.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/UserAccessPrivilege.java new file mode 100644 index 000000000..cafbd37c5 --- /dev/null +++ b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/UserAccessPrivilege.java @@ -0,0 +1,190 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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. + */ +package ch.eitchnet.privilege.policy; + +import java.text.MessageFormat; + +import ch.eitchnet.privilege.base.PrivilegeException; +import ch.eitchnet.privilege.handler.PrivilegeHandler; +import ch.eitchnet.privilege.i18n.PrivilegeMessages; +import ch.eitchnet.privilege.model.IPrivilege; +import ch.eitchnet.privilege.model.PrivilegeContext; +import ch.eitchnet.privilege.model.Restrictable; +import ch.eitchnet.privilege.model.internal.User; +import ch.eitchnet.utils.collections.Tuple; +import ch.eitchnet.utils.dbc.DBC; + +/** + * This {@link PrivilegePolicy} expects a {@link Tuple} as {@link Restrictable#getPrivilegeValue()} and then depending + * on the user specific privileges (see {@link PrivilegeHandler}), uses the basic Allow and + * Deny to detect if the username of the certificate is allowed + * + * @author Robert von Burg + */ +public class UserAccessPrivilege implements PrivilegePolicy { + + @Override + public void validateAction(PrivilegeContext ctx, IPrivilege privilege, Restrictable restrictable) { + String privilegeName = PrivilegePolicyHelper.preValidate(privilege, restrictable); + + // get the value on which the action is to be performed + Object object = restrictable.getPrivilegeValue(); + + // if the object is null, then it means the validation is that the privilege must exist + if (object == null) + return; + + // RoleAccessPrivilege policy expects the privilege value to be a role + if (!(object instanceof Tuple)) { + String msg = Restrictable.class.getName() + + PrivilegeMessages.getString("Privilege.illegalArgument.nontuple"); //$NON-NLS-1$ + msg = MessageFormat.format(msg, restrictable.getClass().getSimpleName()); + throw new PrivilegeException(msg); + } + + // if everything is allowed, then no need to carry on + if (privilege.isAllAllowed()) + return; + + Tuple tuple = (Tuple) object; + + switch (privilegeName) { + case PrivilegeHandler.PRIVILEGE_GET_USER: { + User oldUser = tuple.getFirst(); + User newUser = tuple.getSecond(); + + DBC.INTERIM.assertNull("For " + privilegeName + " first must be null!", oldUser); + DBC.INTERIM.assertNotNull("For " + privilegeName + " second must not be null!", newUser); + + String privilegeValue = newUser.getUsername(); + PrivilegePolicyHelper.checkByAllowDenyValues(ctx, privilege, restrictable, privilegeValue); + + break; + } + case PrivilegeHandler.PRIVILEGE_ADD_USER: { + User oldUser = tuple.getFirst(); + User newUser = tuple.getSecond(); + + DBC.INTERIM.assertNull("For " + privilegeName + " first must be null!", oldUser); + DBC.INTERIM.assertNotNull("For " + privilegeName + " second must not be null!", newUser); + + String privilegeValue = newUser.getUsername(); + PrivilegePolicyHelper.checkByAllowDenyValues(ctx, privilege, restrictable, privilegeValue); + + break; + } + case PrivilegeHandler.PRIVILEGE_REMOVE_USER: { + User oldUser = tuple.getFirst(); + User newUser = tuple.getSecond(); + + DBC.INTERIM.assertNull("For " + privilegeName + " first must be null!", oldUser); + DBC.INTERIM.assertNotNull("For " + privilegeName + " second must not be null!", newUser); + + String privilegeValue = newUser.getUsername(); + PrivilegePolicyHelper.checkByAllowDenyValues(ctx, privilege, restrictable, privilegeValue); + + break; + } + case PrivilegeHandler.PRIVILEGE_MODIFY_USER: { + User oldUser = tuple.getFirst(); + User newUser = tuple.getSecond(); + + DBC.INTERIM.assertNotNull("For " + privilegeName + " first must not be null!", oldUser); + DBC.INTERIM.assertNotNull("For " + privilegeName + " second must not be null!", newUser); + + String privilegeValue = newUser.getUsername(); + DBC.INTERIM.assertEquals("oldUser and newUser names must be the same", oldUser.getUsername(), + privilegeValue); + PrivilegePolicyHelper.checkByAllowDenyValues(ctx, privilege, restrictable, privilegeValue); + + break; + } + case PrivilegeHandler.PRIVILEGE_ADD_ROLE_TO_USER: { + User user = tuple.getFirst(); + String roleName = tuple.getSecond(); + + DBC.INTERIM.assertNotNull("For " + privilegeName + " first must not be null!", user); + DBC.INTERIM.assertNotNull("For " + privilegeName + " second must not be null!", roleName); + + PrivilegePolicyHelper.checkByAllowDenyValues(ctx, privilege, restrictable, roleName); + + break; + } + case PrivilegeHandler.PRIVILEGE_REMOVE_ROLE_FROM_USER: { + User user = tuple.getFirst(); + String roleName = tuple.getSecond(); + + DBC.INTERIM.assertNotNull("For " + privilegeName + " first must not be null!", user); + DBC.INTERIM.assertNotNull("For " + privilegeName + " second must not be null!", roleName); + + PrivilegePolicyHelper.checkByAllowDenyValues(ctx, privilege, restrictable, roleName); + + break; + } + case PrivilegeHandler.PRIVILEGE_SET_USER_STATE: { + User oldUser = tuple.getFirst(); + User newUser = tuple.getSecond(); + + DBC.INTERIM.assertNotNull("For " + privilegeName + " first must not be null!", oldUser); + DBC.INTERIM.assertNotNull("For " + privilegeName + " second must not be null!", newUser); + + String privilegeValue = newUser.getUserState().name(); + PrivilegePolicyHelper.checkByAllowDenyValues(ctx, privilege, restrictable, privilegeValue); + + break; + } + case PrivilegeHandler.PRIVILEGE_SET_USER_LOCALE: { + User oldUser = tuple.getFirst(); + User newUser = tuple.getSecond(); + + DBC.INTERIM.assertNotNull("For " + privilegeName + " first must not be null!", oldUser); + DBC.INTERIM.assertNotNull("For " + privilegeName + " second must not be null!", newUser); + + String privilegeValue = newUser.getUsername(); + + // user can set their own locale + if (ctx.getUsername().equals(privilegeValue)) + return; + + PrivilegePolicyHelper.checkByAllowDenyValues(ctx, privilege, restrictable, privilegeValue); + + break; + } + case PrivilegeHandler.PRIVILEGE_SET_USER_PASSWORD: { + User oldUser = tuple.getFirst(); + User newUser = tuple.getSecond(); + + DBC.INTERIM.assertNotNull("For " + privilegeName + " first must not be null!", oldUser); + DBC.INTERIM.assertNotNull("For " + privilegeName + " second must not be null!", newUser); + + String privilegeValue = newUser.getUsername(); + + // user can set their own password + if (ctx.getUsername().equals(privilegeValue)) + return; + + PrivilegePolicyHelper.checkByAllowDenyValues(ctx, privilege, restrictable, privilegeValue); + + break; + } + + default: + String msg = PrivilegeMessages.getString("Privilege.userAccessPrivilege.unknownPrivilege"); //$NON-NLS-1$ + msg = MessageFormat.format(msg, privilegeName, this.getClass().getName()); + throw new PrivilegeException(msg); + } + } +} diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/UserAccessWithSameOrganisationPrivilege.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/UserAccessWithSameOrganisationPrivilege.java new file mode 100644 index 000000000..599c4a7c3 --- /dev/null +++ b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/UserAccessWithSameOrganisationPrivilege.java @@ -0,0 +1,115 @@ +/* + * Copyright 2015 Robert von Burg + * + * 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. + */ +package ch.eitchnet.privilege.policy; + +import java.text.MessageFormat; + +import ch.eitchnet.privilege.base.AccessDeniedException; +import ch.eitchnet.privilege.base.PrivilegeException; +import ch.eitchnet.privilege.handler.PrivilegeHandler; +import ch.eitchnet.privilege.i18n.PrivilegeMessages; +import ch.eitchnet.privilege.model.IPrivilege; +import ch.eitchnet.privilege.model.PrivilegeContext; +import ch.eitchnet.privilege.model.Restrictable; +import ch.eitchnet.privilege.model.internal.User; +import ch.eitchnet.utils.collections.Tuple; +import ch.eitchnet.utils.dbc.DBC; +import ch.eitchnet.utils.helper.StringHelper; + +/** + * Validates that any access to a privilege User is done only by users in the same organisation + * + * @author Robert von Burg + */ +public class UserAccessWithSameOrganisationPrivilege extends UserAccessPrivilege { + + private static final String PARAM_ORGANISATION = "organisation"; + + @Override + public void validateAction(PrivilegeContext ctx, IPrivilege privilege, Restrictable restrictable) { + String privilegeName = PrivilegePolicyHelper.preValidate(privilege, restrictable); + + // get the value on which the action is to be performed + Object object = restrictable.getPrivilegeValue(); + + // RoleAccessPrivilege policy expects the privilege value to be a role + if (!(object instanceof Tuple)) { + String msg = Restrictable.class.getName() + + PrivilegeMessages.getString("Privilege.illegalArgument.nontuple"); //$NON-NLS-1$ + msg = MessageFormat.format(msg, restrictable.getClass().getSimpleName()); + throw new PrivilegeException(msg); + } + + // get user organisation + String userOrg = ctx.getCertificate().getProperty(PARAM_ORGANISATION); + if (StringHelper.isEmpty(userOrg)) { + throw new AccessDeniedException("No organisation configured for user " + ctx.getUsername()); + } + + Tuple tuple = (Tuple) object; + + switch (privilegeName) { + case PrivilegeHandler.PRIVILEGE_GET_USER: + case PrivilegeHandler.PRIVILEGE_ADD_USER: + case PrivilegeHandler.PRIVILEGE_MODIFY_USER: + case PrivilegeHandler.PRIVILEGE_REMOVE_USER: { + + // make sure old user has same organisation + User oldUser = tuple.getFirst(); + if (oldUser != null) { + String oldOrg = oldUser.getProperty(PARAM_ORGANISATION); + if (!userOrg.equals(oldOrg)) { + throw new AccessDeniedException("User " + ctx.getUsername() + + " may not access users outside of their organisation: " + userOrg + " / " + oldOrg); + } + } + + // make sure new user has same organisation + User newUser = tuple.getSecond(); + DBC.INTERIM.assertNotNull("For " + privilegeName + " second must not be null!", newUser); + String newdOrg = newUser.getProperty(PARAM_ORGANISATION); + if (!userOrg.equals(newdOrg)) { + throw new AccessDeniedException("User " + ctx.getUsername() + + " may not access users outside of their organisations: " + userOrg + " / " + newdOrg); + } + + break; + } + case PrivilegeHandler.PRIVILEGE_ADD_ROLE_TO_USER: + case PrivilegeHandler.PRIVILEGE_REMOVE_ROLE_FROM_USER: { + + User user = tuple.getFirst(); + DBC.INTERIM.assertNotNull("For " + privilegeName + " first must not be null!", user); + String org = user.getProperty(PARAM_ORGANISATION); + if (!userOrg.equals(org)) { + throw new AccessDeniedException("User " + ctx.getUsername() + + " may not access users outside of their organisation: " + userOrg + " / " + org); + } + + break; + } + + default: + String msg = Restrictable.class.getName() + + PrivilegeMessages.getString("Privilege.userAccessPrivilege.unknownPrivilege"); //$NON-NLS-1$ + msg = MessageFormat.format(msg, privilegeName); + throw new PrivilegeException(msg); + } + + // now delegate the rest of the validation to the super class + super.validateAction(ctx, privilege, restrictable); + } +} diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/UsernameFromCertificatePrivilege.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/UsernameFromCertificatePrivilege.java new file mode 100644 index 000000000..425c4183d --- /dev/null +++ b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/UsernameFromCertificatePrivilege.java @@ -0,0 +1,65 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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. + */ +package ch.eitchnet.privilege.policy; + +import java.text.MessageFormat; + +import ch.eitchnet.privilege.base.PrivilegeException; +import ch.eitchnet.privilege.i18n.PrivilegeMessages; +import ch.eitchnet.privilege.model.Certificate; +import ch.eitchnet.privilege.model.IPrivilege; +import ch.eitchnet.privilege.model.PrivilegeContext; +import ch.eitchnet.privilege.model.Restrictable; + +/** + *

+ * This {@link PrivilegePolicy} expects a {@link Certificate} as {@link Restrictable#getPrivilegeValue()} and uses the + * basic Allow and Deny to detect if the username of the certificate is allowed. + *

+ * + *

+ * The {@link Certificate} as privilegeValue is not to be confused with the {@link Certificate} of the current user. + * This certificate is of the user to which access is request, i.e. modifying the session of a logged in user. + *

+ * + * @author Robert von Burg + */ +public class UsernameFromCertificatePrivilege implements PrivilegePolicy { + + @Override + public void validateAction(PrivilegeContext ctx, IPrivilege privilege, Restrictable restrictable) { + PrivilegePolicyHelper.preValidate(privilege, restrictable); + + // get the value on which the action is to be performed + Object object = restrictable.getPrivilegeValue(); + + // RoleAccessPrivilege policy expects the privilege value to be a role + if (!(object instanceof Certificate)) { + String msg = Restrictable.class.getName() + + PrivilegeMessages.getString("Privilege.illegalArgument.noncertificate"); //$NON-NLS-1$ + msg = MessageFormat.format(msg, restrictable.getClass().getSimpleName()); + throw new PrivilegeException(msg); + } + + // if everything is allowed, then no need to carry on + if (privilege.isAllAllowed()) + return; + + Certificate cert = (Certificate) object; + String privilegeValue = cert.getUsername(); + PrivilegePolicyHelper.checkByAllowDenyValues(ctx, privilege, restrictable, privilegeValue); + } +} diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/UsernameFromCertificateWithSameOrganisationPrivilege.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/UsernameFromCertificateWithSameOrganisationPrivilege.java new file mode 100644 index 000000000..124addf28 --- /dev/null +++ b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/UsernameFromCertificateWithSameOrganisationPrivilege.java @@ -0,0 +1,79 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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. + */ +package ch.eitchnet.privilege.policy; + +import java.text.MessageFormat; + +import ch.eitchnet.privilege.base.AccessDeniedException; +import ch.eitchnet.privilege.base.PrivilegeException; +import ch.eitchnet.privilege.i18n.PrivilegeMessages; +import ch.eitchnet.privilege.model.Certificate; +import ch.eitchnet.privilege.model.IPrivilege; +import ch.eitchnet.privilege.model.PrivilegeContext; +import ch.eitchnet.privilege.model.Restrictable; +import ch.eitchnet.utils.helper.StringHelper; + +/** + *

+ * This {@link PrivilegePolicy} expects a {@link Certificate} as {@link Restrictable#getPrivilegeValue()} and uses the + * basic Allow and Deny to detect if the username of the certificate is allowed. + *

+ * + *

+ * The {@link Certificate} as privilegeValue is not to be confused with the {@link Certificate} of the current user. + * This certificate is of the user to which access is request, i.e. modifying the session of a logged in user. + *

+ * + * @author Robert von Burg + */ +public class UsernameFromCertificateWithSameOrganisationPrivilege extends UsernameFromCertificatePrivilege { + + private static final String PARAM_ORGANISATION = "organisation"; + + @Override + public void validateAction(PrivilegeContext ctx, IPrivilege privilege, Restrictable restrictable) { + PrivilegePolicyHelper.preValidate(privilege, restrictable); + + // get the value on which the action is to be performed + Object object = restrictable.getPrivilegeValue(); + + // RoleAccessPrivilege policy expects the privilege value to be a role + if (!(object instanceof Certificate)) { + String msg = Restrictable.class.getName() + + PrivilegeMessages.getString("Privilege.illegalArgument.noncertificate"); //$NON-NLS-1$ + msg = MessageFormat.format(msg, restrictable.getClass().getSimpleName()); + throw new PrivilegeException(msg); + } + + // get object + Certificate cert = (Certificate) object; + + // get user organisation + String userOrg = ctx.getCertificate().getProperty(PARAM_ORGANISATION); + if (StringHelper.isEmpty(userOrg)) { + throw new AccessDeniedException("No organisation configured for user " + ctx.getUsername()); + } + // assert same organisation + String org = cert.getProperty(PARAM_ORGANISATION); + if (!userOrg.equals(org)) { + throw new AccessDeniedException("User " + ctx.getUsername() + + " may not access users outside of their organisation: " + userOrg + " / " + org); + } + + // now delegate the rest of the validation to the super class + super.validateAction(ctx, privilege, restrictable); + } +} diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/CertificateStubsDomWriter.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/CertificateStubsDomWriter.java new file mode 100644 index 000000000..7dab6d30e --- /dev/null +++ b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/CertificateStubsDomWriter.java @@ -0,0 +1,79 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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. + */ +package ch.eitchnet.privilege.xml; + +import java.io.OutputStream; +import java.util.List; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import ch.eitchnet.privilege.helper.XmlConstants; +import ch.eitchnet.privilege.model.Certificate; +import ch.eitchnet.utils.helper.XmlHelper; +import ch.eitchnet.utils.iso8601.ISO8601FormatFactory; + +/** + * @author Robert von Burg + */ +public class CertificateStubsDomWriter { + + private List certificates; + private OutputStream outputStream; + + public CertificateStubsDomWriter(List certificates, OutputStream outputStream) { + this.certificates = certificates; + this.outputStream = outputStream; + } + + public void write() { + + // create document root + Document doc = XmlHelper.createDocument(); + Element rootElement = doc.createElement(XmlConstants.XML_ROOT_CERTIFICATES); + doc.appendChild(rootElement); + + this.certificates.stream().sorted((c1, c2) -> c1.getSessionId().compareTo(c2.getSessionId())).forEach(cert -> { + + // create the certificate element + Element certElement = doc.createElement(XmlConstants.XML_CERTIFICATE); + rootElement.appendChild(certElement); + + // sessionId; + certElement.setAttribute(XmlConstants.XML_ATTR_SESSION_ID, cert.getSessionId()); + + // username; + certElement.setAttribute(XmlConstants.XML_ATTR_USERNAME, cert.getUsername()); + + // authToken; + certElement.setAttribute(XmlConstants.XML_ATTR_AUTH_TOKEN, cert.getAuthToken()); + + // locale; + certElement.setAttribute(XmlConstants.XML_ATTR_LOCALE, cert.getLocale().toString()); + + // loginTime; + certElement.setAttribute(XmlConstants.XML_ATTR_LOGIN_TIME, + ISO8601FormatFactory.getInstance().formatDate(cert.getLoginTime())); + + // lastAccess; + certElement.setAttribute(XmlConstants.XML_ATTR_LAST_ACCESS, + ISO8601FormatFactory.getInstance().formatDate(cert.getLastAccess())); + }); + + // write the container file to disk + XmlHelper.writeDocument(doc, this.outputStream); + } +} diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/CertificateStubsSaxReader.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/CertificateStubsSaxReader.java new file mode 100644 index 000000000..0cbfdb3c2 --- /dev/null +++ b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/CertificateStubsSaxReader.java @@ -0,0 +1,114 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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. + */ +package ch.eitchnet.privilege.xml; + +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Locale; + +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +import ch.eitchnet.privilege.base.PrivilegeException; +import ch.eitchnet.privilege.helper.XmlConstants; +import ch.eitchnet.utils.dbc.DBC; +import ch.eitchnet.utils.helper.XmlHelper; +import ch.eitchnet.utils.iso8601.ISO8601FormatFactory; + +/** + * @author Robert von Burg + */ +public class CertificateStubsSaxReader extends DefaultHandler { + + private InputStream inputStream; + private List stubs; + + public CertificateStubsSaxReader(InputStream inputStream) { + this.inputStream = inputStream; + } + + public List read() { + this.stubs = new ArrayList<>(); + XmlHelper.parseDocument(this.inputStream, this); + return stubs; + } + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + + switch (qName) { + case XmlConstants.XML_ROOT_CERTIFICATES: + break; + case XmlConstants.XML_CERTIFICATE: + + CertificateStub stub = new CertificateStub(); + stub.sessionId = attributes.getValue(XmlConstants.XML_ATTR_SESSION_ID); + stub.username = attributes.getValue(XmlConstants.XML_ATTR_USERNAME); + stub.authToken = attributes.getValue(XmlConstants.XML_ATTR_AUTH_TOKEN); + stub.locale = new Locale(attributes.getValue(XmlConstants.XML_ATTR_LOCALE)); + stub.loginTime = ISO8601FormatFactory.getInstance() + .parseDate(attributes.getValue(XmlConstants.XML_ATTR_LOGIN_TIME)); + stub.lastAccess = ISO8601FormatFactory.getInstance() + .parseDate(attributes.getValue(XmlConstants.XML_ATTR_LAST_ACCESS)); + + DBC.INTERIM.assertNotEmpty("sessionId missing on sessions data!", stub.sessionId); + DBC.INTERIM.assertNotEmpty("username missing on sessions data!", stub.username); + DBC.INTERIM.assertNotEmpty("authToken missing on sessions data!", stub.authToken); + + this.stubs.add(stub); + break; + + default: + throw new PrivilegeException("Unhandled tag " + qName); + } + } + + public class CertificateStub { + private String sessionId; + private String username; + private String authToken; + private Locale locale; + private Date loginTime; + private Date lastAccess; + + public String getSessionId() { + return sessionId; + } + + public String getUsername() { + return username; + } + + public String getAuthToken() { + return authToken; + } + + public Locale getLocale() { + return locale; + } + + public Date getLoginTime() { + return loginTime; + } + + public Date getLastAccess() { + return lastAccess; + } + } +} diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/ElementParser.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/ElementParser.java new file mode 100644 index 000000000..f35a1980e --- /dev/null +++ b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/ElementParser.java @@ -0,0 +1,31 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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. + */ +package ch.eitchnet.privilege.xml; + +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; + +// TODO write JavaDoc... +public interface ElementParser { + + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException; + + public void characters(char[] ch, int start, int length) throws SAXException; + + public void endElement(String uri, String localName, String qName) throws SAXException; + + public void notifyChild(ElementParser child); +} \ No newline at end of file diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/ElementParserAdapter.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/ElementParserAdapter.java new file mode 100644 index 000000000..11a11200e --- /dev/null +++ b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/ElementParserAdapter.java @@ -0,0 +1,42 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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. + */ +package ch.eitchnet.privilege.xml; + +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; + +public abstract class ElementParserAdapter implements ElementParser { + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + // empty implementation + } + + @Override + public void characters(char[] ch, int start, int length) throws SAXException { + // empty implementation + } + + @Override + public void endElement(String uri, String localName, String qName) throws SAXException { + // empty implementation + } + + @Override + public void notifyChild(ElementParser child) { + // empty implementation + } +} \ No newline at end of file diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigDomWriter.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigDomWriter.java new file mode 100644 index 000000000..e2745a7eb --- /dev/null +++ b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigDomWriter.java @@ -0,0 +1,115 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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. + */ +package ch.eitchnet.privilege.xml; + +import java.io.File; +import java.util.Map.Entry; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import ch.eitchnet.privilege.helper.XmlConstants; +import ch.eitchnet.privilege.model.internal.PrivilegeContainerModel; +import ch.eitchnet.privilege.policy.PrivilegePolicy; +import ch.eitchnet.utils.helper.XmlHelper; + +/** + * @author Robert von Burg + * + */ +public class PrivilegeConfigDomWriter { + + private final PrivilegeContainerModel containerModel; + private final File configFile; + + /** + * + */ + public PrivilegeConfigDomWriter(PrivilegeContainerModel containerModel, File configFile) { + this.containerModel = containerModel; + this.configFile = configFile; + } + + /** + * + */ + public void write() { + + // create document root + Document doc = XmlHelper.createDocument(); + Element rootElement = doc.createElement(XmlConstants.XML_ROOT_PRIVILEGE); + doc.appendChild(rootElement); + + Element containerElement = doc.createElement(XmlConstants.XML_CONTAINER); + rootElement.appendChild(containerElement); + + Element parameterElement; + Element parametersElement; + + // Parameters + parametersElement = doc.createElement(XmlConstants.XML_PARAMETERS); + containerElement.appendChild(parametersElement); + for (Entry entry : this.containerModel.getParameterMap().entrySet()) { + parameterElement = doc.createElement(XmlConstants.XML_PARAMETER); + parameterElement.setAttribute(XmlConstants.XML_ATTR_NAME, entry.getKey()); + parameterElement.setAttribute(XmlConstants.XML_ATTR_VALUE, entry.getValue()); + parametersElement.appendChild(parameterElement); + } + + // create EncryptionHandler + Element encryptionHandlerElem = doc.createElement(XmlConstants.XML_HANDLER_ENCRYPTION); + containerElement.appendChild(encryptionHandlerElem); + encryptionHandlerElem.setAttribute(XmlConstants.XML_ATTR_CLASS, + this.containerModel.getEncryptionHandlerClassName()); + // Parameters + parametersElement = doc.createElement(XmlConstants.XML_PARAMETERS); + encryptionHandlerElem.appendChild(parametersElement); + for (Entry entry : this.containerModel.getEncryptionHandlerParameterMap().entrySet()) { + parameterElement = doc.createElement(XmlConstants.XML_PARAMETER); + parameterElement.setAttribute(XmlConstants.XML_ATTR_NAME, entry.getKey()); + parameterElement.setAttribute(XmlConstants.XML_ATTR_VALUE, entry.getValue()); + parametersElement.appendChild(parameterElement); + } + + // create PersistenceHandler + Element persistenceHandlerElem = doc.createElement(XmlConstants.XML_HANDLER_PERSISTENCE); + containerElement.appendChild(persistenceHandlerElem); + persistenceHandlerElem.setAttribute(XmlConstants.XML_ATTR_CLASS, + this.containerModel.getPersistenceHandlerClassName()); + // Parameters + parametersElement = doc.createElement(XmlConstants.XML_PARAMETERS); + persistenceHandlerElem.appendChild(parametersElement); + for (Entry entry : this.containerModel.getPersistenceHandlerParameterMap().entrySet()) { + parameterElement = doc.createElement(XmlConstants.XML_PARAMETER); + parameterElement.setAttribute(XmlConstants.XML_ATTR_NAME, entry.getKey()); + parameterElement.setAttribute(XmlConstants.XML_ATTR_VALUE, entry.getValue()); + parametersElement.appendChild(parameterElement); + } + + // Policies + Element policiesElem = doc.createElement(XmlConstants.XML_POLICIES); + rootElement.appendChild(policiesElem); + for (Entry> entry : this.containerModel.getPolicies().entrySet()) { + Element policyElem = doc.createElement(XmlConstants.XML_POLICY); + policyElem.setAttribute(XmlConstants.XML_ATTR_NAME, entry.getKey()); + policyElem.setAttribute(XmlConstants.XML_ATTR_CLASS, entry.getValue().getName()); + policiesElem.appendChild(policyElem); + } + + // write the container file to disk + XmlHelper.writeDocument(doc, this.configFile); + } +} diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java new file mode 100644 index 000000000..421aff46f --- /dev/null +++ b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java @@ -0,0 +1,181 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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. + */ +package ch.eitchnet.privilege.xml; + +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.HashMap; +import java.util.Map; + +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +import ch.eitchnet.privilege.helper.XmlConstants; +import ch.eitchnet.privilege.model.internal.PrivilegeContainerModel; + +/** + * @author Robert von Burg + */ +public class PrivilegeConfigSaxReader extends DefaultHandler { + + private Deque buildersStack = new ArrayDeque<>(); + + private PrivilegeContainerModel containerModel; + + public PrivilegeConfigSaxReader(PrivilegeContainerModel containerModel) { + this.containerModel = containerModel; + } + + public PrivilegeContainerModel getContainerModel() { + return this.containerModel; + } + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + + if (qName.equals(XmlConstants.XML_CONTAINER)) { + this.buildersStack.push(new ContainerParser()); + } else if (qName.equals(XmlConstants.XML_PARAMETERS)) { + this.buildersStack.push(new ParametersParser()); + } else if (qName.equals(XmlConstants.XML_POLICIES)) { + this.buildersStack.push(new PoliciesParser()); + } + + if (!this.buildersStack.isEmpty()) + this.buildersStack.peek().startElement(uri, localName, qName, attributes); + } + + @Override + public void characters(char[] ch, int start, int length) throws SAXException { + if (!this.buildersStack.isEmpty()) + this.buildersStack.peek().characters(ch, start, length); + } + + @Override + public void endElement(String uri, String localName, String qName) throws SAXException { + + if (!this.buildersStack.isEmpty()) + this.buildersStack.peek().endElement(uri, localName, qName); + + ElementParser elementParser = null; + if (qName.equals(XmlConstants.XML_CONTAINER)) { + elementParser = this.buildersStack.pop(); + } else if (qName.equals(XmlConstants.XML_PARAMETERS)) { + elementParser = this.buildersStack.pop(); + } else if (qName.equals(XmlConstants.XML_POLICIES)) { + elementParser = this.buildersStack.pop(); + } + + if (!this.buildersStack.isEmpty() && elementParser != null) + this.buildersStack.peek().notifyChild(elementParser); + } + + public class ContainerParser extends ElementParserAdapter { + +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// + + private String currentElement; + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) + throws SAXException { + if (qName.equals(XmlConstants.XML_CONTAINER)) { + this.currentElement = qName; + } else if (qName.equals(XmlConstants.XML_HANDLER_ENCRYPTION)) { + this.currentElement = qName; + String className = attributes.getValue(XmlConstants.XML_ATTR_CLASS); + getContainerModel().setEncryptionHandlerClassName(className); + } else if (qName.equals(XmlConstants.XML_HANDLER_PERSISTENCE)) { + this.currentElement = qName; + String className = attributes.getValue(XmlConstants.XML_ATTR_CLASS); + getContainerModel().setPersistenceHandlerClassName(className); + } + } + + @Override + public void notifyChild(ElementParser child) { + if (!(child instanceof ParametersParser)) + return; + + ParametersParser parametersChild = (ParametersParser) child; + + if (this.currentElement.equals(XmlConstants.XML_CONTAINER)) { + getContainerModel().setParameterMap(parametersChild.getParameterMap()); + } else if (this.currentElement.equals(XmlConstants.XML_HANDLER_ENCRYPTION)) { + getContainerModel().setEncryptionHandlerParameterMap(parametersChild.getParameterMap()); + } else if (this.currentElement.equals(XmlConstants.XML_HANDLER_PERSISTENCE)) { + getContainerModel().setPersistenceHandlerParameterMap(parametersChild.getParameterMap()); + } + } + } + + class ParametersParser extends ElementParserAdapter { + +// + + private Map parameterMap = new HashMap<>(); + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) + throws SAXException { + if (qName.equals(XmlConstants.XML_PARAMETER)) { + String key = attributes.getValue(XmlConstants.XML_ATTR_NAME); + String value = attributes.getValue(XmlConstants.XML_ATTR_VALUE); + this.parameterMap.put(key, value); + } + } + + /** + * @return the parameterMap + */ + public Map getParameterMap() { + return this.parameterMap; + } + } + + class PoliciesParser extends ElementParserAdapter { + +// + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) + throws SAXException { + if (qName.equals(XmlConstants.XML_POLICY)) { + String policyName = attributes.getValue(XmlConstants.XML_ATTR_NAME); + String policyClassName = attributes.getValue(XmlConstants.XML_ATTR_CLASS); + + getContainerModel().addPolicy(policyName, policyClassName); + } + } + } +} \ No newline at end of file diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeRolesDomWriter.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeRolesDomWriter.java new file mode 100644 index 000000000..a42963ecf --- /dev/null +++ b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeRolesDomWriter.java @@ -0,0 +1,98 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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. + */ +package ch.eitchnet.privilege.xml; + +import java.io.File; +import java.util.List; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import ch.eitchnet.privilege.helper.XmlConstants; +import ch.eitchnet.privilege.model.IPrivilege; +import ch.eitchnet.privilege.model.internal.Role; +import ch.eitchnet.utils.helper.XmlHelper; + +/** + * @author Robert von Burg + */ +public class PrivilegeRolesDomWriter { + + private List roles; + private File modelFile; + + /** + * @param users + * @param roles + * @param modelFile + */ + public PrivilegeRolesDomWriter(List roles, File modelFile) { + this.roles = roles; + this.modelFile = modelFile; + } + + public void write() { + + // create document root + Document doc = XmlHelper.createDocument(); + Element rootElement = doc.createElement(XmlConstants.XML_ROLES); + doc.appendChild(rootElement); + + this.roles.stream().sorted((r1, r2) -> r1.getName().compareTo(r2.getName())).forEach(role -> { + + // create the role element + Element roleElement = doc.createElement(XmlConstants.XML_ROLE); + rootElement.appendChild(roleElement); + + roleElement.setAttribute(XmlConstants.XML_ATTR_NAME, role.getName()); + + for (String privilegeName : role.getPrivilegeNames()) { + IPrivilege privilege = role.getPrivilege(privilegeName); + + // create the privilege element + Element privilegeElement = doc.createElement(XmlConstants.XML_PRIVILEGE); + roleElement.appendChild(privilegeElement); + + privilegeElement.setAttribute(XmlConstants.XML_ATTR_NAME, privilege.getName()); + privilegeElement.setAttribute(XmlConstants.XML_ATTR_POLICY, privilege.getPolicy()); + + // add the all allowed element + if (privilege.isAllAllowed()) { + Element allAllowedElement = doc.createElement(XmlConstants.XML_ALL_ALLOWED); + allAllowedElement.setTextContent(Boolean.toString(privilege.isAllAllowed())); + privilegeElement.appendChild(allAllowedElement); + } + + // add all the deny values + for (String denyValue : privilege.getDenyList()) { + Element denyValueElement = doc.createElement(XmlConstants.XML_DENY); + denyValueElement.setTextContent(denyValue); + privilegeElement.appendChild(denyValueElement); + } + + // add all the allow values + for (String allowValue : privilege.getAllowList()) { + Element allowValueElement = doc.createElement(XmlConstants.XML_ALLOW); + allowValueElement.setTextContent(allowValue); + privilegeElement.appendChild(allowValueElement); + } + } + }); + + // write the container file to disk + XmlHelper.writeDocument(doc, this.modelFile); + } +} diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeRolesSaxReader.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeRolesSaxReader.java new file mode 100644 index 000000000..5efa039b4 --- /dev/null +++ b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeRolesSaxReader.java @@ -0,0 +1,220 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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. + */ +package ch.eitchnet.privilege.xml; + +import java.text.MessageFormat; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +import ch.eitchnet.privilege.helper.XmlConstants; +import ch.eitchnet.privilege.model.IPrivilege; +import ch.eitchnet.privilege.model.internal.PrivilegeImpl; +import ch.eitchnet.privilege.model.internal.Role; +import ch.eitchnet.utils.helper.StringHelper; + +/** + * @author Robert von Burg + */ +public class PrivilegeRolesSaxReader extends DefaultHandler { + + protected static final Logger logger = LoggerFactory.getLogger(PrivilegeRolesSaxReader.class); + + private Deque buildersStack = new ArrayDeque<>(); + + private List roles; + + public PrivilegeRolesSaxReader() { + this.roles = new ArrayList<>(); + } + + public List getRoles() { + return this.roles; + } + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + + if (qName.equals(XmlConstants.XML_ROLE)) { + this.buildersStack.push(new RoleParser()); + } else if (qName.equals(XmlConstants.XML_PROPERTIES)) { + this.buildersStack.push(new PropertyParser()); + } + + if (!this.buildersStack.isEmpty()) + this.buildersStack.peek().startElement(uri, localName, qName, attributes); + } + + @Override + public void characters(char[] ch, int start, int length) throws SAXException { + if (!this.buildersStack.isEmpty()) + this.buildersStack.peek().characters(ch, start, length); + } + + @Override + public void endElement(String uri, String localName, String qName) throws SAXException { + + if (!this.buildersStack.isEmpty()) + this.buildersStack.peek().endElement(uri, localName, qName); + + ElementParser elementParser = null; + if (qName.equals(XmlConstants.XML_ROLE)) { + elementParser = this.buildersStack.pop(); + } else if (qName.equals(XmlConstants.XML_PROPERTIES)) { + elementParser = this.buildersStack.pop(); + } + + if (!this.buildersStack.isEmpty() && elementParser != null) + this.buildersStack.peek().notifyChild(elementParser); + } + +// +// +// true +// +// +// +// +// true +// +// +// true +// +// + + public class RoleParser extends ElementParserAdapter { + + private StringBuilder text; + + private String roleName; + private String privilegeName; + private String privilegePolicy; + private boolean allAllowed; + private Set denyList; + private Set allowList; + + private Map privileges; + + public RoleParser() { + init(); + } + + private void init() { + this.privileges = new HashMap<>(); + + this.text = null; + + this.roleName = null; + this.privilegeName = null; + this.privilegePolicy = null; + this.allAllowed = false; + this.denyList = new HashSet<>(); + this.allowList = new HashSet<>(); + } + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) + throws SAXException { + + this.text = new StringBuilder(); + + if (qName.equals(XmlConstants.XML_ROLE)) { + this.roleName = attributes.getValue(XmlConstants.XML_ATTR_NAME); + } else if (qName.equals(XmlConstants.XML_PRIVILEGE)) { + this.privilegeName = attributes.getValue(XmlConstants.XML_ATTR_NAME); + this.privilegePolicy = attributes.getValue(XmlConstants.XML_ATTR_POLICY); + } else if (qName.equals(XmlConstants.XML_ALLOW) || qName.equals(XmlConstants.XML_DENY) + || qName.equals(XmlConstants.XML_ALL_ALLOWED)) { + // no-op + } else { + throw new IllegalArgumentException("Unhandled tag " + qName); + } + } + + @Override + public void characters(char[] ch, int start, int length) throws SAXException { + if (this.text != null) + this.text.append(ch, start, length); + } + + @Override + public void endElement(String uri, String localName, String qName) throws SAXException { + + if (qName.equals(XmlConstants.XML_ALL_ALLOWED)) { + this.allAllowed = StringHelper.parseBoolean(this.text.toString().trim()); + } else if (qName.equals(XmlConstants.XML_ALLOW)) { + this.allowList.add(this.text.toString().trim()); + } else if (qName.equals(XmlConstants.XML_DENY)) { + this.denyList.add(this.text.toString().trim()); + } else if (qName.equals(XmlConstants.XML_PRIVILEGE)) { + + IPrivilege privilege = new PrivilegeImpl(this.privilegeName, this.privilegePolicy, this.allAllowed, + this.denyList, this.allowList); + this.privileges.put(this.privilegeName, privilege); + + this.privilegeName = null; + this.privilegePolicy = null; + this.allAllowed = false; + this.denyList = new HashSet<>(); + this.allowList = new HashSet<>(); + + } else if (qName.equals(XmlConstants.XML_ROLE)) { + + Role role = new Role(this.roleName, this.privileges); + + getRoles().add(role); + logger.info(MessageFormat.format("New Role: {0}", role)); //$NON-NLS-1$ + init(); + } + } + } + + class PropertyParser extends ElementParserAdapter { + +// + + public Map parameterMap = new HashMap<>(); + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) + throws SAXException { + if (qName.equals(XmlConstants.XML_PROPERTY)) { + String key = attributes.getValue(XmlConstants.XML_ATTR_NAME); + String value = attributes.getValue(XmlConstants.XML_ATTR_VALUE); + this.parameterMap.put(key, value); + } else if (qName.equals(XmlConstants.XML_PROPERTIES)) { + // NO-OP + } else { + throw new IllegalArgumentException("Unhandled tag " + qName); + } + } + + public Map getParameterMap() { + return this.parameterMap; + } + } +} diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeUsersDomWriter.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeUsersDomWriter.java new file mode 100644 index 000000000..77b7879f0 --- /dev/null +++ b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeUsersDomWriter.java @@ -0,0 +1,114 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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. + */ +package ch.eitchnet.privilege.xml; + +import java.io.File; +import java.util.List; +import java.util.Map.Entry; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import ch.eitchnet.privilege.helper.XmlConstants; +import ch.eitchnet.privilege.model.internal.User; +import ch.eitchnet.utils.helper.StringHelper; +import ch.eitchnet.utils.helper.XmlHelper; + +/** + * @author Robert von Burg + */ +public class PrivilegeUsersDomWriter { + + private List users; + private File modelFile; + + /** + * @param users + * @param modelFile + */ + public PrivilegeUsersDomWriter(List users, File modelFile) { + this.users = users; + this.modelFile = modelFile; + } + + public void write() { + + // create document root + Document doc = XmlHelper.createDocument(); + Element rootElement = doc.createElement(XmlConstants.XML_USERS); + doc.appendChild(rootElement); + + this.users.stream().sorted((u1, u2) -> u1.getUserId().compareTo(u2.getUserId())).forEach(user -> { + + // create the user element + Element userElement = doc.createElement(XmlConstants.XML_USER); + rootElement.appendChild(userElement); + + userElement.setAttribute(XmlConstants.XML_ATTR_USER_ID, user.getUserId()); + userElement.setAttribute(XmlConstants.XML_ATTR_USERNAME, user.getUsername()); + if (StringHelper.isNotEmpty(user.getPassword())) + userElement.setAttribute(XmlConstants.XML_ATTR_PASSWORD, user.getPassword()); + + // add first name element + if (StringHelper.isNotEmpty(user.getFirstname())) { + Element firstnameElement = doc.createElement(XmlConstants.XML_FIRSTNAME); + firstnameElement.setTextContent(user.getFirstname()); + userElement.appendChild(firstnameElement); + } + + // add last name element + if (StringHelper.isNotEmpty(user.getLastname())) { + Element lastnameElement = doc.createElement(XmlConstants.XML_LASTNAME); + lastnameElement.setTextContent(user.getLastname()); + userElement.appendChild(lastnameElement); + } + + // add state element + Element stateElement = doc.createElement(XmlConstants.XML_STATE); + stateElement.setTextContent(user.getUserState().toString()); + userElement.appendChild(stateElement); + + // add locale element + Element localeElement = doc.createElement(XmlConstants.XML_LOCALE); + localeElement.setTextContent(user.getLocale().toString()); + userElement.appendChild(localeElement); + + // add all the role elements + Element rolesElement = doc.createElement(XmlConstants.XML_ROLES); + userElement.appendChild(rolesElement); + for (String roleName : user.getRoles()) { + Element roleElement = doc.createElement(XmlConstants.XML_ROLE); + roleElement.setTextContent(roleName); + rolesElement.appendChild(roleElement); + } + + // add the parameters + if (!user.getProperties().isEmpty()) { + Element parametersElement = doc.createElement(XmlConstants.XML_PROPERTIES); + userElement.appendChild(parametersElement); + for (Entry entry : user.getProperties().entrySet()) { + Element paramElement = doc.createElement(XmlConstants.XML_PROPERTY); + paramElement.setAttribute(XmlConstants.XML_ATTR_NAME, entry.getKey()); + paramElement.setAttribute(XmlConstants.XML_ATTR_VALUE, entry.getValue()); + parametersElement.appendChild(paramElement); + } + } + }); + + // write the container file to disk + XmlHelper.writeDocument(doc, this.modelFile); + } +} diff --git a/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeUsersSaxReader.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeUsersSaxReader.java new file mode 100644 index 000000000..701c617c2 --- /dev/null +++ b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeUsersSaxReader.java @@ -0,0 +1,209 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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. + */ +package ch.eitchnet.privilege.xml; + +import java.text.MessageFormat; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +import ch.eitchnet.privilege.helper.XmlConstants; +import ch.eitchnet.privilege.model.UserState; +import ch.eitchnet.privilege.model.internal.User; + +/** + * @author Robert von Burg + */ +public class PrivilegeUsersSaxReader extends DefaultHandler { + + protected static final Logger logger = LoggerFactory.getLogger(PrivilegeUsersSaxReader.class); + + private Deque buildersStack = new ArrayDeque<>(); + + private List users; + + public PrivilegeUsersSaxReader() { + this.users = new ArrayList<>(); + } + + /** + * @return the users + */ + public List getUsers() { + return this.users; + } + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + if (qName.equals(XmlConstants.XML_USER)) { + this.buildersStack.push(new UserParser()); + } else if (qName.equals(XmlConstants.XML_PROPERTIES)) { + this.buildersStack.push(new PropertyParser()); + } + + if (!this.buildersStack.isEmpty()) + this.buildersStack.peek().startElement(uri, localName, qName, attributes); + } + + @Override + public void characters(char[] ch, int start, int length) throws SAXException { + if (!this.buildersStack.isEmpty()) + this.buildersStack.peek().characters(ch, start, length); + } + + @Override + public void endElement(String uri, String localName, String qName) throws SAXException { + + if (!this.buildersStack.isEmpty()) + this.buildersStack.peek().endElement(uri, localName, qName); + + ElementParser elementParser = null; + if (qName.equals(XmlConstants.XML_USER)) { + elementParser = this.buildersStack.pop(); + } else if (qName.equals(XmlConstants.XML_PROPERTIES)) { + elementParser = this.buildersStack.pop(); + } + + if (!this.buildersStack.isEmpty() && elementParser != null) + this.buildersStack.peek().notifyChild(elementParser); + } + +// +// Application +// Administrator +// ENABLED +// en_GB +// +// PrivilegeAdmin +// AppUser +// +// +// +// +// +// + + public class UserParser extends ElementParserAdapter { + + StringBuilder text; + + String userId; + String username; + String password; + String firstName; + String lastname; + UserState userState; + Locale locale; + Set userRoles; + Map parameters; + + public UserParser() { + this.userRoles = new HashSet<>(); + } + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) + throws SAXException { + + this.text = new StringBuilder(); + + if (qName.equals(XmlConstants.XML_USER)) { + this.userId = attributes.getValue(XmlConstants.XML_ATTR_USER_ID); + this.username = attributes.getValue(XmlConstants.XML_ATTR_USERNAME); + this.password = attributes.getValue(XmlConstants.XML_ATTR_PASSWORD); + } + } + + @Override + public void characters(char[] ch, int start, int length) throws SAXException { + this.text.append(ch, start, length); + } + + @Override + public void endElement(String uri, String localName, String qName) throws SAXException { + + if (qName.equals(XmlConstants.XML_FIRSTNAME)) { + this.firstName = this.text.toString().trim(); + } else if (qName.equals(XmlConstants.XML_LASTNAME)) { + this.lastname = this.text.toString().trim(); + } else if (qName.equals(XmlConstants.XML_STATE)) { + this.userState = UserState.valueOf(this.text.toString().trim()); + } else if (qName.equals(XmlConstants.XML_LOCALE)) { + this.locale = new Locale(this.text.toString().trim()); + } else if (qName.equals(XmlConstants.XML_ROLE)) { + this.userRoles.add(this.text.toString().trim()); + } else if (qName.equals(XmlConstants.XML_ROLES)) { + // NO-OP + } else if (qName.equals(XmlConstants.XML_PARAMETER)) { + // NO-OP + } else if (qName.equals(XmlConstants.XML_PARAMETERS)) { + // NO-OP + } else if (qName.equals(XmlConstants.XML_USER)) { + + User user = new User(this.userId, this.username, this.password, this.firstName, this.lastname, + this.userState, this.userRoles, this.locale, this.parameters); + logger.info(MessageFormat.format("New User: {0}", user)); //$NON-NLS-1$ + getUsers().add(user); + } else { + throw new IllegalArgumentException("Unhandled tag " + qName); + } + } + + @Override + public void notifyChild(ElementParser child) { + if (child instanceof PropertyParser) { + this.parameters = ((PropertyParser) child).parameterMap; + } + } + } + + class PropertyParser extends ElementParserAdapter { + +// + + public Map parameterMap = new HashMap<>(); + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) + throws SAXException { + if (qName.equals(XmlConstants.XML_PROPERTY)) { + String key = attributes.getValue(XmlConstants.XML_ATTR_NAME); + String value = attributes.getValue(XmlConstants.XML_ATTR_VALUE); + this.parameterMap.put(key, value); + } else if (qName.equals(XmlConstants.XML_PROPERTIES)) { + // NO-OP + } else { + throw new IllegalArgumentException("Unhandled tag " + qName); + } + } + + public Map getParameterMap() { + return this.parameterMap; + } + } +} diff --git a/ch.eitchnet.privilege/src/main/resources/Privilege.xsd b/ch.eitchnet.privilege/src/main/resources/Privilege.xsd new file mode 100644 index 000000000..2fbcbdb1d --- /dev/null +++ b/ch.eitchnet.privilege/src/main/resources/Privilege.xsd @@ -0,0 +1,83 @@ + + + + + + Copyright 2013 Robert von Burg <eitch@eitchnet.ch> + + 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. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ch.eitchnet.privilege/src/main/resources/PrivilegeMessages.properties b/ch.eitchnet.privilege/src/main/resources/PrivilegeMessages.properties new file mode 100644 index 000000000..a92f0f135 --- /dev/null +++ b/ch.eitchnet.privilege/src/main/resources/PrivilegeMessages.properties @@ -0,0 +1,14 @@ +Privilege.accessdenied.noprivilege=User {0} does not have the privilege {1} needed for Restrictable {2} +Privilege.illegalArgument.nonstring=\ {0} has returned a non-string privilege value\! +Privilege.illegalArgument.nonrole=\ {0} did not return a Role privilege value\! +Privilege.illegalArgument.noncertificate=\ {0} did not return a Certificate privilege value\! +Privilege.illegalArgument.nonuser=\ {0} did not return a User privilege value\! +Privilege.illegalArgument.privilegeNameMismatch=The passed privilege has the name {0} but the restrictable is referencing privilege {1} +Privilege.illegalArgument.nontuple=\ {0} did not return a Tuple privilege value\! +Privilege.privilegeNameEmpty=The PrivilegeName for the Restrictable is null or empty: {0} +Privilege.privilegeNull=Privilege may not be null\! +Privilege.restrictableNull=Restrictable may not be null\! +Privilege.noprivilege=No Privilege exists with name {0} +Privilege.noprivilege.user=User {0} does not have the privilege {1} +Privilege.roleAccessPrivilege.unknownPrivilege=Unhandled privilege {0} for policy {1} +Privilege.userAccessPrivilege.unknownPrivilege=Unhandled privilege {0} for policy {1} diff --git a/ch.eitchnet.privilege/src/main/resources/PrivilegeModel.xsd b/ch.eitchnet.privilege/src/main/resources/PrivilegeModel.xsd new file mode 100644 index 000000000..d2f82d897 --- /dev/null +++ b/ch.eitchnet.privilege/src/main/resources/PrivilegeModel.xsd @@ -0,0 +1,98 @@ + + + + + +Copyright 2013 Robert von Burg <eitch@eitchnet.ch> + + 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. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/AbstractPrivilegeTest.java b/ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/AbstractPrivilegeTest.java new file mode 100644 index 000000000..9f1c1a3ce --- /dev/null +++ b/ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/AbstractPrivilegeTest.java @@ -0,0 +1,116 @@ +package ch.eitchnet.privilege.test; + +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.nio.file.Files; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ch.eitchnet.privilege.base.PrivilegeException; +import ch.eitchnet.privilege.handler.PrivilegeHandler; +import ch.eitchnet.privilege.helper.PrivilegeInitializationHelper; +import ch.eitchnet.privilege.model.Certificate; +import ch.eitchnet.privilege.model.PrivilegeContext; +import ch.eitchnet.utils.helper.FileHelper; + +public class AbstractPrivilegeTest { + + protected static final Logger logger = LoggerFactory.getLogger(AbstractPrivilegeTest.class); + + protected PrivilegeHandler privilegeHandler; + protected PrivilegeContext ctx; + + protected void login(String username, byte[] password) { + Certificate certificate = privilegeHandler.authenticate(username, password); + assertTrue("Certificate is null!", certificate != null); + PrivilegeContext privilegeContext = privilegeHandler.getPrivilegeContext(certificate); + this.ctx = privilegeContext; + } + + protected void logout() { + if (this.ctx != null) { + try { + PrivilegeContext privilegeContext = this.ctx; + this.ctx = null; + privilegeHandler.invalidateSession(privilegeContext.getCertificate()); + } catch (PrivilegeException e) { + String msg = "There is no PrivilegeContext currently bound to the ThreadLocal!"; + if (!e.getMessage().equals(msg)) + throw e; + } + } + } + + protected static void prepareConfigs(String dst, String configFilename, String usersFilename, + String rolesFilename) { + try { + String pwd = System.getProperty("user.dir"); + + File configPath = new File(pwd, "config"); + + File privilegeConfigFile = new File(configPath, configFilename); + File privilegeUsersFile = new File(configPath, usersFilename); + File privilegeRolesFile = new File(configPath, rolesFilename); + + File targetPath = new File(pwd, "target/" + dst); + if (!targetPath.mkdirs()) + throw new RuntimeException("Could not create parent " + targetPath); + + File dstConfig = new File(targetPath, configFilename); + File dstUsers = new File(targetPath, usersFilename); + File dstRoles = new File(targetPath, rolesFilename); + + // write config + String config = new String(Files.readAllBytes(privilegeConfigFile.toPath()), "UTF-8"); + config = config.replace("${target}", dst); + Files.write(dstConfig.toPath(), config.getBytes("UTF-8")); + + // copy model + Files.copy(privilegeUsersFile.toPath(), dstUsers.toPath()); + Files.copy(privilegeRolesFile.toPath(), dstRoles.toPath()); + + } catch (Exception e) { + logger.error(e.getMessage(), e); + throw new RuntimeException("Initialization failed", e); + } + } + + protected static void removeConfigs(String dst) { + try { + String pwd = System.getProperty("user.dir"); + File targetPath = new File(pwd, "target"); + targetPath = new File(targetPath, dst); + if (targetPath.exists() && !FileHelper.deleteFile(targetPath, true)) { + throw new RuntimeException( + "Tmp configuration still exists and can not be deleted at " + targetPath.getAbsolutePath()); + } + } catch (Exception e) { + logger.error(e.getMessage(), e); + throw new RuntimeException("Initialization failed", e); + } + } + + protected static File getPrivilegeConfigFile(String dst, String configFilename) { + try { + String pwd = System.getProperty("user.dir"); + File targetPath = new File(pwd, "target"); + targetPath = new File(targetPath, dst); + return new File(targetPath, configFilename); + } catch (Exception e) { + logger.error(e.getMessage(), e); + throw new RuntimeException("Initialization failed", e); + } + } + + protected void initialize(String dst, String configFilename) { + try { + File privilegeConfigFile = getPrivilegeConfigFile(dst, configFilename); + this.privilegeHandler = PrivilegeInitializationHelper.initializeFromXml(privilegeConfigFile); + } catch (Exception e) { + logger.error(e.getMessage(), e); + throw new RuntimeException("Initialization failed", e); + } + } +} diff --git a/ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/PersistSessionsTest.java b/ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/PersistSessionsTest.java new file mode 100644 index 000000000..57f6b9d03 --- /dev/null +++ b/ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/PersistSessionsTest.java @@ -0,0 +1,48 @@ +package ch.eitchnet.privilege.test; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.File; + +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +public class PersistSessionsTest extends AbstractPrivilegeTest { + + @BeforeClass + public static void init() throws Exception { + removeConfigs(PersistSessionsTest.class.getSimpleName()); + prepareConfigs(PersistSessionsTest.class.getSimpleName(), "PrivilegeConfig.xml", "PrivilegeUsers.xml", + "PrivilegeRoles.xml"); + } + + @AfterClass + public static void destroy() throws Exception { + removeConfigs(PersistSessionsTest.class.getSimpleName()); + } + + @Before + public void setup() throws Exception { + initialize(PersistSessionsTest.class.getSimpleName(), "PrivilegeConfig.xml"); + } + + @Test + public void shouldPersistAndReloadSessions() { + + // assert no sessions file + File sessionsFile = new File("target/PersistSessionsTest/sessions.dat"); + assertFalse("Sessions File should no yet exist", sessionsFile.exists()); + + // login and assert sessions file was written + login("admin", "admin".getBytes()); + this.privilegeHandler.isCertificateValid(ctx.getCertificate()); + assertTrue("Sessions File should have been created!", sessionsFile.isFile()); + + // re-initialize and assert still logged in + initialize(PersistSessionsTest.class.getSimpleName(), "PrivilegeConfig.xml"); + this.privilegeHandler.isCertificateValid(ctx.getCertificate()); + } +} diff --git a/ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/PrivilegeConflictMergeTest.java b/ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/PrivilegeConflictMergeTest.java new file mode 100644 index 000000000..2fd50fad1 --- /dev/null +++ b/ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/PrivilegeConflictMergeTest.java @@ -0,0 +1,77 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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. + */ +package ch.eitchnet.privilege.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import ch.eitchnet.privilege.model.IPrivilege; + +/** + * @author Robert von Burg + */ +public class PrivilegeConflictMergeTest extends AbstractPrivilegeTest { + + @BeforeClass + public static void init() throws Exception { + removeConfigs(PrivilegeConflictMergeTest.class.getSimpleName()); + prepareConfigs(PrivilegeConflictMergeTest.class.getSimpleName(), "PrivilegeConfigMerge.xml", + "PrivilegeUsersMerge.xml", "PrivilegeRolesMerge.xml"); + } + + @AfterClass + public static void destroy() throws Exception { + removeConfigs(PrivilegeConflictMergeTest.class.getSimpleName()); + } + + @Before + public void setup() throws Exception { + initialize(PrivilegeConflictMergeTest.class.getSimpleName(), "PrivilegeConfigMerge.xml"); + } + + @Test + public void shouldMergePrivileges1() { + try { + login("userA", "admin".getBytes()); + IPrivilege privilege = this.ctx.getPrivilege("Foo"); + assertTrue(privilege.isAllAllowed()); + assertTrue(privilege.getAllowList().isEmpty()); + assertTrue(privilege.getDenyList().isEmpty()); + + } finally { + logout(); + } + } + + @Test + public void shouldMergePrivileges2() { + try { + login("userB", "admin".getBytes()); + IPrivilege privilege = this.ctx.getPrivilege("Bar"); + assertFalse(privilege.isAllAllowed()); + assertEquals(2, privilege.getAllowList().size()); + assertEquals(2, privilege.getDenyList().size()); + } finally { + logout(); + } + } +} diff --git a/ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java b/ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java new file mode 100644 index 000000000..242c265bb --- /dev/null +++ b/ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -0,0 +1,755 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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. + */ +package ch.eitchnet.privilege.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.fail; + +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; + +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ch.eitchnet.privilege.base.AccessDeniedException; +import ch.eitchnet.privilege.base.PrivilegeException; +import ch.eitchnet.privilege.handler.PrivilegeHandler; +import ch.eitchnet.privilege.i18n.PrivilegeMessages; +import ch.eitchnet.privilege.model.Certificate; +import ch.eitchnet.privilege.model.PrivilegeRep; +import ch.eitchnet.privilege.model.Restrictable; +import ch.eitchnet.privilege.model.RoleRep; +import ch.eitchnet.privilege.model.UserRep; +import ch.eitchnet.privilege.model.UserState; +import ch.eitchnet.privilege.test.model.TestRestrictable; +import ch.eitchnet.privilege.test.model.TestSystemUserAction; +import ch.eitchnet.privilege.test.model.TestSystemUserActionDeny; +import ch.eitchnet.utils.helper.ArraysHelper; + +/** + * JUnit for performing Privilege tests. This JUnit is by no means complete, but checks the bare minimum.br /> + * + * TODO add more tests, especially with deny and allow lists + * + * @author Robert von Burg + */ +@SuppressWarnings("nls") +public class PrivilegeTest extends AbstractPrivilegeTest { + + private static final String ROLE_PRIVILEGE_ADMIN = "PrivilegeAdmin"; + private static final String PRIVILEGE_USER_ACCESS = "UserAccessPrivilege"; + private static final String ADMIN = "admin"; + private static final byte[] PASS_ADMIN = "admin".getBytes(); + private static final String BOB = "bob"; + private static final String TED = "ted"; + private static final String SYSTEM_USER_ADMIN = "system_admin"; + private static final String SYSTEM_USER_ADMIN2 = "system_admin2"; + private static final byte[] PASS_BOB = "admin1".getBytes(); + private static final String ROLE_APP_USER = "AppUser"; + private static final String ROLE_MY = "MyRole"; + private static final String ROLE_MY2 = "MyRole2"; + private static final String ROLE_CHANGE_PW = "changePw"; + private static final String ROLE_TEMP = "temp"; + private static final String ROLE_USER = "user"; + private static final byte[] PASS_DEF = "def".getBytes(); + private static final byte[] PASS_BAD = "123".getBytes(); + private static final byte[] PASS_TED = "12345".getBytes(); + + private static final Logger logger = LoggerFactory.getLogger(PrivilegeTest.class); + + @Rule + public ExpectedException exception = ExpectedException.none(); + + @BeforeClass + public static void init() throws Exception { + removeConfigs(PrivilegeTest.class.getSimpleName()); + prepareConfigs(PrivilegeTest.class.getSimpleName(), "PrivilegeConfig.xml", "PrivilegeUsers.xml", + "PrivilegeRoles.xml"); + } + + @AfterClass + public static void destroy() throws Exception { + removeConfigs(PrivilegeTest.class.getSimpleName()); + } + + @Before + public void setup() throws Exception { + initialize(PrivilegeTest.class.getSimpleName(), "PrivilegeConfig.xml"); + } + + @Test + public void testAuthenticationOk() throws Exception { + try { + login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); + } finally { + logout(); + } + } + + public void testFailAuthenticationNOk() throws Exception { + this.exception.expect(AccessDeniedException.class); + this.exception.expectMessage("blabla"); + try { + login(ADMIN, ArraysHelper.copyOf(PASS_BAD)); + } finally { + logout(); + } + } + + public void testFailAuthenticationPWNull() throws Exception { + this.exception.expect(PrivilegeException.class); + this.exception.expectMessage("blabla"); + try { + login(ADMIN, null); + } finally { + logout(); + } + } + + @Test + public void testAddRoleTemp() throws Exception { + try { + login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); + + RoleRep roleRep = new RoleRep(ROLE_TEMP, new ArrayList<>()); + + Certificate certificate = this.ctx.getCertificate(); + privilegeHandler.addRole(certificate, roleRep); + privilegeHandler.persist(certificate); + } finally { + logout(); + } + } + + @Test + public void testPerformRestrictableAsAdmin() throws Exception { + try { + login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); + + // see if admin can perform restrictable + Restrictable restrictable = new TestRestrictable(); + this.ctx.validateAction(restrictable); + + } finally { + logout(); + } + } + + /** + * Tests if an action can be performed as a system user + */ + @Test + public void testPerformSystemRestrictable() throws Exception { + + // create the action to be performed as a system user and then perform the action + TestSystemUserAction action = new TestSystemUserAction(); + privilegeHandler.runAsSystem(SYSTEM_USER_ADMIN, action); + } + + /** + * Checks that the system user can not perform a valid action, but illegal privilege + */ + @Test + public void testPerformSystemRestrictableFailPrivilege() throws Exception { + this.exception.expect(PrivilegeException.class); + this.exception.expectMessage( + "User system_admin does not have the privilege ch.eitchnet.privilege.handler.SystemUserAction"); + try { + // create the action to be performed as a system user + TestSystemUserActionDeny action = new TestSystemUserActionDeny(); + + // and then perform the action + privilegeHandler.runAsSystem(SYSTEM_USER_ADMIN, action); + } finally { + logout(); + } + } + + /** + * Checks that the system user can not perform a valid action, but illegal privilege + */ + @Test + public void testPerformSystemRestrictableFailNoAdditionalPrivilege() throws Exception { + this.exception.expect(PrivilegeException.class); + this.exception.expectMessage( + "User system_admin2 does not have the privilege ch.eitchnet.privilege.handler.SystemUserAction needed for Restrictable ch.eitchnet.privilege.test.model.TestSystemUserActionDeny"); + try { + // create the action to be performed as a system user + TestSystemUserActionDeny action = new TestSystemUserActionDeny(); + + // and then perform the action + privilegeHandler.runAsSystem(SYSTEM_USER_ADMIN2, action); + } finally { + logout(); + } + } + + /** + * System user may not login + */ + @Test + public void testLoginSystemUser() throws Exception { + this.exception.expect(AccessDeniedException.class); + this.exception.expectMessage("User system_admin is a system user and may not login!"); + try { + login(SYSTEM_USER_ADMIN, SYSTEM_USER_ADMIN.getBytes()); + } finally { + logout(); + } + } + + @Test + public void testPrivilegeContext() { + try { + login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); + Restrictable restrictable = new TestRestrictable(); + this.ctx.validateAction(restrictable); + } finally { + logout(); + } + } + + @Test + public void shouldUpdateAdmin() { + try { + login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); + + Certificate certificate = this.ctx.getCertificate(); + + // validate name is not yet set + UserRep user = privilegeHandler.getUser(certificate, ADMIN); + assertNotEquals("The", user.getFirstname()); + assertNotEquals("Admin", user.getLastname()); + + // let's add a new user bob + UserRep userRep = new UserRep(null, ADMIN, "The", "Admin", null, null, null, null); + privilegeHandler.updateUser(certificate, userRep); + + user = privilegeHandler.getUser(certificate, ADMIN); + assertEquals("The", user.getFirstname()); + assertEquals("Admin", user.getLastname()); + + } finally { + logout(); + } + } + + @Test + public void shouldFailUpdateInexistantUser() { + exception.expect(PrivilegeException.class); + exception.expectMessage("User bob does not exist"); + try { + login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); + + Certificate certificate = this.ctx.getCertificate(); + + // let's add a new user bob + UserRep userRep = new UserRep(null, BOB, null, null, null, null, null, null); + privilegeHandler.updateUser(certificate, userRep); + } finally { + logout(); + } + } + + @Test + public void shouldFailUpdateAdminNoChanges() { + exception.expect(PrivilegeException.class); + exception.expectMessage("All updateable fields are empty for update of user admin"); + try { + login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); + + Certificate certificate = this.ctx.getCertificate(); + + // let's add a new user bob + UserRep userRep = new UserRep(null, ADMIN, null, null, null, null, null, null); + privilegeHandler.updateUser(certificate, userRep); + } finally { + logout(); + } + } + + @Test + public void shouldQueryUsers() { + try { + login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); + + Certificate certificate = this.ctx.getCertificate(); + + UserRep selectorRep = new UserRep(null, ADMIN, null, null, null, null, null, null); + List users = privilegeHandler.queryUsers(certificate, selectorRep); + assertEquals(1, users.size()); + assertEquals(ADMIN, users.get(0).getUsername()); + + } finally { + logout(); + } + } + + @Test + public void shouldQueryUsersByRoles() { + try { + login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); + + Certificate certificate = this.ctx.getCertificate(); + + UserRep selectorRep = new UserRep(null, null, null, null, null, + new HashSet<>(Arrays.asList("PrivilegeAdmin")), null, null); + List users = privilegeHandler.queryUsers(certificate, selectorRep); + assertEquals(1, users.size()); + assertEquals(ADMIN, users.get(0).getUsername()); + + } finally { + logout(); + } + } + + @Test + public void shouldQueryUsersByRoles2() { + try { + login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); + + Certificate certificate = this.ctx.getCertificate(); + + UserRep selectorRep = new UserRep(null, null, null, null, null, new HashSet<>(Arrays.asList(ROLE_TEMP)), + null, null); + List users = privilegeHandler.queryUsers(certificate, selectorRep); + assertEquals(0, users.size()); + + } finally { + logout(); + } + } + + @Test + public void shouldDetectPrivilegeConflict1() { + exception.expect(PrivilegeException.class); + exception.expectMessage("User admin has conflicts for privilege "); + try { + login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); + Certificate certificate = this.ctx.getCertificate(); + PrivilegeRep privilegeRep = new PrivilegeRep(PrivilegeHandler.PRIVILEGE_ACTION, "DefaultPrivilege", true, + Collections.emptySet(), Collections.emptySet()); + privilegeHandler.addOrReplacePrivilegeOnRole(certificate, ROLE_APP_USER, privilegeRep); + } finally { + logout(); + } + } + + @Test + public void shouldDetectPrivilegeConflict2() { + exception.expect(PrivilegeException.class); + exception.expectMessage("User admin has conflicts for privilege "); + try { + login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); + Certificate certificate = this.ctx.getCertificate(); + privilegeHandler.addRoleToUser(certificate, ADMIN, ROLE_MY); + privilegeHandler.addRoleToUser(certificate, ADMIN, ROLE_MY2); + } finally { + logout(); + } + } + + /** + * This test performs multiple tests which are dependent on each other as the following is done: + *
    + *
  • add user bob
  • + *
  • fail to auth as bob as user is not enabled
  • + *
  • enable bob
  • + *
  • fail to auth as bot as bob has no role
  • + *
  • add role user to bob
  • + *
  • auth as bob
  • + *
  • fail to add user ted as bob as bob is not admin
  • + *
  • add admin role to bob
  • + *
  • add user ted as bob
  • + *
  • fail to auth as ted as ted has no password
  • + *
  • set ted's password as bob
  • + *
  • ted changes own password
  • + *
  • auth as ted
  • + *
  • fail to perform restrictable as bob as no app role
  • + *
  • add app role to bob
  • + *
  • perform restrictable as bob
  • + *
+ */ + @Test + public void testUserStory() throws Exception { + + addBobAsAdmin(); + failAuthAsBobNotEnabled(); + enableBob(); + addRoleUser(); + addRoleUserToBob(); + authAsBob(); + failAddTedAsBobNotAdmin(); + addRoleAdminToBob(); + addTedAsBob(); + failAuthAsTedNoPass(); + setPassForTedAsBob(); + failSetSystemStateTed(); + failTedChangesPwAndLocale(); + addChangePwRoleToTed(); + tedChangesOwnPassAndLocale(); + failTedChangesOtherPwStateAndLocale(); + authAsTed(); + failPerformRestrictableAsBobNoRoleApp(); + addRoleAppToBob(); + performRestrictableAsBob(); + } + + private void failSetSystemStateTed() { + try { + // testEnableUserBob + login(BOB, ArraysHelper.copyOf(PASS_BOB)); + Certificate certificate = this.ctx.getCertificate(); + + try { + privilegeHandler.setUserState(certificate, TED, UserState.SYSTEM); + fail("Should not be able to set user state to SYSTEM"); + } catch (AccessDeniedException e) { + // ok + } + + } finally { + logout(); + } + } + + private void failTedChangesOtherPwStateAndLocale() { + try { + // testTedChangesOwnPwd + login(TED, ArraysHelper.copyOf(PASS_TED)); + Certificate certificate = this.ctx.getCertificate(); + + try { + privilegeHandler.setUserPassword(certificate, BOB, ArraysHelper.copyOf(PASS_TED)); + fail("Should not be able to set password of other user, as missing privilege"); + } catch (AccessDeniedException e) { + // ok + } + + try { + privilegeHandler.setUserLocale(certificate, BOB, Locale.FRENCH); + fail("Should not be able to set locale of other user, as missing privilege"); + } catch (AccessDeniedException e) { + // ok + } + + try { + privilegeHandler.setUserState(certificate, BOB, UserState.DISABLED); + fail("Should not be able to set state of other user, as missing privilege"); + } catch (AccessDeniedException e) { + // ok + } + + } finally { + logout(); + } + } + + private void failTedChangesPwAndLocale() { + try { + // testTedChangesOwnPwd + login(TED, ArraysHelper.copyOf(PASS_DEF)); + Certificate certificate = this.ctx.getCertificate(); + + try { + privilegeHandler.setUserPassword(certificate, TED, ArraysHelper.copyOf(PASS_TED)); + fail("Should not be able to set password, as missing privilege"); + } catch (AccessDeniedException e) { + // ok + } + + try { + privilegeHandler.setUserLocale(certificate, TED, Locale.FRENCH); + fail("Should not be able to set locale, as missing privilege"); + } catch (AccessDeniedException e) { + // ok + } + + try { + privilegeHandler.setUserState(certificate, TED, UserState.ENABLED); + fail("Should not be able to set state, as missing privilege"); + } catch (AccessDeniedException e) { + // ok + } + + } finally { + logout(); + } + } + + private void addChangePwRoleToTed() { + try { + // add role user + login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); + + PrivilegeRep passwordRep = new PrivilegeRep(PrivilegeHandler.PRIVILEGE_SET_USER_PASSWORD, + PRIVILEGE_USER_ACCESS, false, Collections.emptySet(), Collections.emptySet()); + PrivilegeRep localeRep = new PrivilegeRep(PrivilegeHandler.PRIVILEGE_SET_USER_LOCALE, PRIVILEGE_USER_ACCESS, + false, Collections.emptySet(), Collections.emptySet()); + + RoleRep roleRep = new RoleRep(ROLE_CHANGE_PW, Arrays.asList(passwordRep, localeRep)); + + Certificate certificate = this.ctx.getCertificate(); + privilegeHandler.addRole(certificate, roleRep); + privilegeHandler.addRoleToUser(certificate, TED, ROLE_CHANGE_PW); + logger.info("Added " + ROLE_CHANGE_PW + " to " + TED); + privilegeHandler.persist(certificate); + } finally { + logout(); + } + } + + private void performRestrictableAsBob() { + try { + // testPerformRestrictableAsBob + // Tests if the user bob, who now has AppUser role can perform restrictable + login(BOB, ArraysHelper.copyOf(PASS_BOB)); + // see if bob can perform restrictable + Restrictable restrictable = new TestRestrictable(); + this.ctx.validateAction(restrictable); + } finally { + logout(); + } + } + + private void addRoleAppToBob() { + try { + // testAddAppRoleToBob + login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); + Certificate certificate = this.ctx.getCertificate(); + privilegeHandler.addRoleToUser(certificate, BOB, ROLE_APP_USER); + logger.info("Added " + ROLE_APP_USER + " to " + BOB); + privilegeHandler.persist(certificate); + } finally { + logout(); + } + } + + private void failPerformRestrictableAsBobNoRoleApp() { + try { + // testFailPerformRestrictableAsBob + // Tests if the user bob, who does not have AppUser role can perform restrictable + // this will fail as bob does not have role app + login(BOB, ArraysHelper.copyOf(PASS_BOB)); + // see if bob can perform restrictable + Restrictable restrictable = new TestRestrictable(); + this.ctx.validateAction(restrictable); + fail("Should fail as bob does not have role app"); + } catch (AccessDeniedException e) { + String msg = "User bob does not have the privilege ch.eitchnet.privilege.test.model.TestRestrictable needed for Restrictable ch.eitchnet.privilege.test.model.TestRestrictable"; + assertEquals(msg, e.getLocalizedMessage()); + } finally { + logout(); + } + } + + private void authAsTed() { + try { + // testAuthAsTed + login(TED, ArraysHelper.copyOf(PASS_TED)); + } finally { + logout(); + } + } + + private void tedChangesOwnPassAndLocale() { + try { + // testTedChangesOwnPwd + login(TED, ArraysHelper.copyOf(PASS_DEF)); + Certificate certificate = this.ctx.getCertificate(); + privilegeHandler.setUserPassword(certificate, TED, ArraysHelper.copyOf(PASS_TED)); + privilegeHandler.setUserLocale(certificate, TED, Locale.FRENCH); + } finally { + logout(); + } + } + + private void setPassForTedAsBob() { + try { + // testSetTedPwdAsBob + login(BOB, ArraysHelper.copyOf(PASS_BOB)); + // set ted's password to default + Certificate certificate = this.ctx.getCertificate(); + privilegeHandler.setUserPassword(certificate, TED, ArraysHelper.copyOf(PASS_DEF)); + privilegeHandler.persist(certificate); + } finally { + logout(); + } + } + + private void failAuthAsTedNoPass() { + try { + // testFailAuthAsTedNoPass + // Will fail because user ted has no password + login(TED, ArraysHelper.copyOf(PASS_TED)); + fail("User Ted may not authenticate because the user has no password!"); + } catch (PrivilegeException e) { + String msg = "User ted has no password and may not login!"; + assertEquals(msg, e.getMessage()); + } finally { + logout(); + } + } + + private void addTedAsBob() { + try { + UserRep userRep; + // testAddUserTedAsBob + login(BOB, ArraysHelper.copyOf(PASS_BOB)); + // let's add a new user ted + HashSet roles = new HashSet<>(); + roles.add(ROLE_USER); + userRep = new UserRep(null, TED, "Ted", "Newman", UserState.ENABLED, roles, null, + new HashMap()); + Certificate certificate = this.ctx.getCertificate(); + privilegeHandler.addUser(certificate, userRep, null); + logger.info("Added user " + TED); + privilegeHandler.persist(certificate); + } finally { + logout(); + } + } + + private void addRoleAdminToBob() { + try { + // testAddAdminRoleToBob + login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); + Certificate certificate = this.ctx.getCertificate(); + privilegeHandler.addRoleToUser(certificate, BOB, ROLE_PRIVILEGE_ADMIN); + logger.info("Added " + ROLE_PRIVILEGE_ADMIN + " to " + ADMIN); + privilegeHandler.persist(certificate); + } finally { + logout(); + } + } + + private void failAddTedAsBobNotAdmin() { + Certificate certificate = null; + try { + UserRep userRep; + // testFailAddUserTedAsBob + // Will fail because user bob does not have admin rights + // auth as Bob + login(BOB, ArraysHelper.copyOf(PASS_BOB)); + // let's add a new user Ted + userRep = new UserRep("1", TED, "Ted", "And then Some", UserState.NEW, new HashSet(), null, + new HashMap()); + certificate = this.ctx.getCertificate(); + privilegeHandler.addUser(certificate, userRep, null); + fail("User bob may not add a user as bob does not have admin rights!"); + } catch (PrivilegeException e) { + String msg = MessageFormat.format(PrivilegeMessages.getString("Privilege.noprivilege.user"), //$NON-NLS-1$ + BOB, PrivilegeHandler.PRIVILEGE_ADD_USER); + assertEquals(msg, e.getMessage()); + } finally { + logout(); + } + } + + private void authAsBob() { + try { + // testAuthAsBob + login(BOB, ArraysHelper.copyOf(PASS_BOB)); + } finally { + logout(); + } + } + + private void addRoleUserToBob() { + try { + // testAddRoleUserToBob + login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); + Certificate certificate = this.ctx.getCertificate(); + privilegeHandler.addRoleToUser(certificate, BOB, ROLE_USER); + privilegeHandler.persist(certificate); + logout(); + } finally { + logout(); + } + } + + private void addRoleUser() { + try { + // add role user + login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); + RoleRep roleRep = new RoleRep(ROLE_USER, new ArrayList<>()); + Certificate certificate = this.ctx.getCertificate(); + privilegeHandler.addRole(certificate, roleRep); + privilegeHandler.persist(certificate); + } finally { + logout(); + } + } + + private void enableBob() { + try { + // testEnableUserBob + login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); + Certificate certificate = this.ctx.getCertificate(); + privilegeHandler.setUserState(certificate, BOB, UserState.ENABLED); + privilegeHandler.persist(certificate); + } finally { + logout(); + } + } + + private void failAuthAsBobNotEnabled() { + try { + // testFailAuthAsBob + // Will fail because user bob is not yet enabled + privilegeHandler.authenticate(BOB, ArraysHelper.copyOf(PASS_BOB)); + fail("User Bob may not authenticate because the user is not yet enabled!"); + } catch (PrivilegeException e) { + String msg = "User bob does not have state ENABLED and can not login!"; + assertEquals(msg, e.getMessage()); + } finally { + logout(); + } + } + + private void addBobAsAdmin() { + try { + login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); + + // let's add a new user bob + UserRep userRep = new UserRep(null, BOB, "Bob", "Newman", UserState.NEW, + new HashSet<>(Arrays.asList(ROLE_MY)), null, new HashMap()); + Certificate certificate = this.ctx.getCertificate(); + privilegeHandler.addUser(certificate, userRep, null); + logger.info("Added user " + BOB); + + // set bob's password + privilegeHandler.setUserPassword(certificate, BOB, ArraysHelper.copyOf(PASS_BOB)); + logger.info("Set Bob's password"); + privilegeHandler.persist(certificate); + } finally { + logout(); + } + } +} diff --git a/ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/XmlTest.java b/ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/XmlTest.java new file mode 100644 index 000000000..54a2e1b4b --- /dev/null +++ b/ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/XmlTest.java @@ -0,0 +1,454 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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. + */ +package ch.eitchnet.privilege.test; + +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ch.eitchnet.privilege.handler.DefaultEncryptionHandler; +import ch.eitchnet.privilege.handler.PrivilegeHandler; +import ch.eitchnet.privilege.handler.XmlPersistenceHandler; +import ch.eitchnet.privilege.model.IPrivilege; +import ch.eitchnet.privilege.model.UserState; +import ch.eitchnet.privilege.model.internal.PrivilegeContainerModel; +import ch.eitchnet.privilege.model.internal.PrivilegeImpl; +import ch.eitchnet.privilege.model.internal.Role; +import ch.eitchnet.privilege.model.internal.User; +import ch.eitchnet.privilege.xml.PrivilegeConfigDomWriter; +import ch.eitchnet.privilege.xml.PrivilegeConfigSaxReader; +import ch.eitchnet.privilege.xml.PrivilegeRolesDomWriter; +import ch.eitchnet.privilege.xml.PrivilegeRolesSaxReader; +import ch.eitchnet.privilege.xml.PrivilegeUsersDomWriter; +import ch.eitchnet.privilege.xml.PrivilegeUsersSaxReader; +import ch.eitchnet.utils.helper.FileHelper; +import ch.eitchnet.utils.helper.StringHelper; +import ch.eitchnet.utils.helper.XmlHelper; + +/** + * @author Robert von Burg + */ +@SuppressWarnings("nls") +public class XmlTest { + + private static final String TARGET_TEST = "./target/test"; + private static final Logger logger = LoggerFactory.getLogger(XmlTest.class); + + /** + * @throws Exception + * if something goes wrong + */ + @BeforeClass + public static void init() throws Exception { + + cleanUp(); + + File tmpDir = new File("target/test"); + if (tmpDir.exists()) + FileHelper.deleteFile(tmpDir, false); + tmpDir.mkdirs(); + } + + @AfterClass + public static void cleanUp() throws Exception { + + File tmpDir = new File("target/test"); + if (!tmpDir.exists()) + return; + + File tmpFile = new File("target/test/PrivilegeTest.xml"); + if (tmpFile.exists() && !tmpFile.delete()) { + throw new RuntimeException("Tmp still exists and can not be deleted at " + tmpFile.getAbsolutePath()); + } + + tmpFile = new File("target/test/PrivilegeUsersTest.xml"); + if (tmpFile.exists() && !tmpFile.delete()) { + throw new RuntimeException("Tmp still exists and can not be deleted at " + tmpFile.getAbsolutePath()); + } + + tmpFile = new File("target/test/PrivilegeRolesTest.xml"); + if (tmpFile.exists() && !tmpFile.delete()) { + throw new RuntimeException("Tmp still exists and can not be deleted at " + tmpFile.getAbsolutePath()); + } + + // and temporary parent + if (!tmpDir.delete()) { + throw new RuntimeException("Could not remove temporary parent for tmp " + tmpFile); + } + } + + @Test + public void canReadConfig() { + + PrivilegeContainerModel containerModel = new PrivilegeContainerModel(); + PrivilegeConfigSaxReader saxReader = new PrivilegeConfigSaxReader(containerModel); + File xmlFile = new File("config/PrivilegeConfig.xml"); + XmlHelper.parseDocument(xmlFile, saxReader); + logger.info(containerModel.toString()); + + // assert all objects read + assertNotNull(containerModel.getParameterMap()); + assertNotNull(containerModel.getPolicies()); + assertNotNull(containerModel.getEncryptionHandlerClassName()); + assertNotNull(containerModel.getEncryptionHandlerParameterMap()); + assertNotNull(containerModel.getPersistenceHandlerClassName()); + assertNotNull(containerModel.getPersistenceHandlerParameterMap()); + + assertEquals(6, containerModel.getParameterMap().size()); + assertEquals(3, containerModel.getPolicies().size()); + assertEquals(1, containerModel.getEncryptionHandlerParameterMap().size()); + assertEquals(3, containerModel.getPersistenceHandlerParameterMap().size()); + + // TODO extend assertions to actual model + } + + @Test + public void canWriteConfig() { + + Map parameterMap = new HashMap<>(); + Map encryptionHandlerParameterMap = new HashMap<>(); + Map persistenceHandlerParameterMap = new HashMap<>(); + + parameterMap.put("autoPersistOnPasswordChange", "true"); + encryptionHandlerParameterMap.put("hashAlgorithm", "SHA-256"); + persistenceHandlerParameterMap.put("basePath", TARGET_TEST); + persistenceHandlerParameterMap.put("modelXmlFile", "PrivilegeModel.xml"); + + PrivilegeContainerModel containerModel = new PrivilegeContainerModel(); + containerModel.setParameterMap(parameterMap); + containerModel.setEncryptionHandlerClassName(DefaultEncryptionHandler.class.getName()); + containerModel.setEncryptionHandlerParameterMap(encryptionHandlerParameterMap); + containerModel.setPersistenceHandlerClassName(XmlPersistenceHandler.class.getName()); + containerModel.setPersistenceHandlerParameterMap(persistenceHandlerParameterMap); + + containerModel.addPolicy("DefaultPrivilege", "ch.eitchnet.privilege.policy.DefaultPrivilege"); + + File configFile = new File("./target/test/PrivilegeTest.xml"); + PrivilegeConfigDomWriter configSaxWriter = new PrivilegeConfigDomWriter(containerModel, configFile); + configSaxWriter.write(); + + String fileHash = StringHelper.getHexString(FileHelper.hashFileSha256(configFile)); + assertEquals("22d4ba39605d49c758184d9bd63beae5ccf8786f3dabbab45cd9f59c2afbcbd0", fileHash); + } + + @Test + public void canReadUsers() { + + PrivilegeUsersSaxReader xmlHandler = new PrivilegeUsersSaxReader(); + File xmlFile = new File("config/PrivilegeUsers.xml"); + XmlHelper.parseDocument(xmlFile, xmlHandler); + + List users = xmlHandler.getUsers(); + assertNotNull(users); + + assertEquals(3, users.size()); + + // + // users + // + + // admin + User admin = findUser("admin", users); + assertEquals("1", admin.getUserId()); + assertEquals("admin", admin.getUsername()); + assertEquals("8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918", admin.getPassword()); + assertEquals("Application", admin.getFirstname()); + assertEquals("Administrator", admin.getLastname()); + assertEquals(UserState.ENABLED, admin.getUserState()); + assertEquals("en_gb", admin.getLocale().toString()); + assertThat(admin.getRoles(), containsInAnyOrder("PrivilegeAdmin", "AppUser")); + Map properties = admin.getProperties(); + assertEquals(new HashSet<>(Arrays.asList("organization", "organizationalUnit")), properties.keySet()); + assertEquals("eitchnet.ch", properties.get("organization")); + assertEquals("Development", properties.get("organizationalUnit")); + + // system_admin + User systemAdmin = findUser("system_admin", users); + assertEquals("2", systemAdmin.getUserId()); + assertEquals("system_admin", systemAdmin.getUsername()); + assertEquals(null, systemAdmin.getPassword()); + assertEquals("System User", systemAdmin.getFirstname()); + assertEquals("Administrator", systemAdmin.getLastname()); + assertEquals(UserState.SYSTEM, systemAdmin.getUserState()); + assertEquals("en_gb", systemAdmin.getLocale().toString()); + assertThat(systemAdmin.getRoles(), containsInAnyOrder("system_admin_privileges")); + assertTrue(systemAdmin.getProperties().isEmpty()); + } + + @Test + public void canReadRoles() { + + PrivilegeRolesSaxReader xmlHandler = new PrivilegeRolesSaxReader(); + File xmlFile = new File("config/PrivilegeRoles.xml"); + XmlHelper.parseDocument(xmlFile, xmlHandler); + + List roles = xmlHandler.getRoles(); + assertNotNull(roles); + + assertEquals(6, roles.size()); + + // assert model + + // + // roles + // + + // PrivilegeAdmin + Role privilegeAdmin = findRole("PrivilegeAdmin", roles); + assertEquals("PrivilegeAdmin", privilegeAdmin.getName()); + assertEquals(14, privilegeAdmin.getPrivilegeNames().size()); + IPrivilege privilegeAction = privilegeAdmin.getPrivilege(PrivilegeHandler.PRIVILEGE_ACTION); + assertFalse(privilegeAction.isAllAllowed()); + assertEquals(3, privilegeAction.getAllowList().size()); + assertEquals(0, privilegeAction.getDenyList().size()); + assertEquals("DefaultPrivilege", privilegeAction.getPolicy()); + + IPrivilege privilegeAddRole = privilegeAdmin.getPrivilege(PrivilegeHandler.PRIVILEGE_ADD_ROLE); + assertTrue(privilegeAddRole.isAllAllowed()); + assertEquals(0, privilegeAddRole.getAllowList().size()); + assertEquals(0, privilegeAddRole.getDenyList().size()); + + IPrivilege privilegeRemRoleFromUser = privilegeAdmin + .getPrivilege(PrivilegeHandler.PRIVILEGE_REMOVE_ROLE_FROM_USER); + assertTrue(privilegeRemRoleFromUser.isAllAllowed()); + assertEquals(0, privilegeRemRoleFromUser.getAllowList().size()); + assertEquals(0, privilegeRemRoleFromUser.getDenyList().size()); + + // AppUser + Role appUser = findRole("AppUser", roles); + assertEquals("AppUser", appUser.getName()); + assertEquals(new HashSet<>(Arrays.asList("ch.eitchnet.privilege.test.model.TestRestrictable")), + appUser.getPrivilegeNames()); + + IPrivilege testRestrictable = appUser.getPrivilege("ch.eitchnet.privilege.test.model.TestRestrictable"); + assertEquals("ch.eitchnet.privilege.test.model.TestRestrictable", testRestrictable.getName()); + assertEquals("DefaultPrivilege", testRestrictable.getPolicy()); + assertTrue(testRestrictable.isAllAllowed()); + assertEquals(0, testRestrictable.getAllowList().size()); + assertEquals(0, testRestrictable.getDenyList().size()); + + // system_admin_privileges + Role systemAdminPrivileges = findRole("system_admin_privileges", roles); + assertEquals("system_admin_privileges", systemAdminPrivileges.getName()); + assertEquals(2, systemAdminPrivileges.getPrivilegeNames().size()); + assertThat(systemAdminPrivileges.getPrivilegeNames(), + containsInAnyOrder("ch.eitchnet.privilege.handler.SystemUserAction", + "ch.eitchnet.privilege.test.model.TestSystemRestrictable")); + + IPrivilege testSystemUserAction = systemAdminPrivileges + .getPrivilege("ch.eitchnet.privilege.handler.SystemUserAction"); + assertEquals("ch.eitchnet.privilege.handler.SystemUserAction", testSystemUserAction.getName()); + assertEquals("DefaultPrivilege", testSystemUserAction.getPolicy()); + assertFalse(testSystemUserAction.isAllAllowed()); + assertEquals(1, testSystemUserAction.getAllowList().size()); + assertEquals(1, testSystemUserAction.getDenyList().size()); + + IPrivilege testSystemRestrictable = systemAdminPrivileges + .getPrivilege("ch.eitchnet.privilege.test.model.TestSystemRestrictable"); + assertEquals("ch.eitchnet.privilege.test.model.TestSystemRestrictable", testSystemRestrictable.getName()); + assertEquals("DefaultPrivilege", testSystemRestrictable.getPolicy()); + assertTrue(testSystemRestrictable.isAllAllowed()); + assertEquals(0, testSystemRestrictable.getAllowList().size()); + assertEquals(0, testSystemRestrictable.getDenyList().size()); + + // restrictedRole + Role restrictedRole = findRole("restrictedRole", roles); + assertEquals("restrictedRole", restrictedRole.getName()); + assertEquals(1, restrictedRole.getPrivilegeNames().size()); + assertThat(restrictedRole.getPrivilegeNames(), + containsInAnyOrder("ch.eitchnet.privilege.handler.SystemUserAction")); + + IPrivilege testSystemUserAction2 = restrictedRole + .getPrivilege("ch.eitchnet.privilege.handler.SystemUserAction"); + assertEquals("ch.eitchnet.privilege.handler.SystemUserAction", testSystemUserAction2.getName()); + assertEquals("DefaultPrivilege", testSystemUserAction2.getPolicy()); + assertFalse(testSystemUserAction2.isAllAllowed()); + assertEquals(1, testSystemUserAction2.getAllowList().size()); + assertEquals(1, testSystemUserAction2.getDenyList().size()); + assertThat(testSystemUserAction2.getAllowList(), containsInAnyOrder("hello")); + assertThat(testSystemUserAction2.getDenyList(), containsInAnyOrder("goodbye")); + } + + /** + * @param username + * @param users + * @return + */ + private User findUser(String username, List users) { + for (User user : users) { + if (user.getUsername().equals(username)) + return user; + } + + throw new RuntimeException("No user exists with username " + username); + } + + /** + * @param name + * @param roles + * @return + */ + private Role findRole(String name, List roles) { + for (Role role : roles) { + if (role.getName().equals(name)) + return role; + } + + throw new RuntimeException("No role exists with name " + name); + } + + @Test + public void canWriteUsers() { + + Map propertyMap; + Set userRoles; + + List users = new ArrayList<>(); + propertyMap = new HashMap<>(); + propertyMap.put("prop1", "value1"); + userRoles = new HashSet<>(); + userRoles.add("role1"); + User user1 = new User("1", "user1", "blabla", "Bob", "White", UserState.DISABLED, userRoles, Locale.ENGLISH, + propertyMap); + users.add(user1); + + propertyMap = new HashMap<>(); + propertyMap.put("prop2", "value2"); + userRoles = new HashSet<>(); + userRoles.add("role2"); + User user2 = new User("2", "user2", "haha", "Leonard", "Sheldon", UserState.ENABLED, userRoles, Locale.ENGLISH, + propertyMap); + users.add(user2); + + File modelFile = new File("./target/test/PrivilegeUsersTest.xml"); + PrivilegeUsersDomWriter configSaxWriter = new PrivilegeUsersDomWriter(users, modelFile); + configSaxWriter.write(); + + PrivilegeUsersSaxReader xmlHandler = new PrivilegeUsersSaxReader(); + XmlHelper.parseDocument(modelFile, xmlHandler); + + List parsedUsers = xmlHandler.getUsers(); + assertNotNull(parsedUsers); + assertEquals(2, parsedUsers.size()); + + User parsedUser1 = parsedUsers.stream().filter(u -> u.getUsername().equals("user1")).findAny().get(); + User parsedUser2 = parsedUsers.stream().filter(u -> u.getUsername().equals("user2")).findAny().get(); + + assertEquals(user1.getFirstname(), parsedUser1.getFirstname()); + assertEquals(user1.getLastname(), parsedUser1.getLastname()); + assertEquals(user1.getLocale(), parsedUser1.getLocale()); + assertEquals(user1.getPassword(), parsedUser1.getPassword()); + assertEquals(user1.getProperties(), parsedUser1.getProperties()); + assertEquals(user1.getUserId(), parsedUser1.getUserId()); + assertEquals(user1.getUserState(), parsedUser1.getUserState()); + assertEquals(user1.getRoles(), parsedUser1.getRoles()); + + assertEquals(user2.getFirstname(), parsedUser2.getFirstname()); + assertEquals(user2.getLastname(), parsedUser2.getLastname()); + assertEquals(user2.getLocale(), parsedUser2.getLocale()); + assertEquals(user2.getPassword(), parsedUser2.getPassword()); + assertEquals(user2.getProperties(), parsedUser2.getProperties()); + assertEquals(user2.getUserId(), parsedUser2.getUserId()); + assertEquals(user2.getUserState(), parsedUser2.getUserState()); + assertEquals(user2.getRoles(), parsedUser2.getRoles()); + } + + @Test + public void canWriteRoles() { + + Map privilegeMap; + List roles = new ArrayList<>(); + Set list = Collections.emptySet(); + privilegeMap = new HashMap<>(); + privilegeMap.put("priv1", new PrivilegeImpl("priv1", "DefaultPrivilege", true, list, list)); + Role role1 = new Role("role1", privilegeMap); + roles.add(role1); + + privilegeMap = new HashMap<>(); + Set denyList = new HashSet<>(); + denyList.add("myself"); + Set allowList = new HashSet<>(); + allowList.add("other"); + privilegeMap.put("priv2", new PrivilegeImpl("priv2", "DefaultPrivilege", false, denyList, allowList)); + Role role2 = new Role("role2", privilegeMap); + roles.add(role2); + + File modelFile = new File("./target/test/PrivilegeRolesTest.xml"); + PrivilegeRolesDomWriter configSaxWriter = new PrivilegeRolesDomWriter(roles, modelFile); + configSaxWriter.write(); + + PrivilegeRolesSaxReader xmlHandler = new PrivilegeRolesSaxReader(); + XmlHelper.parseDocument(modelFile, xmlHandler); + + List parsedRoles = xmlHandler.getRoles(); + assertNotNull(parsedRoles); + assertEquals(2, parsedRoles.size()); + + assertEquals(2, parsedRoles.size()); + Role parsedRole1 = parsedRoles.stream().filter(r -> r.getName().equals("role1")).findAny().get(); + Role parsedRole2 = parsedRoles.stream().filter(r -> r.getName().equals("role2")).findAny().get(); + + Set privilegeNames = role1.getPrivilegeNames(); + assertEquals(privilegeNames, parsedRole1.getPrivilegeNames()); + for (String privilegeName : privilegeNames) { + IPrivilege privilege = role1.getPrivilege(privilegeName); + IPrivilege privilege2 = parsedRole1.getPrivilege(privilegeName); + assertNotNull(privilege); + assertNotNull(privilege2); + + assertEquals(privilege.isAllAllowed(), privilege2.isAllAllowed()); + assertEquals(privilege.getAllowList(), privilege2.getAllowList()); + assertEquals(privilege.getDenyList(), privilege2.getDenyList()); + assertEquals(privilege.getName(), privilege2.getName()); + assertEquals(privilege.getPolicy(), privilege2.getPolicy()); + } + + assertEquals(role2.getPrivilegeNames(), parsedRole2.getPrivilegeNames()); + privilegeNames = role2.getPrivilegeNames(); + for (String privilegeName : privilegeNames) { + IPrivilege privilege = role2.getPrivilege(privilegeName); + IPrivilege privilege2 = parsedRole2.getPrivilege(privilegeName); + assertNotNull(privilege); + assertNotNull(privilege2); + + assertEquals(privilege.isAllAllowed(), privilege2.isAllAllowed()); + assertEquals(privilege.getAllowList(), privilege2.getAllowList()); + assertEquals(privilege.getDenyList(), privilege2.getDenyList()); + assertEquals(privilege.getName(), privilege2.getName()); + assertEquals(privilege.getPolicy(), privilege2.getPolicy()); + } + } +} diff --git a/ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/model/TestRestrictable.java b/ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/model/TestRestrictable.java new file mode 100644 index 000000000..af7bb4e7f --- /dev/null +++ b/ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/model/TestRestrictable.java @@ -0,0 +1,42 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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. + */ +package ch.eitchnet.privilege.test.model; + +import ch.eitchnet.privilege.model.Restrictable; + +/** + * @author Robert von Burg + * + */ +public class TestRestrictable implements Restrictable { + + /** + * @see ch.eitchnet.privilege.model.Restrictable#getPrivilegeName() + */ + @Override + public String getPrivilegeName() { + return TestRestrictable.class.getName(); + } + + /** + * @see ch.eitchnet.privilege.model.Restrictable#getPrivilegeValue() + */ + @Override + public Object getPrivilegeValue() { + return TestRestrictable.class.getName(); + } + +} diff --git a/ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/model/TestSystemRestrictable.java b/ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/model/TestSystemRestrictable.java new file mode 100644 index 000000000..d73c731f5 --- /dev/null +++ b/ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/model/TestSystemRestrictable.java @@ -0,0 +1,42 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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. + */ +package ch.eitchnet.privilege.test.model; + +import ch.eitchnet.privilege.model.Restrictable; + +/** + * @author Robert von Burg + * + */ +public class TestSystemRestrictable implements Restrictable { + + /** + * @see ch.eitchnet.privilege.model.Restrictable#getPrivilegeName() + */ + @Override + public String getPrivilegeName() { + return TestSystemRestrictable.class.getName(); + } + + /** + * @see ch.eitchnet.privilege.model.Restrictable#getPrivilegeValue() + */ + @Override + public Object getPrivilegeValue() { + return TestSystemRestrictable.class.getName(); + } + +} diff --git a/ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java b/ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java new file mode 100644 index 000000000..b3942b7cc --- /dev/null +++ b/ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java @@ -0,0 +1,32 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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. + */ +package ch.eitchnet.privilege.test.model; + +import ch.eitchnet.privilege.handler.SystemUserAction; +import ch.eitchnet.privilege.model.PrivilegeContext; + +/** + * @author Robert von Burg + * + */ +public class TestSystemUserAction extends SystemUserAction { + + @Override + public void execute(PrivilegeContext context) { + TestSystemRestrictable restrictable = new TestSystemRestrictable(); + context.validateAction(restrictable); + } +} diff --git a/ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserActionDeny.java b/ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserActionDeny.java new file mode 100644 index 000000000..51c1574cc --- /dev/null +++ b/ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserActionDeny.java @@ -0,0 +1,32 @@ +/* + * Copyright 2013 Robert von Burg + * + * 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. + */ +package ch.eitchnet.privilege.test.model; + +import ch.eitchnet.privilege.handler.SystemUserAction; +import ch.eitchnet.privilege.model.PrivilegeContext; + +/** + * @author Robert von Burg + * + */ +public class TestSystemUserActionDeny extends SystemUserAction { + + @Override + public void execute(PrivilegeContext privilegeContext) { + TestRestrictable restrictable = new TestRestrictable(); + privilegeContext.validateAction(restrictable); + } +} diff --git a/ch.eitchnet.privilege/src/test/resources/log4j.xml b/ch.eitchnet.privilege/src/test/resources/log4j.xml new file mode 100644 index 000000000..0a2a73d06 --- /dev/null +++ b/ch.eitchnet.privilege/src/test/resources/log4j.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +