From 2fbf6e47992026011de275c5db2865f66dae4bd1 Mon Sep 17 00:00:00 2001 From: eitch Date: Thu, 20 May 2010 19:36:16 +0000 Subject: [PATCH 002/173] --- .classpath | 6 + .project | 17 ++ .settings/org.eclipse.jdt.core.prefs | 12 ++ src/ch/eitchnet/privilege/base/Locator.java | 168 ++++++++++++++++++ .../privilege/base/PrivilegeException.java | 28 +++ .../privilege/base/PrivilegeHelper.java | 19 ++ .../privilege/handler/PrivilegeHandler.java | 19 ++ .../eitchnet/privilege/model/Certificate.java | 103 +++++++++++ .../eitchnet/privilege/model/Permission.java | 19 ++ src/ch/eitchnet/privilege/model/User.java | 137 ++++++++++++++ .../eitchnet/privilege/model/UserState.java | 22 +++ 11 files changed, 550 insertions(+) create mode 100644 .classpath create mode 100644 .project create mode 100644 .settings/org.eclipse.jdt.core.prefs create mode 100644 src/ch/eitchnet/privilege/base/Locator.java create mode 100644 src/ch/eitchnet/privilege/base/PrivilegeException.java create mode 100644 src/ch/eitchnet/privilege/base/PrivilegeHelper.java create mode 100644 src/ch/eitchnet/privilege/handler/PrivilegeHandler.java create mode 100644 src/ch/eitchnet/privilege/model/Certificate.java create mode 100644 src/ch/eitchnet/privilege/model/Permission.java create mode 100644 src/ch/eitchnet/privilege/model/User.java create mode 100644 src/ch/eitchnet/privilege/model/UserState.java diff --git a/.classpath b/.classpath new file mode 100644 index 000000000..18d70f02c --- /dev/null +++ b/.classpath @@ -0,0 +1,6 @@ + + + + + + diff --git a/.project b/.project new file mode 100644 index 000000000..2bbc43fc5 --- /dev/null +++ b/.project @@ -0,0 +1,17 @@ + + + Privilege + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 000000000..650ae7f9a --- /dev/null +++ b/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,12 @@ +#Wed May 19 19:28:29 CEST 2010 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.6 diff --git a/src/ch/eitchnet/privilege/base/Locator.java b/src/ch/eitchnet/privilege/base/Locator.java new file mode 100644 index 000000000..c9184fea7 --- /dev/null +++ b/src/ch/eitchnet/privilege/base/Locator.java @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.base; + +import java.io.Serializable; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.List; +import java.util.ListIterator; + +/** + * Locator to access RSP objects by a generalized path similar to locator used for accessing files in file systems or + * URL. + *

+ * The locator is made of the following elements: + *

  • A identifier of the map to take the top level object from (Resource, Order, Workflow or Script) + *
  • The id of the top level object + *
  • The sequence of id's of the elements to navigate to the object + *

    + * It's string representation is similar to a UNIX path. For example a string version if the Locator of a parameter + * (with id = p1) of the bounds of a resource takes the form: /Resource/Bounds/p1. + * + * @author msmock + */ +public class Locator implements Serializable { + + private static final char LOCATOR_SEPARATOR = '/'; + private static final String LOCATOR_SEPARATOR_S = "/"; + private static final long serialVersionUID = 1L; + + // the path with string entries + private List path; + + /** + * simple constructor + */ + public Locator() { + path = new ArrayList(); + } + + /** + * @param path + */ + public Locator(List path) { + this.path = path; + } + + /** + * @param s + */ + public Locator(String s) { + + // parse the key to the list of path elements + s.trim(); + String[] pathElements = s.split(LOCATOR_SEPARATOR_S); + path = new ArrayList(); + + // for the case that we received a non locator string, just set it as + // the first path, helps while debugging + if (pathElements.length == 1) { + path.add(s); + } else { + + for (int i = 1; i < pathElements.length; i++) { + path.add(pathElements[i]); + } + } + } + + /** + * @return the path + */ + public List getPath() { + return path; + } + + /** + * @param path + * the path to set + */ + public void setPath(List path) { + this.path = path; + } + + /** + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + + StringWriter sw = new StringWriter(); + for (String s : path) { + sw.append(LOCATOR_SEPARATOR); + sw.append(s); + } + return sw.toString(); + } + + /** + * @param s + */ + public void append(String s) { + path.add(s); + } + + /** + * @return ListIterator + */ + public ListIterator iterator() { + return path.listIterator(); + } + + /** + * get the number of path element strings + * + * @return int + */ + public int length() { + return path.size(); + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object other) { + + if (this == other) + return true; + + if (!(other instanceof Locator)) + return false; + + final Locator that = (Locator) other; + + // return false, if the size does not match + if (path.size() != that.path.size()) + return false; + + // compare the path elements + for (int i = 0; i < path.size(); i++) { + String thisS = path.get(i); + String thatS = that.path.get(i); + if (!thisS.equals(thatS)) + return false; + } + + // all path elements match + return true; + } + + /** + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return toString().hashCode(); + } + +} diff --git a/src/ch/eitchnet/privilege/base/PrivilegeException.java b/src/ch/eitchnet/privilege/base/PrivilegeException.java new file mode 100644 index 000000000..74a7c2ce9 --- /dev/null +++ b/src/ch/eitchnet/privilege/base/PrivilegeException.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.base; + +/** + * @author rvonburg + * + */ +public class PrivilegeException extends RuntimeException { + + /** + * @param string + */ + public PrivilegeException(String string) { + super(string); + } + + private static final long serialVersionUID = 1L; + +} diff --git a/src/ch/eitchnet/privilege/base/PrivilegeHelper.java b/src/ch/eitchnet/privilege/base/PrivilegeHelper.java new file mode 100644 index 000000000..a546a8aa9 --- /dev/null +++ b/src/ch/eitchnet/privilege/base/PrivilegeHelper.java @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.base; + +/** + * @author rvonburg + * + */ +public class PrivilegeHelper { + +} diff --git a/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java b/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java new file mode 100644 index 000000000..95bd41707 --- /dev/null +++ b/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.handler; + +/** + * @author rvonburg + * + */ +public interface PrivilegeHandler { + +} diff --git a/src/ch/eitchnet/privilege/model/Certificate.java b/src/ch/eitchnet/privilege/model/Certificate.java new file mode 100644 index 000000000..841693abd --- /dev/null +++ b/src/ch/eitchnet/privilege/model/Certificate.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.model; + +import java.io.Serializable; +import java.util.Locale; + +import ch.eitchnet.privilege.base.PrivilegeException; + +/** + * @author rvonburg + * + */ +public class Certificate implements Serializable { + + private static final long serialVersionUID = 1L; + + private final String sessionId; + private final String username; + private final String authToken; + private final String authPassword; + + private Locale locale; + + /** + * @param sessionId + * @param username + * @param authToken + * @param authPassword + * @param locale + */ + public Certificate(String sessionId, String username, String authToken, String authPassword, Locale locale) { + + // validate arguments are not null + if (sessionId == null || username == null || authToken == null || authPassword == null) { + throw new PrivilegeException("One of the arguments is null!"); + } + + this.sessionId = sessionId; + this.username = username; + this.authToken = authToken; + this.authPassword = authPassword; + + // if no locale is given, set default + if (locale == null) + this.locale = Locale.getDefault(); + else + this.locale = locale; + } + + /** + * @return the locale + */ + public Locale getLocale() { + return locale; + } + + /** + * @param locale + * the locale to set + */ + public void setLocale(Locale locale) { + this.locale = locale; + } + + /** + * @return the sessionId + */ + public String getSessionId() { + return sessionId; + } + + /** + * @return the username + */ + public String getUsername() { + return username; + } + + /** + * Returns the authToken if the given authPassword is corret, null otherwise + * + * @param authPassword + * the auth password with which this certificate was created + * + * @return the authToken if the given authPassword is corret, null otherwise + */ + public String getAuthToken(String authPassword) { + if (this.authPassword.equals(authPassword)) { + return authToken; + } else { + return null; + } + } +} diff --git a/src/ch/eitchnet/privilege/model/Permission.java b/src/ch/eitchnet/privilege/model/Permission.java new file mode 100644 index 000000000..31b66c48d --- /dev/null +++ b/src/ch/eitchnet/privilege/model/Permission.java @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.model; + +/** + * @author rvonburg + * + */ +public interface Permission { + +} diff --git a/src/ch/eitchnet/privilege/model/User.java b/src/ch/eitchnet/privilege/model/User.java new file mode 100644 index 000000000..31fa54940 --- /dev/null +++ b/src/ch/eitchnet/privilege/model/User.java @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.model; + +import java.util.List; + +/** + * @author rvonburg + * + */ +public class User { + + private final String sessionId; + private final String authToken; + private final String authPassword; + + private final String username; + private final String firstname; + private final String surname; + + private final UserState userState; + + private List roleList; + + /** + * The {@link User} constructor is private to ensure no unauthorized creation of {@link User} objects + * + * @param sessionId + * @param username + * @param firstname + * @param surname + * @param userState + * @param validated + * @param roleList + */ + private User(String sessionId, String authToken, String authPassword, String username, String firstname, + String surname, UserState userState, List roleList) { + + this.sessionId = sessionId; + this.authToken = authToken; + this.authPassword = authPassword; + + this.username = username; + this.userState = userState; + + this.firstname = firstname; + this.surname = surname; + + this.roleList = roleList; + } + + /** + * @return the sessionId + */ + public String getSessionId() { + return sessionId; + } + + /** + * @return the authToken + */ + public String getAuthToken() { + return authToken; + } + + /** + * @return the authPassword + */ + public String getAuthPassword() { + return authPassword; + } + + /** + * @return the username + */ + public String getUsername() { + return username; + } + + /** + * @return the firstname + */ + public String getFirstname() { + return firstname; + } + + /** + * @return the surname + */ + public String getSurname() { + return surname; + } + + /** + * @return the userState + */ + public UserState getUserState() { + return userState; + } + + /** + * @return the roleList + */ + public List getRoleList() { + return roleList; + } + + /** + * @param sessionId + * @param authToken + * @param authPassword + * @param username + * @param firstname + * @param surname + * @param userState + * @param roleList + * + * @return a new {@link User} object which is authenticated on the current Java Virtual Machine + */ + public static User buildUser(String sessionId, String authToken, String authPassword, String username, + String firstname, String surname, UserState userState, List roleList) { + + // TODO validate who is creating this User object + + User user = new User(sessionId, authToken, authPassword, username, firstname, surname, userState, roleList); + + return user; + } +} diff --git a/src/ch/eitchnet/privilege/model/UserState.java b/src/ch/eitchnet/privilege/model/UserState.java new file mode 100644 index 000000000..24cb3128e --- /dev/null +++ b/src/ch/eitchnet/privilege/model/UserState.java @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.model; + +/** + * @author rvonburg + * + */ +public enum UserState { + NEW, + ENABLED, + DISABLED, + DEACTIVATED; +} From 78c894caaff911f540fba1ea981f191ddc8e1344 Mon Sep 17 00:00:00 2001 From: eitch Date: Mon, 24 May 2010 19:21:46 +0000 Subject: [PATCH 003/173] --- src/ch/eitchnet/privilege/base/Locator.java | 168 ------------------ .../privilege/base/PrivilegeContainer.java | 63 +++++++ .../privilege/base/PrivilegeHelper.java | 19 -- .../handler/DefaultPolicyHandler.java | 44 +++++ .../handler/DefaultPrivilegeHandler.java | 143 +++++++++++++++ .../privilege/handler/PolicyHandler.java | 23 +++ .../privilege/handler/PrivilegeHandler.java | 25 +++ .../privilege/i18n/AccessDeniedException.java | 29 +++ .../{base => i18n}/PrivilegeException.java | 6 +- .../eitchnet/privilege/model/Certificate.java | 65 ++++++- .../{Permission.java => Restrictable.java} | 5 +- .../privilege/model/RestrictionPolicy.java | 20 +++ src/ch/eitchnet/privilege/model/Session.java | 130 ++++++++++++++ src/ch/eitchnet/privilege/model/User.java | 86 ++++----- 14 files changed, 582 insertions(+), 244 deletions(-) delete mode 100644 src/ch/eitchnet/privilege/base/Locator.java create mode 100644 src/ch/eitchnet/privilege/base/PrivilegeContainer.java delete mode 100644 src/ch/eitchnet/privilege/base/PrivilegeHelper.java create mode 100644 src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java create mode 100644 src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java create mode 100644 src/ch/eitchnet/privilege/handler/PolicyHandler.java create mode 100644 src/ch/eitchnet/privilege/i18n/AccessDeniedException.java rename src/ch/eitchnet/privilege/{base => i18n}/PrivilegeException.java (90%) rename src/ch/eitchnet/privilege/model/{Permission.java => Restrictable.java} (67%) create mode 100644 src/ch/eitchnet/privilege/model/RestrictionPolicy.java create mode 100644 src/ch/eitchnet/privilege/model/Session.java diff --git a/src/ch/eitchnet/privilege/base/Locator.java b/src/ch/eitchnet/privilege/base/Locator.java deleted file mode 100644 index c9184fea7..000000000 --- a/src/ch/eitchnet/privilege/base/Locator.java +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Copyright (c) 2010 - * - * Robert von Burg - * eitch@eitchnet.ch - * - * All rights reserved. - * - */ - -package ch.eitchnet.privilege.base; - -import java.io.Serializable; -import java.io.StringWriter; -import java.util.ArrayList; -import java.util.List; -import java.util.ListIterator; - -/** - * Locator to access RSP objects by a generalized path similar to locator used for accessing files in file systems or - * URL. - *

    - * The locator is made of the following elements: - *

  • A identifier of the map to take the top level object from (Resource, Order, Workflow or Script) - *
  • The id of the top level object - *
  • The sequence of id's of the elements to navigate to the object - *

    - * It's string representation is similar to a UNIX path. For example a string version if the Locator of a parameter - * (with id = p1) of the bounds of a resource takes the form: /Resource/Bounds/p1. - * - * @author msmock - */ -public class Locator implements Serializable { - - private static final char LOCATOR_SEPARATOR = '/'; - private static final String LOCATOR_SEPARATOR_S = "/"; - private static final long serialVersionUID = 1L; - - // the path with string entries - private List path; - - /** - * simple constructor - */ - public Locator() { - path = new ArrayList(); - } - - /** - * @param path - */ - public Locator(List path) { - this.path = path; - } - - /** - * @param s - */ - public Locator(String s) { - - // parse the key to the list of path elements - s.trim(); - String[] pathElements = s.split(LOCATOR_SEPARATOR_S); - path = new ArrayList(); - - // for the case that we received a non locator string, just set it as - // the first path, helps while debugging - if (pathElements.length == 1) { - path.add(s); - } else { - - for (int i = 1; i < pathElements.length; i++) { - path.add(pathElements[i]); - } - } - } - - /** - * @return the path - */ - public List getPath() { - return path; - } - - /** - * @param path - * the path to set - */ - public void setPath(List path) { - this.path = path; - } - - /** - * @see java.lang.Object#toString() - */ - @Override - public String toString() { - - StringWriter sw = new StringWriter(); - for (String s : path) { - sw.append(LOCATOR_SEPARATOR); - sw.append(s); - } - return sw.toString(); - } - - /** - * @param s - */ - public void append(String s) { - path.add(s); - } - - /** - * @return ListIterator - */ - public ListIterator iterator() { - return path.listIterator(); - } - - /** - * get the number of path element strings - * - * @return int - */ - public int length() { - return path.size(); - } - - /** - * @see java.lang.Object#equals(java.lang.Object) - */ - @Override - public boolean equals(Object other) { - - if (this == other) - return true; - - if (!(other instanceof Locator)) - return false; - - final Locator that = (Locator) other; - - // return false, if the size does not match - if (path.size() != that.path.size()) - return false; - - // compare the path elements - for (int i = 0; i < path.size(); i++) { - String thisS = path.get(i); - String thatS = that.path.get(i); - if (!thisS.equals(thatS)) - return false; - } - - // all path elements match - return true; - } - - /** - * @see java.lang.Object#hashCode() - */ - @Override - public int hashCode() { - return toString().hashCode(); - } - -} diff --git a/src/ch/eitchnet/privilege/base/PrivilegeContainer.java b/src/ch/eitchnet/privilege/base/PrivilegeContainer.java new file mode 100644 index 000000000..3e99d76c0 --- /dev/null +++ b/src/ch/eitchnet/privilege/base/PrivilegeContainer.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.base; + +import java.io.File; + +import ch.eitchnet.privilege.handler.PolicyHandler; +import ch.eitchnet.privilege.handler.PrivilegeHandler; + +/** + * + * + * @author rvonburg + */ +public class PrivilegeContainer { + + private static final PrivilegeContainer instance; + + static { + instance = new PrivilegeContainer(); + } + + private PrivilegeHandler privilegeHandler; + private PolicyHandler policyHandler; + + public static PrivilegeContainer getInstance() { + return instance; + } + + /** + * private constructor to force singleton + */ + private PrivilegeContainer() { + // private constructor + } + + /** + * @return the privilegeHandler + */ + public PrivilegeHandler getPrivilegeHandler() { + return privilegeHandler; + } + + /** + * @return the policyHandler + */ + public PolicyHandler getPolicyHandler() { + return policyHandler; + } + + public void initialize(File privilegeContainerXml) { + // TODO implement + } + +} diff --git a/src/ch/eitchnet/privilege/base/PrivilegeHelper.java b/src/ch/eitchnet/privilege/base/PrivilegeHelper.java deleted file mode 100644 index a546a8aa9..000000000 --- a/src/ch/eitchnet/privilege/base/PrivilegeHelper.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (c) 2010 - * - * Robert von Burg - * eitch@eitchnet.ch - * - * All rights reserved. - * - */ - -package ch.eitchnet.privilege.base; - -/** - * @author rvonburg - * - */ -public class PrivilegeHelper { - -} diff --git a/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java new file mode 100644 index 000000000..e9e5176c2 --- /dev/null +++ b/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.handler; + +import java.io.File; +import java.util.Map; + +import ch.eitchnet.privilege.model.Restrictable; +import ch.eitchnet.privilege.model.RestrictionPolicy; +import ch.eitchnet.privilege.model.User; + +/** + * @author rvonburg + * + */ +public class DefaultPolicyHandler implements PolicyHandler { + + private Map policyMap; + + /** + * @see ch.eitchnet.privilege.handler.PolicyHandler#actionAllowed(ch.eitchnet.privilege.model.User, + * ch.eitchnet.privilege.model.Restrictable) + */ + @Override + public boolean actionAllowed(User user, Restrictable restrictable) { + + // TODO auth user + + // TODO Auto-generated method stub + return false; + } + + public void initialize(File policyXml) { + // TODO implement + } +} diff --git a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java new file mode 100644 index 000000000..e71292b22 --- /dev/null +++ b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.handler; + +import java.io.File; +import java.util.Map; + +import ch.eitchnet.privilege.base.PrivilegeContainer; +import ch.eitchnet.privilege.i18n.AccessDeniedException; +import ch.eitchnet.privilege.i18n.PrivilegeException; +import ch.eitchnet.privilege.model.Certificate; +import ch.eitchnet.privilege.model.Restrictable; +import ch.eitchnet.privilege.model.Session; +import ch.eitchnet.privilege.model.User; +import ch.eitchnet.privilege.model.UserState; + +/** + * @author rvonburg + * + */ +public class DefaultPrivilegeHandler implements PrivilegeHandler { + + private Map userMap; + + private Map sessionMap; + + /** + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#actionAllowed(ch.eitchnet.privilege.model.Certificate, + * ch.eitchnet.privilege.model.Restrictable) + * + * @throws AccessDeniedException + * if the {@link Certificate} is not for a currently logged in {@link User} or if the user may not + * perform the action defined by the {@link Restrictable} implementation + */ + @Override + public boolean actionAllowed(Certificate certificate, Restrictable restrictable) { + + if (certificate == null) + throw new PrivilegeException("Certificate may not be null!"); + else if (restrictable == null) + throw new PrivilegeException("Restrictable may not be null!"); + + // first see if a session exists for this certificate + CertificateSessionPair certificateSessionPair = sessionMap.get(certificate.getSessionId()); + if (certificateSessionPair == null) + throw new AccessDeniedException("There is no session information for " + certificate.toString()); + + Certificate sessionCertificate = certificateSessionPair.certificate; + if (!sessionCertificate.equals(certificate)) + throw new PrivilegeException("Received illegal certificate for session id " + certificate.getSessionId()); + + // get authtoken from certificate using the sessions password + String authToken = certificate.getAuthToken(certificateSessionPair.session.getAuthPassword()); + if (authToken == null || !authToken.equals(certificateSessionPair.session.getAuthToken())) + throw new PrivilegeException("Received illegal certificate data for session id " + + certificate.getSessionId()); + + // get user object + User user = userMap.get(certificateSessionPair.session.getUsername()); + if (user == null) { + throw new PrivilegeException( + "Oh now, how did this happen: No User in user map although certificate is valid!"); + } + + // now validate on policy handler + PrivilegeContainer.getInstance().getPolicyHandler().actionAllowed(user, restrictable); + + // TODO Auto-generated method stub + return false; + } + + /** + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#authenticate(java.lang.String, java.lang.String) + * + * @throws AccessDeniedException + * if the user credentials are not valid + */ + @Override + public Certificate authenticate(String username, String password) { + + // both username and password must at least have 3 characters in length + if (username == null || username.length() < 3) + throw new PrivilegeException("The given username is shorter than 3 characters"); + else if (password == null || password.length() < 3) + throw new PrivilegeException("The given password is shorter than 3 characters"); + + // we only work with hashed passwords + String passwordHash = password; // TODO hash password + + // get user object + User user = userMap.get(username); + // no user means no authentication + if (user == null) + throw new AccessDeniedException("There is no user defined with the credentials: " + username + " / ***..."); + + // validate password + if (!user.getPassword().equals(passwordHash)) + throw new AccessDeniedException("Password is incorrect for " + username + " / ***..."); + + // validate if user is allowed to login + if (user.getState() != UserState.ENABLED) + throw new AccessDeniedException("User " + username + " is not ENABLED. State is: " + user.getState()); + + // get 2 auth tokens + String authToken = ""; // TODO get auth token + String authPassword = ""; // TODO get auth password + + String sessionId = ""; // TODO get next session id + + // create certificate + Certificate certificate = new Certificate(sessionId, username, authToken, authPassword, user.getLocale()); + + // create and save a new session + Session session = new Session(sessionId, authToken, authPassword, user.getUsername(), System + .currentTimeMillis()); + sessionMap.put(sessionId, new CertificateSessionPair(session, certificate)); + + // return the certificate + return certificate; + } + + public void initialize(File userFile) { + // TODO implement + } + + private class CertificateSessionPair { + private Session session; + private Certificate certificate; + + public CertificateSessionPair(Session session, Certificate certificate) { + this.session = session; + this.certificate = certificate; + } + } +} diff --git a/src/ch/eitchnet/privilege/handler/PolicyHandler.java b/src/ch/eitchnet/privilege/handler/PolicyHandler.java new file mode 100644 index 000000000..992a45f16 --- /dev/null +++ b/src/ch/eitchnet/privilege/handler/PolicyHandler.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.handler; + +import ch.eitchnet.privilege.model.Restrictable; +import ch.eitchnet.privilege.model.User; + +/** + * @author rvonburg + * + */ +public interface PolicyHandler { + + public boolean actionAllowed(User user, Restrictable restrictable); +} diff --git a/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java b/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java index 95bd41707..b62df401f 100644 --- a/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java +++ b/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java @@ -10,10 +10,35 @@ package ch.eitchnet.privilege.handler; +import ch.eitchnet.privilege.i18n.AccessDeniedException; +import ch.eitchnet.privilege.model.Certificate; +import ch.eitchnet.privilege.model.Restrictable; +import ch.eitchnet.privilege.model.User; + /** * @author rvonburg * */ public interface PrivilegeHandler { + /** + * @param certificate + * @param restrictable + * @return + * + * @throws AccessDeniedException + * if the {@link Certificate} is not for a currently logged in {@link User} or if the user may not + * perform the action defined by the {@link Restrictable} implementation + */ + public boolean actionAllowed(Certificate certificate, Restrictable restrictable); + + /** + * @param user + * @param password + * @return + * + * @throws AccessDeniedException + * if the user credentials are not valid + */ + public Certificate authenticate(String user, String password); } diff --git a/src/ch/eitchnet/privilege/i18n/AccessDeniedException.java b/src/ch/eitchnet/privilege/i18n/AccessDeniedException.java new file mode 100644 index 000000000..1ff487f4b --- /dev/null +++ b/src/ch/eitchnet/privilege/i18n/AccessDeniedException.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.i18n; + +/** + * @author rvonburg + * + */ +public class AccessDeniedException extends PrivilegeException { + + /** + * @param string + */ + public AccessDeniedException(String string) { + super(string); + // TODO Auto-generated constructor stub + } + + private static final long serialVersionUID = 1L; + +} diff --git a/src/ch/eitchnet/privilege/base/PrivilegeException.java b/src/ch/eitchnet/privilege/i18n/PrivilegeException.java similarity index 90% rename from src/ch/eitchnet/privilege/base/PrivilegeException.java rename to src/ch/eitchnet/privilege/i18n/PrivilegeException.java index 74a7c2ce9..11ba8dc4f 100644 --- a/src/ch/eitchnet/privilege/base/PrivilegeException.java +++ b/src/ch/eitchnet/privilege/i18n/PrivilegeException.java @@ -8,7 +8,7 @@ * */ -package ch.eitchnet.privilege.base; +package ch.eitchnet.privilege.i18n; /** * @author rvonburg @@ -16,6 +16,8 @@ package ch.eitchnet.privilege.base; */ public class PrivilegeException extends RuntimeException { + private static final long serialVersionUID = 1L; + /** * @param string */ @@ -23,6 +25,4 @@ public class PrivilegeException extends RuntimeException { super(string); } - private static final long serialVersionUID = 1L; - } diff --git a/src/ch/eitchnet/privilege/model/Certificate.java b/src/ch/eitchnet/privilege/model/Certificate.java index 841693abd..42b473ab4 100644 --- a/src/ch/eitchnet/privilege/model/Certificate.java +++ b/src/ch/eitchnet/privilege/model/Certificate.java @@ -13,7 +13,7 @@ package ch.eitchnet.privilege.model; import java.io.Serializable; import java.util.Locale; -import ch.eitchnet.privilege.base.PrivilegeException; +import ch.eitchnet.privilege.i18n.PrivilegeException; /** * @author rvonburg @@ -100,4 +100,67 @@ public class Certificate implements Serializable { return null; } } + + /** + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((authPassword == null) ? 0 : authPassword.hashCode()); + result = prime * result + ((authToken == null) ? 0 : authToken.hashCode()); + result = prime * result + ((locale == null) ? 0 : locale.hashCode()); + result = prime * result + ((sessionId == null) ? 0 : sessionId.hashCode()); + result = prime * result + ((username == null) ? 0 : username.hashCode()); + return result; + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + @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 (authPassword == null) { + if (other.authPassword != null) + return false; + } else if (!authPassword.equals(other.authPassword)) + return false; + if (authToken == null) { + if (other.authToken != null) + return false; + } else if (!authToken.equals(other.authToken)) + return false; + if (locale == null) { + if (other.locale != null) + return false; + } else if (!locale.equals(other.locale)) + return false; + if (sessionId == null) { + if (other.sessionId != null) + return false; + } else if (!sessionId.equals(other.sessionId)) + return false; + if (username == null) { + if (other.username != null) + return false; + } else if (!username.equals(other.username)) + return false; + return true; + } + + /** + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "Certificate [locale=" + locale + ", sessionId=" + sessionId + ", username=" + username + "]"; + } } diff --git a/src/ch/eitchnet/privilege/model/Permission.java b/src/ch/eitchnet/privilege/model/Restrictable.java similarity index 67% rename from src/ch/eitchnet/privilege/model/Permission.java rename to src/ch/eitchnet/privilege/model/Restrictable.java index 31b66c48d..38a989f9b 100644 --- a/src/ch/eitchnet/privilege/model/Permission.java +++ b/src/ch/eitchnet/privilege/model/Restrictable.java @@ -14,6 +14,9 @@ package ch.eitchnet.privilege.model; * @author rvonburg * */ -public interface Permission { +public interface Restrictable { + public String getType(); + + public Object getValue(); } diff --git a/src/ch/eitchnet/privilege/model/RestrictionPolicy.java b/src/ch/eitchnet/privilege/model/RestrictionPolicy.java new file mode 100644 index 000000000..7bcccea64 --- /dev/null +++ b/src/ch/eitchnet/privilege/model/RestrictionPolicy.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.model; + +/** + * @author rvonburg + * + */ +public interface RestrictionPolicy { + + public boolean actionAllowed(User user, Restrictable restrictable); +} diff --git a/src/ch/eitchnet/privilege/model/Session.java b/src/ch/eitchnet/privilege/model/Session.java new file mode 100644 index 000000000..b9dd1ea83 --- /dev/null +++ b/src/ch/eitchnet/privilege/model/Session.java @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.model; + +/** + * @author rvonburg + * + */ +public class Session { + + private final String sessionId; + private final String username; + private final long loginTime; + + private final String authToken; + private final String authPassword; + + /** + */ + public Session(String sessionId, String authToken, String authPassword, String username, long loginTime) { + this.sessionId = sessionId; + this.authToken = authToken; + this.authPassword = authPassword; + this.username = username; + this.loginTime = loginTime; + } + + /** + * @return the sessionId + */ + public String getSessionId() { + return sessionId; + } + + /** + * @return the authToken + */ + public String getAuthToken() { + return authToken; + } + + /** + * @return the authPassword + */ + public String getAuthPassword() { + return authPassword; + } + + /** + * @return the username + */ + public String getUsername() { + return username; + } + + /** + * @return the loginTime + */ + public long getLoginTime() { + return loginTime; + } + + /** + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((authPassword == null) ? 0 : authPassword.hashCode()); + result = prime * result + ((authToken == null) ? 0 : authToken.hashCode()); + result = prime * result + (int) (loginTime ^ (loginTime >>> 32)); + result = prime * result + ((sessionId == null) ? 0 : sessionId.hashCode()); + result = prime * result + ((username == null) ? 0 : username.hashCode()); + return result; + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (!(obj instanceof Session)) + return false; + Session other = (Session) obj; + if (authPassword == null) { + if (other.authPassword != null) + return false; + } else if (!authPassword.equals(other.authPassword)) + return false; + if (authToken == null) { + if (other.authToken != null) + return false; + } else if (!authToken.equals(other.authToken)) + return false; + if (loginTime != other.loginTime) + return false; + if (sessionId == null) { + if (other.sessionId != null) + return false; + } else if (!sessionId.equals(other.sessionId)) + return false; + if (username == null) { + if (other.username != null) + return false; + } else if (!username.equals(other.username)) + return false; + return true; + } + + /** + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "Session [loginTime=" + loginTime + ", sessionId=" + sessionId + ", username=" + username + "]"; + } +} diff --git a/src/ch/eitchnet/privilege/model/User.java b/src/ch/eitchnet/privilege/model/User.java index 31fa54940..9cf9d58c4 100644 --- a/src/ch/eitchnet/privilege/model/User.java +++ b/src/ch/eitchnet/privilege/model/User.java @@ -10,7 +10,9 @@ package ch.eitchnet.privilege.model; +import java.util.Collections; import java.util.List; +import java.util.Locale; /** * @author rvonburg @@ -18,64 +20,34 @@ import java.util.List; */ public class User { - private final String sessionId; - private final String authToken; - private final String authPassword; - private final String username; + private final String password; + private final String firstname; private final String surname; private final UserState userState; - private List roleList; + private final List roleList; + + private final Locale locale; /** * The {@link User} constructor is private to ensure no unauthorized creation of {@link User} objects - * - * @param sessionId - * @param username - * @param firstname - * @param surname - * @param userState - * @param validated - * @param roleList */ - private User(String sessionId, String authToken, String authPassword, String username, String firstname, - String surname, UserState userState, List roleList) { - - this.sessionId = sessionId; - this.authToken = authToken; - this.authPassword = authPassword; + private User(String username, String password, String firstname, String surname, UserState userState, + List roleList, Locale locale) { this.username = username; + this.password = password; this.userState = userState; this.firstname = firstname; this.surname = surname; this.roleList = roleList; - } - /** - * @return the sessionId - */ - public String getSessionId() { - return sessionId; - } - - /** - * @return the authToken - */ - public String getAuthToken() { - return authToken; - } - - /** - * @return the authPassword - */ - public String getAuthPassword() { - return authPassword; + this.locale = locale; } /** @@ -85,6 +57,13 @@ public class User { return username; } + /** + * @return the password + */ + public String getPassword() { + return password; + } + /** * @return the firstname */ @@ -102,7 +81,7 @@ public class User { /** * @return the userState */ - public UserState getUserState() { + public UserState getState() { return userState; } @@ -114,23 +93,26 @@ public class User { } /** - * @param sessionId - * @param authToken - * @param authPassword - * @param username - * @param firstname - * @param surname - * @param userState - * @param roleList - * + * @return the locale + */ + public Locale getLocale() { + return locale; + } + + /** * @return a new {@link User} object which is authenticated on the current Java Virtual Machine */ - public static User buildUser(String sessionId, String authToken, String authPassword, String username, - String firstname, String surname, UserState userState, List roleList) { + public static User buildUser(String username, String password, String firstname, String surname, + UserState userState, List roleList, Locale locale) { + + // set a default locale + if (locale == null) + locale = Locale.getDefault(); // TODO validate who is creating this User object - User user = new User(sessionId, authToken, authPassword, username, firstname, surname, userState, roleList); + User user = new User(username, password, firstname, surname, userState, Collections.unmodifiableList(roleList), + locale); return user; } From cd9097e5da1954c65c766cf4b7acc6eca87bdd8f Mon Sep 17 00:00:00 2001 From: eitch Date: Mon, 24 May 2010 19:23:50 +0000 Subject: [PATCH 004/173] --- .../eitchnet/privilege/handler/DefaultPrivilegeHandler.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index e71292b22..b548a13cf 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -53,11 +53,13 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { if (certificateSessionPair == null) throw new AccessDeniedException("There is no session information for " + certificate.toString()); + // validate certificate has not been tampered with Certificate sessionCertificate = certificateSessionPair.certificate; if (!sessionCertificate.equals(certificate)) throw new PrivilegeException("Received illegal certificate for session id " + certificate.getSessionId()); - // get authtoken from certificate using the sessions password + // TODO is this maybe overkill? + // validate authToken from certificate using the sessions authPassword String authToken = certificate.getAuthToken(certificateSessionPair.session.getAuthPassword()); if (authToken == null || !authToken.equals(certificateSessionPair.session.getAuthToken())) throw new PrivilegeException("Received illegal certificate data for session id " From 1cbe3cfca81e7754c1a311fd315974a268e2f308 Mon Sep 17 00:00:00 2001 From: eitch Date: Mon, 24 May 2010 20:16:08 +0000 Subject: [PATCH 005/173] --- src/ch/eitchnet/privilege/model/Restrictable.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ch/eitchnet/privilege/model/Restrictable.java b/src/ch/eitchnet/privilege/model/Restrictable.java index 38a989f9b..c80f4598c 100644 --- a/src/ch/eitchnet/privilege/model/Restrictable.java +++ b/src/ch/eitchnet/privilege/model/Restrictable.java @@ -16,7 +16,7 @@ package ch.eitchnet.privilege.model; */ public interface Restrictable { - public String getType(); + public String getRestrictionName(); - public Object getValue(); + public Object getRestrictionValue(); } From a960410b6959b24e7ff8de8ad46bdd136016c92a Mon Sep 17 00:00:00 2001 From: eitch Date: Mon, 24 May 2010 22:55:58 +0000 Subject: [PATCH 006/173] --- .../privilege/model/DefaultRestriction.java | 131 ++++++++++++++++++ .../privilege/model/Restrictable.java | 2 +- 2 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 src/ch/eitchnet/privilege/model/DefaultRestriction.java diff --git a/src/ch/eitchnet/privilege/model/DefaultRestriction.java b/src/ch/eitchnet/privilege/model/DefaultRestriction.java new file mode 100644 index 000000000..a24a622f5 --- /dev/null +++ b/src/ch/eitchnet/privilege/model/DefaultRestriction.java @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.model; + +import java.util.List; +import java.util.Map; + +import org.dom4j.Element; + +import ch.eitchnet.privilege.i18n.PrivilegeException; + +/** + * @author rvonburg + * + */ +public class DefaultRestriction implements RestrictionPolicy { + + private String restrictionKey; + + private Map roleRestrictionMap; + + /** + * @see ch.eitchnet.privilege.model.RestrictionPolicy#actionAllowed(ch.eitchnet.privilege.model.User, + * ch.eitchnet.privilege.model.Restrictable) + */ + @Override + public boolean actionAllowed(User user, Restrictable restrictable) { + + // validate user is not null + if (user == null) + throw new PrivilegeException("User may not be null!"); + + // validate Restrictable is set for this RestrictionPolicy + if (!restrictionKey.equals(restrictable.getRestrictionKey())) { + throw new PrivilegeException(RestrictionPolicy.class.getSimpleName() + " " + + DefaultRestriction.class.getSimpleName() + " with restriction key " + restrictionKey + + " can not validate " + Restrictable.class.getSimpleName() + " with key " + + restrictable.getRestrictionKey()); + } + + // default is that user does not have privilege + boolean hasPrivilege = false; + + // iterate user roles and validate role has privilege + for (String role : user.getRoleList()) { + + hasPrivilege = internalActionAllowed(role, restrictable); + + // if privilege is found, then stop iterating + if (hasPrivilege) + break; + } + + return hasPrivilege; + } + + /** + * @param role + * @param restrictable + * @return + */ + private boolean internalActionAllowed(String role, Restrictable restrictable) { + + // get restriction object for users role + Restriction restriction = roleRestrictionMap.get(role); + + // no restriction object means no privilege + if (restriction == null) + return false; + + // does this role have privilege for any values? + if (restriction.anyValue) + return true; + + // get the value on which the action is to be performed + Object object = restrictable.getRestrictionValue(); + + // DefaultRestriction policy expects the restriction value to be a string + if (!(object instanceof String)) { + throw new PrivilegeException(Restrictable.class.getName() + " " + restrictable.getClass().getSimpleName() + + " has returned a non-string restriction value!"); + } + + String restrictionValue = (String) object; + + // first check values not allowed + for (String notAllowed : restriction.valuesNotAllowed) { + if (notAllowed.equals(restrictionValue)) + return false; + } + + // now check values allowed + for (String allowed : restriction.valuesAllowed) { + if (allowed.equals(restrictionValue)) + return true; + } + + // default is not allowed + return false; + } + + public void initialize(Element element) { + + // TODO implement + } + + private class Restriction { + private boolean anyValue; + private List valuesAllowed; + private List valuesNotAllowed; + + /** + * @param allAllowed + * @param valuesAllowed + * @param valuesNotAllowed + */ + public Restriction(boolean anyValue, List valuesAllowed, List valuesNotAllowed) { + this.anyValue = anyValue; + this.valuesAllowed = valuesAllowed; + this.valuesNotAllowed = valuesNotAllowed; + } + } +} diff --git a/src/ch/eitchnet/privilege/model/Restrictable.java b/src/ch/eitchnet/privilege/model/Restrictable.java index c80f4598c..9a2952fac 100644 --- a/src/ch/eitchnet/privilege/model/Restrictable.java +++ b/src/ch/eitchnet/privilege/model/Restrictable.java @@ -16,7 +16,7 @@ package ch.eitchnet.privilege.model; */ public interface Restrictable { - public String getRestrictionName(); + public String getRestrictionKey(); public Object getRestrictionValue(); } From 36e29f4597db858159cd74581d543dd1433c767b Mon Sep 17 00:00:00 2001 From: eitch Date: Mon, 24 May 2010 22:56:21 +0000 Subject: [PATCH 007/173] --- .classpath | 1 + lib/dom4j-1.6.1-src.zip | Bin 0 -> 440064 bytes lib/dom4j-1.6.1.jar | Bin 0 -> 313898 bytes 3 files changed, 1 insertion(+) create mode 100644 lib/dom4j-1.6.1-src.zip create mode 100644 lib/dom4j-1.6.1.jar diff --git a/.classpath b/.classpath index 18d70f02c..63421de7a 100644 --- a/.classpath +++ b/.classpath @@ -2,5 +2,6 @@ + diff --git a/lib/dom4j-1.6.1-src.zip b/lib/dom4j-1.6.1-src.zip new file mode 100644 index 0000000000000000000000000000000000000000..39b4137d350e6bad0405b9b3aca2ea6143ea79de GIT binary patch literal 440064 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%0ArZNyr#a_uiccET<7nN;-(B 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 Z!Q0dci2%c+Q1#&NE-;_kWe5G|{{hteC$azl literal 0 HcmV?d00001 diff --git a/lib/dom4j-1.6.1.jar b/lib/dom4j-1.6.1.jar new file mode 100644 index 0000000000000000000000000000000000000000..c8c4dbb92d6c23a7fbb2813eb721eb4cce91750c GIT binary patch literal 313898 zcma&O1DIvovNf2fv~AnAD{Whqw(UyWwr$(CZQFL&x%YM7KCj<9{cnGJkF~xLF(YQg zj2UaoNdo_X1o-P_Vqe4fuMhwA0`ccvT3AVdMqEaOPVNs3836px-T1%3kpJH>X#p8= z5n)9oT4|BUm`UqCdia3rqz~A4pxPG;xK_Y3K%p`Tr4THQ!Sfx`TB3S6R}btjWOlpE zS7+k}3{DHv;nA?ZDfi@`x{n3u?NN-@^xKF+G--I|;0jW}1o1MY#le}oz8T2zos}r(pMpP!hetKh1Qbzp7GXj@m4tM9phXVG3S2MY=Afx@x07BDGl)Gz}aMmfjk* zHhIMKGHH4OLcZVS{B8k{Tzi)DQ8+FIH-sW2x3GYrmL$T|HseF zxt^>3|1sF!$?~6sL;D+ve}QfF93749|4D`Le>=#*#mxGjM)B|D9qo<&iO=7`PL5`l z{~+n#dAR-|Zu$@CzmKDk%At;!@7U@J=4!M6qG+c4*~RVzyG&x|GQTc{!O?4^46b!T=u^yIZ;tt7D)h^ zXCAIe8?EYZLcWahy0L7 zv1B(nyw26c2FLM+$MnV5=kqxm02H00mOn;t6m)P!6t)UG_rhsQ8PWj)9-f2Rd`*>} zAihdnp{8wGSC4+~u5zVBewUr8-IBYo-tsd~@xq5d( z(cyw;5jniuX2}W=63B4lv*u&mrB_@brV(PGi2734oWlz?G0_)VF$KL#F8d>L`P$d8 zmpa*4Lh7ktIWgegj~9HN^+Jkx;E>KV?P5oKg@i`bP${GXIqyUj={Du_-;k}!KZPjg zk~hq+ue?rYXsMj*$7Rw?M!7xo6N*HXx5-y~^R)FU-5}9tV6N0v`Ai!+>OPm`d0}1a z$9@3EyXyD!wD_K<%+#3uLY0Nt=wkv7ZzPmb_79M@ruABN>GeMr?OfgH;c^=71WU(u_SfmPam+wTVYtt~l(27QVp9Y7=H&;6 zXbN+Na1K|2QG#M4Cp~#ch1b&{tK%c6YefpED@6*3Ms|{zB}6_ZpCXE44yFE;#&`+~ zBR&COK5P`QW|s?pX=2AjUSB%(JC>&iau~cB;dQo>q39|pp=!1lq|Hy~(K)A9glck)63ZZP1Mj4jwc$D>BrC28o+?CNxpdMI-B%-lv_n$I z7VrLmU(_W!%*X_p{9;`l}_v1(a7#qs){aVPtEj|6hAos3R5zVhg z0=*y4G)8xADpOK(wrvG;^H&gniOWS!id==MX+5}f=R>Ti zRB2`^k)E3i7C;_!9_q5p)Fa%?<_RzkTECdLWJEOQ20HkEl_@NMa5{*nXP43)Kc0cm z=;kG6X%*%q9yONrUO(ygbQs5Ud|E9YMqp<9*l6m-wUZOg$7{~1G)jq6(HT!2CO-gq zh^`ux6)c8StsCJdyKX}y^Alb%rsN;q?S%$}Z{RfZt7|STJFMXX3FK=n*no#r@NZOl zI|pU3214sJ)3>v^f0Rr?#ir#gl*B{s0-54PiDPBs6em*UIy&9N|b^TzQEht4U6gQ`W7j zKvXSIpq#QOs@KciH+%?+d2kh?{&E<^9N z%krww2R&BG=pj$fQno|PmV-~3EO7^sXP zeOgR!qJB@}k7$mpOTDAd)gD_hZl`wE8Z2(9+=I1d*fIfnZ#siR7A@_x#w?r=>M~Yo z^fR*>SZkp6g+1PqRX)qe@STjGVC$(bXwz7(urO#&$2rfyggF-T?9VfSXw~2}Fs@9R zJ6_DIqqa3jkKG*JcXrhkl09$>-{_(_&);ci7n`hQKXFrCa(S@S$L)X6j+jMTfx}DY zob=SXyc189n#P?u2ArO8{k>H++m@XLJUR?N+8@0mS%ll0EmcWygXI?u|0e&&D`~*z zWhl@Eco!At0#7M3vYJ- z2&7M%`h_8%6!!t)4aXt~K95e8@uQSO%3Q(mj@b;W*Wm#!K$(OFRdi8aQyHvOEG{~l z+c{1*Z2{_TeC5sfvc!>*Ow4KK;`kCAcPH3t7C>b<1lita3Pllpb3fJ-s2;)2u_X>v+C3(igy|S+K6F; za*LI@^(eP_P_^R%7xwV7d&*dLQIHXWo0lR5h*#!IrHSJp_T2|#M$N$MR{eJqW#R$? z*$og;E4fgk%wIEv4`Ec!1Z2*NQ@)bUB+!kr-s3QIa)2i*LFFg?gqtZO$yWrT&oPx; z7YV*O0NG8c!%ceTz(T9v^9rlHI*(IylWc>16vKIZUNH)fumB*3yZaoqYrMnw6Dg zGEvk9X`dR`xfje9E1fo%=5F@FZaA@Y@&@Nkqhbe|la=|$J#o(3&wBuKq(4PTd-U*8 zhFejyzTh}(CNjl)hl!H*P_!wuQ_tVeWj($+!bWKKww7^N+4=e@xoiCgwT5gp1y57*aodJ@WZ0 zhEyw#58wQWADMsB&;P6q|9ehE{y)bMAsYiHDVBZ3GZ2BE`8R)<{Rt7VO7}iPyT)_DmFlEa@J4#7`q~6o;uIb~C2= zFBRiAQKvQ+1;b$#(n^yK&P%WM?X9h=)32}NXFh;}+wh3<7J}WP0Cfz&jkwvsbw(L{ zyk7jP5#p^ldNLxvcCC&{mTNIr)gjsrw4qMi4!A^v<9_m+Q9_qeyV@RM@jgP;WFoab zFhoa;&yisHU0Hvq_;4ePXFc%}drKX^N%+#F$hfN2qF(svG!+VMW~$@lx%)Kp0y1$e z2B^j`;}u2oUe?M)yC5L})$&Dly?r38rr$zEYItMDo=!q)yr#$0i zDCk_NRq}NtXzkEkZo!*6^#$}N!||Y`a^)kI^CZsdc+xZ$sIn~8N=6@-L;;UlEu#|( zv!@36M&*3|vHZ~7BxOg??($z|WtG~CELA4#iKf2WBc&)-9gDu+rxaJ{y!T$*EP^^CH^^zk=# zD;UyDC+dtW5}JEA8@I?Qa4?hlE7#dMU}ZYBw%Jr^yc43)&xirT4Yb zH&zHn!#(76{xmt|kA6*-$>E|AlbVPT{m>do}twxlwQtKy~sN8 zH;sNaH#CAa??KW_sHm>O!?H7U&Z3Rb3Q>cQ#?o|{#g{3HjSRRWYEr!}FInMu$R8nX z0olkO5h!E_I8o9;T~@Lrls!$61~I5oRAf_FsZq@sl^V*D5BB&B2g}-EpArnyyP1(m z&ph{>>n0|@pyaE4G!07^PbD265}6J&5O8R(to@(P zr*H*5RzBP!^&B|az zHTSZy>3g`Yu;NS_Kia?qjG3l(pMrW_ft|Jiwt0?p6q_L*U4p))`F|T?%J;`(qpG5E z(x0woY6+gy(o4odLmVPyr)JniMu%YFzxZU`WPSZk)Ig%qrewILY77&I1O?B)XgEwhN^My zp6|1%W;%a`X|Z#_i?51yaz8IJAw}7|$9e*d!Fq)<5iu2g=IPHeb;Nl;&eGqm29GWB z1wLnIN45@gKi#!Z60CH(%28ylWtyHzw%RdrrGzam4zFPXRb${vdI@T ziUr;xsfjw~c}5iN(m3gjXUZD^%nAGZVFvp-+u>6*{`^<@DvaY~?D-C6+c)y;EjDP= zMUV8fO9bB7TG^^wP&$GI)rO{uL`v1Kqc=bRU#_$$hmMDMp4WI~u&sYFdXH|jy2?46G z@6X9#-xD;DkW^JDoJ4fJR*sS~!z?a1M{4cfY!;t&y35+hj<^bImZSQ{{aN@eziwl^ zT;ABhI0%=&ntJWg?b2m&r2GB4Lj-{FAvh$g6<345TNeS46^}5Gi2k%2&X_fvY?#*B zV?sA}GAQY}Kkj*H#H*DUJ4EH_n&{JoIIMc3%H)j z6Q}C~3ZgsVhAL>Ox7K%_ET|6YIQSimbVx>;@dBG=;wP!i#8vhvG-m5qMUiH|`4r>C zxEFLTh4dl@Vm{NEFpHwX<4bJW-7di1)YaHAj(?%)GzCfuZtgf`CdO1{E=-`yI#dQ6QZ=5Yse84G^4 zqG|~GhSxKO3voAAnY&ETU%PJUCrL1O(3)(1wkV}J>i%Vg_P(^;%>-lyyzNYOTr2ErbE0{jN+wUn?0`89XEOERrZ35qAxg$2p(*Rj-1OqZ513|G9+ zL=1)5Hdv}HR@C!|*8-)Pdh6}I>h)F<&(<2ytVl?7nrZ2u@db}LS(@9H#bccrP0Y&* z7b(b=!uqh{49y2jSd?gaMHh&imknHdnn78g+I#v0p`Vv+3}Gx*N>1EtsDYs17=_@D zC1ez#i-dq8u~3+bVbI+8EFPJwbYLlVmm!n*dD`}^QPHm|Lk(dFm`k*40;T5ZQ9eMs zm2Cm8DIGs~$ah^Jy`%YV5)C|`M0v3kZs2iMZpboJssJ0)D)(dB3)S{F;KfF!dyor&vJ23>2?Spmymlq+MDV zqC+pz)?`c^g{ah0fzt4rccE)!?cONVvanI}fwpV^EwiTbQSYwo$@`h0*@?46 zyHX&_Rh&oA%jDd@eY8C-RI{&bcZ=^88S5G2oj%6~e_HkPOkL!mP0l+)8Ai>u<4Q> zR6blVh#FGV4!OWB1{*5;PBhn!2|^xUj{5IsC4}{3|1Ie@9v~ho$@Y^;T6ZSiNN<8o z>Wd$NnFdjr@eTcMgY%o?G4njvL^@1!)J=@w@v?>rRX9Xfm|+G0{(W*A4iN-kU*uj6 z^9X>tvOatSe3Wv{s|vK{0|G|qLv~Eh}$uB0So{@8S$So1cLuLLy$Id)D!x%GxhHjAyf5L6>Ayg3zQfp0+c?Sp{@r@ z93BX4*-j%E9us<{NG}v-DA)Q7Rv+4bFhq=q&TNy_ZjM=dj#xTzZ^DLv|KhG&tJA#_w$R7Dh1oukSy2gdaH^gU?bOVvb|$qrB#T{d4#Hr|momtHz6Zm;$=2Q$!*tT)RJ6${Fu8;oyCAg!AOyB?UwoQeIbw|q>owlQf^O|r?XfhI`6v^#0H5)PS*eS;d)m>g)40!@+AXQ^6-&>giu9nz+5PCOqwY ziB6>z+)Fa(j}V~S_3tXI!I{v{SrMn41CPIF9ntr57hI-^!cz$6Y5t|bF^v;GsySCgrZdLh@+m;j7wVdLyD`8 zeY_`Pfs3eQWBqyk1NkY3L!GS7D`OM2Ez%c)6GC8|P90`@$*z*a)lwgrHt8#o!7LXW zHIUL1zeO~cDmmVoNyA8O!g(C;ykqHaQ7%|+Lq0nEU8Ea`s`h!1l5e^~;cQn2f?GQF z6zxdD>hVrrB15BZHIc0Lb`)Q5*0(tyiG#bwg4*TZ!Z*lBc?VNM!ER$hNj|a)kC4c9 z5H0$v_TfmsLio&9_9V-1kd5{6$Q?&gbTw~m(jIxoF)>T4n6ahWwQHXu>Cj5+5`nCr zEs|p2x7;3xmwoPVb_BZLp3ESWncW&}&1#1$7_}5C7m@kAuebC$YUgY4%TzX3sN)Bz zv6jA8zLhe(NxovH<#l_roUkt1c(fCn_TA%E7$wJkMtP7rrNs|>TE2H^dG21^F{oiu zE(SAY9g-F#7+jur7}dmIm#K6;QO;76^rR99580I?Qw%ejk((RB$rD*1z@W$ai zmpbT~DtLc1z8Yj8<|cJ+`ikv_+75x3Rl!FCxk}E`~TcumP0Vu&M4*EW_icbP-?-WvR z^DDsX0zK%KT4dF6IA&meWCuKZMD&(%=(Q{Jmq*3`8GF>ZE9iI5hPG#X(r?9>r4@5%JlNoS7hs7rlMhS+3Vte%n{nEq>SR}NF zy95_h#UuJ##rBvB$Jv{-1&O_(G{H6Y=kIzVAm;N2_0mUBjUy4W6ibevQjErzooqFR(IQjKL4`Tm zdy7$|84^2$Wfex);w2)VkHoLC&%KgBAALQM{204>H6stCyYa~mAM1(QnDz55fn;tc zdyT(IqEMXv_%71PEmOTVkYF@xETuRyoLUG^fVH)ZV+_B-+1zx4;j;d9oH+YptM1HSRU62vFu6rZcYY&lWEDWZqv|%3*mcD!-c}MSP!E zdT4WDuN|%htGxI%XzP`|3x44iNr9Z9OaK&8GE8>j8~uc|F^1O8(;u!^2m48*n zNtRZQQoepVLVNcCpn9C3eBAw41TVw=x$f{Mf6Yh|CH zX|qO;JRGelqLP!G*z2zDLS+%Jou-Xf)YZ#Lfk>H7`RL0=XQwWwPc*Ew$-9R81o)I& zgo7nWQDBa@LzaF2+`2lo?bH1Y@Eh(L=LW}hzef`YgqBbw=L}Rwb0iBd^g(c&Ph_9K zcWzdA1e6k$+hGR>kn9Md{UpQevtkVjoci<^4Z}fLPhL5VQBqBU-Qov^Cuz-f)B8il z8^j^cx6o+HGi>oZZsb79Dg=nC)Udis=WJ1K7*aQm$(r_TD9|9W@=wGP-PwrCHs6TS zC3c0yOqhdAM71J+!2MYv<@Box3JN$MbZrtT^h6|#rr@DB{;(J$3|7P&sZl-CAmN|Cpm^tOhx-U?JPA^uoKhLn?1C*)Q5>}o}Jy=j|N8) zNN?$+v^fz3G3w~-{KD@}&Tvi;5Y*B;<4oJIb&fr<16@m~RH?{nJW3%Z#&OD~)RDGL z5~OvUV*z|YK4|uu^~!(i{%G+pC#-@L1EX=#gFsWmPk^DSv1|b<53;bGw#6w6dd@Xg z3kD0qQfUTE57Ml$dYL(MvFyNcHGEyUf#7EG1Z_`(89z?)2zuYqa8IJ6*2V0abNWSB zed_}V`vv@$O%28n8^HhBQ3n6V9c7fi*_5D=fRez!O^WN$W|>#n~#KVnOG8!dSV10AZ}(_d_r>h zS$0f9npV#cUQ{6=RxKe#D^BAePAzU^XryPP=f}VVH2$A=AmUVXHNd|()6ug&+5KS@ z@aOrz`vK11Ih*R)|Jk!Jvj0yO`ycMse^@*JB?0Xp?gM^)3mpkD3t@;Npz<);6$5ob zpBV!kj-NYmc@rIu7L)WjdH<151eD-7VjyqeAZ~#$c_2=RCk9jm^!6jq@iXB=E1ud5 z*-Qi!Fd+0xT)lLZ-{J*6?DI}hVHF(%9V6v;3dRYi2&xDuDyRy4xG#(tBSQ`FAA!KY z(d3D@lm5_e`9}f&-YF$$WA&dJ-hV{K1L%HM!kw1W# z1Rog#o%S{#1r-YhIdN10HBl`&Lo0FP2uUq*Y-Fr&tS>}JDhB`WSYMF8hy(qLct{Re zj^iKFA^%96^3SjT>7x688s7dInZG*U{#)Noq3XL2wld21lyzPGD)b~5ciI`Nv{_xd z5lC$VNZF|*l=$0-AP|s@vrQaCdg|qb<;ET`UP%eKGCw&vCHdGqavDwH-5r?sP4wM9 zhl{ZZTfO3>DBr8$RqJ!s@U!UxBG0M`W70@C#mpQ=|g1jp5mepiXvjiFAR;V7L4xp$VT}? zW^kO+yLt}}647WAwWvJDrmrsfPT1n{De>;E z&7(?cy`2nl$$YCcoor>L2AuaQs862=S8SiczYcr(mbY6e@-$5H~#^K(vBT?=p>Eia}Xj8~!PxOAZlxj`F0C zW0TeOims`3JSczXsL0j#zHQwu8Gu$vi+8Q_o2YJSpK+!Gv7cyL93Z~BM1w` zL(Iw|c}6lRVw&O{#BK~VTqJRd0~3&#(mGh3O__ei@t}w3bbzW!0K9YIE?!(9Y!7z{ zhhaROqCd3`JWen5fQ<6x6OwseH8oCr#frV2l*38rHsuuaio>3$VCNEUt*=(XBJa>P zw2qD#qR&jWHTorN#7rS&UJ^w?jMEoAG}^kym)A%b7q^dXTmA*)HHaf>l`(LgLt2{TpYY- z;1`c-6Zxq!&{Ev#a2bgFQToohQ>%<(FCxLVO3eg|UOQ9L^ooyU@$b|FKRwjWuyK)N zI>8bu79%_JwXziuyNzG-y-8smfd*zP(R}7FK-P=4SY0K0cvmyOehK$qz96OsGT>HG z(?V(vQeo;0W-EQD_gkPZ-=KXc_n*7^7qXT5&B6Wfg)M5upTpehW5*$*Q6%K4&=Z*o zp7)ILZ}7o8TK%xvdl!Jw)oX^^BS5(}eZg8%)>^osrNbR;343PN>I(|OtkxQ83wl@ZvYNkp<~*Ksr>7ZaXfuboR$ebeb2H#2Uehx%t^7Q{M*_CIu&GL5bWg?ePj zS9sm`_)1(HfXiocQDNjC2#@nP#~OJa8X6*Iuy$LFrn3m}4PwDUtzWzl#=ZIOxolik z(Bw&{=EO5B9)!+RwI^$ci+tf{a(d_6lvWx8RqZptBXigY1g5H%8RgG87K|MlFyZ>&}Spz z_zZ!5*~GZv;`hN#p7@2M9d$J%=mI*AK!FIQ_~B|8Ht{2Q-f{z#)thk671g2Z7JApv z^E1^FCi@t&ux%pfe9i(?HZHh0rf*&;{y0758g6PSy87KDzq$h4HI%-?gm8B=8q9Ma z#g7ar(Sj_!)z1+-J{4PVl40S3G{ye_*fr~;li$qS_lQoPOUL|7a}r|uGS+*2CeYiT z-=S~b<=2msOuZR8Q9th#^Ij%ip|uG~zuimA9?8)~)sdq`(kDc~f`E$yY_;K^Eq5CB z-;E<LbXcK5?*g-&EnC)(*F-C`EW zrjOe0I31z~Ha`wr@9oH|HAq!J&DFl{EGUs4l(@4 zN+SI~NBRG`lSK8?2k8;VM>qaq?jeV{a1rRIEv4BSywDM|ktv0dp~d_hGUyb<^1&Kt ze0!XhY5Qbux*}Vq^*w@<^%U5KBNj5aY`R2q?@?CvcY5d3*OvBtfw)51ZEpFYQ#&sxJ~$?%fRLQ&sC<#7uu}HJr0cR_**Z$C49OdypUZt zJeB=Gk&ck$4p;S+hp=DVr2C7Y9YM>TFS&3wJEA%tYBAlXeEHD7+P%YmAe4d>(u~_3GSXX=S$OvQ)n# zLgtv67}@iEj7erk2<=Z1YwkG4I!#z%g|gB#rg#m*;QAFkVkmWKHkUH-`H@LS#jP%( zi{J3s=x3*eQA>%SP_z@wE0^!BjT&Z;@B0W zI!UrJrs_@}1exMy91!Og>S;BmK?_76{-LXnc9*bHU7ny?1 z9I;c9rr669DIjif!OcIo#vWArPsbBVyD8NCn>5wxHiGoOr257*5*&@CAy~AD2Ut_h zqmGhbPw}na88uR45t~5DJC^Jkb4m}2mBWm))jEVYiVvYMc=PUq3Pud>K`Xl`3#3Og ziqbTq&5q{sLE<67#_)DVF00%XzX={gFh@(?T!{r#ygK$0N$eJOq!T}OC=cccam!p& z4c~w&a1A8TdRG+s;kR&?Xnt{v3Pnkrlw1y)zn}b6W#y9z2QQ7~AXKWTbxj40?cl^Y zou6#rV7k~)P&LrVrcQy#k3n*7RD~Z#trDX!BWy=Yc#9Ae0Hg?*q{3H2bZXcu{ekMqv` z#pRCTXi)B_t1ku(fkB3x@qmo9ynIdFNtYotSt zpekz=OeAcG4TD{)I_OwP{1dxuklx|j5Ms3BEqOc7FTnRz%jAX%fSdFf1CXfx`kTFMTQx9DfjE%WdzPurHPPD`FVo< zRkee+LPAVN98lG*%D_@|KQS2vF`p43La3;yh*f;GM*hQctKoENq|4VEE5>#$R3GD8 z>4xq@v)81yQprKM6b%ku^;>V~M1;Z|3FSwJW*zlxd@t5%Js}Qve%@_u77e8mH5>Ju zGUQofKL|@d=mIA7cx&&hphg@|fG*Iaa^bMG3Y2tVrE$G}TeTWCdBuWhF^Wluly<&j+|Y<0Nmgtu73KWUp#_Rr@Ng zV&U%!VFw&}`gpcRYQ+iiltS9xJ}#k4QmdH-%V$rV5^m!IsW5F3-GCd<0@_I1`2rdd zX!R$M%3LEPyi3sl3J(3)ZT#Yw(KW<=4%Rsi!}kTK*m{onCY>^*Cw{(JafMR|b=onoAQ?gRQ=dD@W`@E$@gMT7c(*CIo@BdDh?REmJ)> z_SwF{?{A+SG1EOBbBMQYQC2s$9mx@zGeY?)b149kuRyXWn&@vd^O*F%Tk=gKoq$_R zW^Mc4?BMIpD(88lsGnes;2swsu4c?!xx#N~N}klw1ceNm3#%qc9T!3ut)Ueo;FE0! z9{WLV%lGOBoKR+4)V9E1pVaPvSdGN9ren!eh=f|B&UseO#is0?9jp^UO^XoWbo=TI zZi4uR>U^t^!fzB>v4SHYs6{bpM${k$n|{ajsd&at#fz=`d96&VlYRm_#)lVM9yghn zhB6w>;T`6457*>cppJ)|at3K~(g~h&N24HqUv4NY;F!Avhnw<738XusiGQM=HVqc& zsk#d1gd`EFi11`yX5E__(*@J9#YU?!4%PCpUy zmF4UUmcGT97t;?a7p)M5oA)_4`x4Y@6`wt=cru`Y-NDaJGL$PFXxHJc{V^pUaVJuz zLeZyWQps%JGBQa=2!@@>)$8UPZIKKQr`uckr7Zs}T(RYYV1d5d+|VAbHm;_oInY)Q z?une#Hf7KW^o;y8EpGft@qY0@978TEiuwH1?cp=RIf?u;xxlrHdz>5%rT37 z=Lo$AMD(svN0{mjXw)vZ4xQe1z+wT1Sb`zw!jJx0VUBcLaJc&m0)4W)t^V{)==q97 zBs2+~mS1j?bt;^N3+gLauust%3cegz@8rF8v(E|n*QK7;?H;(^(fHk@U%0+}`*m6L z&N#zeCGYM$_*%y;{M6N#{>%s^554I^H#F4!>k*GGGl9=)K-*-jc=S%P^gm_ejM@!_rI5@{@!H!|0#t0tu5x^5#dWEu5-*CY26;TfuRxtemzbgp(uBNfY=Vv+#Cy=FD(W@TXOl1vs}Bj-98f_ za1NEcfm7mP+F`SxV`)Op*o%j;rNV5`St}Q z=x%Qq(>wC^lu`JWYp}X!=tGv3I;!?yP`Z_FsX$KicCo%z!{ltwg?cHDJIgZZJg8pV zc)QO~?#Ya!0_NpGD0CEJQq@>hB07v#3O|6M#`AXOHIBXKSwDdxuW-|BHx#-~5}v7# zi;E-b&^O^fZn!zn9l{2RJ^C63l^Q$nH`EP!5ijN_(3d)P)41UhDSSpBkT>QI=zyyG zaT|rySEOq88PZ$GPptBbf9Y}^-@?Ri>A!SY&?{iik22KS=A?hubI<3xLK*AavukK;g;6BdY`9bdJfDn z%vX`%rOWq|lXpO%jRJU9`2GpzggrWX<;?){6d*gmX`WrC7h?p^Rc*L4;vLL0Z%+}& z=U_DZCNzq#-Vv;;Xdei+tfge1&Q-VfCMaBJSl3Q`& z==fIHwCg}YTzPFX4|472%umip31M3>^i?@U=(BkGdC8z=iJ9oZ6-ABarNI$G53jz_fL=OF6(|+TYwpGnHfkQTj(=E1xn=BH~E-+DLZ3ZRb zvd0JwpL@Y#wvLrlcZAOnvGp)xg-_Y1^+bx9+guU-MO~X85P%1>D;w_=mgUa6+#O{2 z)P(-Q%^9kJ7Q0T>DgF+w8-3$+G0f8tUkKblC9YwC^hF>W7pfcZu%gv>>9f-5c!haIqhjNP|8W@b<@}4#~gBDmCy%dG; zi;OjXu-TA)D1Rp+-~&;Ds0<5C zuB_Zs2l9;S$?hgK4dbg><41u7_1)Tle8cqZlF6~r0(p@4RQ1DL2IwqAd9VWV3g+ML zgeCXFn8WmMt90vN5#O%iiC3j}jk9NO2-@}0fyTN-EsH+#^yY7i7JDZ^`E(mQ9+u~A zr|lSwNoBRIPhD1wrK04!{QpMyyZkf{kbE)FL;_%FAMRdew>#&Au#hB3NVGn0W0o-o4g5Socf1_?WhQBMs`ABek3HWeak0magMDew8)2*c&YK{z*28N&r7LSMQ!xP@$=sO#O}>yS4)(Wrt7y+0^I8g#Kt z-D&)`nKhzq(6;Kr{oQ@Bk`DJw-z-$G@5XDoirMRY_o?hpc-76W~VONftT#sXsuU@UT6(wIfJEpeE61i5j zyZXVSmhb_bF4V}Ru-KElW#`_EGnc0KR`s0H6ZBju^LzjGH3gn*^cM#vN&h4IQQhg% zqsaX1dE|c8eKnSHXL~#|linK8a(pDOjI~fE^nsvZHI1Dce+f27TVFZy!3nj<9hIo< z5$2~=Q0qffN|E$5DhP$0Z^lLEBB&C<9pD|u^E}HCl?vryRcFSY z_4cNzyovO6LPw&~x*5wO&43_vbPup7>EMJl#~!pXro~&|*})t|iY|7(w3iTXP+~Kk zvR(r91X~n)sYug$^b$A7k=65>%4$GNfv9S`$bud;#991jGVvgn5DI)yf%nWZgyIGe zh_pmdkqxLfpr40Q;Wj_Rw@d&S_Ms!Z(QFxRO2S=YuIR)v1L`PhU8P4#to-?dZb2bg zw+Sq+2nEwgE-?-WxQB1TDa%cMF%n1x{a&YcXhA%&Qh0%IFACJT#SlSsorzRNK-AnZ zvCtSGiVz%i#2+WGl=-Hc*+-7;)aGf=9F!vs*G% zDemIV!+iiR79hES*h5#n1($pg0iZ+d!FHNRHzcr2fcN6ymyNZ;;t_rA3Tq}88mdmh z53vy^plEA-L8h}C<73d62`faM%FNKWtWJqH<=ryEM#}2)kGQ6=-KjM zzBB$I**;yp%ZV@|)L%;c<}FCR{qe6Z%7YuZ`v+hEfY3kxvGTts_W#R-B5v)hXK7}r zXkcn&r6=raU}WoPW@G(d%^7mN62J`bUh2!*+U1M-f3}|D8-LMmQGy@@_U#!JK0C5m zD%OdM&nxha$M5vQ-Q*9fDnI0aA*FP<-5*WHG(RV+0$^wUz%m_PiMH#3*QUt-h{^Rx zni`O>rI8wCx=x?~>tqmgHGmqOkx7hw9ycf4^iMI>=PaEJ#Dyw8`v`~rRH8wDD|I5F zvvlIv54`E)n8&kSv~z1 zp1t<1OZe%@a}{F`v<09Luf?+Zt zJH2AeS>IFG09vX1TAlW)qWfK=$VgMeYqguA(X@r9uggN zt)LiQm9-KmtfoNK!{*JWEARhPxU=TE_@RHC8#4Z{W6S^S-0=TEl4T97O^lU)pkzf8 zM_Vfw6NmqN;$&4VH6<~OZR7-;1e`F)h!N=KG6+blDpz?;(EAX1*n{Hb=eVeSeY&(P zdGl*2z0FqMH+4NP>(z<|^VN%n+Ajs`7in3#IQ4Tw#>Z*)-K(!{Q`w(_-OBfiYa zjCS47$B%42;y{Bcrd_4jpiN2gc$^&aQF$!=;A63%W3e)`QPLN0jf=FV@n}#} zUIm<**=^Ts^p71fq^kdKlYo&Wtyb{~n!$rJp*Hj}qU9=MS)DYx6f(17CY{!w5r_=U z=Z;EJw;|9etaUx}@F?z$+WmP}( zS70^R^cIKLGII3^bZ;7ufM=$yvjm+SaB2w4R9Fi z%m+pi!b`_6$1*X*`uXRFm~zr7{M*BthAa<|=rZl(_y2AjRD`2?ks^4S^~+Qpl=bCX zJ7xFDXKt!*b~l_eGS-JAl8xB)53RnA453P=yTwrY4{o+E0CQtGr|GCC0MI%u7qLg- zv})u%Yub%f7VA}~lBRg5y%nq+RtGD~8jTP)l|(W)P!w4I-L z+{)hN=w0AioQsI5E2q17dWFsn`|Ga+-%5`DZZ6+oP!^h;OWSzQ7%9urY_xAA&TH@3 zOd5>RE8}q59#c6)3jN)jI{^z`AO&zLw&K7&I4+OY7Q^c`bPTqu1dMhwPd)MKzndlUIPHkbwU|JLD1|L2kUKU7p~Gp$z=Jg~nFs;GiGCzi1x({G#`;XiW@I#)sS#y+ znwFO}&gJ&#!bk|NG*9b2nwHkyEgR1~A2}_xv%H;a8=CJty`HBtBrphkU#By--#2gB zUtY6spgXUlkMxKNaj|XZ%XHf53BEFAt*}Sesr?(HU10w(K;zUR921ww+ zST`fu!>%7LD9Gr!BiDyB{%p}bn`_f#cFa93w)IAWyO~qK)0_xgIp6EU!R4%j**RBuxKQYUpQ$b z3+dF%oq=3U98jG}P1PhUfm!EPikU`A zi0hh3S;ldnT6W7Bd1U34It0a{PeNXPFo+lBTWk|O9Cp~Va;KSZQ$2KK;Z-~2LSOYX zCuI+-D}GQfnWtxYFAkkOJboxvI3#=1**{3es(qOA(7n!|b&HN%jbJt?pF)2S zq0_zHXJDT!93DihaS)0w^V%d3W1A>cIZPYL{L__wSfHONY6}$^G$Ikb$=aiSm^1>2 zX2xsW-@O<7l`*(iQ9Ly?R!q--f+Y)(zA zVoEamCmb$a&f_U|UM3u3%{bGuxgQ%S*o*pRh)4qUx)oqhks@ddPb zSG}-QSAD8CPIATAc+L6>lJ)FR0wm5EJvbN9e1_W+0bbVV))t5cAja03C>xSovw%8f zl?gyeohkY(-%S=DZqjmkEMHGq#zs6u>JCZ8Z7>8c{G9e~AxD}hZz9hWAKg^Elk@=m zjvA)qBiR-lNaaA0RgF@zc%hkK-R~quw3f1yAXQ_1E{?Da=nOHQ!6uwc`>tcwxU zg?fZQ(gxrnfQ^M)ho3aIm37IHeL}Zzw*(#HDoSA+ZWi#X>Oz}K2)9Of6%N48mK5CI zvFUH$$4~y|V`nHY)Tk4S$(9G&*U2m%VpNkty6d5J6x5&|yvNWbDB8#yBE-is-@h`* zQ1jVhQM^Bn1DX<<4BmvrpQ7UHS$(xm~undQRuQ1y-Rg zrGX^dYIrk$PS&AOihU+p0_mOZ+!s#2e2(^>A=|p3=Ir5jV}Gya)DuO&dhW52|A^{U z9NAm(s1lW0>0a7YzO!zXpCZkd0LG;^xO(A*dn-698Q}c zU&7q@My=|5aj?y$H_QHO+w;T4U^E1FL3&h$ocjRLmni){b9y5e5d97d`gL|d*gM1i zAj7pg)#@j=zb<%Mdtc49E%WkA2s#Rh?z>~A^`6~NHqt8~-5G*#leo(>kl@ zGUx(auvbd8696vSqq46I+yniVyogk4_K9s69>%9e@C`ejq=!bxry0+e73}1WI|tJH zyq11s3tPJH;Y{>0jt@2~&Xl35fXD(liuBC@moy`xWrvk@%HEke6B{KrzriZAflJ%*|_Bo_C-#6`JaK$`LpEnUM>W{2#F!X4-Cy&?OJp~BQQG;#Y+{@S7c(TJ=2xZ4(qMVIHk+s1vF~uW7Hcibs z^=7Sw+jVvh!gWj1?Rmlhzc*mn_EV;1DF2QI3Y2VCpM$($=y7F?_ih~N3&DDJKQ_u0 z<}67{P<1eZRVopYyH4u3MSR3j9v?ZYyF8S$sX3sQVo-SM3din-yqJ8en6PqK%Ev}p z)cHlq)_fvxY$3(nTHC7fe|ML5*}nWxLq9?G_L=?-gj7tImxR&qqzE5gE2eokGGWGs zAd?9vp+~LU(}BL4?~8+&itd-2cvqY>dw1`LS<4=Zjx{yKiqC_WE)PsTh+(*u@_Ne2 z%dcL-sVEX`s!rsBc-Rp6gDomhRz+YKsTExbmwG?Cjk~wZh zZC)7U%2+%ZRkT7Ctbah}8tQ1$!aC(@%l{FcucU*r+!VqNj|7=|P%2Xdfc|^Z*gIiw z`$f2TML*B$A(db6#Hk}7`~_%>Vo`@pr4D@W-uT=GeCry0>so&6ntk8=c>*fe1l-Z|c)rr?7t2k#TEni#nH_R0VTIj=s~(>Tlz4OD-4lhTyR0&I?CZ z4Ak_)4FY~x43?Zj2u!CGS2aYaxpvTE7RiCd%BQM1`f~H@%`w`^jkw!_K&-AKN*Qfh zGt`uRS@0%X{E#U*^)C@;tmZ;o%*a#FfBVIPJnl z_B#2B-dJ)gBS%8El6hUxqc4k^30E6g2tB`>x*m0uQ(lgp-U14bN6b7M$yjs0B9@oo;#5IU-n%$2823aB z;DlJ*X4!!`l~G63iMo;{R>xJA)B>&ZgP=d+g06qi*#Dg<9D_-n~K zs}T{p5|Y}2rgHaP+bpejmo?a2bp?04LRMTVUHuEc5v1E1#Op{Mo%^jV0e|o0k!E?O zpeq{tsOb^FzPOlGz(bX7BbcyEAc9vEnzxGPSS;|Y?UB|K-K|5BB36d%*_x1uC(_VP z=nl#w8hdC7`i0FSAV)M^vMBY0GQ*`WhR!21>(`M)@0ZoB43e$*M5F2=F=F*#XhKpO zdDsN6UDTIT_$B)AP$~Mbu;17$g$CK$xLo!1!QA6s;!hwQ&GWxYb_->dMS87N=5 zo;h(z3UD}bbqkoyzW^Y=xLJvsWS-!3g!zggx}izj;LjhKJA!Yxc;26HsIK z(c|Zc%sa;OK&0k&@`e%E@kf9j>4JgInxh4#&H*{iW`SSG1v&LBmj>O0-G&Q`i%iD8eL?>;-3y|RhX__w8b!s0KoJJQB?Sj93H;4wuEO@IS^dK!t%*Ei-NKy%Nn93sA_~#acR(8vE6W@(9ur~Qed9^ zYRfXqYr4i(++J|a*9@(eQP%to8!i(rGmNKk=oWAU@bCrwSH<&x(x}>kO70Lch6ndf zeAkndV?X{g%vQL-i7%O)d=jGeAO@eJECZ+&`K;a~{96K>{$@MV6o z&QGOY3ee(q62%4EPi_xBY+1j;5PVC%?t)rHcU5t7FSV6o3r=@wgQ!gT=bP9vd)O#&u1 znl`AemQ5*)Nr7ksoMEZ*T#BX*8|%DT^gxvb@7ccAnqJ}a(0b-+eyutRq1rVd<>b!K zLF%{6p@e%a(b%g1YgnclZ#K57;+>`u2Pj2D$O6_TLL0HjZtACUgFcC45;EE48Cw^A z#mqA_Mcilcb0o4PZvr?o#<+F_X}fgV4IxpoC6i4{=-9C{kgH#4f|XoY|CnwgtJENH zsF!A$1r9Y&03kiN(p+7z)Kj}DUj69q?2i~Ow|3&UfRtPe5jsQOAL*P%w^L$usj*M=(Z(~^9lia~34rbVN%rHfw(xSXlD(*B)=P}z3R z%aG&cw->y_5SyDMudA~Xa3GUPO5K++iPqR?u26o-MO=6BWH;5Rjs0F9oU|wNW8M+n zpEDj=_Yh@98>tkDzhrwGr4Fxzu!W0))zM zKp?MX3S0J8vyh8H+HIDP|I)OB{9T*3dWn|8PT;g&jq@n&tXtmUtbpfEXGOuFF)Sk% z9Tk)5;91m=Ex~Dz7}q~P$yb@YQTXzNU0{z-i1H7%>}kIICL^B%|M0k{cU^Pq@HP^yHFYa1z|s))t1;&>-T` zmC=(P)R3!|&vrFYzlF)`aj%W|Xa%p#dl5rs$rI-ziAa>) zJRuasQL)S4c0aR__5#MJwJJPY?7ZF>a zgK;l)r2xHtRbWhKchO;qEl0Nrf@MxU*(vk_lLpZ=&Nq~9c#>hbwIBt&AW4%VE5Wjv z?q0QpM$NvFQMYgwyOK+Wr^Zq@xp~w5`m6X^VI$K$WdXd*2w%mc)i|}WTdQ%B>bB*| zA;HztMdP%aq*ME>o266x=u6T?s<$@qfW&0XUZBm*t7aoG5LXM>6sL0{S2$JhqJuR_)S!=b1|;VSz+_`x1HrD*2E} zXh+)(zJnH=8_i&82O9%w=J)flovwPCjlP^Jit%|KY-Mpb671 zHFlH5a3?MVWV!rh>?LXp3p4U#fDfG(efjC1g)ucJ2R&^g=Gqh@BP3M8tm5nV7LDDo z3Nqnn$skz1%&oTEkn}%c?o=89A5Inlmy^f4jlve~4Exo5)HPd3;D9u@mZ7uK0}y+w z--5_qo=>zn=mU{h~pOQl7A6dq`6B}oukU<;Lif)tc8_`Ci|a1_)@ zApu-IPDUNeCYdzSzxTCs3TgA0eDD4E$8N63f>x1wF1I^bo-^BytzTcKA9BA$tcvVu z0|lf_8GXt=_tn-c4^Qw{CPozHilkcc@lqS~a)4h`ZodDzS16#HYDx4riI2 zPQ-EBURzgItZDs%A%C#;Oi4Dy9uewar+QJ#{KA-u#XArW z*`xl5#{CD5*1Xaw;@`Wk_1r0Au>P5+F-lznz6;WNA=$fV`8xkAqo);(7d>V+m`d7P z-d@TYB43rgE2Qo%fjE?L&hRU!$`FGRavyTA@j3*d%aEg{zO99+-AY|ci*>XGMR}AG z#?q~fI936d+%5ql)7Gc<{+$&aDPyrdN({zZbg!!aMWhrK*F?gAsMJ7YlxhS3R?Prw z$+f~r6Ay|T8)z;#h#a!zi-!`9_q5FWv*Y|Jl5%JrU|6|9Y$*!BPG|<~LNmdL8Rf}2 zMRG+aLD$#5-U?$TTv*Rts(MaakAN-?GJJi4pWFK93w6jSpbsC>RF|H!h`5V(nwqZT zaq%jx`7<|}nuGT%f^bX4;35cHB<#3R! zijOtpIQ&fu)^S40)_4sW0y2Z=0`SDLsNnPmW7x_KZ5GmD-RVNO<0Ffzx>Vv#*RvJ5 zTR0_Uu%VVC_7N}K)M;Q!c${yDXhWSxO2Ua}=*6#vhbd8U;yu!1zKPxP(fDA-=xd|> z8yJHp(YuPz^qg~vpK+Nd>W#xkxh85P?!97h_6-(wOcpOSG(3z?!<_yAP^1ti-0(Ae zfO|hD*7u(jU>-4ux*AGa6=bXHX{5lc6E_mH_2q8 zP5jbHkl2`OXkxJjh_}v>SSKPuOfaQ)6)BN38=Sz(KtmBh5S39x5D=g=6%d(7X%^7f zsB9r@p`)NT-F(n_%y_%*{Lmmu0AJ~RJ6o=|oV%PaRxevGla95#Zzz7I_Wm)phul~_ zmHVDtz^^?qlx~z^_#pQ}xb%l&SUxyIasjWD$UO!7W>`Kog;-)045+3h-EGy!x;Dd6@~^H2{$!R>X)~#^0UJklH$9db6`& z&=i{0v{cu6a zr)#e4dee6hujS^X%ZO1U$vg_rIeII{Ie!O7NT|4ZO6RV|-ASP2Lu;lTYpyY1F^g5X zQAt=X$BdIgWwIb{cdF$PS(`RMMuJryj<+?d>PvbH(U)6XN0~v<-qa)KtHP$MdNLV_ zBr+y=8~P(glvi3e9;1O+cw>e&5M@kVu`L1{)DEs@1}Z47ifHNSSw$k%2{2ZN8f@St(O5vOmBHf*Li1+kma)-)$j7Zh98I+|jKv(}0KKw{whQH|Yg;=N2wB2GX{Ig;3R-Cferkyu*jSZx3t1!&{nF{&@_Y^AHCd(ZDd$p zf6+ej0o1Twl|_a$E{h4Fxd8Z5iirDkw!W{TZ_Nh$n~G1KkD2B5$igH59u2#UDV_bL z3MnLu2FkJR`d_GE%AltG*7uk_m5@$b^vu_|bE+|pSA*OdgCwKS$Y~fwDl_n-&XNO_ z>&}t7^QrW-d4Yt!4(0AuL`=r`$(H|4iloEJyQQNDp~B77UoncRG0v!SI&P~davC}< z>~GiQjxwow0|?I)mY}bT77b%0OLV_73z7GEZbo*Qfvg#_DN_-dtB|Gs{hFezL_&U8 z$uN&FuMp=UW~)LCVS?y=09YwH!shi}RTQ6f*-mEr5~_?08WJnM(PO;rk1L391=`eh z<@H*+?6s+{-OR{Cw64e>z^!}#yxfhpsI+5p3|l9F&T2AZ(=?j{tmXNZ$q2>saXI9m z+|;vzhclts+|1NTU6TMo&LJp;Y+(qgB6LeC;n}59hl(|_^Q!NRsRzjotwV8TM463q z&&K?BVQD<0+9*FQ!Ov|4|qnBcsvhd;%^AYGJ71jm5go3yPJyN4(?1pF2Tnp+gfm4k^QB6qQxxOvOMv|!8@J?u zr>R88Ji6;}_*jw$c4VO}Qq{Q-^3KdP{$P?|ehjeuYLrRXKXSy{VZ6T;p0E$BQsmW` zPN4I?Jluq;kZ8e}^87f*g*G;3wW8b2)gq5G)Q!TL4{#!q3W*&F zSnJ?FV6`LJ@6Y-TO;BPN2N+k|!E6S!D2YD-Dz<8Z9gAJ8t#EhEBvZ*8R>cGJ#;*f2 zF?KiK=y)@xRd!4!!5`ChOqt?yGmrg?IpS$T^TL)M;z*r_fSO+Q&PVS2AQ9up5iQHb zArB#|zHH#=@1~wF=tdhBJ4D^Vsh3A1K0Z7e1P!kaEH_#@wjr`VmV1M*VPtIB1v*)$ zgQVlj2Ksx4uR;D&cIE!H2cSY$h`33|1&su~Sr-;zboI$}IXi|f-66 zTTFzrZnXASb~<$x4_WqX_^a-Rk{>n7W}*{*IJ`Jr9=vvP*tJYz;6Yz#Ul{G#s z7j8oKQO_BI+SGf<0hEkblHT?W?Od0bF59&&5DEgZ>ATwv7%x+0!pY62Sg_!*6?3`L zCZ%i*JaK>9ASO}5fu^!=(qg)+>hv|g*(lhllt5Y9PRWIf-akukKAb1KOVqX931;t^ zPu*RqF23H~?73WrkXdU#I;97jLsyX}Ky*pGMAz=lpX`e3L|xn7N-xC%dXwBZyoFKR zr0A>?=f5J;f!)IK^&?{2k&^Ns%>+t{8k6VK6uB*kf;FxC^h(6UrVe?^;Fo%8#+#`P zX4>f&f+bxz`>i0yRyyN55gyS%orW&lwAiwuv>t?ZKofb7^)P!}biGg5dl{0w0(5c0 zj+n2ui1YK~GPjH#p6O*1XRRVukSakn1S~&B{u8u zg^s|>-iKxDNPs^<03cyF1Ytk^(gLJK$RE|{Zw!LDMsqlI3EN75H*R;e22GV z@re=ge6AQyCZYg$O5ty_LTEC3>kzPS6234(#pVoAZbb4TF2QvXN1y|^1LvlC${}p! zN}vNdaboFGjZ8JW$AXqkqFw)qF^&?k5d;t6jqqd>#6Fh>QTG17;-oN{7?e?Sw!i7b zG=Zvk!5DpHBNOdLS9*TT3^?@fkI0Q-wHWTM#sU^VC(6W@d|z#odDYP_RyE}_!$q;N@x~2rOJj-6CB1gEf#2FpeeRXZK(mU@{Td1i?ZRtg` zRh73yO~MP0YnH2ZET&e$yaWqgmfbYqRzXx<#6WxX>s~dHed)1vdj3Ld zx9zX42)HIwZ%O3!5B9d27SUbm)LwC_bZc}%igi1D2D4FE>4LO%1RfY+CyGW?{6L|? zkL*Ueah71ffeaz;k2`@FQ;;3l9ETP8t}ydub=$|M*ss+}j@}J-MHLdksF;WTn=g@P zGuVl$9F?~Q?;IVffNW8q*j`i%z>&($;b#`gJ(=!`U3es%*o!s5!n4OIM||~MI=RI0 zcaG`X96xXAo2KF&i=U*RAF1D}{6wy~qljU}+pZWmgxe~YU#;}2F)ups?LfbegRUPC zsOtMbF$4vTjdo+dpUv??L zbH}0PxK#B}fKTQ3?i2X>#akx-!mVJM`YsvH71G$9r$>Qk0#8wTTgcIr@1f$NvI#{; zi2p|MhN|Y%K4~>`zr0`o#MvQrjIQG)g=vtC{%;sbv~8YIkPhaISFAURD^LDZHZSl|^j;l!QF2o8~<`^|wO|H2vcn-HAi!8|I%&oW2;I zoW2aN(9wSY%a*B)y4N@OT10A7W1In4iCb5P*YR(t&>`y!)e?Jrin2pxl z5Hh*2OnkLH^9~i7?WsFT;Y7-ny7$xVKHGdh28-(!Z?Vdjz+I%eUIoJuC8UF!~RJ1pUqMDa;c zlr5Tsjmw|i6zaB*LOXZL`QqeZJ&PotW1EXa5c0UZvTPjCC53k)qP!%{olCtk3H+e} z1uILl=jFM5x~Dca*sn%SXCaXbup)6zm>z%%epC$>iqstSB zsFPr$6F-6w6NW(80`bRy%yVAD)gLrvyP7dAwB6M+E&x0-vFHU>P3v{ZR?QK zVnvsTl4d=xmnbp%*DA%2M|5TC=)jc88p5heU#Ujec!gPTL|9PYTV*0EU0TGmIsSCG zl!Qg;P*n+z7A2fU*I$<1(#9WPzwXvdd83J@P^}={*F=?{T}CwO{$r2UCd&LG7nNY4 z4N*w^w7?_zR_JEu60lup?;Z3z;qRdwu$ro)i#Yaj^qJd-PAZ$5R2$a)Xte}Y)G5DW zE|tB35R=J6k8O5eJTDgxjSf*7S$93Jly^fb<^2MPw-_10^pT>+(#nZ9IRN^sqbg_N zkW8y2vE;B_iauq^K_2J#(ku-0&KVQwbaLzUy?lozb!u{Wzd7)CWX~TJxMpY`& z7^$EaMya_ho&a*it?^f$ZbAru086517Q~Qj#Wx~tVl%hCTR>D2PCD$6 z-a@(tQ?9gFYwdQu%uJjtKl{Ns2qxiQE3VXiSd4^=!ohNLRqW_z3}Q~l&MeLI(Ok+) zC%_q99$H(?>T%Fh#`$lqH{C+)rBY$|ZWBk(u_~O|hZG9Y1;M|M(hq3ub6A@2>v$`0 zD6{`ii^cX-FsH+AbIWeg;U4)5?0oMPUXP`eNO4IwDg=6YZCK4g1KH4rEhOKvFPK_t z3ZT{A@6l0xhec7b)vW~KBd4i6=0^=HBYd!;Ih$sGbe+ zBMDIl8PtuHjSU(wRPKqo)Q6?+nfSg-4j8^`4qQG1hmD*l9p&01Tn~AGnJbLbEF?x^ z37I?&%jo{h9OP1c58M75@m2mrmn(b4$9p;aN0l>#oOGz7BV2Jn`A5b~aTN=7cCSlv z9Zgw9rBRA8r_*Cc@Bkf8WeGwARkr|*cc0um=|NFrrvf5OQw5|xc|%CVXS}9NDR^R^ zYvi7H#DlqVJE$BVrP6dVhcKJn)KQ^xG>==2iBL;!Up_CO`iMFmR;61=ODhasn~Je# zj=LlC5X+c$tU6t$+?;m{P@)<&0md?IT?mIFL2>M&_A>@vlV)n~%Mu~Ls$IjVXe$5a z>?mYcHO|0tv4(@y2-PiA3b)NjKVq=vgVGwA(oH?$Z|4%hUCw1edOu6)R$(c7TT*-+ z-S0THTuZ z3z3EPYNsPWlSn~cae(Ja*01jYIB57u$OZP*7VghhI6+-XPs*Iq+~TGO(g8@fn;q&n z0gr#XydC7qe5M7Z0Z+#3o=rj31zX=C*{UvS1Blv0OC zPAGXKd{4dJZlYJgt^^D?e-G-`PHKI{D&08Yd_q^mj_Lx411VAY1VOx=E5ioUwUDNM+1+&=U8k^$;%8>x08Rlf)KRXe{txeSv@+pX`qK=)2GaOjpdZ z1?K38j)MKpW3vyosG0#8`^?fdn>j>-6%{Rqkh1wluh2fD?CoO zp})oA?9H@mxTYGJ<)+vyH7vXTsz33^vXQZ~>=uR&?@!=~02ljd&Jby~q&Q&KNSCDO z=so}g)TyUW17ve!)UF)ddU%bFz#|OIO_(^qkcljuGL5>#&_6B4g%Mh6*s2N*eNx0# zz2rLQG+)q{dPg)8ZsG0Gr7KXB%mDGGXdMg6fO#jJ_~^uqr#JHHOxG6tni1=9(J?hl zfs^iMbD^Cg_5!IFSCYc3wj^RjKb~JH&k`_ZC}L^O9Wu$@iDQpFLkxOr{~^paaU4w^ zJ`kr_$Vn$4*B9R5^7z&ajTfW1hPfXddA$w;vqS`^5e}H6(T4pn#={^~BPQo)3(im@ z83=BTSi>1~LW!dVgWqhR&ujP<#KDZ+|4 z{HhojsDLdhrZ$-)V+hy8OXhQnXT%D$V5%AcJP+N9cuWpS*8r?{J%d;U@iH4;D)wRA z*EARd+`q(db>Nj{D(Yc$38QDZ)R%`2$GL83|1oZ6Su5HS|AehK{KQ`WztIMq|8d;> z58dB?0=v%tHx_%em?ci|+_xsq<}L(x6Qx{HB`#pCvmBMv!3u>tOg9=7{45{gkV0ic z5$$=g^o1n{^VX-Q2gpxwuYWBt99#*wJ8851h*QmF*g;kwXC?*nLtmLGd0`DrusAl2 znY>-%m7UF#cy2g={#Y}@7?CiYSde}PYx1#sRo-t$P(zo3j5tSJm4Uj62_)nrMv|ID z2g;~Ex9<}jKe!aInnKO@?@y|0@(j06)g;lroZvxMpg4Ik_CM#3ncHn>kdhAZbJ0_H zOdco5r_AL)wahu{+WLt)=dia31akw9{(1+kkWKqYznXvR7OS!9tzNBeRlkARP+#}! z6n|xa$JE~-+@eMK{2%9J%=uM*xF6v4@BgZ*B=~Rd`jd4}FDxhX|8wQdR(*3s5kdJj zm1vl}u_GbOf}{yW5Z8~#28IaAjQa%%bl{&TLyoz2Mc3F!NPy=?fEPv)5djqt0j{WN zBCtgDwaqv2(4D@%E@4|+RHdxj;kom%^Ah8e-TUwDOYRrspD;tj1sW!r28Aqt)M3G+ zg1UG!#wjy1ch&)XQ8&++VRtn8y0*Rp5j6TAyR~8JDzA98+6K4i;Y*sGuzjQ*m_r8; zTMJ0T_^D9TtX;oa&*!n@#*jdnCa^>cT zcVwr$=|=X&W!pyf?^fFNkkb#tc$@>$!WAo!(=lVFMwYAiWD!lR+Idd;?|n|!ta4d% zI_IX+wzVdw+Aguh>n0Zay8(0JtaC;;WrX6O@d>r4)jvOhPqah0q%VauxYR(=6farQ zzcGyfgX<}hMW&!`36Vm! zY!>sQfadJEFfym zE}OkX2Hxd*#7rlz5kqF1i`OhQ?z`X^)t#IERIca!l$b9XxH@yAdH+qWoDpU&xI=2( zZJL9e*$iteL+I!gM?X;Xrjx#+CEP99?a9eD0y%A&c^Z;>Z>(Z!w9D%I4Iv7G}9yGL;b#K^yes6 z8mfAExMR^^&2*qrL1pcjQ9>}Vg`I`6?^|I_b~uXSedV{R*OIov02}Ahkn`y5i>TF< zkj=u1RF4FTs?P4z1<&rwktP{?tTWM=S`7m$8h7ax=#VtIe>}9nj77drC-Zt(SR~kh zR#d`wpo$b$o`7fQYC&~Lri-i@5z=$|!eu8lXc=>`tYwEP0+cQhLRI8 zh;e=BG4uT2Xs*j%b#>q)mC0@iO_(mp;G4lv??6N#ou7zgV{MV+7U6-NQ_GL`#Ol$k za5h8t2o9=O2Ef@h!(6k2HNWl^l($}Rl{rVIO~h--2~wZHVp(Lg;2n|^vB|y=lHnEp zHU_mX^e^!^(3OOwI6NDy`$RAJFt@~|D6c9AA0=7mb=c4-tyaq_%guKLe9si@S|6*+ zBxzWyHf-E%lK=UTY>1s0*#sU*?zx9!f|D323>zQuL>cMF&|FTRKc2!NxNKocU)M-T z*IBz3S-Fab1~4@e1DO)?Rq?E8B_t|HCq#E9bFQCu!eYs#M0+NUd?b%386Tb|jesYO zD487U7Taeov}v;4sA1s?e;6rwOQv7I;V8CM$44(pwuYAb*z7iW3JIP{Xk1E107PI~ zJsphG;iaLsZ}D)mS;kn*oRd`Z8NHXj^mH$b=}*TegGW6$RS%9iWZiVxKyUk;IFZpO zpZ+m|bBC=R61RASF~+`7n2t5kBVJDeou$F!KANNF3w_R(g7fg3;g||7+NPBoNO>>Q zF0F{!7(C#oITuuzJ`+#9Rl3DmW{K@V$sAXfjj%2qdJw3=_AG+U0@BX@NL}-?sgd7z z6#y=dOZP(>05BcDGLp_X3(ez@y&I(q63-m7pgN%GW-fk(w?9mEh*a59hr=YfADoQa zZ{fZmvzJLrYlC%_9vQule3R0-TjNvE-&1C(b_qO+)$_|<)K0Kd9Gk=bCn>>3|4H5- z?ANdB|J8_={C{m{vbM%1G6r`4IWc6b-YQ|MpzvgZuyt+&!v;gC!xmQ)@(bi|qzFn; zLa>5ZYf>#&NIgJAj@yu}FZtZV-}~MPWzWymGk?DpFn<+t?R>as44A4s4eoS#d+xTM zW_|3~=yrd4Yo;64W;ysxs$GLH&D09m@$d8}}ttbntsM7%*8Lnz-#(h`2c&4YN%QmA&DM!_t8T*He>;{6GM8s5u~2K7ZXf(W(EB$Uu%JfS zX;L*>tu7fgW5_!p8pLq-I-~h%`%Lc>jRj@xDMpJ9g*01T8mPt2YRahEGe^(Ur6*3q zO_PhD0riuQhYbVljUwT*X&`BJ07T|Xaq=lqjK<#tNX#}XlZsZSg*zQ(Tyi4V76T;7 z`&OzmM>Oe7aT>8zize%&_tO3iHT=Y1oCc}eJOKFVrzY+MdWtr*{c%VgrnO?xX_|=8 zmv;5wkF?a8zbu0PA7k$nU0E1ziNYN+qP}nwr!`Pifx+}yJA$*Io;=s+dW42 z8TVzqte3s_+GG9SoZp1mY$)m9Tz$vvr((^v&I1S0)@PUpM#f+!P!UY^`|q3!|R70M8ucS;DN?=o|zpzA$>&0h1M0xt@s@FYu2G1Nt{;@1LxPt zz$iR&71j9ykHr(B4c0Bi;%St8Or&)-#nF6VSPl5ABO6e(6V~?zo~%dQq^<1@3^5K; zu}uuq1XsepC2UGnP52?Mfb6zx6%VnAu_*3b)wJhfCVC1ksxR{^8_~LJ$ynVjyH)E! za{H8|m0}z=0_hju_ln2TOHUS73%Zb$?l`}0I(^(ho zA&2JRm}`cyNRQB4Nrk|kE7o6ZRgN}aw@FX1rGwX#7>v>eDK3Omt|PCx3mM`S_fWf~ zp7}TGR{Wza8=#aMQ3XgJK{v*pF~*38fQY0*-#GOiIgb#1_QgFL;5qrJugG;#*r-!r-40#42 z#Kk)V=HzA0X8tOeF4NWN;oGx!gkF?;4%fE%f1hN_*ZkLH$y<=~%HFUQmFWwE=b(?D z*v%%nl;#Tw6YY~MLdRIL^Qgy}xm3wVPOs^ljH6dYxp5N$N$D#o5i35u7Zsluxm+8< zdr5Zy;FdA$6Gv=~sA)GTvTGqoRdYX7*2zxcZwBKr$(enzz#*IpZUVj`-MONLPJx zx&3y?Mwu?5eu+~22#3P*t3SZ{i`&a1A-jOb>ETo=Yph5bYWr7_pqBpy;@lIJ{hey( z2zYX{SQx`?66!^&mG2p02#erD|R_BARwC`F!TQx z$o%KP{lB%W;{-2+_>L>YkI?dE4DP-sLYL6=%^dl;u*N3bKOE7f zU>Y$^9&*N@(#ki#qQo$?B&iVP#uJ7&gx;(9`^Sx}Y4J`@C5Smf>_Htl<$DDi8tGc9Y|6Cgf*WUw3lg*8RfE_CM2GH*``YUmA91 zn#-Gd+lriyp5vZsi*DPn@+{A{Qe^;%lU!;rD(<)$PnC~x$4?3do%Xi7$&@V2tirsL@{-t?uTb>w=-)@4j= zd~v5eLnN1WJM){O(sGrhwk8N%Xedo+QY~KOo_4w0Ow$eOpc~4n?(bRIFG!1LHprgi zmY6!pZ~o+2TN*HnXwOV5}bA>E#L8^3ZWX4;3KAKXcw5jakOxbT6D1H%PxUGCF&{X1claH04ViliI_jy zN}CKkpd#C+8@ac6N+@(mzVP4{(Alip#Yt>z0g{}E{I7K0)oV8{m1p6yEzLXWE)2RO zznV_W&-0+meY_*%b%|A_c=L|y5<0V-ibk@fPN@#@Ia^B8M%Otq0C^dzw@p%2S_EiwW&{uZ)d99c@@x|!TcHR4-QI)EZ-hrY)#PE4) zi3#YJ*Ki%^w|AUOVy9`3O(3l4r(UL_#o1J;ZpJ@bfqJvfj-^+l@qCM*VSP%5MG@3s z_A97f9Sc6+Z~OlQr;2~7j>x@cMT5Tnir#vCLh>*Ap}LiS$d$BcgChuTL}PvqGaql) z6gxQTpKim7EbZ;`L*ZYJzR}p~sqLi5auvxR7sb^6A$!fQdC_|%aw7!u=z|1ma0lkr z40h*~9<`O`5<5#5#9yU{bDd!$2CAQFFGmiq-O)Tw8c^Sg_X?jfBfswnoE3JV(NyCZ z*30YHSTFdr*{ytPP@At5F2M@N#$SA5@~6+rgJnMXmg~Y*Yy)KeCN9c!yTU-cicg%8 zicztHvo&z!$*ioAw*TRIv+fqX22_^z|7t?;GB-aSTOgaN>4}z^AO5u{z;iG?<>91e zRLKJZ)31DJmd*QDn&XPgF--!P<(Hp`rtxvN%An(Ru_b z)r6IL@KQ=VcLSVqK-ji)L01DdXC8>NM4S~F(#7s4Z(m9FJ5X^Y`T`jcWr~4f*(8GQ zw&H9`q#HyhI!IDZSM0>HyT8ExW+|ccDHzXwCTI*!+2Hzlq!c5u5zU^^JbI5yS%odO zPZZR+Nu;U=o}gId&H$Pbxxb=(r%o{^+$VtyP-#tP#K0JT!O8P%U*Hs+^Lkt(a-$;c zYL&lOe8%-GgQTZNjG2Gpw{$}23Mmxxa+eWv3>mK9`zPGE*h>^UrS{zvUWJzVRA@B%~zo%L4@ zn$06n^)V5A1nX&`(sEFVnY@Krr`0$q?TV}JYPO|0;?t#W`K@69mkIR)RySw|p`)Nl zU~GM#Dc#FI!MOhH8QA$loxn{izH8dWC$}pypu%%GV=1>J0Oi3QVrq^+XvAvZNJOPn z?W|)0K6&U4yJ`8ih&QIJbT2V!qcb4Q7-Ua{Fo@j_2M4deaI9tYblA5EcSlNpkIc=~ zAt>D~2y^U!s-xC#ahMhfIzjT^21DHv>_`tTpG01tl72;rvGEPjV=uV#=SdH@AF6%2 zXuD+WmA{oKq+`tKqGvhHX~Ll}Q#&NA>AQN*$kgMHq4i#Pmps9v3Gpc#&WD?!cwXZ=Q?LAGG7>`VE4TLPiX9Y@$C(BPoOD?CTdq`;u;8 zz7g}!$Da~~t$+&HY*IYFIHnDCK_AQq$H#MRUvRk0^USc3sVbw5urjN(#>Ci(qs+*` zDoTmG#vS%w(TImO8 z#~M9}-K^KSlx|mww{V`Pk^Vj2*&?o$)s4;fzxXf6FU0?t!|a{Z+W3DW`)>bRLizvH zFZ|aW_P-aa<*9!d0Mq~iExhg{07fR(JNQaUL^RQ|>F^y`9?tHT*=Z0h zb3`9aEi0{!Dd{O`Ik}kYe?9@K1+!UL?h)M{K?wx>xm+)^Z~k*f`9AJn|88G?0~Z4O z!wO^OQ``$9&GyHT&L(?C4NsBkL-*G_@=6@Cp){m;h79MB&Zcyuxjhu8dHM-kUP z9rc0_>8yp@+NBy9x4DXAso}GeG40RV;Mkb9o68M?2i5r|Ni(;q$TdBF^h~k*W{N|3 z&GLN3mj9xQ7B_cjTaBx!1`g$SFzq8R?&C;EaW=iXw4Xe4$!gJ?jgHU{Tt20c5PTA^ zv0I1^`=e8$gB-{xF$77WGOJ9LIcQ_e+X&f!PXoceh{`poKvC}#n1@uKz|8rlYwgKq zr%O?cA`smq8wq>F0Y1>!_>Aj`-0@vmD={hcD7FL%jiq6=n5nU=?%+{6!AM>M(mXU9 zdQUXk(dj3tW>Um88ObW$NITD5O17%;O{0-|UYmAK)7mE0=w)o)2BeIas&Z3oJ9XIQ zCbtmPrs-z#=_S?q%swkZ@GcpK2qQ5S*&Bq(dFblqjAlu{t#=8^t;QD1NEc3dS#Pmq z7)#_5N)Ml_{vylw3E61LWllG`HxcE^H-c`P z39c2_V>4^&66Er4z2LmhFC<8)lxqSkPfOJ?S_@R( z^Jp2bM{lTlib&5a7ykHV#u|yY7l?XTM&9R<7L6n$siV)RxZev^upqSyG1nD@PRChn z50j8$nd7vko2ZEk{ypEXL!Mr#AKK$PHsmuC+jdC1*xI`rucWlWwKb-TmJsx%6+!o< z6k*wqH--(Xzv_qMIf8B(dkqUsG{)?aUHWj0&}18BSGFM=iSPFA_xlCLMC> zBpHJ4q!9tTndql{C)&gbFEEQ7@WT?{4}!z~Bp#yD&c3UGqsmM)f?Ko)6g$xmBwx`? z8P*oX!BbJqO!hFmkSA``O%jzN>`b+ivXHL-`G@9s^FRyv8jV}pc&$D=2>cWpDeqV} z*uv;iaVcA#&(y)eF&>fUBG*!g0zxy`dpMbTdcs6lUE_%WJwL8>x?>kd6^_z$M*)0W z_X@at*oq0M&mRSzEeQW?c!+H{M><5p&s&AWt}U1q9EO&;Pho;XsU1jIM0*{U3UkJ* zZx2HJ28y;>lMi%7WFuHEg4^s~Q3R7I#BYNcgAuS=16|)dY6RwrtXVj|2n6{Ay<4l- z;{fK*uZ0kjv1`U2qZK)ch`R$zP84tDU(PX1k=eQ_k`o!IgR^Ub*Gh|UZown4Qs_#Z znLku!Urz!Btdtks(MxFBpiagI$xt2uFoI${9ea^_&@Qi!F7v{T`$Tb&mNF}Lx2}BK zOY|e>q(AqF;3noGmPz5PEFg4GgJCB@cw09>>Ba>;h?X;sIzl0D0k2Qtd(`yOi44*_ z#U7_r(mL`=n+xn6ltlcU2u4cJ{rbR~Cj)xIjuG=NREYePV7I8jr+529!gu~ep7e

    *!6!<9OiFeAg`)uUhag6vMgygy)q#FGy6%^y0nPeh3u))pXh@ zhW7kJK!51cQkvHID<7G4$k>0m0E2x9+H)1$0#-9EoVqU%<1aulNNCFnte9S*gd)<~ zA6IXUD-Jsj-mqlvFx{VZdJ$sw2C5XK31al>g< zQ#MP5#C|DZOPQ}ZM`)H6l84BodRH|!{M75V{mGB++qGj+YPnp0T;XzhlAl3-SJ;W2Q)R1)zc-55b)(`}*l%Sa2gfkcy(yvn^ zat?^QQf7#Hcc>upd+J&7a@~v1MONywZ zyh&BHV;DDN7sF<5n0c&l07$B$V7IWy)sck3!a_?xAOogN5xpUXcy`0)Jz&HtAe zQQ_qo9zhVcul3plB5*&X5Yh-F@JSE#J3@f+4rT8%$>FQCzu@h?86@D~(g5uh$G|{o|JL_k1i{zDS^(7@wZV5? zf57Xz0X7m8W>VbGNzdIRI3Nt}2ZK(f8LPPatL5lw*Uk_0)UC}8?rOUQ(a1_+5Yq}v zI<~$$a4dZ`uQxAhe7zE%qq{XSi*Zy3IhsT4NXZ3O*vM~4AIw0)SW<%WGHUdxuW3PO z2Xh|6rdR&)@6h=l$}UsgR)u&fOSQ{e(VpE))14u0^Ew+5Mgrf`e4 z@}2bL^*4~;l^|GvHEotGCi$C5SuF7uu4-#(CJlPClO!lPa>F)wNot}3asZ;vW z-@J&UuB2!4_V9W*<&yhU`^@Wi$6&p-CVro>4TVzk`?05?b3duQl+#6X#z)gwV!`cq z)FvV(c&vBJL&f(Im?(DTo5Xb4i+k4b+;Jg3XQKkevFBr5tY!VxY;$*S;xLm5A-dyd zIyHKjV?omN!fdj@XBo9S_;c@MK}#>n?28Umae`)|otqls??`}bTLPjbiF;(&LlPlNtL)P7FJe!YMr&p_3wRR{D7l(K(fki>2L* z<>IO&OPzA#Fimrd$9u9mkHDg$yjcDz@^tD^a-?qZUuttl0}_VjBFv>(iTB-=cS{z)1xuaC1=DRz6D3zgYGV| zE;59{g!bYy_k8b-@=v)(Zf;+One@V%180;LP1nn5$uPtW;MH{HF^ekfbhKRXItu;y zS&C{$OY-x{F{7=TuAI`Y?Ml4vW`l7Qf|Ry;*;QoMM?0!>AYFcIe5`yzVtZro>LF7M z8+JtjDx~DtLxusy(~)O-N;j{QdU)g1>hfWDj_ELvp6kHfR)S%W0*Ju)!PK@9_GluN z)SAVEzcj0Z!_BBgJuwHJ(Zm~LQ}x+N6V)OQaM5TQl}D&^YmC`uRhq&JX2!s&Fox&q&nhnOF;cA_THxw~* znv!a^!{lf=PHDk()Vc{3NI|5ns=9LNqimh&z6W&b)Z3w#yw1f?Ri4!KFPcMo`l_Aa z9Et;TI(VwvkrhONFdYZy_;{SyYPKVCsx{`4+TrWdKHJ5ISf0_*Ivf0MZ~W>=C#Sl> zjg$|wtggLfDg>*e#^&ZiX;*=TEF(KJ-2zPLt1>9VR$Uqs!{#XK+%8)JIFZq*q)8YP z%d)3UZ3(m`P9t0OW651XjRSP{9?31AuA2E|RR1J7z0kpG%i-je+tVb8?X}};Bm+`4 zu_&_%oF*%_h$ip7Z;QA8X>W5wQ(t!D^i(U_Eh-N>AG(y1FpX*}-bx+&`8<;=w2BxA zz*1Hj{(8|m!I`qplUnhQfARkX+Kfjgz)91BC$_*iXMaP+_Yh8t*!gD*o(`WYlZ5o- zwExdg^GB%SF>kRt*)GEGFhWQ^yqJ2#GQ;w?c4v2Sz=Gf@$<*Tnkw$^7*q>?Vp4ie^U--Bbx~!U8 zw9oGA@tLuL8*`tBT=3_vtfpM~M7f)rcmo{+O4mkG9hP4lJ`0ce>dO{fW=M} zUqwWJ%Rv79{2^^P9wQ7rEXxj{3*st}E{LJHu_DFrWiYXM+2$JBQfr?!!NU#wvyRja zU{Ge+0yq~oqk5w0HZI1w@LrGyH|A+#tP}6SZNW?&7sGte53m#epkFzEG?@YRw%iCK z%=7A$;c}!s8z6Np89aqVMU-=xef_}!y@3LP^mk47(0y;5lt21Q#lpN&9@@{w-rWI> zFek53S@6}^EEp^Q{;2RLbe2E?UyfG_!bBrzZDze;=W-gPPkF*zSO+A`txzVwqB}~k z2Pe`P;^Nl$SE{98fSrdZ8$^-NV6E2m69ilu!k3r$FD~!=f^0?FtvueKvmC3 zUTs6@A9t8Cu6=GON(0v{QI=mlC6)WL3S3uPfl>CySy$wjcqw(7w;DI0+Ib<#Hf<;@ z&16sUX}2gW-U4ORX15heG5A>;u9FlxlvKwJ1}8e#indVa7%vh=7hF(0QDVLiJaULq z6wzca_efcIq=+z71M=Y49oTeMC`>~S5&UwgNS`{G51}q6a9WDuF*qkFofifd5YEK- zXFaPd_Vg3{gc7}bQ7SiO_%g(b{((#xyQEausaIG-B1{e=d8kXoP+GF1VDs2PPqKy# zSwep&Q~|#do+|xsHub2?f`P`Ri=EPIA3d`{y)pW5j%e;@@@zEOm>L;#W37F&H>4(C zQiiEAE!vEM9Pp|0o5RL=n>~jd)=-JFh#+)Lq1wXEY63egro}GnAe| zZh8BXO<$g(U8&yw9r~d#JorIjQ1J8todY}kAw6gZjDr@TPzC7InYhANdvJrIm%`Uz znq=u*$u5{(B0=U=15Rj}p4HzR5l;=_9@>A_{%|&=d#`vJuAZ*6&3Bu!@f_DUJ{NsMt+Yv4ePzmwNlT1LJDq({G4F~%k8t-P z?lM@0i>fG*S-xl2{cEsIzvGq1R1n1CJ-4Q>#?7aG4Qv!k8_^W3QHx2MI)bEHXufp& zN6PA{_ng|QACQ^6oW4i@r$&nZlaW%CaY>3$0;RL8XsZBshi5>bR6EX#DzNwGdomeUI$FS zkgkt1uB7D733y-4_6bmXPyMWnFv3ryeV?9P}uAjg*1|429tX!1mr2 z<{!PyFOD=kV~;^Skj&pOur8sP>4HS*zaHtG1!MLzIq_p^ow@aHyASYz1tgvOT>IFQ zs%FoQv>w20^U_J!4bZ@x@@x83d3;yyybiLgxdH1eLNK81ng^^W(46#{OKo-L+$`)R z+Y)WVz3U^_&q~?dqX+b9jta_rji_cVP}e|^5<2ce1IP3V;JC#=d783+jsn!6D&3Vp zCeK3}{vj)NPTnu8Trk4HunR-w=%E$q5`G-S%&zSyU&7mYqi~lOKH%RGv{;Liv!yS+ z28Mm@|F7(RkzDOMEwv)tZOR=D{E z2`U{*wTKd3QYG;uwa|o(+Q;tAvnM52_1H85o#|*CG&F1%f{jGmXeBz1m8}WBG5uLi zWLAVL%TE~k9(I)2G#-j`hz%s^>*k@6p0Jb#Y55DOM1*UJ0Z#OyR3Q!iVliko^1jR+5GVd>@!{waTkUs4x#8lRu|bM6Bj!}k_RWDr9Z ze;yuMwvk5EObpo=U>y6njv;%G_?Dr&!(4#MK!=&6mS9yfXhcsA{nhkGkrm|vZi$^K ziSbEkcT@U0`XW7PWdQK_j}I(^e6F0Nvxg+T7M$K2+Gd+DZgWl$PV#_=5nm}8_jqY(k&i`4#8-z48!@KmIJwj1ApL4GXV0!}^=WhKrgTXUOBqM{UW+A)J?o^6=4ZLFlXUV{?dGA+h=31R=vXe%WOUM_`&W@%Klcl_xThWA#hS0El~Z; zB6-8m|BA+-{7c#=6zQEP!`nPF@f04)moWL76aD*$V0nt~ zuil6N3-g#ywVD&GzV*UPbzpSI0YBRJV2stTlgXjU*3G7l?i4yRm>F;AN#+}s-Gt;= zjY#Qr$L&T~Mz(7E=nQsY0d(*+?>MhAeYA*jn}A`gz$LC11iV!dT{lqZ9apVpUe2l7 z=}w*RwON+_=+1~4V549kU);M~xn8H!4L!)7v6^Xn1yc?)l!c_(9i{~?loE^*Ihivp zT?IN`f$A~wX^}&o2yNxQkt`vHh@f)~@Ka01+GM`mn-1oZxA4=Vb1l7vK_iQIhN;yD zN?58FTCGpEwz;?TeFMMQQ_<>@;UYQv;W5_`ilsM%T>LCO%%y}G-;p%s_Hqt}RRoSD z342)5*2RdP!I=>L`+uMAOB?cc1|E<&HhrA6!jN^< zVu3nMIr-pc!&7!A>N&q&LH1XZkb0$Ymn5n4u8}2Cy5{Xju8UO4 z>3)HjIl_eLwXvlr+E^4uC&^suH=7Ye&(rLq>(I6v`;VUy!KhmiJa6^;m3AiNhiNFX zYU*eRgdMtIspA6-+0e7#N4xcn(}(K|+_{WB0^NVPatrFF2VOts+``lsnl`$_aF`A5 zB^BEQfg1dDaZ$SB^+!!?7Vn-_;G<0eLXQ|Q~3F#E@^TeX9RM@}6Fid_XR%DyJiB0#}@OV2Z1g=!&*-Z#cfrZa2|^55h-ez7w_hPuQ)2AB2;I=Kb^=fbIgIV zQ7I-QchD|(pL6^?3-uNGLo!4Wc-8w8uJTr zlEoQqLB8D6nv%MTKvdh--%kJZQ@J8@oQ?9$71x}G?Vn~SgrWd9$SFS-7&WNC6foE5 zP`+Si-e@&XICxK8%3nFr)n|x5gnKK%t zZR4>}eR&zSf^djc%Ny$b(8VbB8+yZwT?Sn;lo*0mA)45wC0$CgWHk5WVwT;o$#&TIo>o`aL>4*phscY2 z`@h)*B9a9k$ZWBCB4;X+6ev>Gy|K2O1($thQ9Xgv71!Afskl7Y`23cy-_NY5c7niL zAej#4m#5m4gF zv_IL9qU`ERJ0YHt?lR|JdaYALlkz<}ouM@QKEpi=49@?E9BTp!pmsjK|7WV;<1l|)fV$mgaiHb!I1IaX{Wfo&@NNy@=gvQ1GWh!ktX=*+` zo%~Do1m;bzY{AAj^QjbUTD^ytn$WQ&O1AmL@sG#AG# zDAq>P?%uyj#VEP*iU*Yc-RLw=wY;FsY|n1HS4Jt>NcC-il4QZVn%=GTsBPW6?0!0R zBav{4?NdKYU&YJlM!0A{`fw@IcH2C;?W@%xqbNC|%3Ok>CF!+Yt)oYpV8U==)N-cU z^=87iUs__WMPoXFaV5xO%F?465D!b^IIFuTAF_Sn%xOM@LLA2N*zP2jlNmN;N&De% zk0#U>4O@zX(grG!r^fG%rp>`?biJD7ip8;c%F$w`YZGgyPqdyFp)!sNjJC-GOV*S8I(9{+*6PTTL6rHx+|ShH8kq3$Z-XsQPxL+&dsoy*)lzNR)9Ic6HUT zR48ZHk4|Ok=RY!-%odn^#>o2&YE8Ok*;$L1vqp)qIDg#!$6MXhAOCW3YW|kN`xKV{eqF|EEyz>lTXAVP9aVS@21WT zbD@sy$DlSH&kgsX&JFU>G>5geb?x*PYsy=exh&*4bv0#MYTb_SQ8&la;s)vQB7NwC zLA@)3IX$33qwim%wj}jRUd2xwbr|=B<%6;HwA@oObh+-eVM&*4rm8dTgw0!>tPlUT z)&mv5`$A%go>niU&|iD`SF>n3x4jsc>ONK{2UbXo@Ze5Ek-lu-1I@8Jh<~iTCKHZ* z{IzGkxC533SjV3mcWJG5B0z11n_}n&0d&IbLZK>%sQvjY?WT@pdz5XO_Ncm(6kM=21W9fDC=4hRG~TCB}- zK}7=akOtSC9RB=rg2BEH)x)>LbU&ipUm)ka#tcU|WC?EVJ}DIYvlCodhwo5vt8pCH zzm&@XGUwUDS`0!bLMXxr`nZ$Afp>GC5z&-Y=P>r>4R_zG=_){a^#0(AJtE}ay8n(; zRTfQkm87mx0Qthx7IQ2B_yPvL{N0@$1(B0L-Nu0=R%-&g^S3}{a1S?1m$cWu(QF%(j=#vM}$qZ0Y zdH#axBLo+KoO8K594kn{{T&R+NDJL940c*(Bw?7UF%9JaZEdlJTl@GftP?l%TLxTI z+pSlq4m~ki;w|{6EQ1Oc96eR}#7Id!N=Y|X+21s^DV(yxC|80@bq`rRCabeck^}SM zCyaJZ73+h`B=#~tg01-KlJY|w&#hJ(@2J^k8Tg%UtSaF!`}CWlwE(t_j~&wH8y&D` zo2)*t=fEHxec;qIu$PQJkS+r*Q)taaD$d6k4F+V2VzECNX5 zNlzm)(plvQ(oA*g+Fd|Eg_e{FZl$)iJ{Brw>jNa;{a76K;>|5^$OQgpwn~;Q7X(?~fZ85P^aAipcO; zLBM_R`}H8;;jci?XXyFY8mz$ap1{%ZX;07at$sOPFh)|q8h9hVp1$kD0BRs;<=yP1 z7{T}RLkbWBH35z%Gzv8DoNHVU7{K=L5D+^md$^|+QC`!EgvQqEAhQ7d7@F+H?9?Ps z{UCxqH`*1kWi#Avppm}%!@XDE%A~Bm;UpVO{gVjp+I<*~m)6{roPVrgN3asO4jsDBL?!DSZ$|=IH)NwUex5UT^6Y-`sx+TqjeO~mlr>k*^1MWw zf5hT`UM*J&4M|S#d{+-$%sty_UxQJ7aHPmq~i*-iL0e;{b zF(Q+^5)Im|S#=q*rA!tXI0C5{L0fw~t1EE{gya~0gvGrp>froTwXifVb`X^hWP6>Yb*PO0|M1x391 zz4;@HSugI-QuODWT>fGe3*_`}IWJv@`1@}A?D{t14QYkz1=*@AKbI<$jmqME_|=It z_Gp%ax~!C*?S6FXeD#G@XZU2}7do__A!$)X36*#hf2Aj-H>Y&7@FZBxXigrJ&q`ER z|B&J^+IB8&IGGXNyyEpI6$hVAg|p;zMzI};mWl1x;!n9k-$H9U_)vw~^QtvrLnKg& z#S-Ievku(|n5>Rtab4G$WQ}s5mZ;kB0>1sBdyE`MN|tbuISM$26_a`Bm-6)KA7r+c z%JR3xG;ILw`ns(F3Xa8Zw&}E1S8Hmqb9;A~?syY(Ijz#inuVExc%tqx%!ahgNPUfU zA9?Xsm7^*x96hV|871??)aWYLy?kGN9twR17vq5*k3I-JTHBpLrfN1e8(E0dIibt8 zWa5c9pDh#pv};mSl`oEDSApTL7)jB*h~r6@DEyjmR=Y zxSA12`~&ku%E2^K>w8>DFwp`&m^+T3}3 zB^2lEx*B(h(Gf|99%-r!xzzJ`&R&0YIn$ssU`%%zhTPD`?jxfm?lI9Y2WeALSd6d) zW1nNAJz7TK*LWcf%!({EKrVvl5H_auKfPIjE~B5*O)_ zD&}mP_*rby@f*hrZ=+<5$G)myOoLt2Fk|NuQl>0oBDacOnH}gEg<5KpWSB%KO$OCF zB+TM#_EB(dTVe-VWrVaeqn`KBqGo21GS1g?Eb1(OVJ5ZA|Aefh`_zg2O(zrNLSf%1 zP~&%*K2K{cztTs?a(T9Hd&!g7NVjgm?eI5G|6SZ_y0}P@&c3Yar}?IQQ_sb!%E6Mr zb$5pmcLZ-@$T2vG`&XYTcqH~n0S!R~3wNe;ZiCitkTqSP$~2sz!%sBl?Ou+hC+sMR0$i71@;EQ3UXKF@)B*MzhMh#yZ==r2g*0@|e%CaG7;$dOl zM!0UE;zU+#!66+@={{WlQuSII`z>dCV(2*WsoTi)Z)(2b z9_z+&(cOaAD8o90ve3Z6wbW(S`C)9*f{e1ozhvVG*xyV=amr(ENj;xfDL2EGShdm1WNwu}2S}2yx2-XuLRq0%uw)h(cC<&RTIU&LI0y zuYZBO0-|Z0FiAjIe}kX8{$NrnS;{W>Zce+10C%EWS8v1^Qyw*^nf$)+6xRFs(@`Srn&*S z%!aoSt${lZL3%(hs3@x@iEzt-xoL@5n@pteJ0Q*}Mo2&ouK2tUg}hcn+jfT4CwM_o zyzg8~KG%;vc;^oQOgDE6`oKB+O3**kTQ;06hL&ojy)phCi@%lWsy^MTZX8oDT1#(H zAMJ|~*Q1WmjXOyBMTX0znS^vB+$TxUC%J{J)b?vxr|3rE8_T1inT(nsq-_XuYnn$v z?_kw7X#=e@&h;hi^brxcGn&$QO#3#VIDrgW{qeCe3MUW}TY+9l`$bR; zaYk5v!-`7QS6!<>LBkCAwz;2 z3y${!)m$0>P8s&k0Ouww0u{c3amD+N6A~1VoYS0{;nOGrHPVQq^A*?7IuBaXauJcC`hBXL4MKDFnWRS%wP`!GqMNlMixuy^ zG8W=$-@mYXbV3)^k)`2tr4xbr}>55eDt z?J;H=)Go}ZZ;f~wPwZSNRTZ$Qt*!$XxXPxz(AAyI@t}N@_ZT20EZC*Da&598+DI6$ z2}S8acv$A~<@1XL23RW9X;rV%djF0fzGN!eBzp~vtl$Z4>oKkK>b%eFRr8OB`|AZ; zuW{&oEPGIPw_rPPPqg83(^TDhr|nuL2>J55Gp=fK~4rZ4mEzvPtu%| zFa$L-fs^un=oMJj@&8RE5A$K7QG|G)+3JDMK8ilk5~K?Jimml_IS;ZI>i&xEI?zEy z_tR(n!fP9Go6OD7&;N=a7qG|iCq7wpYeMq37`Y+nNEw9FtKBQ-#GZU!-=?5~WSw(y zbS*M<;=Zt`2)78d!4pSF(KMQnj{XalkOv zvv#eZ!pFQpDyC8byzX2cTnKN=#m#jeJhn#Kh5H^Qx{-P{m9B=zP zr}V_Zb%Q3h!g&6EIje{NqD5_^_!nw&@?D}?ApuH+ExSks_O5V*GY+LuX;iu-5)I=U zBRCMhe2-SJ<}I(?r!!*Shd$&K)v>0|$v8LGB}442*=QXq0K@||Ip(`Vl#rl!`dBAV zDVP~FVRb9kjRx`{-)r6GlAY5&=r)zwRk`G&GcZ8B|F^nw6wSYm*tVN_Rpz@BZoGL`{OHd>q6Wr z{#&Re?{z-KT=)IosRW&7H+U-qPGEapd9^Ej&G;83``kdPWg@Xt+HyEIgyh0Y%k@Lu zJ4F_5AwHkYN*fpD5w7zxt^+cTU*9ozWPi;;!|qx{2aG<=toAIuI-y&pjJ%1`D4Xn`ckO(#>d$r6(YGE!NqC1>MGC?Ib{mwvF?oG zA9G5!=R_#b3o&Ho6Do`_@>5p2;{0L6Y0gQ#ehc$-AnTd^3m22CEMbbS@y0tq#4egu zyXShXp1*J^S{c<`>gU~V<7u_;N5gUS=tI;tdCGfmjEv8#PkC@$U#{EPxgj>Vsgm3s zF$zLdhW7(qnUAQnIDRC!o13|jkc|CYFbbI zfgzK)98p(! zPMbKi9273ez_%jgY^rS*X&udz8j+~*tJKHe+Gp=B_&79^U&)g3*p|5C4$D76Ih z>r8LKc&qfKhIlCf zg+z0bT_J0`R#lpfj4Uj{u$OO|PB|3zKctVJKm5t4_U}C7Y8rT}rQ95rE>?|pXEzz9 zvdnFnFaJfdf)i)+#*8SF0QWyA`^M++qRRA?WE(R zPu_QCojLQZZ)UCY<5|z&y?0gJb=OrFaAPWZ(vo>Xu^d~nn;p52+8d`S?ridKmj$bj zDLrM^0FwP-zmqOQ5l+>{VxK~+QVo+k#x=L-(&xP>zFVu;#r>X7Q=`Z@Xq8JHOR=3! zhdL@W<A=Pin=1pPZ%7eC6;HttF0X{m_$Ul;Sy|pH-UVs4n}j3 z)VMaeGvQmuN+JF?e%}D9xhq{!-2`{Qz+mU4olYocl<$MNykjRvcsl>If<)unZA`l1 zETs?hZ$VX*5PXMYn1a0Oh^t4@r=0+awZlaSjfL(Ic0ov}C*F_>+#Tfkz~rC-VTUj1 zI6@j1=IR86q|XSSVlolk?V+wQm-Jv|!5-GvyEv&pSW2Qe>6L6pe+~((hX|aIysjfx z9uZF|Xf#>CWc1tXlAwC|7%(NsC+L+@_HC+2d5TCr351MeOvrK?3C1EDiSCqRc0$0u z@J$)oCgW5UMBl#BO#oUa2UKoC5M=R=tMHAr(oF(drvTKnWFMvDIB0F>2lv<`?P7>t z%9mqgka>~ijKL}|GpU}8LK~xfpL>Ohwa1##_*SFjeh5Y047@SexDBfl8?z7pY|@?jlK*V+$rt^iM)LHr=*Ie(yc4f%hrAu5S>kkT=X^K+ zfl7CnmY`MZbD6veV&cyh%%Oj34LW64&$l<6S|(WcJTSL;Zki| zOcT>aP>7)PZsO}CgYF<3C?2C(9*!c%{)6393>Wq-GbnqQs5+d_jw2L zM#8fy3qP&6^r`puV)w^c?rV2NP0iJAp8ua$Tp^fR6pNu!xbG;>aCxjgeFjWb=k&?6!>3zmG*mrQeju*rAm?O&Y^~9{^uLC&i z$)~o)Ba)ipcC5`O9cFgUOgszqRrywvdYCxV#Z%cCQ|2on9VS(pYzql3tYOVItrYPC zV>JE~4{S{(w(hLWcIMZ1tR=(8W@fQfH+ZC^5Y+G^=(tA&)}lsMWs@BxVSy%*!y)7i zB*(N$DwO!naOv;tY&4>5D&C$W^}4@!U5VA$2RTD_k9D<7d?Z?PEsx7+e)KakT+H=1 zg~5&?EM-~o?aqWp+bM+{6rrMeWA8qbT@2mI2FvdW$8raSX~l_oD`K7QTCCOG@jb-B zTRTa(;d@R)9%jJ1C9xh9Ts)ctb?vV81#PURSyta?z&K0org88Ky5z9w*6t8eSfPc( z|LD_lm*u^t&$7m-{yv~Nt$y(`1dV}xYx&#SGjG3n{MI5?uN^1lrXz3i z41_nWxyN%@rz`6*^a>$5E{DB_KTG@GNlXLX5_i3KE5o7p`r-gfYkz$iV!vUtZFRU= zHEqM=^lmh6lub&#HT(#2+qcAa`T^RlpW|firgFm#rv?zb8!0-fXDZdOyB4!*!NU%X zVWtA8fk3Xh)u;3r^1)ESDOb`)bJHEz%$9e%0{UKGS?-AE9=Pe)Nsd0%g|NR)T#ory zZW+3l9OJ969ke9~wLkbc1Fl`+c0=O~Tdr9746qEbh7i9DiwonV-i!8$2jKM1GK@1u zy5qp#tBkRGPCO>()aq*U+um&pckkw8exZ}FGjPOo9;SONPOukl^DJvPvkn!O=ab{a zP$~C2g;eI(KCIKQty5p8pCgytQCRYd0d22%c(9j;qU$E{g=%{MyW|~syTg$$Z^AtLLR77wlRPSsce z*_sa0R|MeTvz3CYY5$zSkSHr!V{i#arjSMv6gspY0;&(#)T224F;rw9Dpl&hn<6`x z{43{_PiW~n$v~Z`3=>a8IQA-&_h9J2(95Z&e!2!UJ8{K3oQprOe<(jU_)5UF7bEcM6x-r*j`DFM@`C`y|#xAIUl~#J&~R z+!Rpc!$;2Rv!N1DSbxGavF(waDFwa!I$SgxC09K+#q6nwI_9pkwSRfw!alS)S!2{; zCB@HMa`)mo@i2}THHz9!3#Z>YmMthJ4U)h3c}Qtq)1rFn9DTbf>5Nk^{Y0vwf?c44 z{j8WYXTNzp`1=lSc6~DZ=F1GpCREmWuD8l^d$@Jx6QZ>@KNxX{WaJI8??k0glAIMs-e zE2{CYW6XPAj&R>ruCE1wS~UO)!%EoXblNsG-*+MGvdXktLpR3Tj0?;P>3IbZoNM9ufNdGb^-|w=)*2`#dY*@IH;j;mZVb*!y1lE4MCy<0Hm#O#-jlm+tZ5zs5czEx`;0pR_6nMImf^cp7;`0juu zRNzWq!=#SV@Lo2}$??`+yd(LBT*~}b^W&Jis-Qd*WRd2hSk8tQedPN{ANsmU{qLC) zCbwCtSvU81i>dpneQa;6of|=YoCfUOK{&K|hCU`Uy`C*%Wz>YXo;!O~u+E*q+p$d0 z=?>fjUTKEh^82t(_KH9Ehbgh9q3dEcB7hPEa#K3ZeT6+0yvO$Lrweti0jxdBz3fYZPAp4ija`uIB*<0Rr;+uVJE$ z|LdOouURE#>)_(~*DRB@bawg242xBqkOvV)s?sE&g$jbAGBT3o3&45$d0TKBgd?TK zWzovWq#1S*e8C{_IToBN%l8cImr`%8fx2tZ>GGuFuDUYmm@_uLfj5j1j*581epBCv3FL=y?OeE~Rm^U() zXOK+{i}_$ZcO*E?IFz{bvHKLVh52D?zdmMI>`8oD&)K{`c#Hdr+A=Dl11?p(quc{g z0(2;ebWtAKev}Yf;|Hwth=iItY!)G{++t0cE1^81+6AD^1*WZjT#N*{%oOS9nY;=k#wifIr zWz$m-31Yb{128bmFCh&S+!v;Sn(C8hRk*CCs)H3;8vR;Tj!3w(C^N7)8$B(5EG(!h zHHh&-#y-w9#E)?QI(+BUjtG|77 zM`H&Lw&whL8u`Y#_DGHC3&;dy3;7y`kWrP8n3jxS-YKFXcmYw;6+aZ84eoK_!g9X;eq ze@3-G!iCo<%HY0$>~4uaWphtM1A>v^{>vwjI3G+x><>xteq?_BbPTmo6W>8pJ zTlLmT(yRQ86HCjUTj7{Gyo9sz%vE;V1Z10^&X!yA6|5CCy_+~Ww|x+>hA(5D@lYe$ zFWWp4>#{D(s=Kh(*yVp+-L=8PV}E&dc5a@voFfb0jHgFni!F%&Facj-@?054sh{*J zX$TwEmA!JK6Bc%-GklCgxaL&;82;qiR&k{)6!%F_jEXcrFXnY7f@sxL;zp}&)vdT* zT_#0p$+|^m1&7o3axo#x95*;`hUfMkDq-IO;D*PfwI*U&olX=PMG}W|#=ZrlCZwri zjI&w>mn34#Ns&g2*87ag78{R&b>gtFb`95M5-=AW$?Unf`s3u zm78S0443fJmvqbLE!a?_0UE%!Ud1^ZI-1CMea3zv8VUM8V;}K>FJQ~hdh}~3= zRCDV|NkEe3z-cKE4tCgAz|@pDihxY4aU$b0N~Q*x2QyX9)431JatUHUO4`|(lgsoE zJiL(IvONo@FU~VAfDWpxmv+ch9A*heA=SyX3MI$!&&kfuOrtEtrf#X4-rU{*Y);ma zpbY+e-VT$vi3|0Obor*OwW8B;smgD~ocNcVVJhb%nraH^L+lq-%B-mz2ZnHo(i2Ut zltpTccWlN1LF*{JUB0rqe<&@?jo%|L5+mN8pT~}r!=PHJ=g{IboPD+}sQMW_U7ll` z*b#}a4?jF$r>*zgdg#}}d_h}GVv~s$7Wo1`3rT@=cfm-~q=ClBDOQBUiwvRXs`Nh^ zMky{Z+pvr}xV{(Jm~%j5=pT~d2;!rcS!k&X{vKkWPO^3El_O*2mYW4|-~dW`g!G-* z(Ahah4yw{Qd+CuAmo0H^S57Ni9q4Bk+v3h$bB7vT+hv(^yvo#%x$)*L5oeY>31(J2 zff*N18z0~h>iQ2BJ&_rgTZKp2NP6z@=h`B#hC+WJ2X-O_HeC#cWG=Uf1z>w_?mR&E z{YpTNr$qb#N}Rn#%v>8bNVbGA&pnCks9E3)%U59Ai3VRFh(53_%VP37HX-mMGQksU z4K6}Kr7W$z!Ai?Bb9&;Glm~wYod^Fom8W*kIx$ApU3^q%q5Q^2XLc?@4P14_oS9XQ271SC3Tk+WLs!ypS5y>%v0ySgMP@sYnO(wvIw8`7k8;a+fGzc{qZT*ExT4;HK z`hIGt7{nePO_@PqMPYdt6GKjQ)U2Z8BD?&7TOUAu3(OmsE4OIl@t2)AtJSmu$1)pN zZmW^J0&WmIxmU{vIME{;kNkWVUONDu2SHSK&sN=d*ZZ8eP#6E~i{B%PvRg@6E)O&@5;Kw2!Pnu#;AlKu{FNZC5S;hS0H zMJjvhj3APk9fiTGrS%R|Qt7B2x@DNHyT~*axIt=u*#A)PSh9-$D{B7%C0tq0&1|dM z+5rpHR`30hi<;zwkBP)>8a7bFi&ATz!-XtU=WVcw^jOR>gl9^>82+|`S|O%S-r`YU z>TKWW$PMq7B7g}o4(HY-Qe~?YMsrGfY3P2o#>Xwbg!PE2Q$8+gQ@z7@EbUTg5@>}j zOsvt}13m-!7VZ12&RGF&1k^|vmxCiF1%NB#)(kQ?Z6A|9lTQvaGOsr_+HDV~mvPR= zMpwv}Osi#6^uqL{ATQFeqSmEmlw z%(_!jW?~?;O&T<7*^g7)xYQtSjZ?@{B9gp(m{Fujz^S^{>eg|;b&H0mOLkV zKzsd6<>&#LqytP&i!4qpwg*ldqjk0i#5_K#^YL6sej#x@MVf7rtX&EN-uMd_TMq=4 zKzd)=`2E`eq`Ki8w#?lm`Z#d^tKiMU!}AMpxtco4#_ z#Q$`Lr_D>uLi!PQ?G?^%|E`l7*?5|N>otK7diQ$;TFtR_JVVCVC~ghE`pH`y?)~$R z{LUf9p>9j9V2e*IwSEW14Lg}m#e=DD#l+LtT~~LETev^&u^{mAb+|F*?mTtt9Z|Mm zEP+d!U6F*t-MeSpJ+AGs`o=V1b$Z0+*tq4MIYH~>-SIWy@Le|nHGER~&_{vm7pfX^ z&SAk;a?BnZ^dBs|7ql23Jl7jAgsIX?W!X{~%hu7?)RsirV#}AQJG+gh?_ad8dHV6D zp?S6&-XDt5>ScUEZ`@4GHpjonVpT)mu(7-|2Qi)5eGwo8Wt>l7xjgbp&Aj>;bnxv{ zcbC{esXa?gtfMdcF`|!kFa=_o=&5!KKtIJsPy~c~%%$uGYl$jB$YU6}JWd=meE5%{ zD(d7agofS&FUSdtz3E*iA{Og+9M-$x(z_2mTzqU$lE-O1lgA0=naX`OOH9MXCbCOt zRpL*kRUo(ah(boMUo~ao;2!Mi7j9-}jhzmO7bt z1XA=ru^a?p<_zI96!;+C;qGb=V}YfAwDPL5LGT6it9`m__f;!GMD0_~`UGM~17Ml- zAct|evDh)f!npF%u>2IZaXEH~JN+;0RL{iJ*cv7)0J@Qypns+uxlBw@k#h9zMfL0KM~L2rtnrS4Z< z9A9b*3tk*yy|9W12wz~iH%z?L7sT;0)O*Lq>3jgfS(0*j2i*STOga|^M3mrf#HzcBim~P&8h@_JXnSMNQ7}}JGOl0?y*sdfPucQz! z-+0q1PXlsykNXvts|6Xg5k?=(RxA{)ra%FWedKbMFrDl$Ps2a*h9(JzK1u$2T+tY& zh&_R;I&~1=nF)9kS26&`Rk{=9pc=%pvOIFbg0;4kU|WdSgY@#qjiohsdqn%hZd;mg zpTR4^+Xm#ifbOZ$N|L$bJ|fu_y&%Y8lBK|S0K@EW$C{{D>_0SZ>!mQ z0dx3x#i)EjPxVrW&aw@y=-atMPiS;rjtsVmbY2G%@dk>MSmw4EGwXD@%^GO#@scgh zFFtU|ac4)h#$s@2oa)U%?WX5*l8!ob=3mNJ5TG`%f~*?)^=s86E^9$Dv_u4XU~6dKPPO!HQ(c=ulq$S%mg!tmI=IXe9(` z?%&6)7H2zi`wbK!j`K>s(;mNwTLg%?h;#Rh>n`CS_vALH>P3UopTJw0pMp*a*BbSy zpC{A7Exm%le*Szz)5RT@0)vPBJ~yfL2TOOvCG<$s-6Iwnn3u%x4SqBW^&y}q!XtGc zPjDhnFov;ZX{3|rCe9Hg-5`SKlj!A{q2gdagG)fvusdAsCVXyI{wIW>{L`JUi{LWB zUUJf)aBFLrT_V{l^WqSBq`*NRv{XU*8IvR|ikNOry%e8uvIoRbdE*Y8Zc5w1w{BgK z9J`9aY>J5ALQh!3^B08a<3&Y(Seh?w+a;}GhOzvp%wuk~6Z~#}5zF)$%JhwwVBW-f z6BsYACuf-C7B(N}6}he@>DZ%tFa17i>JY+ZIP`U_xWA%yx?earj8^GOFNmx8JJGb!S+3SPMqz^Kg4t!4n{$kCIw7^_0 zNK2esIFlZ-T13RbE#iZU#0a{#5#Rxi^b2_K>5iyd$e*4#x0Eq0#6lhEG7d=)Xb=(j zmWId=Bq9%FubD4mp<(SgagQ-2ummPOyh29+rX9o+Nxz<`+v<;)OQ223D)DzT|0T2Yj_8s5WpvTA z9t)YtbTMHSzswdQVt|0yI_e7u8>aTBIL;Hh>~u)jp$Ou2zs^Z12ld4p{9mAl`j$3k z2@(*{71e(OJ?Q>7$V$P<-q_UH+0yQ72XJf+3u{*8&rW>!n0~RtdcE+gjX`2ggbNFmJ}DH?=rYv0D8SuT>1_ zQ@6G!;>_ORPZh$bxNB>c^q%=`nVgE)1+9^;hXm3#f2fJGinD8%a_d*QmAXmZ>7(*3*=#pCqQhF2m`t^pcJ7xhA^fWL*5Yhn z8#(!n0`S{%Y&$33a`W+LP@$E#{@9v1QR~BAri9xp2C0-}{NWr8E9x{mgeQxp69tfp z3Ey~VdIoQX*n%{dGkem=6q)9AR@~jgeNb~_0*y}F3 zzCFNLFlg-jOC6^;F?wgXX~u1Bb~{vv!al$1NS|kZMDmp$$u^NK!%%v{+RbBL}m+(pvhoRzO{Kma^H>}tN+BSUu%37u;I7GEae z%#Oz$U3gaY6alXd&O3l`x){chZq3De>Qq(-pJd{eXc^t>ivHMdPUF&hOv5Kug4(T+ zA1%&l&;!HIc2?Xx;Gi5T@T?SC$aY4_Dnuc7gymD*Z|R{IYS>!bFU7L&pdG60fLm#) zNez#HW={e`a3(Vw>V$$l#YWG`Ts>7M8g>`G5@q67-4A;c3xl(%7}y7(kxe|X)Ow3! z^FuVRdy6snBPR*S?ZxC+Xn>wzo36Y8#p~rMuacvkZ9h0!k16(AEmC9JWZBjZ#--nH ztS4hj9YHob$gJ+&Hkf#1RQ?i$*Mm`C=EoM4}|jY}E- z=(#le4z8lK6LwI?C*fNBhLvurv5i1xk^6*fbL3I)dd!t}Nu*Zoe`$%`vS%)bY5=~U zzKY?q7c0II(q9Xjn4CKB2(#HXs7=%P+)p(6~_`=}3)g1*5J=S4;kVclCt497X zloypzl`GSfYlCznvXivRz+4GCl#w~ff;~y~_Q5}lFQ8y9umqgAWH#Z^NKF`Wkw`_; zMDbE=X%h000@tGTQ5NkK&W~1#-ei1{9l&cb`bvv;fT=xVp_Yqwn$SAcz*wn0kixh; zcp<~lkjo(x@=5Nn@X|x7(fhb9R}F3W{7#EJy%n^zj`OF_kFOF?WC!A5Bwy$%XJ4l}6ew$j^{`@tn>;TaRA;ApADoT@Um0xcM%8y98s2{?5in zN*xw!)YwBo4*^fXGE=a&>TgVAx1C3p$}X5R%|dD^=oYDHLrYH!(v7*A(f zw)~dZ6OH?feYQi8ds!P;#EAx22RfF<0qoWveE!XJpy(nYkosaeM(5p`-o5O_35P(WGPvtQ3Izr4KTNm-5`UylAhJ}*v3 z+q}nrX4;N(y{9k6{_y+Y36Xx{im}?6-|kXf_2$rB8Qmgd5SrfBLkUdJbZBl4?4i;L zjP2=RJk@{2m%M56Aq{yxv&9Px>^0IMG(78I<;Cy7Dyr-w%^)^@g_eAU3nmPGqZ8M_ z`zyJGDmq*f&-m69Bfc(i-;fVS3{O9Dr}F(BCwfGKZ@RXJUlOZDvUB*)o0d@u$%&Hj^#E$7B+P?}Ln1T$tp~8pw{a}CdHKfmN(D8*Np+YeOhi~j&V(6j+P4}y#S59f zwL;=e^Aq{$0^N{sxCstc%XPP&U6+&Ku$(2-;THO89J7)13ZGh{p{cYvbA*Le0>bcy zPoj21E<+Q1Bl1!(%Z^=RJNQ(a(Ef|55W(|hW$xT{)Dra5zhg@v3I_Pe9%BdUy-oY! zd22bmt>{6`XvUly{MtpTr<(BfT?;MOa=Wj~a`V(YJMdUoxjQB<T1+%93!NUHk#p_^)uSQ1_;KQC`tKiSYiif=GS9Jqi<8$s-LY@B@&Qk zW@0g+bny7NcQ$4h78Fe8as1o}4cEjt-mI1^VsR1lHHrW7__V~*gx8AyA-eoCm*okE z0eDU=%PrmAJf_IJ=mu=SMfBWNqBN181BPM?lG5)-;*~pu4oy4p!i7%lehN4w5u zRZnw~%x-}K(sU@*+|h&QlR`ZcK8s8{MFFuc(wW9`84)(iBDj&8rtvj1fH&V&pJ(M# zQw4RQUFjjQ!Sw#WJ4^Gk8%rkhLjHYEK(iA2Wy2m-cL z{-K>-HyR`C!;Uvd`t6Am2Cs%rUjgPXOi5DGgh50wS&WXGs9qwy{0**M8n>2X+RQNU z3#}NLBz&2ZQ*tqbbZ*;p^?5H`9c_iD0Mf+fyA9OOp|`syUktCk-e(V<-JSK znZ3==ZN2b(UC2F1tOr7a}#~q>F4LHbS?GT1N~{hTl1(0OUOJnE2SBh zBUJBW8WcI}3uvfc);JS;?jSu9;k|^j$isx-2s=1uWcZm_67(g!%#6(ZgLz<|G3OfZ zVmq~XpFO+RAb_1^rcdIVub7gZU(bk#u?@$TT*f|I^>BrbU2~^T5 zcjUxrtTk|!(Kw`PH|6Sk=xIb~><`zJnR?a5#Y)R~ZmG*&rjAuAz;z9j>yvAn;j3~% zQc<`#w~ON@tM+^!%JwQ47GT&M85nhP5>C&QbriMqFI~3CAjbTIiV z;TAbeo4}B_2U*(Z!^}#72UIkyXtS|V!Pd8TvdNs~bn(W9ux5Gcq(Xl~Dpe+lrq4+=L7Bl~Llx z^c~lUSVmf*d+c|-gsp%nFOSc-_=@w-PCvB1Zz#Jg=?We&0{PY-YM=02Vt$R){vfp99)3mD4auWKh*xLGPD;?&8ps>!r!4U-}6 z;?x{5H8W%WzJTV!vQ$tH z=SU!)(Khk$x{YH^%^lGkoj8Pq6#x}JHp5)Q(b!w7?Kr)4JPXskX+Ll3=Y{I>DY%o) zm4H}Ix3q4lv6I0S`GYUH(feZok1xBa-t9ETIIp)*Tcb6#DX3DB^|`oXNhctfoWz%Z zdspH$hb_y4*k0lKiO#bpd^CT6{IR3fqK9)&SK$2FT#251ge3D(I7rY%8t~Kxtz@x7 z(b=&IG<0CI2IT9$b7CDa;nz9BS(myT^bnH}cvz@Gr|l7PGolOoJ6CU8gv_3JPkU!h z;0X)XwhQz6-o~<$dK|gi?Ki206Qq%jomogcarXyQn%dkR%d0(BpA&-+o6g*}1BjxQ zMJHijLvyn`u@dh!0iA)41DqQ|pOwT758<+cc=8G-IPOV(LwRR61y_*j;P0>!1z))r zC^gPdYY|E!D-rhHLJd?QIS;hsxMMBUl4PG4w+}9iU1++YWR2M5gFng-!nbU$?dD~& zf-lA4~`r6UP4=%q(hZZ1YbhM9MFFR0*V!9_zdr z%;USluy-ajha{zU(m`;g^&|r;Ns({#6&z~oB)1N6jOC<%pdK^l0o|&$#h*k_qS}#a zA~!H72uifKqu>6NRj_)SQ|c`Ho()V*UQJK>nYlfEejd+Y0eB;gp-TwmF|Lfus}W^L z)+F5*dqrl4-5qB6HmlOF#|EiZjaHvotC=oYrFdS*SxsXemh>k2bk!$PFI6`F$V^fm zKi~m^ebfq#sG)px{?uoM<8L+OjrM+XVoFqTHLE+l$5jk)>^GFo@NSMCnDSLNyrUW2 z4*P*W@v}`8Felz=BOC>G* zUV6lwYjltpkHZm(ltNrHrXI^2c_5;!l2l{Eu}>z$A@FsrrWRANZZJN7!Sx!H6@8f2lO@kIAeoqwp( z8mE?8@hPvGdtuuFyXcATU7U!8sexH!TOBGwwm< zQ@0S45H==gcx5i>v{Rm4ti2-czL4}C1kbag{cNICJJN(KJJJlEwXn3P(DfAVlIHyD zBLQi9LXTvpJRw39EYT!sNo2jOyYCYp4R-SOf-S9F#Pke|MDz?yRP+pT$rzZpBr{SZ z?$E4TD0##;MbY^aW16O(_B*YZ+2r=yvgySyxGBXMuq*>*=S$$r{073I6+%IN2@}!@ zs8$I-2@i~;UWy<`%OH~!f-L+Jtfdo*a76EeQ>ccofLV?Li&BV)1tnFXlhlGk-ih>; zCaVdE_6bItP(+`CeX7B@%OU1Ih<+(fKJg0~5sb=0iasoan$=$RAbs*#@k!_lOg*_l zCVpR-^DBRTeI8!VWlq0ADfQI4jww7sfVeW|z{kG`3E4~_R*QEWZG5LaJ*iXS^{@rd ztDlsDz6*hWN-{oCzT&>if^U`tZ

    ^<7W@(9yr9mUm2tK?C|&v@%Rm35X`o{%fo+w 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%0A?GZzYp7)bLPx}z&<;hZaX^DZ#RA|ACLXD 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%0Aok&dtGbv;D|BgjT@f(sv0`S4#(FudZ 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 literal 0 HcmV?d00001 From 06569dead31c06a5f315cab23eeab0045e106c81 Mon Sep 17 00:00:00 2001 From: eitch Date: Tue, 25 May 2010 19:41:34 +0000 Subject: [PATCH 008/173] --- .../privilege/base/PrivilegeContainer.java | 15 +++- .../handler/DefaultEncryptionHandler.java | 79 +++++++++++++++++++ ...andler.java => DefaultSessionHandler.java} | 38 ++++++--- .../privilege/handler/EncryptionHandler.java | 22 ++++++ ...vilegeHandler.java => SessionHandler.java} | 2 +- .../privilege/i18n/PrivilegeException.java | 8 ++ .../eitchnet/privilege/model/Certificate.java | 2 +- src/ch/eitchnet/privilege/model/Session.java | 2 +- 8 files changed, 149 insertions(+), 19 deletions(-) create mode 100644 src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java rename src/ch/eitchnet/privilege/handler/{DefaultPrivilegeHandler.java => DefaultSessionHandler.java} (81%) create mode 100644 src/ch/eitchnet/privilege/handler/EncryptionHandler.java rename src/ch/eitchnet/privilege/handler/{PrivilegeHandler.java => SessionHandler.java} (96%) diff --git a/src/ch/eitchnet/privilege/base/PrivilegeContainer.java b/src/ch/eitchnet/privilege/base/PrivilegeContainer.java index 3e99d76c0..9a1560f03 100644 --- a/src/ch/eitchnet/privilege/base/PrivilegeContainer.java +++ b/src/ch/eitchnet/privilege/base/PrivilegeContainer.java @@ -13,7 +13,8 @@ package ch.eitchnet.privilege.base; import java.io.File; import ch.eitchnet.privilege.handler.PolicyHandler; -import ch.eitchnet.privilege.handler.PrivilegeHandler; +import ch.eitchnet.privilege.handler.SessionHandler; +import ch.eitchnet.privilege.handler.EncryptionHandler; /** * @@ -28,8 +29,9 @@ public class PrivilegeContainer { instance = new PrivilegeContainer(); } - private PrivilegeHandler privilegeHandler; + private SessionHandler privilegeHandler; private PolicyHandler policyHandler; + private EncryptionHandler encryptionHandler; public static PrivilegeContainer getInstance() { return instance; @@ -45,7 +47,7 @@ public class PrivilegeContainer { /** * @return the privilegeHandler */ - public PrivilegeHandler getPrivilegeHandler() { + public SessionHandler getPrivilegeHandler() { return privilegeHandler; } @@ -56,6 +58,13 @@ public class PrivilegeContainer { return policyHandler; } + /** + * @return the encryptionHandler + */ + public EncryptionHandler getEncryptionHandler() { + return encryptionHandler; + } + public void initialize(File privilegeContainerXml) { // TODO implement } diff --git a/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java b/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java new file mode 100644 index 000000000..8bdde3da3 --- /dev/null +++ b/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.handler; + +import java.io.UnsupportedEncodingException; +import java.math.BigInteger; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; + +import org.dom4j.Element; + +import ch.eitchnet.privilege.i18n.PrivilegeException; + +/** + * @author rvonburg + * + */ +public class DefaultEncryptionHandler implements EncryptionHandler { + + public static final String HASH_ALGORITHM = "SHA-1"; + + /** + * Hex char table for fast calculating of hex value + */ + private static final byte[] HEX_CHAR_TABLE = { (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', + (byte) '5', (byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', + (byte) 'e', (byte) 'f' }; + + /** + * @see ch.eitchnet.privilege.handler.EncryptionHandler#convertToHash(java.lang.String) + */ + @Override + public String convertToHash(String string) { + try { + + MessageDigest digest = MessageDigest.getInstance(HASH_ALGORITHM); + byte[] hashArray = digest.digest(string.getBytes()); + + byte[] hex = new byte[2 * hashArray.length]; + int index = 0; + + for (byte b : hashArray) { + int v = b & 0xFF; + hex[index++] = HEX_CHAR_TABLE[v >>> 4]; + hex[index++] = HEX_CHAR_TABLE[v & 0xF]; + } + + return new String(hex, "ASCII"); + + } catch (NoSuchAlgorithmException e) { + throw new PrivilegeException("Algorithm " + HASH_ALGORITHM + " was not found!", e); + } catch (UnsupportedEncodingException e) { + throw new PrivilegeException("Charset ASCII is not supported!", e); + } + } + + /** + * @see ch.eitchnet.privilege.handler.EncryptionHandler#nextToken() + */ + @Override + public String nextToken() { + SecureRandom secureRandom = new SecureRandom(); + String randomString = new BigInteger(130, secureRandom).toString(32); + return randomString; + } + + public void initialize(Element element) { + // TODO implement + } +} diff --git a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java similarity index 81% rename from src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java rename to src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java index b548a13cf..9e3cf646b 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java @@ -13,6 +13,8 @@ package ch.eitchnet.privilege.handler; import java.io.File; import java.util.Map; +import org.apache.log4j.Logger; + import ch.eitchnet.privilege.base.PrivilegeContainer; import ch.eitchnet.privilege.i18n.AccessDeniedException; import ch.eitchnet.privilege.i18n.PrivilegeException; @@ -26,14 +28,17 @@ import ch.eitchnet.privilege.model.UserState; * @author rvonburg * */ -public class DefaultPrivilegeHandler implements PrivilegeHandler { +public class DefaultSessionHandler implements SessionHandler { + + private static final Logger logger = Logger.getLogger(DefaultSessionHandler.class); + + private static long lastSessionId; private Map userMap; - private Map sessionMap; /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#actionAllowed(ch.eitchnet.privilege.model.Certificate, + * @see ch.eitchnet.privilege.handler.SessionHandler#actionAllowed(ch.eitchnet.privilege.model.Certificate, * ch.eitchnet.privilege.model.Restrictable) * * @throws AccessDeniedException @@ -58,7 +63,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { if (!sessionCertificate.equals(certificate)) throw new PrivilegeException("Received illegal certificate for session id " + certificate.getSessionId()); - // TODO is this maybe overkill? + // TODO is this overkill? // validate authToken from certificate using the sessions authPassword String authToken = certificate.getAuthToken(certificateSessionPair.session.getAuthPassword()); if (authToken == null || !authToken.equals(certificateSessionPair.session.getAuthToken())) @@ -73,14 +78,11 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } // now validate on policy handler - PrivilegeContainer.getInstance().getPolicyHandler().actionAllowed(user, restrictable); - - // TODO Auto-generated method stub - return false; + return PrivilegeContainer.getInstance().getPolicyHandler().actionAllowed(user, restrictable); } /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#authenticate(java.lang.String, java.lang.String) + * @see ch.eitchnet.privilege.handler.SessionHandler#authenticate(java.lang.String, java.lang.String) * * @throws AccessDeniedException * if the user credentials are not valid @@ -94,8 +96,10 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { else if (password == null || password.length() < 3) throw new PrivilegeException("The given password is shorter than 3 characters"); + EncryptionHandler encryptionHandler = PrivilegeContainer.getInstance().getEncryptionHandler(); + // we only work with hashed passwords - String passwordHash = password; // TODO hash password + String passwordHash = encryptionHandler.convertToHash(password); // get user object User user = userMap.get(username); @@ -112,10 +116,11 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { throw new AccessDeniedException("User " + username + " is not ENABLED. State is: " + user.getState()); // get 2 auth tokens - String authToken = ""; // TODO get auth token - String authPassword = ""; // TODO get auth password + String authToken = encryptionHandler.nextToken(); + String authPassword = encryptionHandler.nextToken(); - String sessionId = ""; // TODO get next session id + // get next session id + String sessionId = nextSessionId(); // create certificate Certificate certificate = new Certificate(sessionId, username, authToken, authPassword, user.getLocale()); @@ -125,10 +130,17 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { .currentTimeMillis()); sessionMap.put(sessionId, new CertificateSessionPair(session, certificate)); + // log + logger.info("Authenticated: " + session); + // return the certificate return certificate; } + private String nextSessionId() { + return Long.toString(++lastSessionId % Long.MAX_VALUE); + } + public void initialize(File userFile) { // TODO implement } diff --git a/src/ch/eitchnet/privilege/handler/EncryptionHandler.java b/src/ch/eitchnet/privilege/handler/EncryptionHandler.java new file mode 100644 index 000000000..bda572e0c --- /dev/null +++ b/src/ch/eitchnet/privilege/handler/EncryptionHandler.java @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.handler; + +/** + * @author rvonburg + * + */ +public interface EncryptionHandler { + + public String nextToken(); + + public String convertToHash(String string); +} diff --git a/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java b/src/ch/eitchnet/privilege/handler/SessionHandler.java similarity index 96% rename from src/ch/eitchnet/privilege/handler/PrivilegeHandler.java rename to src/ch/eitchnet/privilege/handler/SessionHandler.java index b62df401f..39b35cc45 100644 --- a/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java +++ b/src/ch/eitchnet/privilege/handler/SessionHandler.java @@ -19,7 +19,7 @@ import ch.eitchnet.privilege.model.User; * @author rvonburg * */ -public interface PrivilegeHandler { +public interface SessionHandler { /** * @param certificate diff --git a/src/ch/eitchnet/privilege/i18n/PrivilegeException.java b/src/ch/eitchnet/privilege/i18n/PrivilegeException.java index 11ba8dc4f..4960a42f9 100644 --- a/src/ch/eitchnet/privilege/i18n/PrivilegeException.java +++ b/src/ch/eitchnet/privilege/i18n/PrivilegeException.java @@ -25,4 +25,12 @@ public class PrivilegeException extends RuntimeException { super(string); } + /** + * + * @param string + * @param t + */ + public PrivilegeException(String string, Throwable t) { + super(string, t); + } } diff --git a/src/ch/eitchnet/privilege/model/Certificate.java b/src/ch/eitchnet/privilege/model/Certificate.java index 42b473ab4..bb92989fa 100644 --- a/src/ch/eitchnet/privilege/model/Certificate.java +++ b/src/ch/eitchnet/privilege/model/Certificate.java @@ -161,6 +161,6 @@ public class Certificate implements Serializable { */ @Override public String toString() { - return "Certificate [locale=" + locale + ", sessionId=" + sessionId + ", username=" + username + "]"; + return "Certificate [sessionId=" + sessionId + ", username=" + username + ", locale=" + locale + "]"; } } diff --git a/src/ch/eitchnet/privilege/model/Session.java b/src/ch/eitchnet/privilege/model/Session.java index b9dd1ea83..a90249f3d 100644 --- a/src/ch/eitchnet/privilege/model/Session.java +++ b/src/ch/eitchnet/privilege/model/Session.java @@ -125,6 +125,6 @@ public class Session { */ @Override public String toString() { - return "Session [loginTime=" + loginTime + ", sessionId=" + sessionId + ", username=" + username + "]"; + return "Session [username=" + username + ", sessionId=" + sessionId + ", loginTime=" + loginTime + "]"; } } From 4d46267b1d264514dca281c83071c120fe212bd4 Mon Sep 17 00:00:00 2001 From: eitch Date: Tue, 25 May 2010 21:15:35 +0000 Subject: [PATCH 009/173] --- .classpath | 1 + config/PrivilegeContainer.xml | 20 +++++++ lib/log4j-1.2.15.jar | Bin 0 -> 391834 bytes .../privilege/base/PrivilegeContainer.java | 45 +++++++++++++--- .../base/PrivilegeContainerObject.java | 22 ++++++++ .../handler/DefaultEncryptionHandler.java | 21 ++++++-- .../handler/DefaultPolicyHandler.java | 38 ++++++++++--- .../handler/DefaultSessionHandler.java | 10 ++-- .../privilege/handler/EncryptionHandler.java | 4 +- .../privilege/handler/PolicyHandler.java | 3 +- .../privilege/handler/SessionHandler.java | 3 +- .../privilege/helper/ClassHelper.java | 33 ++++++++++++ .../privilege/helper/ConfigurationHelper.java | 39 ++++++++++++++ .../eitchnet/privilege/helper/XmlHelper.java | 50 ++++++++++++++++++ 14 files changed, 265 insertions(+), 24 deletions(-) create mode 100644 config/PrivilegeContainer.xml create mode 100644 lib/log4j-1.2.15.jar create mode 100644 src/ch/eitchnet/privilege/base/PrivilegeContainerObject.java create mode 100644 src/ch/eitchnet/privilege/helper/ClassHelper.java create mode 100644 src/ch/eitchnet/privilege/helper/ConfigurationHelper.java create mode 100644 src/ch/eitchnet/privilege/helper/XmlHelper.java diff --git a/.classpath b/.classpath index 63421de7a..bba4c498c 100644 --- a/.classpath +++ b/.classpath @@ -3,5 +3,6 @@ + diff --git a/config/PrivilegeContainer.xml b/config/PrivilegeContainer.xml new file mode 100644 index 000000000..b40c6a226 --- /dev/null +++ b/config/PrivilegeContainer.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lib/log4j-1.2.15.jar b/lib/log4j-1.2.15.jar new file mode 100644 index 0000000000000000000000000000000000000000..c930a6ab4d4b73c1a6feb9e929091205664bb340 GIT binary patch literal 391834 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 z≫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

    sl*CtvD7j$_;?KD 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

    {0o23%x0zG^lL-+)0hE&z$21eha>paa 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 LL+IIXJH`J1EdB>a literal 0 HcmV?d00001 diff --git a/src/ch/eitchnet/privilege/base/PrivilegeContainer.java b/src/ch/eitchnet/privilege/base/PrivilegeContainer.java index 9a1560f03..522b7ada2 100644 --- a/src/ch/eitchnet/privilege/base/PrivilegeContainer.java +++ b/src/ch/eitchnet/privilege/base/PrivilegeContainer.java @@ -12,9 +12,13 @@ package ch.eitchnet.privilege.base; import java.io.File; +import org.dom4j.Element; + +import ch.eitchnet.privilege.handler.EncryptionHandler; import ch.eitchnet.privilege.handler.PolicyHandler; import ch.eitchnet.privilege.handler.SessionHandler; -import ch.eitchnet.privilege.handler.EncryptionHandler; +import ch.eitchnet.privilege.helper.ClassHelper; +import ch.eitchnet.privilege.helper.XmlHelper; /** * @@ -23,13 +27,17 @@ import ch.eitchnet.privilege.handler.EncryptionHandler; */ public class PrivilegeContainer { + public static final String XML_HANDLER_ENCRYPTION = "EncryptionHandler"; + public static final String XML_HANDLER_SESSION = "SessionHandler"; + public static final String XML_HANDLER_POLICY = "PolicyHandler"; + private static final PrivilegeContainer instance; static { instance = new PrivilegeContainer(); } - private SessionHandler privilegeHandler; + private SessionHandler sessionHandler; private PolicyHandler policyHandler; private EncryptionHandler encryptionHandler; @@ -45,10 +53,10 @@ public class PrivilegeContainer { } /** - * @return the privilegeHandler + * @return the sessionHandler */ - public SessionHandler getPrivilegeHandler() { - return privilegeHandler; + public SessionHandler getSessionHandler() { + return sessionHandler; } /** @@ -66,7 +74,30 @@ public class PrivilegeContainer { } public void initialize(File privilegeContainerXml) { - // TODO implement - } + // parse container xml file to XML document + Element containerRootElement = XmlHelper.parseDocument(privilegeContainerXml).getRootElement(); + + // instantiate session handler + Element sessionHandlerElement = containerRootElement.element(XML_HANDLER_SESSION); + String sessionHandlerClassName = sessionHandlerElement.attributeValue("class"); + SessionHandler sessionHandler = ClassHelper.instantiateClass(sessionHandlerClassName); + sessionHandler.initialize(sessionHandlerElement); + + // instantiate encryption handler + Element encryptionHandlerElement = containerRootElement.element(XML_HANDLER_ENCRYPTION); + String encryptionHandlerClassName = encryptionHandlerElement.attributeValue("class"); + EncryptionHandler encryptionHandler = ClassHelper.instantiateClass(encryptionHandlerClassName); + encryptionHandler.initialize(encryptionHandlerElement); + + // instantiate policy handler + Element policyHandlerElement = containerRootElement.element(XML_HANDLER_POLICY); + String policyHandlerClassName = policyHandlerElement.attributeValue("class"); + PolicyHandler policyHandler = ClassHelper.instantiateClass(policyHandlerClassName); + policyHandler.initialize(policyHandlerElement); + + this.sessionHandler = sessionHandler; + this.encryptionHandler = encryptionHandler; + this.policyHandler = policyHandler; + } } diff --git a/src/ch/eitchnet/privilege/base/PrivilegeContainerObject.java b/src/ch/eitchnet/privilege/base/PrivilegeContainerObject.java new file mode 100644 index 000000000..88a8b2c2f --- /dev/null +++ b/src/ch/eitchnet/privilege/base/PrivilegeContainerObject.java @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.base; + +import org.dom4j.Element; + +/** + * @author rvonburg + * + */ +public interface PrivilegeContainerObject { + + public void initialize(Element element); +} diff --git a/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java b/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java index 8bdde3da3..ad855e4fd 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java @@ -15,9 +15,11 @@ import java.math.BigInteger; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; +import java.util.Map; import org.dom4j.Element; +import ch.eitchnet.privilege.helper.ConfigurationHelper; import ch.eitchnet.privilege.i18n.PrivilegeException; /** @@ -26,7 +28,9 @@ import ch.eitchnet.privilege.i18n.PrivilegeException; */ public class DefaultEncryptionHandler implements EncryptionHandler { - public static final String HASH_ALGORITHM = "SHA-1"; + public static final String XML_PARAM_HASH_ALGORITHM = "hashAlgorithm"; + + public String hashAlgorithm; /** * Hex char table for fast calculating of hex value @@ -42,7 +46,7 @@ public class DefaultEncryptionHandler implements EncryptionHandler { public String convertToHash(String string) { try { - MessageDigest digest = MessageDigest.getInstance(HASH_ALGORITHM); + MessageDigest digest = MessageDigest.getInstance(hashAlgorithm); byte[] hashArray = digest.digest(string.getBytes()); byte[] hex = new byte[2 * hashArray.length]; @@ -57,7 +61,7 @@ public class DefaultEncryptionHandler implements EncryptionHandler { return new String(hex, "ASCII"); } catch (NoSuchAlgorithmException e) { - throw new PrivilegeException("Algorithm " + HASH_ALGORITHM + " was not found!", e); + throw new PrivilegeException("Algorithm " + hashAlgorithm + " was not found!", e); } catch (UnsupportedEncodingException e) { throw new PrivilegeException("Charset ASCII is not supported!", e); } @@ -73,7 +77,16 @@ public class DefaultEncryptionHandler implements EncryptionHandler { return randomString; } + /** + * @see ch.eitchnet.privilege.base.PrivilegeContainerObject#initialize(org.dom4j.Element) + */ public void initialize(Element element) { - // TODO implement + + Element parameterElement = element.element("Parameters"); + Map parameterMap = ConfigurationHelper.convertToParameterMap(parameterElement); + + // get and test configured algorithm + hashAlgorithm = parameterMap.get(XML_PARAM_HASH_ALGORITHM); + convertToHash("test"); } } diff --git a/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java index e9e5176c2..96418bf6e 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java @@ -10,9 +10,11 @@ package ch.eitchnet.privilege.handler; -import java.io.File; import java.util.Map; +import org.dom4j.Element; + +import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.Restrictable; import ch.eitchnet.privilege.model.RestrictionPolicy; import ch.eitchnet.privilege.model.User; @@ -31,14 +33,36 @@ public class DefaultPolicyHandler implements PolicyHandler { */ @Override public boolean actionAllowed(User user, Restrictable restrictable) { - - // TODO auth user - - // TODO Auto-generated method stub - return false; + + // user and restrictable must not be null + if (user == null) + throw new PrivilegeException("User may not be null!"); + else if (restrictable == null) + throw new PrivilegeException("Restrictable may not be null!"); + + // validate restriction key for this restrictable + String restrictionKey = restrictable.getRestrictionKey(); + if (restrictionKey == null || restrictionKey.length() < 3) { + throw new PrivilegeException( + "The RestrictionKey may not be shorter than 3 characters. Invalid Restrictable " + + restrictable.getClass().getName()); + } + + // get restriction policy + RestrictionPolicy policy = policyMap.get(restrictionKey); + if (policy == null) { + throw new PrivilegeException("No RestrictionPolicy exists for the RestrictionKey " + restrictionKey + + " for Restrictable " + restrictable.getClass().getName()); + } + + // delegate checking to restriction policy + return policy.actionAllowed(user, restrictable); } - public void initialize(File policyXml) { + /** + * @see ch.eitchnet.privilege.base.PrivilegeContainerObject#initialize(org.dom4j.Element) + */ + public void initialize(Element element) { // TODO implement } } diff --git a/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java b/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java index 9e3cf646b..56e33ebba 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java @@ -10,10 +10,10 @@ package ch.eitchnet.privilege.handler; -import java.io.File; import java.util.Map; import org.apache.log4j.Logger; +import org.dom4j.Element; import ch.eitchnet.privilege.base.PrivilegeContainer; import ch.eitchnet.privilege.i18n.AccessDeniedException; @@ -48,6 +48,7 @@ public class DefaultSessionHandler implements SessionHandler { @Override public boolean actionAllowed(Certificate certificate, Restrictable restrictable) { + // certificate and restrictable must not be null if (certificate == null) throw new PrivilegeException("Certificate may not be null!"); else if (restrictable == null) @@ -74,7 +75,7 @@ public class DefaultSessionHandler implements SessionHandler { User user = userMap.get(certificateSessionPair.session.getUsername()); if (user == null) { throw new PrivilegeException( - "Oh now, how did this happen: No User in user map although certificate is valid!"); + "Oh boy, how did this happen: No User in user map although the certificate is valid!"); } // now validate on policy handler @@ -141,7 +142,10 @@ public class DefaultSessionHandler implements SessionHandler { return Long.toString(++lastSessionId % Long.MAX_VALUE); } - public void initialize(File userFile) { + /** + * @see ch.eitchnet.privilege.base.PrivilegeContainerObject#initialize(org.dom4j.Element) + */ + public void initialize(Element element) { // TODO implement } diff --git a/src/ch/eitchnet/privilege/handler/EncryptionHandler.java b/src/ch/eitchnet/privilege/handler/EncryptionHandler.java index bda572e0c..c26be38a4 100644 --- a/src/ch/eitchnet/privilege/handler/EncryptionHandler.java +++ b/src/ch/eitchnet/privilege/handler/EncryptionHandler.java @@ -10,11 +10,13 @@ package ch.eitchnet.privilege.handler; +import ch.eitchnet.privilege.base.PrivilegeContainerObject; + /** * @author rvonburg * */ -public interface EncryptionHandler { +public interface EncryptionHandler extends PrivilegeContainerObject{ public String nextToken(); diff --git a/src/ch/eitchnet/privilege/handler/PolicyHandler.java b/src/ch/eitchnet/privilege/handler/PolicyHandler.java index 992a45f16..93a4b45d6 100644 --- a/src/ch/eitchnet/privilege/handler/PolicyHandler.java +++ b/src/ch/eitchnet/privilege/handler/PolicyHandler.java @@ -10,6 +10,7 @@ package ch.eitchnet.privilege.handler; +import ch.eitchnet.privilege.base.PrivilegeContainerObject; import ch.eitchnet.privilege.model.Restrictable; import ch.eitchnet.privilege.model.User; @@ -17,7 +18,7 @@ import ch.eitchnet.privilege.model.User; * @author rvonburg * */ -public interface PolicyHandler { +public interface PolicyHandler extends PrivilegeContainerObject { public boolean actionAllowed(User user, Restrictable restrictable); } diff --git a/src/ch/eitchnet/privilege/handler/SessionHandler.java b/src/ch/eitchnet/privilege/handler/SessionHandler.java index 39b35cc45..9b9d26468 100644 --- a/src/ch/eitchnet/privilege/handler/SessionHandler.java +++ b/src/ch/eitchnet/privilege/handler/SessionHandler.java @@ -10,6 +10,7 @@ package ch.eitchnet.privilege.handler; +import ch.eitchnet.privilege.base.PrivilegeContainerObject; import ch.eitchnet.privilege.i18n.AccessDeniedException; import ch.eitchnet.privilege.model.Certificate; import ch.eitchnet.privilege.model.Restrictable; @@ -19,7 +20,7 @@ import ch.eitchnet.privilege.model.User; * @author rvonburg * */ -public interface SessionHandler { +public interface SessionHandler extends PrivilegeContainerObject { /** * @param certificate diff --git a/src/ch/eitchnet/privilege/helper/ClassHelper.java b/src/ch/eitchnet/privilege/helper/ClassHelper.java new file mode 100644 index 000000000..521c4df4f --- /dev/null +++ b/src/ch/eitchnet/privilege/helper/ClassHelper.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.helper; + +import ch.eitchnet.privilege.i18n.PrivilegeException; + +/** + * @author rvonburg + * + */ +public class ClassHelper { + + @SuppressWarnings("unchecked") + public static T instantiateClass(String className) { + try { + + Class clazz = (Class) Class.forName(className); + + return clazz.getConstructor().newInstance(); + + } catch (Exception e) { + throw new PrivilegeException("The class " + className + " could not be instantiated: ", e); + } + } +} diff --git a/src/ch/eitchnet/privilege/helper/ConfigurationHelper.java b/src/ch/eitchnet/privilege/helper/ConfigurationHelper.java new file mode 100644 index 000000000..177c5a3d7 --- /dev/null +++ b/src/ch/eitchnet/privilege/helper/ConfigurationHelper.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.helper; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.dom4j.Element; + +/** + * @author rvonburg + * + */ +public class ConfigurationHelper { + + @SuppressWarnings("unchecked") + public static Map convertToParameterMap(Element element) { + + Map parameterMap = new HashMap(); + + List elements = element.elements("Parameter"); + for (Element parameter : elements) { + String name = parameter.attributeValue("name"); + String value = parameter.attributeValue("value"); + parameterMap.put(name, value); + } + + return parameterMap; + } +} diff --git a/src/ch/eitchnet/privilege/helper/XmlHelper.java b/src/ch/eitchnet/privilege/helper/XmlHelper.java new file mode 100644 index 000000000..3a8f2d4b4 --- /dev/null +++ b/src/ch/eitchnet/privilege/helper/XmlHelper.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.helper; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; + +import org.apache.log4j.Logger; +import org.dom4j.Document; +import org.dom4j.DocumentException; +import org.dom4j.io.SAXReader; + +import ch.eitchnet.privilege.i18n.PrivilegeException; + +/** + * @author rvonburg + * + */ +public class XmlHelper { + + private static final Logger logger = Logger.getLogger(XmlHelper.class); + + public static Document parseDocument(File xmlFile) { + try { + + InputStream inStream = new FileInputStream(xmlFile); + + SAXReader reader = new SAXReader(); + Document document = reader.read(inStream); + + logger.info("Read Xml document " + document.getName()); + return document; + + } catch (FileNotFoundException e) { + throw new PrivilegeException("The Xml file does not exist or is not readable: " + xmlFile.getAbsolutePath()); + } catch (DocumentException e) { + throw new PrivilegeException("the Xml file " + xmlFile.getAbsolutePath() + " is not parseable:", e); + } + } +} From 7de4b5f4f12c6f04e21ddc94120b483586e7f4bf Mon Sep 17 00:00:00 2001 From: eitch Date: Sat, 29 May 2010 18:47:04 +0000 Subject: [PATCH 010/173] --- .../privilege/base/PrivilegeContainer.java | 16 ++--- .../eitchnet/privilege/base/XmlConstants.java | 27 ++++++++ .../handler/DefaultEncryptionHandler.java | 7 +- .../handler/DefaultPolicyHandler.java | 14 ++-- .../handler/DefaultSessionHandler.java | 39 +++++++++-- .../privilege/handler/PersistenceHandler.java | 27 ++++++++ .../privilege/handler/PolicyHandler.java | 4 +- .../privilege/handler/SessionHandler.java | 2 +- .../{ => internal}/DefaultRestriction.java | 66 ++++--------------- .../privilege/model/internal/Privilege.java | 57 ++++++++++++++++ .../{ => internal}/RestrictionPolicy.java | 6 +- .../privilege/model/internal/Role.java | 39 +++++++++++ .../model/{ => internal}/Session.java | 2 +- .../privilege/model/{ => internal}/User.java | 4 +- 14 files changed, 221 insertions(+), 89 deletions(-) create mode 100644 src/ch/eitchnet/privilege/base/XmlConstants.java create mode 100644 src/ch/eitchnet/privilege/handler/PersistenceHandler.java rename src/ch/eitchnet/privilege/model/{ => internal}/DefaultRestriction.java (53%) create mode 100644 src/ch/eitchnet/privilege/model/internal/Privilege.java rename src/ch/eitchnet/privilege/model/{ => internal}/RestrictionPolicy.java (53%) create mode 100644 src/ch/eitchnet/privilege/model/internal/Role.java rename src/ch/eitchnet/privilege/model/{ => internal}/Session.java (98%) rename src/ch/eitchnet/privilege/model/{ => internal}/User.java (95%) diff --git a/src/ch/eitchnet/privilege/base/PrivilegeContainer.java b/src/ch/eitchnet/privilege/base/PrivilegeContainer.java index 522b7ada2..f715748bd 100644 --- a/src/ch/eitchnet/privilege/base/PrivilegeContainer.java +++ b/src/ch/eitchnet/privilege/base/PrivilegeContainer.java @@ -27,10 +27,6 @@ import ch.eitchnet.privilege.helper.XmlHelper; */ public class PrivilegeContainer { - public static final String XML_HANDLER_ENCRYPTION = "EncryptionHandler"; - public static final String XML_HANDLER_SESSION = "SessionHandler"; - public static final String XML_HANDLER_POLICY = "PolicyHandler"; - private static final PrivilegeContainer instance; static { @@ -79,20 +75,20 @@ public class PrivilegeContainer { Element containerRootElement = XmlHelper.parseDocument(privilegeContainerXml).getRootElement(); // instantiate session handler - Element sessionHandlerElement = containerRootElement.element(XML_HANDLER_SESSION); - String sessionHandlerClassName = sessionHandlerElement.attributeValue("class"); + Element sessionHandlerElement = containerRootElement.element(XmlConstants.XML_HANDLER_SESSION); + String sessionHandlerClassName = sessionHandlerElement.attributeValue(XmlConstants.XML_ATTR_CLASS); SessionHandler sessionHandler = ClassHelper.instantiateClass(sessionHandlerClassName); sessionHandler.initialize(sessionHandlerElement); // instantiate encryption handler - Element encryptionHandlerElement = containerRootElement.element(XML_HANDLER_ENCRYPTION); - String encryptionHandlerClassName = encryptionHandlerElement.attributeValue("class"); + Element encryptionHandlerElement = containerRootElement.element(XmlConstants.XML_HANDLER_ENCRYPTION); + String encryptionHandlerClassName = encryptionHandlerElement.attributeValue(XmlConstants.XML_ATTR_CLASS); EncryptionHandler encryptionHandler = ClassHelper.instantiateClass(encryptionHandlerClassName); encryptionHandler.initialize(encryptionHandlerElement); // instantiate policy handler - Element policyHandlerElement = containerRootElement.element(XML_HANDLER_POLICY); - String policyHandlerClassName = policyHandlerElement.attributeValue("class"); + Element policyHandlerElement = containerRootElement.element(XmlConstants.XML_HANDLER_POLICY); + String policyHandlerClassName = policyHandlerElement.attributeValue(XmlConstants.XML_ATTR_CLASS); PolicyHandler policyHandler = ClassHelper.instantiateClass(policyHandlerClassName); policyHandler.initialize(policyHandlerElement); diff --git a/src/ch/eitchnet/privilege/base/XmlConstants.java b/src/ch/eitchnet/privilege/base/XmlConstants.java new file mode 100644 index 000000000..5d8a32519 --- /dev/null +++ b/src/ch/eitchnet/privilege/base/XmlConstants.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.base; + +/** + * @author rvonburg + * + */ +public class XmlConstants { + public static final String XML_HANDLER_ENCRYPTION = "EncryptionHandler"; + public static final String XML_HANDLER_SESSION = "SessionHandler"; + public static final String XML_HANDLER_POLICY = "PolicyHandler"; + + public static final String XML_PARAMETERS = "Parameters"; + + public static final String XML_ATTR_CLASS = "class"; + + public static final String XML_PARAM_HASH_ALGORITHM = "hashAlgorithm"; +} diff --git a/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java b/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java index ad855e4fd..a927e2ac7 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java @@ -19,6 +19,7 @@ import java.util.Map; import org.dom4j.Element; +import ch.eitchnet.privilege.base.XmlConstants; import ch.eitchnet.privilege.helper.ConfigurationHelper; import ch.eitchnet.privilege.i18n.PrivilegeException; @@ -28,8 +29,6 @@ import ch.eitchnet.privilege.i18n.PrivilegeException; */ public class DefaultEncryptionHandler implements EncryptionHandler { - public static final String XML_PARAM_HASH_ALGORITHM = "hashAlgorithm"; - public String hashAlgorithm; /** @@ -82,11 +81,11 @@ public class DefaultEncryptionHandler implements EncryptionHandler { */ public void initialize(Element element) { - Element parameterElement = element.element("Parameters"); + Element parameterElement = element.element(XmlConstants.XML_PARAMETERS); Map parameterMap = ConfigurationHelper.convertToParameterMap(parameterElement); // get and test configured algorithm - hashAlgorithm = parameterMap.get(XML_PARAM_HASH_ALGORITHM); + hashAlgorithm = parameterMap.get(XmlConstants.XML_PARAM_HASH_ALGORITHM); convertToHash("test"); } } diff --git a/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java index 96418bf6e..8d3780411 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java @@ -16,8 +16,8 @@ import org.dom4j.Element; import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.Restrictable; -import ch.eitchnet.privilege.model.RestrictionPolicy; -import ch.eitchnet.privilege.model.User; +import ch.eitchnet.privilege.model.internal.RestrictionPolicy; +import ch.eitchnet.privilege.model.internal.Role; /** * @author rvonburg @@ -28,15 +28,15 @@ public class DefaultPolicyHandler implements PolicyHandler { private Map policyMap; /** - * @see ch.eitchnet.privilege.handler.PolicyHandler#actionAllowed(ch.eitchnet.privilege.model.User, + * @see ch.eitchnet.privilege.handler.PolicyHandler#actionAllowed(ch.eitchnet.privilege.model.internal.Role, * ch.eitchnet.privilege.model.Restrictable) */ @Override - public boolean actionAllowed(User user, Restrictable restrictable) { + public boolean actionAllowed(Role role, Restrictable restrictable) { // user and restrictable must not be null - if (user == null) - throw new PrivilegeException("User may not be null!"); + if (role == null) + throw new PrivilegeException("Role may not be null!"); else if (restrictable == null) throw new PrivilegeException("Restrictable may not be null!"); @@ -56,7 +56,7 @@ public class DefaultPolicyHandler implements PolicyHandler { } // delegate checking to restriction policy - return policy.actionAllowed(user, restrictable); + return policy.actionAllowed(role, restrictable); } /** diff --git a/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java b/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java index 56e33ebba..8e07079d3 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java @@ -20,9 +20,10 @@ import ch.eitchnet.privilege.i18n.AccessDeniedException; import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.Certificate; import ch.eitchnet.privilege.model.Restrictable; -import ch.eitchnet.privilege.model.Session; -import ch.eitchnet.privilege.model.User; import ch.eitchnet.privilege.model.UserState; +import ch.eitchnet.privilege.model.internal.Role; +import ch.eitchnet.privilege.model.internal.Session; +import ch.eitchnet.privilege.model.internal.User; /** * @author rvonburg @@ -35,6 +36,7 @@ public class DefaultSessionHandler implements SessionHandler { private static long lastSessionId; private Map userMap; + private Map roleMap; private Map sessionMap; /** @@ -64,7 +66,7 @@ public class DefaultSessionHandler implements SessionHandler { if (!sessionCertificate.equals(certificate)) throw new PrivilegeException("Received illegal certificate for session id " + certificate.getSessionId()); - // TODO is this overkill? + // TODO is validating authToken overkill since the two certificates have already been checked on equality? // validate authToken from certificate using the sessions authPassword String authToken = certificate.getAuthToken(certificateSessionPair.session.getAuthPassword()); if (authToken == null || !authToken.equals(certificateSessionPair.session.getAuthToken())) @@ -78,8 +80,28 @@ public class DefaultSessionHandler implements SessionHandler { "Oh boy, how did this happen: No User in user map although the certificate is valid!"); } - // now validate on policy handler - return PrivilegeContainer.getInstance().getPolicyHandler().actionAllowed(user, restrictable); + // default is to not allow the action + // TODO should default deny/allow policy be configurable? + boolean actionAllowed = false; + + // now iterate roles and validate on policy handler + PolicyHandler policyHandler = PrivilegeContainer.getInstance().getPolicyHandler(); + for (String roleName : user.getRoleList()) { + + Role role = roleMap.get(roleName); + if (role == null) { + logger.error("No role is defined with name " + roleName + " which is configured for user " + user); + continue; + } + + actionAllowed = policyHandler.actionAllowed(role, restrictable); + + // if action is allowed, then break iteration as a privilege match has been made + if (actionAllowed) + break; + } + + return actionAllowed; } /** @@ -116,6 +138,11 @@ public class DefaultSessionHandler implements SessionHandler { if (user.getState() != UserState.ENABLED) throw new AccessDeniedException("User " + username + " is not ENABLED. State is: " + user.getState()); + // validate user has at least one role + if (user.getRoleList().isEmpty()) { + throw new PrivilegeException("User " + username + " does not have any roles defined!"); + } + // get 2 auth tokens String authToken = encryptionHandler.nextToken(); String authPassword = encryptionHandler.nextToken(); @@ -138,7 +165,7 @@ public class DefaultSessionHandler implements SessionHandler { return certificate; } - private String nextSessionId() { + private synchronized String nextSessionId() { return Long.toString(++lastSessionId % Long.MAX_VALUE); } diff --git a/src/ch/eitchnet/privilege/handler/PersistenceHandler.java b/src/ch/eitchnet/privilege/handler/PersistenceHandler.java new file mode 100644 index 000000000..bd12a2b5d --- /dev/null +++ b/src/ch/eitchnet/privilege/handler/PersistenceHandler.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.handler; + +import java.util.List; + +import ch.eitchnet.privilege.model.internal.RestrictionPolicy; +import ch.eitchnet.privilege.model.internal.User; + +/** + * @author rvonburg + * + */ +public interface PersistenceHandler { + + public List getAllUsers(); + public void saveUsers(List users); + public List getAllRestrictionPolicies(); +} diff --git a/src/ch/eitchnet/privilege/handler/PolicyHandler.java b/src/ch/eitchnet/privilege/handler/PolicyHandler.java index 93a4b45d6..5c55ad7a6 100644 --- a/src/ch/eitchnet/privilege/handler/PolicyHandler.java +++ b/src/ch/eitchnet/privilege/handler/PolicyHandler.java @@ -12,7 +12,7 @@ package ch.eitchnet.privilege.handler; import ch.eitchnet.privilege.base.PrivilegeContainerObject; import ch.eitchnet.privilege.model.Restrictable; -import ch.eitchnet.privilege.model.User; +import ch.eitchnet.privilege.model.internal.Role; /** * @author rvonburg @@ -20,5 +20,5 @@ import ch.eitchnet.privilege.model.User; */ public interface PolicyHandler extends PrivilegeContainerObject { - public boolean actionAllowed(User user, Restrictable restrictable); + public boolean actionAllowed(Role role, Restrictable restrictable); } diff --git a/src/ch/eitchnet/privilege/handler/SessionHandler.java b/src/ch/eitchnet/privilege/handler/SessionHandler.java index 9b9d26468..3c46a11f9 100644 --- a/src/ch/eitchnet/privilege/handler/SessionHandler.java +++ b/src/ch/eitchnet/privilege/handler/SessionHandler.java @@ -14,7 +14,7 @@ import ch.eitchnet.privilege.base.PrivilegeContainerObject; import ch.eitchnet.privilege.i18n.AccessDeniedException; import ch.eitchnet.privilege.model.Certificate; import ch.eitchnet.privilege.model.Restrictable; -import ch.eitchnet.privilege.model.User; +import ch.eitchnet.privilege.model.internal.User; /** * @author rvonburg diff --git a/src/ch/eitchnet/privilege/model/DefaultRestriction.java b/src/ch/eitchnet/privilege/model/internal/DefaultRestriction.java similarity index 53% rename from src/ch/eitchnet/privilege/model/DefaultRestriction.java rename to src/ch/eitchnet/privilege/model/internal/DefaultRestriction.java index a24a622f5..0516b4281 100644 --- a/src/ch/eitchnet/privilege/model/DefaultRestriction.java +++ b/src/ch/eitchnet/privilege/model/internal/DefaultRestriction.java @@ -8,14 +8,12 @@ * */ -package ch.eitchnet.privilege.model; - -import java.util.List; -import java.util.Map; +package ch.eitchnet.privilege.model.internal; import org.dom4j.Element; import ch.eitchnet.privilege.i18n.PrivilegeException; +import ch.eitchnet.privilege.model.Restrictable; /** * @author rvonburg @@ -25,18 +23,16 @@ public class DefaultRestriction implements RestrictionPolicy { private String restrictionKey; - private Map roleRestrictionMap; - /** - * @see ch.eitchnet.privilege.model.RestrictionPolicy#actionAllowed(ch.eitchnet.privilege.model.User, + * @see ch.eitchnet.privilege.model.internal.RestrictionPolicy#actionAllowed(ch.eitchnet.privilege.model.User, * ch.eitchnet.privilege.model.Restrictable) */ @Override - public boolean actionAllowed(User user, Restrictable restrictable) { + public boolean actionAllowed(Role role, Restrictable restrictable) { // validate user is not null - if (user == null) - throw new PrivilegeException("User may not be null!"); + if (role == null) + throw new PrivilegeException("Role may not be null!"); // validate Restrictable is set for this RestrictionPolicy if (!restrictionKey.equals(restrictable.getRestrictionKey())) { @@ -46,38 +42,15 @@ public class DefaultRestriction implements RestrictionPolicy { + restrictable.getRestrictionKey()); } - // default is that user does not have privilege - boolean hasPrivilege = false; - - // iterate user roles and validate role has privilege - for (String role : user.getRoleList()) { - - hasPrivilege = internalActionAllowed(role, restrictable); - - // if privilege is found, then stop iterating - if (hasPrivilege) - break; - } - - return hasPrivilege; - } - - /** - * @param role - * @param restrictable - * @return - */ - private boolean internalActionAllowed(String role, Restrictable restrictable) { - // get restriction object for users role - Restriction restriction = roleRestrictionMap.get(role); + Privilege privilege = role.getPrivilege(restrictionKey); // no restriction object means no privilege - if (restriction == null) + if (privilege == null) return false; // does this role have privilege for any values? - if (restriction.anyValue) + if (privilege.isAllAllowed()) return true; // get the value on which the action is to be performed @@ -92,13 +65,13 @@ public class DefaultRestriction implements RestrictionPolicy { String restrictionValue = (String) object; // first check values not allowed - for (String notAllowed : restriction.valuesNotAllowed) { + for (String notAllowed : privilege.getValuesNotAllowed()) { if (notAllowed.equals(restrictionValue)) return false; } // now check values allowed - for (String allowed : restriction.valuesAllowed) { + for (String allowed : privilege.getValuesAllowed()) { if (allowed.equals(restrictionValue)) return true; } @@ -111,21 +84,4 @@ public class DefaultRestriction implements RestrictionPolicy { // TODO implement } - - private class Restriction { - private boolean anyValue; - private List valuesAllowed; - private List valuesNotAllowed; - - /** - * @param allAllowed - * @param valuesAllowed - * @param valuesNotAllowed - */ - public Restriction(boolean anyValue, List valuesAllowed, List valuesNotAllowed) { - this.anyValue = anyValue; - this.valuesAllowed = valuesAllowed; - this.valuesNotAllowed = valuesNotAllowed; - } - } } diff --git a/src/ch/eitchnet/privilege/model/internal/Privilege.java b/src/ch/eitchnet/privilege/model/internal/Privilege.java new file mode 100644 index 000000000..779fdd802 --- /dev/null +++ b/src/ch/eitchnet/privilege/model/internal/Privilege.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.model.internal; + +import java.util.Collections; +import java.util.List; + +/** + * @author rvonburg + * + */ +public class Privilege { + + private final boolean allAllowed; + private final List valuesAllowed; + private final List valuesNotAllowed; + + /** + * @param allAllowed + * @param valuesAllowed + * @param valuesNotAllowed + */ + public Privilege(boolean allAllowed, List valuesAllowed, List valuesNotAllowed) { + this.allAllowed = allAllowed; + this.valuesAllowed = Collections.unmodifiableList(valuesAllowed); + this.valuesNotAllowed = Collections.unmodifiableList(valuesNotAllowed); + } + + /** + * @return the allAllowed + */ + public boolean isAllAllowed() { + return allAllowed; + } + + /** + * @return the valuesAllowed + */ + public List getValuesAllowed() { + return valuesAllowed; + } + + /** + * @return the valuesNotAllowed + */ + public List getValuesNotAllowed() { + return valuesNotAllowed; + } +} diff --git a/src/ch/eitchnet/privilege/model/RestrictionPolicy.java b/src/ch/eitchnet/privilege/model/internal/RestrictionPolicy.java similarity index 53% rename from src/ch/eitchnet/privilege/model/RestrictionPolicy.java rename to src/ch/eitchnet/privilege/model/internal/RestrictionPolicy.java index 7bcccea64..35c18859a 100644 --- a/src/ch/eitchnet/privilege/model/RestrictionPolicy.java +++ b/src/ch/eitchnet/privilege/model/internal/RestrictionPolicy.java @@ -8,7 +8,9 @@ * */ -package ch.eitchnet.privilege.model; +package ch.eitchnet.privilege.model.internal; + +import ch.eitchnet.privilege.model.Restrictable; /** * @author rvonburg @@ -16,5 +18,5 @@ package ch.eitchnet.privilege.model; */ public interface RestrictionPolicy { - public boolean actionAllowed(User user, Restrictable restrictable); + public boolean actionAllowed(Role role, Restrictable restrictable); } diff --git a/src/ch/eitchnet/privilege/model/internal/Role.java b/src/ch/eitchnet/privilege/model/internal/Role.java new file mode 100644 index 000000000..be6c7e841 --- /dev/null +++ b/src/ch/eitchnet/privilege/model/internal/Role.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.model.internal; + +import java.util.Collections; +import java.util.Map; + +/** + * @author rvonburg + * + */ +public class Role { + + private final Map privilegeMap; + + /** + * @param privilegeMap + */ + public Role(Map privilegeMap) { + this.privilegeMap = Collections.unmodifiableMap(privilegeMap); + } + + /** + * @param key + * @return + * @see java.util.Map#get(java.lang.Object) + */ + public Privilege getPrivilege(String key) { + return privilegeMap.get(key); + } +} diff --git a/src/ch/eitchnet/privilege/model/Session.java b/src/ch/eitchnet/privilege/model/internal/Session.java similarity index 98% rename from src/ch/eitchnet/privilege/model/Session.java rename to src/ch/eitchnet/privilege/model/internal/Session.java index a90249f3d..d648a6fb1 100644 --- a/src/ch/eitchnet/privilege/model/Session.java +++ b/src/ch/eitchnet/privilege/model/internal/Session.java @@ -8,7 +8,7 @@ * */ -package ch.eitchnet.privilege.model; +package ch.eitchnet.privilege.model.internal; /** * @author rvonburg diff --git a/src/ch/eitchnet/privilege/model/User.java b/src/ch/eitchnet/privilege/model/internal/User.java similarity index 95% rename from src/ch/eitchnet/privilege/model/User.java rename to src/ch/eitchnet/privilege/model/internal/User.java index 9cf9d58c4..fad6be185 100644 --- a/src/ch/eitchnet/privilege/model/User.java +++ b/src/ch/eitchnet/privilege/model/internal/User.java @@ -8,12 +8,14 @@ * */ -package ch.eitchnet.privilege.model; +package ch.eitchnet.privilege.model.internal; import java.util.Collections; import java.util.List; import java.util.Locale; +import ch.eitchnet.privilege.model.UserState; + /** * @author rvonburg * From d4f471028e9b608f19ebc53bc18020b5c855f9fa Mon Sep 17 00:00:00 2001 From: eitch Date: Sat, 29 May 2010 19:11:28 +0000 Subject: [PATCH 011/173] --- config/RestrictionPolicy.xml | 6 +++ .../privilege/base/PrivilegeContainer.java | 45 +++++++++++++++++-- .../eitchnet/privilege/base/XmlConstants.java | 3 +- .../handler/DefaultEncryptionHandler.java | 21 ++++++++- .../handler/DefaultPolicyHandler.java | 28 +++++++++++- 5 files changed, 96 insertions(+), 7 deletions(-) create mode 100644 config/RestrictionPolicy.xml diff --git a/config/RestrictionPolicy.xml b/config/RestrictionPolicy.xml new file mode 100644 index 000000000..c1ec64418 --- /dev/null +++ b/config/RestrictionPolicy.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/ch/eitchnet/privilege/base/PrivilegeContainer.java b/src/ch/eitchnet/privilege/base/PrivilegeContainer.java index f715748bd..953b3b7ae 100644 --- a/src/ch/eitchnet/privilege/base/PrivilegeContainer.java +++ b/src/ch/eitchnet/privilege/base/PrivilegeContainer.java @@ -12,6 +12,7 @@ package ch.eitchnet.privilege.base; import java.io.File; +import org.apache.log4j.Logger; import org.dom4j.Element; import ch.eitchnet.privilege.handler.EncryptionHandler; @@ -19,6 +20,7 @@ import ch.eitchnet.privilege.handler.PolicyHandler; import ch.eitchnet.privilege.handler.SessionHandler; import ch.eitchnet.privilege.helper.ClassHelper; import ch.eitchnet.privilege.helper.XmlHelper; +import ch.eitchnet.privilege.i18n.PrivilegeException; /** * @@ -26,6 +28,7 @@ import ch.eitchnet.privilege.helper.XmlHelper; * @author rvonburg */ public class PrivilegeContainer { + private static final Logger logger = Logger.getLogger(PrivilegeContainer.class); private static final PrivilegeContainer instance; @@ -37,6 +40,8 @@ public class PrivilegeContainer { private PolicyHandler policyHandler; private EncryptionHandler encryptionHandler; + private String basePath; + public static PrivilegeContainer getInstance() { return instance; } @@ -69,8 +74,24 @@ public class PrivilegeContainer { return encryptionHandler; } + /** + * @return the basePath + */ + public String getBasePath() { + return basePath; + } + public void initialize(File privilegeContainerXml) { + // make sure file exists + if (!privilegeContainerXml.exists()) { + throw new PrivilegeException("Privilige file does not exist at path " + + privilegeContainerXml.getAbsolutePath()); + } + + // set base path from privilege container xml + basePath = privilegeContainerXml.getParentFile().getAbsolutePath(); + // parse container xml file to XML document Element containerRootElement = XmlHelper.parseDocument(privilegeContainerXml).getRootElement(); @@ -78,20 +99,38 @@ public class PrivilegeContainer { Element sessionHandlerElement = containerRootElement.element(XmlConstants.XML_HANDLER_SESSION); String sessionHandlerClassName = sessionHandlerElement.attributeValue(XmlConstants.XML_ATTR_CLASS); SessionHandler sessionHandler = ClassHelper.instantiateClass(sessionHandlerClassName); - sessionHandler.initialize(sessionHandlerElement); // instantiate encryption handler Element encryptionHandlerElement = containerRootElement.element(XmlConstants.XML_HANDLER_ENCRYPTION); String encryptionHandlerClassName = encryptionHandlerElement.attributeValue(XmlConstants.XML_ATTR_CLASS); EncryptionHandler encryptionHandler = ClassHelper.instantiateClass(encryptionHandlerClassName); - encryptionHandler.initialize(encryptionHandlerElement); // instantiate policy handler Element policyHandlerElement = containerRootElement.element(XmlConstants.XML_HANDLER_POLICY); String policyHandlerClassName = policyHandlerElement.attributeValue(XmlConstants.XML_ATTR_CLASS); PolicyHandler policyHandler = ClassHelper.instantiateClass(policyHandlerClassName); - policyHandler.initialize(policyHandlerElement); + try { + sessionHandler.initialize(sessionHandlerElement); + } catch (Exception e) { + logger.error(e, e); + throw new PrivilegeException("SessionHandler " + sessionHandlerClassName + " could not be initialized"); + } + try { + encryptionHandler.initialize(encryptionHandlerElement); + } catch (Exception e) { + logger.error(e, e); + throw new PrivilegeException("EncryptionHandler " + encryptionHandlerClassName + + " could not be initialized"); + } + try { + policyHandler.initialize(policyHandlerElement); + } catch (Exception e) { + logger.error(e, e); + throw new PrivilegeException("PolicyHandler " + policyHandlerClassName + " could not be initialized"); + } + + // keep references to the handlers this.sessionHandler = sessionHandler; this.encryptionHandler = encryptionHandler; this.policyHandler = policyHandler; diff --git a/src/ch/eitchnet/privilege/base/XmlConstants.java b/src/ch/eitchnet/privilege/base/XmlConstants.java index 5d8a32519..692188cee 100644 --- a/src/ch/eitchnet/privilege/base/XmlConstants.java +++ b/src/ch/eitchnet/privilege/base/XmlConstants.java @@ -20,8 +20,9 @@ public class XmlConstants { public static final String XML_HANDLER_POLICY = "PolicyHandler"; public static final String XML_PARAMETERS = "Parameters"; - + public static final String XML_ATTR_CLASS = "class"; public static final String XML_PARAM_HASH_ALGORITHM = "hashAlgorithm"; + public static final String XML_PARAM_POLICY_FILE = "policyXmlFile"; } diff --git a/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java b/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java index a927e2ac7..2810ef699 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java @@ -17,6 +17,7 @@ import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.Map; +import org.apache.log4j.Logger; import org.dom4j.Element; import ch.eitchnet.privilege.base.XmlConstants; @@ -28,6 +29,7 @@ import ch.eitchnet.privilege.i18n.PrivilegeException; * */ public class DefaultEncryptionHandler implements EncryptionHandler { + private static final Logger logger = Logger.getLogger(DefaultEncryptionHandler.class); public String hashAlgorithm; @@ -73,6 +75,7 @@ public class DefaultEncryptionHandler implements EncryptionHandler { public String nextToken() { SecureRandom secureRandom = new SecureRandom(); String randomString = new BigInteger(130, secureRandom).toString(32); + logger.info("Token: " + randomString); // XXX remove this line after testing!!! return randomString; } @@ -81,11 +84,25 @@ public class DefaultEncryptionHandler implements EncryptionHandler { */ public void initialize(Element element) { + // get parameters Element parameterElement = element.element(XmlConstants.XML_PARAMETERS); Map parameterMap = ConfigurationHelper.convertToParameterMap(parameterElement); - // get and test configured algorithm + // get hash algorithm parameters hashAlgorithm = parameterMap.get(XmlConstants.XML_PARAM_HASH_ALGORITHM); - convertToHash("test"); + if (hashAlgorithm == null || hashAlgorithm.isEmpty()) { + throw new PrivilegeException("[" + EncryptionHandler.class.getName() + "] Defined parameter " + + XmlConstants.XML_PARAM_HASH_ALGORITHM + " is invalid"); + } + + // test hash algorithm + try { + convertToHash("test"); + logger.info("Using hashing algorithm " + hashAlgorithm); + } catch (Exception e) { + throw new PrivilegeException("[" + EncryptionHandler.class.getName() + "] Defined parameter " + + XmlConstants.XML_PARAM_HASH_ALGORITHM + " is invalid because of underlying exception: " + + e.getLocalizedMessage(), e); + } } } diff --git a/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java index 8d3780411..339752bbc 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java @@ -10,10 +10,15 @@ package ch.eitchnet.privilege.handler; +import java.io.File; import java.util.Map; import org.dom4j.Element; +import ch.eitchnet.privilege.base.PrivilegeContainer; +import ch.eitchnet.privilege.base.XmlConstants; +import ch.eitchnet.privilege.helper.ConfigurationHelper; +import ch.eitchnet.privilege.helper.XmlHelper; import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.Restrictable; import ch.eitchnet.privilege.model.internal.RestrictionPolicy; @@ -63,6 +68,27 @@ public class DefaultPolicyHandler implements PolicyHandler { * @see ch.eitchnet.privilege.base.PrivilegeContainerObject#initialize(org.dom4j.Element) */ public void initialize(Element element) { - // TODO implement + + // get parameters + Element parameterElement = element.element(XmlConstants.XML_PARAMETERS); + Map parameterMap = ConfigurationHelper.convertToParameterMap(parameterElement); + + // get policy file name + String policyFileName = parameterMap.get(XmlConstants.XML_PARAM_POLICY_FILE); + if (policyFileName == null || policyFileName.isEmpty()) { + throw new PrivilegeException("[" + PolicyHandler.class.getName() + "] Defined parameter " + + XmlConstants.XML_PARAM_POLICY_FILE + " is invalid"); + } + + // get policy file + File policyFile = new File(PrivilegeContainer.getInstance().getBasePath() + "/" + policyFileName); + if (!policyFile.exists()) { + throw new PrivilegeException("[" + PolicyHandler.class.getName() + "] Defined parameter " + + XmlConstants.XML_PARAM_POLICY_FILE + " is invalid as policy file does not exist at path " + + policyFile.getAbsolutePath()); + } + + // parse policy xml file to XML document + Element containerRootElement = XmlHelper.parseDocument(policyFile).getRootElement(); } } From 00ed10014db84e84ebf5dfd7c9d7b3992a090191 Mon Sep 17 00:00:00 2001 From: eitch Date: Sat, 29 May 2010 22:32:53 +0000 Subject: [PATCH 012/173] --- config/PrivilegeRoles.xml | 12 +++++++ config/PrivilegeUsers.xml | 10 ++++++ config/RestrictionPolicies.xml | 6 ++++ config/RestrictionPolicy.xml | 6 ---- .../eitchnet/privilege/base/XmlConstants.java | 2 ++ .../handler/DefaultPolicyHandler.java | 29 +++++++++++++--- .../privilege/handler/PersistenceHandler.java | 2 +- .../privilege/helper/ClassHelper.java | 23 +++++++++++++ .../privilege/model/internal/Privilege.java | 27 ++++++++------- .../DefaultRestriction.java | 34 +++++++------------ .../RestrictionPolicy.java | 3 +- 11 files changed, 107 insertions(+), 47 deletions(-) create mode 100644 config/PrivilegeRoles.xml create mode 100644 config/PrivilegeUsers.xml create mode 100644 config/RestrictionPolicies.xml delete mode 100644 config/RestrictionPolicy.xml rename src/ch/eitchnet/privilege/{model/internal => policy}/DefaultRestriction.java (61%) rename src/ch/eitchnet/privilege/{model/internal => policy}/RestrictionPolicy.java (77%) diff --git a/config/PrivilegeRoles.xml b/config/PrivilegeRoles.xml new file mode 100644 index 000000000..9a1a73074 --- /dev/null +++ b/config/PrivilegeRoles.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/config/PrivilegeUsers.xml b/config/PrivilegeUsers.xml new file mode 100644 index 000000000..d3571ea83 --- /dev/null +++ b/config/PrivilegeUsers.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/config/RestrictionPolicies.xml b/config/RestrictionPolicies.xml new file mode 100644 index 000000000..878d30e8a --- /dev/null +++ b/config/RestrictionPolicies.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/config/RestrictionPolicy.xml b/config/RestrictionPolicy.xml deleted file mode 100644 index c1ec64418..000000000 --- a/config/RestrictionPolicy.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/src/ch/eitchnet/privilege/base/XmlConstants.java b/src/ch/eitchnet/privilege/base/XmlConstants.java index 692188cee..d7f1e50f3 100644 --- a/src/ch/eitchnet/privilege/base/XmlConstants.java +++ b/src/ch/eitchnet/privilege/base/XmlConstants.java @@ -19,9 +19,11 @@ public class XmlConstants { public static final String XML_HANDLER_SESSION = "SessionHandler"; public static final String XML_HANDLER_POLICY = "PolicyHandler"; + public static final String XML_POLICY = "Policy"; public static final String XML_PARAMETERS = "Parameters"; public static final String XML_ATTR_CLASS = "class"; + public static final String XML_ATTR_NAME = "name"; public static final String XML_PARAM_HASH_ALGORITHM = "hashAlgorithm"; public static final String XML_PARAM_POLICY_FILE = "policyXmlFile"; diff --git a/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java index 339752bbc..a72f5c996 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java @@ -11,18 +11,21 @@ package ch.eitchnet.privilege.handler; import java.io.File; +import java.util.HashMap; +import java.util.List; import java.util.Map; import org.dom4j.Element; import ch.eitchnet.privilege.base.PrivilegeContainer; import ch.eitchnet.privilege.base.XmlConstants; +import ch.eitchnet.privilege.helper.ClassHelper; import ch.eitchnet.privilege.helper.ConfigurationHelper; import ch.eitchnet.privilege.helper.XmlHelper; import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.Restrictable; -import ch.eitchnet.privilege.model.internal.RestrictionPolicy; import ch.eitchnet.privilege.model.internal.Role; +import ch.eitchnet.privilege.policy.RestrictionPolicy; /** * @author rvonburg @@ -30,7 +33,7 @@ import ch.eitchnet.privilege.model.internal.Role; */ public class DefaultPolicyHandler implements PolicyHandler { - private Map policyMap; + private Map> policyMap; /** * @see ch.eitchnet.privilege.handler.PolicyHandler#actionAllowed(ch.eitchnet.privilege.model.internal.Role, @@ -53,13 +56,16 @@ public class DefaultPolicyHandler implements PolicyHandler { + restrictable.getClass().getName()); } - // get restriction policy - RestrictionPolicy policy = policyMap.get(restrictionKey); - if (policy == null) { + // get restriction policy class + Class policyClazz = policyMap.get(restrictionKey); + if (policyClazz == null) { throw new PrivilegeException("No RestrictionPolicy exists for the RestrictionKey " + restrictionKey + " for Restrictable " + restrictable.getClass().getName()); } + // instantiate policy + RestrictionPolicy policy = ClassHelper.instantiateClass(policyClazz); + // delegate checking to restriction policy return policy.actionAllowed(role, restrictable); } @@ -67,6 +73,7 @@ public class DefaultPolicyHandler implements PolicyHandler { /** * @see ch.eitchnet.privilege.base.PrivilegeContainerObject#initialize(org.dom4j.Element) */ + @SuppressWarnings("unchecked") public void initialize(Element element) { // get parameters @@ -88,7 +95,19 @@ public class DefaultPolicyHandler implements PolicyHandler { + policyFile.getAbsolutePath()); } + policyMap = new HashMap>(); + // parse policy xml file to XML document Element containerRootElement = XmlHelper.parseDocument(policyFile).getRootElement(); + + List policyElements = containerRootElement.elements(XmlConstants.XML_POLICY); + for (Element policyElement : policyElements) { + String policyName = policyElement.attributeValue(XmlConstants.XML_ATTR_NAME); + String policyClass = policyElement.attributeValue(XmlConstants.XML_ATTR_CLASS); + + Class clazz = ClassHelper.loadClass(policyClass); + + policyMap.put(policyName, clazz); + } } } diff --git a/src/ch/eitchnet/privilege/handler/PersistenceHandler.java b/src/ch/eitchnet/privilege/handler/PersistenceHandler.java index bd12a2b5d..617bb98c2 100644 --- a/src/ch/eitchnet/privilege/handler/PersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/PersistenceHandler.java @@ -12,8 +12,8 @@ package ch.eitchnet.privilege.handler; import java.util.List; -import ch.eitchnet.privilege.model.internal.RestrictionPolicy; import ch.eitchnet.privilege.model.internal.User; +import ch.eitchnet.privilege.policy.RestrictionPolicy; /** * @author rvonburg diff --git a/src/ch/eitchnet/privilege/helper/ClassHelper.java b/src/ch/eitchnet/privilege/helper/ClassHelper.java index 521c4df4f..34c9a1139 100644 --- a/src/ch/eitchnet/privilege/helper/ClassHelper.java +++ b/src/ch/eitchnet/privilege/helper/ClassHelper.java @@ -30,4 +30,27 @@ public class ClassHelper { throw new PrivilegeException("The class " + className + " could not be instantiated: ", e); } } + + public static T instantiateClass(Class clazz) { + try { + + return clazz.getConstructor().newInstance(); + + } catch (Exception e) { + throw new PrivilegeException("The class " + clazz.getName() + " could not be instantiated: ", e); + } + } + + @SuppressWarnings("unchecked") + public static Class loadClass(String className) { + try { + + Class clazz = (Class) Class.forName(className); + + return clazz; + + } catch (Exception e) { + throw new PrivilegeException("The class " + className + " could not be instantiated: ", e); + } + } } diff --git a/src/ch/eitchnet/privilege/model/internal/Privilege.java b/src/ch/eitchnet/privilege/model/internal/Privilege.java index 779fdd802..4e1d7201b 100644 --- a/src/ch/eitchnet/privilege/model/internal/Privilege.java +++ b/src/ch/eitchnet/privilege/model/internal/Privilege.java @@ -20,18 +20,18 @@ import java.util.List; public class Privilege { private final boolean allAllowed; - private final List valuesAllowed; - private final List valuesNotAllowed; + private final List allowList; + private final List denyList; /** * @param allAllowed - * @param valuesAllowed - * @param valuesNotAllowed + * @param allowList + * @param denyList */ - public Privilege(boolean allAllowed, List valuesAllowed, List valuesNotAllowed) { + public Privilege(boolean allAllowed, List allowList, List denyList) { this.allAllowed = allAllowed; - this.valuesAllowed = Collections.unmodifiableList(valuesAllowed); - this.valuesNotAllowed = Collections.unmodifiableList(valuesNotAllowed); + this.allowList = Collections.unmodifiableList(allowList); + this.denyList = Collections.unmodifiableList(denyList); } /** @@ -42,16 +42,17 @@ public class Privilege { } /** - * @return the valuesAllowed + * @return the allowList */ - public List getValuesAllowed() { - return valuesAllowed; + public List getAllowList() { + return allowList; } /** - * @return the valuesNotAllowed + * @return the denyList */ - public List getValuesNotAllowed() { - return valuesNotAllowed; + public List getDenyList() { + return denyList; } + } diff --git a/src/ch/eitchnet/privilege/model/internal/DefaultRestriction.java b/src/ch/eitchnet/privilege/policy/DefaultRestriction.java similarity index 61% rename from src/ch/eitchnet/privilege/model/internal/DefaultRestriction.java rename to src/ch/eitchnet/privilege/policy/DefaultRestriction.java index 0516b4281..33ff8709d 100644 --- a/src/ch/eitchnet/privilege/model/internal/DefaultRestriction.java +++ b/src/ch/eitchnet/privilege/policy/DefaultRestriction.java @@ -8,12 +8,12 @@ * */ -package ch.eitchnet.privilege.model.internal; - -import org.dom4j.Element; +package ch.eitchnet.privilege.policy; import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.Restrictable; +import ch.eitchnet.privilege.model.internal.Privilege; +import ch.eitchnet.privilege.model.internal.Role; /** * @author rvonburg @@ -21,11 +21,9 @@ import ch.eitchnet.privilege.model.Restrictable; */ public class DefaultRestriction implements RestrictionPolicy { - private String restrictionKey; - /** - * @see ch.eitchnet.privilege.model.internal.RestrictionPolicy#actionAllowed(ch.eitchnet.privilege.model.User, - * ch.eitchnet.privilege.model.Restrictable) + * @see ch.eitchnet.privilege.policy.RestrictionPolicy#actionAllowed(java.lang.String, + * ch.eitchnet.privilege.model.internal.Role, ch.eitchnet.privilege.model.Restrictable) */ @Override public boolean actionAllowed(Role role, Restrictable restrictable) { @@ -34,18 +32,17 @@ public class DefaultRestriction implements RestrictionPolicy { if (role == null) throw new PrivilegeException("Role may not be null!"); - // validate Restrictable is set for this RestrictionPolicy - if (!restrictionKey.equals(restrictable.getRestrictionKey())) { - throw new PrivilegeException(RestrictionPolicy.class.getSimpleName() + " " - + DefaultRestriction.class.getSimpleName() + " with restriction key " + restrictionKey - + " can not validate " + Restrictable.class.getSimpleName() + " with key " - + restrictable.getRestrictionKey()); + // get the restriction key + String restrictionKey = restrictable.getRestrictionKey(); + if (restrictionKey == null || restrictionKey.isEmpty()) { + throw new PrivilegeException("The restriction key for the Restrictable is null or empty: " + restrictable); } // get restriction object for users role Privilege privilege = role.getPrivilege(restrictionKey); // no restriction object means no privilege + // TODO should default deny/allow policy be configurable? if (privilege == null) return false; @@ -65,13 +62,13 @@ public class DefaultRestriction implements RestrictionPolicy { String restrictionValue = (String) object; // first check values not allowed - for (String notAllowed : privilege.getValuesNotAllowed()) { - if (notAllowed.equals(restrictionValue)) + for (String denied : privilege.getDenyList()) { + if (denied.equals(restrictionValue)) return false; } // now check values allowed - for (String allowed : privilege.getValuesAllowed()) { + for (String allowed : privilege.getAllowList()) { if (allowed.equals(restrictionValue)) return true; } @@ -79,9 +76,4 @@ public class DefaultRestriction implements RestrictionPolicy { // default is not allowed return false; } - - public void initialize(Element element) { - - // TODO implement - } } diff --git a/src/ch/eitchnet/privilege/model/internal/RestrictionPolicy.java b/src/ch/eitchnet/privilege/policy/RestrictionPolicy.java similarity index 77% rename from src/ch/eitchnet/privilege/model/internal/RestrictionPolicy.java rename to src/ch/eitchnet/privilege/policy/RestrictionPolicy.java index 35c18859a..093ca1468 100644 --- a/src/ch/eitchnet/privilege/model/internal/RestrictionPolicy.java +++ b/src/ch/eitchnet/privilege/policy/RestrictionPolicy.java @@ -8,9 +8,10 @@ * */ -package ch.eitchnet.privilege.model.internal; +package ch.eitchnet.privilege.policy; import ch.eitchnet.privilege.model.Restrictable; +import ch.eitchnet.privilege.model.internal.Role; /** * @author rvonburg From 89e4f30bcf5dbdae2fc661330de0dd785e7287ad Mon Sep 17 00:00:00 2001 From: eitch Date: Sun, 30 May 2010 19:28:39 +0000 Subject: [PATCH 013/173] --- config/PrivilegeContainer.xml | 5 +- config/PrivilegeRoles.xml | 6 +- config/PrivilegeUsers.xml | 8 +- config/RestrictionPolicies.xml | 2 +- .../eitchnet/privilege/base/XmlConstants.java | 16 +++ .../handler/DefaultSessionHandler.java | 102 ++++++++++++++++++ 6 files changed, 131 insertions(+), 8 deletions(-) diff --git a/config/PrivilegeContainer.xml b/config/PrivilegeContainer.xml index b40c6a226..c2b5beab8 100644 --- a/config/PrivilegeContainer.xml +++ b/config/PrivilegeContainer.xml @@ -3,7 +3,8 @@ - + + @@ -13,7 +14,7 @@ - + diff --git a/config/PrivilegeRoles.xml b/config/PrivilegeRoles.xml index 9a1a73074..23e686431 100644 --- a/config/PrivilegeRoles.xml +++ b/config/PrivilegeRoles.xml @@ -1,9 +1,9 @@ - - - + + + true diff --git a/config/PrivilegeUsers.xml b/config/PrivilegeUsers.xml index d3571ea83..e215250cd 100644 --- a/config/PrivilegeUsers.xml +++ b/config/PrivilegeUsers.xml @@ -1,9 +1,13 @@ - + + Robert + von Burg + NEW + en_GB - + admin diff --git a/config/RestrictionPolicies.xml b/config/RestrictionPolicies.xml index 878d30e8a..bcc0629bc 100644 --- a/config/RestrictionPolicies.xml +++ b/config/RestrictionPolicies.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/src/ch/eitchnet/privilege/base/XmlConstants.java b/src/ch/eitchnet/privilege/base/XmlConstants.java index d7f1e50f3..a5cee78dc 100644 --- a/src/ch/eitchnet/privilege/base/XmlConstants.java +++ b/src/ch/eitchnet/privilege/base/XmlConstants.java @@ -19,12 +19,28 @@ public class XmlConstants { public static final String XML_HANDLER_SESSION = "SessionHandler"; public static final String XML_HANDLER_POLICY = "PolicyHandler"; + public static final String XML_ROLES = "Roles"; + public static final String XML_ROLE = "role"; + public static final String XML_USER = "User"; + public static final String XML_PRIVILEGE = "Privilege"; public static final String XML_POLICY = "Policy"; public static final String XML_PARAMETERS = "Parameters"; + public static final String XML_ALL_ALLOWED = "allAllowed"; + public static final String XML_DENY = "deny"; + public static final String XML_ALLOW = "allow"; + public static final String XML_FIRSTNAME = "firstname"; + public static final String XML_SURNAME = "surname"; + public static final String XML_STATE = "state"; + public static final String XML_LOCALE = "locale"; public static final String XML_ATTR_CLASS = "class"; public static final String XML_ATTR_NAME = "name"; + public static final String XML_ATTR_POLICY = "policy"; + public static final String XML_ATTR_USERNAME = "username"; + public static final String XML_ATTR_PASSWORD = "password"; public static final String XML_PARAM_HASH_ALGORITHM = "hashAlgorithm"; public static final String XML_PARAM_POLICY_FILE = "policyXmlFile"; + public static final String XML_PARAM_ROLES_FILE = "rolesXmlFile"; + public static final String XML_PARAM_USERS_FILE = "usersXmlFile"; } diff --git a/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java b/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java index 8e07079d3..c7c7667d5 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java @@ -10,12 +10,19 @@ package ch.eitchnet.privilege.handler; +import java.io.File; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; import java.util.Map; import org.apache.log4j.Logger; import org.dom4j.Element; import ch.eitchnet.privilege.base.PrivilegeContainer; +import ch.eitchnet.privilege.base.XmlConstants; +import ch.eitchnet.privilege.helper.ConfigurationHelper; +import ch.eitchnet.privilege.helper.XmlHelper; import ch.eitchnet.privilege.i18n.AccessDeniedException; import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.Certificate; @@ -173,9 +180,104 @@ public class DefaultSessionHandler implements SessionHandler { * @see ch.eitchnet.privilege.base.PrivilegeContainerObject#initialize(org.dom4j.Element) */ public void initialize(Element element) { + + // get parameters + Element parameterElement = element.element(XmlConstants.XML_PARAMETERS); + Map parameterMap = ConfigurationHelper.convertToParameterMap(parameterElement); + + // get roles file name + String rolesFileName = parameterMap.get(XmlConstants.XML_PARAM_ROLES_FILE); + if (rolesFileName == null || rolesFileName.isEmpty()) { + throw new PrivilegeException("[" + SessionHandler.class.getName() + "] Defined parameter " + + XmlConstants.XML_PARAM_ROLES_FILE + " is invalid"); + } + + // get roles file + File rolesFile = new File(PrivilegeContainer.getInstance().getBasePath() + "/" + rolesFileName); + if (!rolesFile.exists()) { + throw new PrivilegeException("[" + SessionHandler.class.getName() + "] Defined parameter " + + XmlConstants.XML_PARAM_ROLES_FILE + " is invalid as roles file does not exist at path " + + rolesFile.getAbsolutePath()); + } + + // parse roles xml file to XML document + Element rolesRootElement = XmlHelper.parseDocument(rolesFile).getRootElement(); + readRoles(rolesRootElement); + + // TODO read roles + + // get users file name + String usersFileName = parameterMap.get(XmlConstants.XML_PARAM_USERS_FILE); + if (usersFileName == null || usersFileName.isEmpty()) { + throw new PrivilegeException("[" + SessionHandler.class.getName() + "] Defined parameter " + + XmlConstants.XML_PARAM_USERS_FILE + " is invalid"); + } + + // get users file + File usersFile = new File(PrivilegeContainer.getInstance().getBasePath() + "/" + usersFileName); + if (!usersFile.exists()) { + throw new PrivilegeException("[" + SessionHandler.class.getName() + "] Defined parameter " + + XmlConstants.XML_PARAM_USERS_FILE + " is invalid as users file does not exist at path " + + usersFile.getAbsolutePath()); + } + + // parse users xml file to XML document + Element usersRootElement = XmlHelper.parseDocument(usersFile).getRootElement(); + readUsers(usersRootElement); + + // TODO read users + // TODO implement } + /** + * @param usersRootElement + */ + private void readUsers(Element usersRootElement) { + + List userElements = usersRootElement.elements(XmlConstants.XML_USER); + for (Element userElement : userElements) { + + String username = userElement.attributeValue(XmlConstants.XML_ATTR_USERNAME); + String password = userElement.attributeValue(XmlConstants.XML_ATTR_PASSWORD); + + String firstname = userElement.element(XmlConstants.XML_FIRSTNAME).getTextTrim(); + String surname = userElement.element(XmlConstants.XML_SURNAME).getTextTrim(); + + UserState userState = UserState.valueOf(userElement.element(XmlConstants.XML_STATE).getTextTrim()); + + // TODO better handling needed + String localeName = userElement.element(XmlConstants.XML_LOCALE).getTextTrim(); + Locale locale = new Locale(localeName); + + Element rolesElement = userElement.element(XmlConstants.XML_ROLES); + List rolesElementList = rolesElement.elements(XmlConstants.XML_ROLE); + List roleList = new LinkedList(); + for (Element roleElement : rolesElementList) { + String roleName = roleElement.getTextTrim(); + if (roleList.isEmpty()) { + logger.warn("User " + username + " has an role defined with empty name, Skipped."); + } else { + roleList.add(roleName); + } + } + + // create user + User user = User.buildUser(username, password, firstname, surname, userState, roleList, locale); + + // put user in map + userMap.put(username, user); + } + } + + /** + * @param rolesRootElement + */ + private void readRoles(Element rolesRootElement) { + // TODO Auto-generated method stub + + } + private class CertificateSessionPair { private Session session; private Certificate certificate; From 0775f52b0cd7de9e1e2cea3cbd5ca714a9d5aaad Mon Sep 17 00:00:00 2001 From: eitch Date: Mon, 31 May 2010 19:34:26 +0000 Subject: [PATCH 014/173] --- config/PrivilegeRoles.xml | 2 +- config/RestrictionPolicies.xml | 2 +- .../privilege/base/PrivilegeContainer.java | 2 - .../handler/DefaultSessionHandler.java | 61 +++++++++++++++++-- .../privilege/handler/PersistenceHandler.java | 4 +- .../privilege/i18n/AccessDeniedException.java | 6 +- .../eitchnet/privilege/model/Certificate.java | 2 +- .../privilege/model/internal/Privilege.java | 28 +++++++-- .../privilege/model/internal/Role.java | 13 +++- .../privilege/model/internal/Session.java | 2 +- .../privilege/model/internal/User.java | 15 ++++- 11 files changed, 112 insertions(+), 25 deletions(-) diff --git a/config/PrivilegeRoles.xml b/config/PrivilegeRoles.xml index 23e686431..49ea61ef9 100644 --- a/config/PrivilegeRoles.xml +++ b/config/PrivilegeRoles.xml @@ -2,7 +2,7 @@ - + true diff --git a/config/RestrictionPolicies.xml b/config/RestrictionPolicies.xml index bcc0629bc..acda375fe 100644 --- a/config/RestrictionPolicies.xml +++ b/config/RestrictionPolicies.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/src/ch/eitchnet/privilege/base/PrivilegeContainer.java b/src/ch/eitchnet/privilege/base/PrivilegeContainer.java index 953b3b7ae..5b737c4d9 100644 --- a/src/ch/eitchnet/privilege/base/PrivilegeContainer.java +++ b/src/ch/eitchnet/privilege/base/PrivilegeContainer.java @@ -23,8 +23,6 @@ import ch.eitchnet.privilege.helper.XmlHelper; import ch.eitchnet.privilege.i18n.PrivilegeException; /** - * - * * @author rvonburg */ public class PrivilegeContainer { diff --git a/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java b/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java index c7c7667d5..714fddc43 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java @@ -11,6 +11,8 @@ package ch.eitchnet.privilege.handler; import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Locale; @@ -28,6 +30,7 @@ import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.Certificate; import ch.eitchnet.privilege.model.Restrictable; import ch.eitchnet.privilege.model.UserState; +import ch.eitchnet.privilege.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.Role; import ch.eitchnet.privilege.model.internal.Session; import ch.eitchnet.privilege.model.internal.User; @@ -202,9 +205,9 @@ public class DefaultSessionHandler implements SessionHandler { // parse roles xml file to XML document Element rolesRootElement = XmlHelper.parseDocument(rolesFile).getRootElement(); - readRoles(rolesRootElement); - // TODO read roles + // read roles + readRoles(rolesRootElement); // get users file name String usersFileName = parameterMap.get(XmlConstants.XML_PARAM_USERS_FILE); @@ -223,11 +226,12 @@ public class DefaultSessionHandler implements SessionHandler { // parse users xml file to XML document Element usersRootElement = XmlHelper.parseDocument(usersFile).getRootElement(); + + // read users readUsers(usersRootElement); - // TODO read users - - // TODO implement + logger.info("Read " + userMap.size() + " Users"); + logger.info("Read " + roleMap.size() + " Roles"); } /** @@ -274,8 +278,53 @@ public class DefaultSessionHandler implements SessionHandler { * @param rolesRootElement */ private void readRoles(Element rolesRootElement) { - // TODO Auto-generated method stub + List roleElements = rolesRootElement.elements(XmlConstants.XML_ROLE); + for (Element roleElement : roleElements) { + + String roleName = roleElement.attributeValue(XmlConstants.XML_ATTR_NAME); + + List privilegeElements = roleElement.elements(XmlConstants.XML_PRIVILEGE); + Map privilegeMap = new HashMap(); + for (Element privilegeElement : privilegeElements) { + + String privilegeName = privilegeElement.attributeValue(XmlConstants.XML_ATTR_NAME); + String privilegePolicy = privilegeElement.attributeValue(XmlConstants.XML_ATTR_POLICY); + + String allAllowedS = privilegeElement.element(XmlConstants.XML_ALL_ALLOWED).getTextTrim(); + boolean allAllowed = Boolean.valueOf(allAllowedS); + + List denyElements = privilegeElement.elements(XmlConstants.XML_DENY); + List denyList = new ArrayList(denyElements.size()); + for (Element denyElement : denyElements) { + String denyValue = denyElement.getTextTrim(); + if (denyValue.isEmpty()) { + logger.error("Role " + roleName + " has privilege " + privilegeName + + " with an empty deny value!"); + } else { + denyList.add(denyValue); + } + } + + List allowElements = privilegeElement.elements(XmlConstants.XML_ALLOW); + List allowList = new ArrayList(allowElements.size()); + for (Element allowElement : allowElements) { + String allowValue = allowElement.getTextTrim(); + if (allowValue.isEmpty()) { + logger.error("Role " + roleName + " has privilege " + privilegeName + + " with an empty allow value!"); + } else { + allowList.add(allowValue); + } + } + + Privilege privilege = new Privilege(privilegeName, privilegePolicy, allAllowed, denyList, allowList); + privilegeMap.put(privilegeName, privilege); + } + + Role role = new Role(roleName, privilegeMap); + roleMap.put(roleName, role); + } } private class CertificateSessionPair { diff --git a/src/ch/eitchnet/privilege/handler/PersistenceHandler.java b/src/ch/eitchnet/privilege/handler/PersistenceHandler.java index 617bb98c2..ba73ff7ea 100644 --- a/src/ch/eitchnet/privilege/handler/PersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/PersistenceHandler.java @@ -17,11 +17,13 @@ import ch.eitchnet.privilege.policy.RestrictionPolicy; /** * @author rvonburg - * + * */ public interface PersistenceHandler { public List getAllUsers(); + public void saveUsers(List users); + public List getAllRestrictionPolicies(); } diff --git a/src/ch/eitchnet/privilege/i18n/AccessDeniedException.java b/src/ch/eitchnet/privilege/i18n/AccessDeniedException.java index 1ff487f4b..c84978e3f 100644 --- a/src/ch/eitchnet/privilege/i18n/AccessDeniedException.java +++ b/src/ch/eitchnet/privilege/i18n/AccessDeniedException.java @@ -16,14 +16,12 @@ package ch.eitchnet.privilege.i18n; */ public class AccessDeniedException extends PrivilegeException { + private static final long serialVersionUID = 1L; + /** * @param string */ public AccessDeniedException(String string) { super(string); - // TODO Auto-generated constructor stub } - - private static final long serialVersionUID = 1L; - } diff --git a/src/ch/eitchnet/privilege/model/Certificate.java b/src/ch/eitchnet/privilege/model/Certificate.java index bb92989fa..d7269cde2 100644 --- a/src/ch/eitchnet/privilege/model/Certificate.java +++ b/src/ch/eitchnet/privilege/model/Certificate.java @@ -19,7 +19,7 @@ import ch.eitchnet.privilege.i18n.PrivilegeException; * @author rvonburg * */ -public class Certificate implements Serializable { +public final class Certificate implements Serializable { private static final long serialVersionUID = 1L; diff --git a/src/ch/eitchnet/privilege/model/internal/Privilege.java b/src/ch/eitchnet/privilege/model/internal/Privilege.java index 4e1d7201b..ccb7a65c0 100644 --- a/src/ch/eitchnet/privilege/model/internal/Privilege.java +++ b/src/ch/eitchnet/privilege/model/internal/Privilege.java @@ -17,21 +17,39 @@ import java.util.List; * @author rvonburg * */ -public class Privilege { +public final class Privilege { + private final String name; + private final String policy; private final boolean allAllowed; - private final List allowList; private final List denyList; + private final List allowList; /** * @param allAllowed - * @param allowList * @param denyList + * @param allowList */ - public Privilege(boolean allAllowed, List allowList, List denyList) { + public Privilege(String name, String policy, boolean allAllowed, List denyList, List allowList) { + this.name = name; + this.policy = policy; this.allAllowed = allAllowed; - this.allowList = Collections.unmodifiableList(allowList); this.denyList = Collections.unmodifiableList(denyList); + this.allowList = Collections.unmodifiableList(allowList); + } + + /** + * @return the name + */ + public String getName() { + return name; + } + + /** + * @return the policy + */ + public String getPolicy() { + return policy; } /** diff --git a/src/ch/eitchnet/privilege/model/internal/Role.java b/src/ch/eitchnet/privilege/model/internal/Role.java index be6c7e841..4032e9d8b 100644 --- a/src/ch/eitchnet/privilege/model/internal/Role.java +++ b/src/ch/eitchnet/privilege/model/internal/Role.java @@ -17,17 +17,26 @@ import java.util.Map; * @author rvonburg * */ -public class Role { +public final class Role { + private final String roleName; private final Map privilegeMap; /** * @param privilegeMap */ - public Role(Map privilegeMap) { + public Role(String roleName, Map privilegeMap) { + this.roleName = roleName; this.privilegeMap = Collections.unmodifiableMap(privilegeMap); } + /** + * @return the roleName + */ + public String getRoleName() { + return roleName; + } + /** * @param key * @return diff --git a/src/ch/eitchnet/privilege/model/internal/Session.java b/src/ch/eitchnet/privilege/model/internal/Session.java index d648a6fb1..cdcd1cda5 100644 --- a/src/ch/eitchnet/privilege/model/internal/Session.java +++ b/src/ch/eitchnet/privilege/model/internal/Session.java @@ -14,7 +14,7 @@ package ch.eitchnet.privilege.model.internal; * @author rvonburg * */ -public class Session { +public final class Session { private final String sessionId; private final String username; diff --git a/src/ch/eitchnet/privilege/model/internal/User.java b/src/ch/eitchnet/privilege/model/internal/User.java index fad6be185..df66c3bea 100644 --- a/src/ch/eitchnet/privilege/model/internal/User.java +++ b/src/ch/eitchnet/privilege/model/internal/User.java @@ -14,13 +14,14 @@ import java.util.Collections; import java.util.List; import java.util.Locale; +import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.UserState; /** * @author rvonburg * */ -public class User { +public final class User { private final String username; private final String password; @@ -112,6 +113,18 @@ public class User { locale = Locale.getDefault(); // TODO validate who is creating this User object + + if (username.length() < 3) { + throw new PrivilegeException("The given username is shorter than 3 characters"); + } + + if (firstname.isEmpty()) { + throw new PrivilegeException("The given firstname is empty"); + } + + if (surname.isEmpty()) { + throw new PrivilegeException("The given firstname is empty"); + } User user = new User(username, password, firstname, surname, userState, Collections.unmodifiableList(roleList), locale); From 756ae1e3e91a17d5f35107580fdb8f2978ade5b6 Mon Sep 17 00:00:00 2001 From: eitch Date: Mon, 31 May 2010 21:44:15 +0000 Subject: [PATCH 015/173] --- config/PrivilegeContainer.xml | 2 +- config/PrivilegeRoles.xml | 6 +- config/PrivilegeUsers.xml | 12 ++-- .../eitchnet/privilege/base/XmlConstants.java | 16 ++--- .../handler/DefaultEncryptionHandler.java | 36 ++++------- .../handler/DefaultSessionHandler.java | 9 ++- .../privilege/helper/EncryptionHelper.java | 47 +++++++++++++++ .../privilege/helper/PasswordCreator.java | 54 +++++++++++++++++ .../helper/TestConfigurationHelper.java | 59 +++++++++++++++++++ .../eitchnet/privilege/helper/XmlHelper.java | 2 +- 10 files changed, 196 insertions(+), 47 deletions(-) create mode 100644 src/ch/eitchnet/privilege/helper/EncryptionHelper.java create mode 100644 src/ch/eitchnet/privilege/helper/PasswordCreator.java create mode 100644 src/ch/eitchnet/privilege/helper/TestConfigurationHelper.java diff --git a/config/PrivilegeContainer.xml b/config/PrivilegeContainer.xml index c2b5beab8..547e13e78 100644 --- a/config/PrivilegeContainer.xml +++ b/config/PrivilegeContainer.xml @@ -9,7 +9,7 @@ - + diff --git a/config/PrivilegeRoles.xml b/config/PrivilegeRoles.xml index 49ea61ef9..dab3a4382 100644 --- a/config/PrivilegeRoles.xml +++ b/config/PrivilegeRoles.xml @@ -3,9 +3,9 @@ - true - - + true + + diff --git a/config/PrivilegeUsers.xml b/config/PrivilegeUsers.xml index e215250cd..b9e54eb55 100644 --- a/config/PrivilegeUsers.xml +++ b/config/PrivilegeUsers.xml @@ -1,13 +1,13 @@ - - Robert - von Burg - NEW - en_GB + + Robert + von Burg + ENABLED + en_GB - admin + admin diff --git a/src/ch/eitchnet/privilege/base/XmlConstants.java b/src/ch/eitchnet/privilege/base/XmlConstants.java index a5cee78dc..c98317907 100644 --- a/src/ch/eitchnet/privilege/base/XmlConstants.java +++ b/src/ch/eitchnet/privilege/base/XmlConstants.java @@ -20,18 +20,18 @@ public class XmlConstants { public static final String XML_HANDLER_POLICY = "PolicyHandler"; public static final String XML_ROLES = "Roles"; - public static final String XML_ROLE = "role"; + public static final String XML_ROLE = "Role"; public static final String XML_USER = "User"; public static final String XML_PRIVILEGE = "Privilege"; public static final String XML_POLICY = "Policy"; public static final String XML_PARAMETERS = "Parameters"; - public static final String XML_ALL_ALLOWED = "allAllowed"; - public static final String XML_DENY = "deny"; - public static final String XML_ALLOW = "allow"; - public static final String XML_FIRSTNAME = "firstname"; - public static final String XML_SURNAME = "surname"; - public static final String XML_STATE = "state"; - public static final String XML_LOCALE = "locale"; + public static final String XML_ALL_ALLOWED = "AllAllowed"; + public static final String XML_DENY = "Deny"; + public static final String XML_ALLOW = "Allow"; + public static final String XML_FIRSTNAME = "Firstname"; + public static final String XML_SURNAME = "Surname"; + public static final String XML_STATE = "State"; + public static final String XML_LOCALE = "Locale"; public static final String XML_ATTR_CLASS = "class"; public static final String XML_ATTR_NAME = "name"; diff --git a/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java b/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java index 2810ef699..8cb679885 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java @@ -11,8 +11,6 @@ package ch.eitchnet.privilege.handler; import java.io.UnsupportedEncodingException; -import java.math.BigInteger; -import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.Map; @@ -22,6 +20,7 @@ import org.dom4j.Element; import ch.eitchnet.privilege.base.XmlConstants; import ch.eitchnet.privilege.helper.ConfigurationHelper; +import ch.eitchnet.privilege.helper.EncryptionHelper; import ch.eitchnet.privilege.i18n.PrivilegeException; /** @@ -31,14 +30,8 @@ import ch.eitchnet.privilege.i18n.PrivilegeException; public class DefaultEncryptionHandler implements EncryptionHandler { private static final Logger logger = Logger.getLogger(DefaultEncryptionHandler.class); - public String hashAlgorithm; - - /** - * Hex char table for fast calculating of hex value - */ - private static final byte[] HEX_CHAR_TABLE = { (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', - (byte) '5', (byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', - (byte) 'e', (byte) 'f' }; + private SecureRandom secureRandom; + private String hashAlgorithm; /** * @see ch.eitchnet.privilege.handler.EncryptionHandler#convertToHash(java.lang.String) @@ -47,19 +40,7 @@ public class DefaultEncryptionHandler implements EncryptionHandler { public String convertToHash(String string) { try { - MessageDigest digest = MessageDigest.getInstance(hashAlgorithm); - byte[] hashArray = digest.digest(string.getBytes()); - - byte[] hex = new byte[2 * hashArray.length]; - int index = 0; - - for (byte b : hashArray) { - int v = b & 0xFF; - hex[index++] = HEX_CHAR_TABLE[v >>> 4]; - hex[index++] = HEX_CHAR_TABLE[v & 0xF]; - } - - return new String(hex, "ASCII"); + return EncryptionHelper.encryptString(hashAlgorithm, string); } catch (NoSuchAlgorithmException e) { throw new PrivilegeException("Algorithm " + hashAlgorithm + " was not found!", e); @@ -73,9 +54,10 @@ public class DefaultEncryptionHandler implements EncryptionHandler { */ @Override public String nextToken() { - SecureRandom secureRandom = new SecureRandom(); - String randomString = new BigInteger(130, secureRandom).toString(32); - logger.info("Token: " + randomString); // XXX remove this line after testing!!! + byte[] bytes = new byte[16]; + secureRandom.nextBytes(bytes); + String randomString = new String(bytes); + //String randomString = new BigInteger(80, secureRandom).toString(32); // 80 big integer bits = 16 chars return randomString; } @@ -84,6 +66,8 @@ public class DefaultEncryptionHandler implements EncryptionHandler { */ public void initialize(Element element) { + secureRandom = new SecureRandom(); + // get parameters Element parameterElement = element.element(XmlConstants.XML_PARAMETERS); Map parameterMap = ConfigurationHelper.convertToParameterMap(parameterElement); diff --git a/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java b/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java index 714fddc43..7452e71ba 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java @@ -184,6 +184,11 @@ public class DefaultSessionHandler implements SessionHandler { */ public void initialize(Element element) { + lastSessionId = 0l; + roleMap = new HashMap(); + userMap = new HashMap(); + sessionMap = new HashMap(); + // get parameters Element parameterElement = element.element(XmlConstants.XML_PARAMETERS); Map parameterMap = ConfigurationHelper.convertToParameterMap(parameterElement); @@ -259,8 +264,8 @@ public class DefaultSessionHandler implements SessionHandler { List roleList = new LinkedList(); for (Element roleElement : rolesElementList) { String roleName = roleElement.getTextTrim(); - if (roleList.isEmpty()) { - logger.warn("User " + username + " has an role defined with empty name, Skipped."); + if (roleName.isEmpty()) { + logger.warn("User " + username + " has a role defined with no name, Skipped."); } else { roleList.add(roleName); } diff --git a/src/ch/eitchnet/privilege/helper/EncryptionHelper.java b/src/ch/eitchnet/privilege/helper/EncryptionHelper.java new file mode 100644 index 000000000..afc52c1f2 --- /dev/null +++ b/src/ch/eitchnet/privilege/helper/EncryptionHelper.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.helper; + +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * @author rvonburg + * + */ +public class EncryptionHelper { + + /** + * Hex char table for fast calculating of hex value + */ + private static final byte[] HEX_CHAR_TABLE = { (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', + (byte) '5', (byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', + (byte) 'e', (byte) 'f' }; + + public static String encryptString(String hashAlgorithm, String string) throws NoSuchAlgorithmException, + UnsupportedEncodingException { + + MessageDigest digest = MessageDigest.getInstance(hashAlgorithm); + byte[] hashArray = digest.digest(string.getBytes()); + + byte[] hex = new byte[2 * hashArray.length]; + int index = 0; + + for (byte b : hashArray) { + int v = b & 0xFF; + hex[index++] = HEX_CHAR_TABLE[v >>> 4]; + hex[index++] = HEX_CHAR_TABLE[v & 0xF]; + } + + return new String(hex, "ASCII"); + } +} diff --git a/src/ch/eitchnet/privilege/helper/PasswordCreator.java b/src/ch/eitchnet/privilege/helper/PasswordCreator.java new file mode 100644 index 000000000..77b4cf677 --- /dev/null +++ b/src/ch/eitchnet/privilege/helper/PasswordCreator.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.helper; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.security.MessageDigest; + +/** + * @author rvonburg + * + */ +public class PasswordCreator { + + /** + * @param args + */ + 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(); + System.out.print("Hash is: " + EncryptionHelper.encryptString(hashAlgorithm, password)); + } + +} diff --git a/src/ch/eitchnet/privilege/helper/TestConfigurationHelper.java b/src/ch/eitchnet/privilege/helper/TestConfigurationHelper.java new file mode 100644 index 000000000..ff384bb7c --- /dev/null +++ b/src/ch/eitchnet/privilege/helper/TestConfigurationHelper.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.helper; + +import java.io.File; + +import org.apache.log4j.BasicConfigurator; +import org.apache.log4j.ConsoleAppender; +import org.apache.log4j.Level; +import org.apache.log4j.Logger; +import org.apache.log4j.PatternLayout; + +import ch.eitchnet.privilege.base.PrivilegeContainer; +import ch.eitchnet.privilege.model.Certificate; + +/** + * @author rvonburg + * + */ +public class TestConfigurationHelper { + private static final Logger logger = Logger.getLogger(TestConfigurationHelper.class); + + /** + * @param args + */ + public static void main(String[] args) { + BasicConfigurator.resetConfiguration(); + BasicConfigurator.configure(new ConsoleAppender(new PatternLayout("%d %5p [%t] %C{1} %M - %m%n"))); + Logger.getRootLogger().setLevel(Level.INFO); + + // initialize container + String pwd = System.getProperty("user.dir"); + File privilegeContainerXml = new File(pwd + "/config/PrivilegeContainer.xml"); + PrivilegeContainer.getInstance().initialize(privilegeContainerXml); + + for (int i = 0; i < 10; i++) { + // let's authenticate a session + auth("eitch", "592038"); + } + } + + /** + * + */ + private static void auth(String username, String password) { + long start = System.currentTimeMillis(); + Certificate certificate = PrivilegeContainer.getInstance().getSessionHandler().authenticate(username, password); + logger.info("Auth took " + (System.currentTimeMillis() - start)); + logger.info("Authenticated with certificate: " + certificate); + } +} diff --git a/src/ch/eitchnet/privilege/helper/XmlHelper.java b/src/ch/eitchnet/privilege/helper/XmlHelper.java index 3a8f2d4b4..693cfee3b 100644 --- a/src/ch/eitchnet/privilege/helper/XmlHelper.java +++ b/src/ch/eitchnet/privilege/helper/XmlHelper.java @@ -38,7 +38,7 @@ public class XmlHelper { SAXReader reader = new SAXReader(); Document document = reader.read(inStream); - logger.info("Read Xml document " + document.getName()); + logger.info("Read Xml document " + document.getRootElement().getName()); return document; } catch (FileNotFoundException e) { From 152d58d5154dc2dc06f98d3ff8c2e0ee373d95df Mon Sep 17 00:00:00 2001 From: eitch Date: Mon, 31 May 2010 21:55:52 +0000 Subject: [PATCH 016/173] --- config/PrivilegeUsers.xml | 2 +- src/ch/eitchnet/privilege/helper/PasswordCreator.java | 2 +- src/ch/eitchnet/privilege/helper/TestConfigurationHelper.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/config/PrivilegeUsers.xml b/config/PrivilegeUsers.xml index b9e54eb55..b0e7a7604 100644 --- a/config/PrivilegeUsers.xml +++ b/config/PrivilegeUsers.xml @@ -1,7 +1,7 @@ - + Robert von Burg ENABLED diff --git a/src/ch/eitchnet/privilege/helper/PasswordCreator.java b/src/ch/eitchnet/privilege/helper/PasswordCreator.java index 77b4cf677..701e58a94 100644 --- a/src/ch/eitchnet/privilege/helper/PasswordCreator.java +++ b/src/ch/eitchnet/privilege/helper/PasswordCreator.java @@ -47,7 +47,7 @@ public class PasswordCreator { } System.out.print("Password: "); - String password = r.readLine(); + String password = r.readLine().trim(); System.out.print("Hash is: " + EncryptionHelper.encryptString(hashAlgorithm, password)); } diff --git a/src/ch/eitchnet/privilege/helper/TestConfigurationHelper.java b/src/ch/eitchnet/privilege/helper/TestConfigurationHelper.java index ff384bb7c..df0fbb9ae 100644 --- a/src/ch/eitchnet/privilege/helper/TestConfigurationHelper.java +++ b/src/ch/eitchnet/privilege/helper/TestConfigurationHelper.java @@ -43,7 +43,7 @@ public class TestConfigurationHelper { for (int i = 0; i < 10; i++) { // let's authenticate a session - auth("eitch", "592038"); + auth("eitch", "1234567890"); } } From ddb1aa279ae73509beed702615124a314c7ad617 Mon Sep 17 00:00:00 2001 From: eitch Date: Thu, 3 Jun 2010 20:37:00 +0000 Subject: [PATCH 017/173] - remodelled privilege to be its own object and put all models into the persistence handler --- config/PrivilegeContainer.xml | 6 +- config/PrivilegeRoles.xml | 6 +- config/Privileges.xml | 10 + .../privilege/base/PrivilegeContainer.java | 22 ++ .../eitchnet/privilege/base/XmlConstants.java | 2 + .../handler/DefaultPersistenceHandler.java | 309 ++++++++++++++++++ .../handler/DefaultSessionHandler.java | 167 +--------- .../privilege/handler/PersistenceHandler.java | 23 +- .../privilege/model/internal/Role.java | 20 +- .../privilege/model/internal/User.java | 20 +- .../privilege/policy/DefaultRestriction.java | 3 +- 11 files changed, 397 insertions(+), 191 deletions(-) create mode 100644 config/Privileges.xml create mode 100644 src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java diff --git a/config/PrivilegeContainer.xml b/config/PrivilegeContainer.xml index 547e13e78..dad421977 100644 --- a/config/PrivilegeContainer.xml +++ b/config/PrivilegeContainer.xml @@ -1,12 +1,14 @@ - + + - + + diff --git a/config/PrivilegeRoles.xml b/config/PrivilegeRoles.xml index dab3a4382..c78b0ca4e 100644 --- a/config/PrivilegeRoles.xml +++ b/config/PrivilegeRoles.xml @@ -2,11 +2,7 @@ - - true - - - + \ No newline at end of file diff --git a/config/Privileges.xml b/config/Privileges.xml new file mode 100644 index 000000000..e1fb1de4e --- /dev/null +++ b/config/Privileges.xml @@ -0,0 +1,10 @@ + + + + + true + + + + + \ No newline at end of file diff --git a/src/ch/eitchnet/privilege/base/PrivilegeContainer.java b/src/ch/eitchnet/privilege/base/PrivilegeContainer.java index 5b737c4d9..945721e83 100644 --- a/src/ch/eitchnet/privilege/base/PrivilegeContainer.java +++ b/src/ch/eitchnet/privilege/base/PrivilegeContainer.java @@ -16,6 +16,7 @@ import org.apache.log4j.Logger; import org.dom4j.Element; import ch.eitchnet.privilege.handler.EncryptionHandler; +import ch.eitchnet.privilege.handler.PersistenceHandler; import ch.eitchnet.privilege.handler.PolicyHandler; import ch.eitchnet.privilege.handler.SessionHandler; import ch.eitchnet.privilege.helper.ClassHelper; @@ -37,6 +38,7 @@ public class PrivilegeContainer { private SessionHandler sessionHandler; private PolicyHandler policyHandler; private EncryptionHandler encryptionHandler; + private PersistenceHandler persistenceHandler; private String basePath; @@ -72,6 +74,13 @@ public class PrivilegeContainer { return encryptionHandler; } + /** + * @return the persistenceHandler + */ + public PersistenceHandler getPersistenceHandler() { + return persistenceHandler; + } + /** * @return the basePath */ @@ -93,6 +102,11 @@ public class PrivilegeContainer { // parse container xml file to XML document Element containerRootElement = XmlHelper.parseDocument(privilegeContainerXml).getRootElement(); + // instantiate persistence handler + Element persistenceHandlerElement = containerRootElement.element(XmlConstants.XML_HANDLER_PERSISTENCE); + String persistenceHandlerClassName = persistenceHandlerElement.attributeValue(XmlConstants.XML_ATTR_CLASS); + PersistenceHandler persistenceHandler = ClassHelper.instantiateClass(persistenceHandlerClassName); + // instantiate session handler Element sessionHandlerElement = containerRootElement.element(XmlConstants.XML_HANDLER_SESSION); String sessionHandlerClassName = sessionHandlerElement.attributeValue(XmlConstants.XML_ATTR_CLASS); @@ -108,6 +122,13 @@ public class PrivilegeContainer { String policyHandlerClassName = policyHandlerElement.attributeValue(XmlConstants.XML_ATTR_CLASS); PolicyHandler policyHandler = ClassHelper.instantiateClass(policyHandlerClassName); + try { + persistenceHandler.initialize(persistenceHandlerElement); + } catch (Exception e) { + logger.error(e, e); + throw new PrivilegeException("PersistenceHandler " + persistenceHandlerElement + + " could not be initialized"); + } try { sessionHandler.initialize(sessionHandlerElement); } catch (Exception e) { @@ -129,6 +150,7 @@ public class PrivilegeContainer { } // keep references to the handlers + this.persistenceHandler = persistenceHandler; this.sessionHandler = sessionHandler; this.encryptionHandler = encryptionHandler; this.policyHandler = policyHandler; diff --git a/src/ch/eitchnet/privilege/base/XmlConstants.java b/src/ch/eitchnet/privilege/base/XmlConstants.java index c98317907..b21a5322b 100644 --- a/src/ch/eitchnet/privilege/base/XmlConstants.java +++ b/src/ch/eitchnet/privilege/base/XmlConstants.java @@ -15,6 +15,7 @@ package ch.eitchnet.privilege.base; * */ public class XmlConstants { + public static final String XML_HANDLER_PERSISTENCE = "PersistenceHandler"; public static final String XML_HANDLER_ENCRYPTION = "EncryptionHandler"; public static final String XML_HANDLER_SESSION = "SessionHandler"; public static final String XML_HANDLER_POLICY = "PolicyHandler"; @@ -43,4 +44,5 @@ public class XmlConstants { public static final String XML_PARAM_POLICY_FILE = "policyXmlFile"; public static final String XML_PARAM_ROLES_FILE = "rolesXmlFile"; public static final String XML_PARAM_USERS_FILE = "usersXmlFile"; + public static final String XML_PARAM_PRIVILEGES_FILE = "privilegesXmlFile"; } diff --git a/src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java new file mode 100644 index 000000000..f11f28956 --- /dev/null +++ b/src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java @@ -0,0 +1,309 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.handler; + +import java.io.File; +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 org.apache.log4j.Logger; +import org.dom4j.Element; + +import ch.eitchnet.privilege.base.PrivilegeContainer; +import ch.eitchnet.privilege.base.XmlConstants; +import ch.eitchnet.privilege.helper.ConfigurationHelper; +import ch.eitchnet.privilege.helper.XmlHelper; +import ch.eitchnet.privilege.i18n.PrivilegeException; +import ch.eitchnet.privilege.model.Certificate; +import ch.eitchnet.privilege.model.UserState; +import ch.eitchnet.privilege.model.internal.Privilege; +import ch.eitchnet.privilege.model.internal.Role; +import ch.eitchnet.privilege.model.internal.User; + +/** + * @author rvonburg + * + */ +public class DefaultPersistenceHandler implements PersistenceHandler { + + private static final Logger logger = Logger.getLogger(DefaultPersistenceHandler.class); + + private Map userMap; + private Map roleMap; + private Map privilegesMap; + + private Map transientUserMap; + private Map transientRoleMap; + private Map transientPrivilegesMap; + + /** + * @see ch.eitchnet.privilege.handler.PersistenceHandler#addPrivilege(ch.eitchnet.privilege.model.Certificate, + * ch.eitchnet.privilege.model.internal.Privilege) + */ + @Override + public void addPrivilege(Certificate certificate, Privilege privilege) { + // TODO validate who is doing this + + privilegesMap.put(privilege.getName(), privilege); + transientPrivilegesMap.put(privilege.getName(), privilege); + } + + /** + * @see ch.eitchnet.privilege.handler.PersistenceHandler#addRole(ch.eitchnet.privilege.model.Certificate, + * ch.eitchnet.privilege.model.internal.Role) + */ + @Override + public void addRole(Certificate certificate, Role role) { + // TODO validate who is doing this + + roleMap.put(role.getRoleName(), role); + transientRoleMap.put(role.getRoleName(), role); + } + + /** + * @see ch.eitchnet.privilege.handler.PersistenceHandler#addUser(ch.eitchnet.privilege.model.Certificate, + * ch.eitchnet.privilege.model.internal.User) + */ + @Override + public void addUser(Certificate certificate, User user) { + // TODO Auto-generated method stub + + userMap.put(user.getUsername(), user); + transientUserMap.put(user.getUsername(), user); + } + + /** + * @see ch.eitchnet.privilege.handler.PersistenceHandler#getPrivilege(java.lang.String) + */ + @Override + public Privilege getPrivilege(String privilegeName) { + return privilegesMap.get(privilegeName); + } + + /** + * @see ch.eitchnet.privilege.handler.PersistenceHandler#getRole(java.lang.String) + */ + @Override + public Role getRole(String roleName) { + return roleMap.get(roleName); + } + + /** + * @see ch.eitchnet.privilege.handler.PersistenceHandler#getUser(java.lang.String) + */ + @Override + public User getUser(String username) { + return userMap.get(username); + } + + /** + * @see ch.eitchnet.privilege.handler.PersistenceHandler#persist() + */ + @Override + public void persist(Certificate certificate) { + + // TODO Auto-generated method stub + + // TODO Auto-generated method stub + + } + + /** + * @see ch.eitchnet.privilege.base.PrivilegeContainerObject#initialize(org.dom4j.Element) + */ + @Override + public void initialize(Element element) { + + roleMap = new HashMap(); + userMap = new HashMap(); + privilegesMap = new HashMap(); + + // get parameters + Element parameterElement = element.element(XmlConstants.XML_PARAMETERS); + Map parameterMap = ConfigurationHelper.convertToParameterMap(parameterElement); + + // get roles file name + String rolesFileName = parameterMap.get(XmlConstants.XML_PARAM_ROLES_FILE); + if (rolesFileName == null || rolesFileName.isEmpty()) { + throw new PrivilegeException("[" + SessionHandler.class.getName() + "] Defined parameter " + + XmlConstants.XML_PARAM_ROLES_FILE + " is invalid"); + } + + // get roles file + File rolesFile = new File(PrivilegeContainer.getInstance().getBasePath() + "/" + rolesFileName); + if (!rolesFile.exists()) { + throw new PrivilegeException("[" + SessionHandler.class.getName() + "] Defined parameter " + + XmlConstants.XML_PARAM_ROLES_FILE + " is invalid as roles file does not exist at path " + + rolesFile.getAbsolutePath()); + } + + // parse roles xml file to XML document + Element rolesRootElement = XmlHelper.parseDocument(rolesFile).getRootElement(); + + // read roles + readRoles(rolesRootElement); + + // get users file name + String usersFileName = parameterMap.get(XmlConstants.XML_PARAM_USERS_FILE); + if (usersFileName == null || usersFileName.isEmpty()) { + throw new PrivilegeException("[" + SessionHandler.class.getName() + "] Defined parameter " + + XmlConstants.XML_PARAM_USERS_FILE + " is invalid"); + } + + // get users file + File usersFile = new File(PrivilegeContainer.getInstance().getBasePath() + "/" + usersFileName); + if (!usersFile.exists()) { + throw new PrivilegeException("[" + SessionHandler.class.getName() + "] Defined parameter " + + XmlConstants.XML_PARAM_USERS_FILE + " is invalid as users file does not exist at path " + + usersFile.getAbsolutePath()); + } + + // parse users xml file to XML document + Element usersRootElement = XmlHelper.parseDocument(usersFile).getRootElement(); + + // read users + readUsers(usersRootElement); + + // get privileges file name + String privilegesFileName = parameterMap.get(XmlConstants.XML_PARAM_PRIVILEGES_FILE); + if (privilegesFileName == null || privilegesFileName.isEmpty()) { + throw new PrivilegeException("[" + SessionHandler.class.getName() + "] Defined parameter " + + XmlConstants.XML_PARAM_PRIVILEGES_FILE + " is invalid"); + } + + // get privileges file + File privilegesFile = new File(PrivilegeContainer.getInstance().getBasePath() + "/" + privilegesFileName); + if (!privilegesFile.exists()) { + throw new PrivilegeException("[" + SessionHandler.class.getName() + "] Defined parameter " + + XmlConstants.XML_PARAM_PRIVILEGES_FILE + " is invalid as privileges file does not exist at path " + + privilegesFile.getAbsolutePath()); + } + + // parse privileges xml file to XML document + Element privilegesRootElement = XmlHelper.parseDocument(privilegesFile).getRootElement(); + + // read privileges + readPrivileges(privilegesRootElement); + + logger.info("Read " + userMap.size() + " Users"); + logger.info("Read " + roleMap.size() + " Roles"); + logger.info("Read " + privilegesMap.size() + " Privileges"); + } + + /** + * @param usersRootElement + */ + private void readUsers(Element usersRootElement) { + + List userElements = usersRootElement.elements(XmlConstants.XML_USER); + for (Element userElement : userElements) { + + String username = userElement.attributeValue(XmlConstants.XML_ATTR_USERNAME); + String password = userElement.attributeValue(XmlConstants.XML_ATTR_PASSWORD); + + String firstname = userElement.element(XmlConstants.XML_FIRSTNAME).getTextTrim(); + String surname = userElement.element(XmlConstants.XML_SURNAME).getTextTrim(); + + UserState userState = UserState.valueOf(userElement.element(XmlConstants.XML_STATE).getTextTrim()); + + // TODO better handling needed + String localeName = userElement.element(XmlConstants.XML_LOCALE).getTextTrim(); + Locale locale = new Locale(localeName); + + Element rolesElement = userElement.element(XmlConstants.XML_ROLES); + List rolesElementList = rolesElement.elements(XmlConstants.XML_ROLE); + Set roles = new HashSet(); + for (Element roleElement : rolesElementList) { + String roleName = roleElement.getTextTrim(); + if (roleName.isEmpty()) { + logger.warn("User " + username + " has a role defined with no name, Skipped."); + } else { + roles.add(roleName); + } + } + + // create user + User user = User.buildUser(username, password, firstname, surname, userState, roles, locale); + + // put user in map + userMap.put(username, user); + } + } + + /** + * @param rolesRootElement + */ + private void readRoles(Element rolesRootElement) { + + List roleElements = rolesRootElement.elements(XmlConstants.XML_ROLE); + for (Element roleElement : roleElements) { + + String roleName = roleElement.attributeValue(XmlConstants.XML_ATTR_NAME); + + List privilegeElements = roleElement.elements(XmlConstants.XML_PRIVILEGE); + Set privileges = new HashSet(); + for (Element privilegeElement : privilegeElements) { + + String privilegeName = privilegeElement.attributeValue(XmlConstants.XML_ATTR_NAME); + privileges.add(privilegeName); + } + + Role role = new Role(roleName, privileges); + roleMap.put(roleName, role); + } + } + + /** + * @param rolesRootElement + */ + private void readPrivileges(Element privilegesRootElement) { + + List privilegeElements = privilegesRootElement.elements(XmlConstants.XML_PRIVILEGE); + for (Element privilegeElement : privilegeElements) { + + String privilegeName = privilegeElement.attributeValue(XmlConstants.XML_ATTR_NAME); + String privilegePolicy = privilegeElement.attributeValue(XmlConstants.XML_ATTR_POLICY); + + String allAllowedS = privilegeElement.element(XmlConstants.XML_ALL_ALLOWED).getTextTrim(); + boolean allAllowed = Boolean.valueOf(allAllowedS); + + List denyElements = privilegeElement.elements(XmlConstants.XML_DENY); + List denyList = new ArrayList(denyElements.size()); + for (Element denyElement : denyElements) { + String denyValue = denyElement.getTextTrim(); + if (denyValue.isEmpty()) { + logger.error("Privilege " + privilegeName + " has an empty deny value!"); + } else { + denyList.add(denyValue); + } + } + + List allowElements = privilegeElement.elements(XmlConstants.XML_ALLOW); + List allowList = new ArrayList(allowElements.size()); + for (Element allowElement : allowElements) { + String allowValue = allowElement.getTextTrim(); + if (allowValue.isEmpty()) { + logger.error("Privilege " + privilegeName + " has an empty allow value!"); + } else { + allowList.add(allowValue); + } + } + + Privilege privilege = new Privilege(privilegeName, privilegePolicy, allAllowed, denyList, allowList); + privilegesMap.put(privilegeName, privilege); + } + } +} diff --git a/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java b/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java index 7452e71ba..807eceb4e 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java @@ -10,27 +10,18 @@ package ch.eitchnet.privilege.handler; -import java.io.File; -import java.util.ArrayList; import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Locale; import java.util.Map; import org.apache.log4j.Logger; import org.dom4j.Element; import ch.eitchnet.privilege.base.PrivilegeContainer; -import ch.eitchnet.privilege.base.XmlConstants; -import ch.eitchnet.privilege.helper.ConfigurationHelper; -import ch.eitchnet.privilege.helper.XmlHelper; import ch.eitchnet.privilege.i18n.AccessDeniedException; import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.Certificate; import ch.eitchnet.privilege.model.Restrictable; import ch.eitchnet.privilege.model.UserState; -import ch.eitchnet.privilege.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.Role; import ch.eitchnet.privilege.model.internal.Session; import ch.eitchnet.privilege.model.internal.User; @@ -45,11 +36,11 @@ public class DefaultSessionHandler implements SessionHandler { private static long lastSessionId; - private Map userMap; - private Map roleMap; private Map sessionMap; /** + * TODO What is better, validate from {@link Restrictable} to {@link User} or the opposite direction? + * * @see ch.eitchnet.privilege.handler.SessionHandler#actionAllowed(ch.eitchnet.privilege.model.Certificate, * ch.eitchnet.privilege.model.Restrictable) * @@ -84,7 +75,8 @@ public class DefaultSessionHandler implements SessionHandler { + certificate.getSessionId()); // get user object - User user = userMap.get(certificateSessionPair.session.getUsername()); + User user = PrivilegeContainer.getInstance().getPersistenceHandler().getUser( + certificateSessionPair.session.getUsername()); if (user == null) { throw new PrivilegeException( "Oh boy, how did this happen: No User in user map although the certificate is valid!"); @@ -96,9 +88,9 @@ public class DefaultSessionHandler implements SessionHandler { // now iterate roles and validate on policy handler PolicyHandler policyHandler = PrivilegeContainer.getInstance().getPolicyHandler(); - for (String roleName : user.getRoleList()) { + for (String roleName : user.getRoles()) { - Role role = roleMap.get(roleName); + Role role = PrivilegeContainer.getInstance().getPersistenceHandler().getRole(roleName); if (role == null) { logger.error("No role is defined with name " + roleName + " which is configured for user " + user); continue; @@ -135,7 +127,7 @@ public class DefaultSessionHandler implements SessionHandler { String passwordHash = encryptionHandler.convertToHash(password); // get user object - User user = userMap.get(username); + User user = PrivilegeContainer.getInstance().getPersistenceHandler().getUser(username); // no user means no authentication if (user == null) throw new AccessDeniedException("There is no user defined with the credentials: " + username + " / ***..."); @@ -149,7 +141,7 @@ public class DefaultSessionHandler implements SessionHandler { throw new AccessDeniedException("User " + username + " is not ENABLED. State is: " + user.getState()); // validate user has at least one role - if (user.getRoleList().isEmpty()) { + if (user.getRoles().isEmpty()) { throw new PrivilegeException("User " + username + " does not have any roles defined!"); } @@ -185,151 +177,8 @@ public class DefaultSessionHandler implements SessionHandler { public void initialize(Element element) { lastSessionId = 0l; - roleMap = new HashMap(); - userMap = new HashMap(); sessionMap = new HashMap(); - // get parameters - Element parameterElement = element.element(XmlConstants.XML_PARAMETERS); - Map parameterMap = ConfigurationHelper.convertToParameterMap(parameterElement); - - // get roles file name - String rolesFileName = parameterMap.get(XmlConstants.XML_PARAM_ROLES_FILE); - if (rolesFileName == null || rolesFileName.isEmpty()) { - throw new PrivilegeException("[" + SessionHandler.class.getName() + "] Defined parameter " - + XmlConstants.XML_PARAM_ROLES_FILE + " is invalid"); - } - - // get roles file - File rolesFile = new File(PrivilegeContainer.getInstance().getBasePath() + "/" + rolesFileName); - if (!rolesFile.exists()) { - throw new PrivilegeException("[" + SessionHandler.class.getName() + "] Defined parameter " - + XmlConstants.XML_PARAM_ROLES_FILE + " is invalid as roles file does not exist at path " - + rolesFile.getAbsolutePath()); - } - - // parse roles xml file to XML document - Element rolesRootElement = XmlHelper.parseDocument(rolesFile).getRootElement(); - - // read roles - readRoles(rolesRootElement); - - // get users file name - String usersFileName = parameterMap.get(XmlConstants.XML_PARAM_USERS_FILE); - if (usersFileName == null || usersFileName.isEmpty()) { - throw new PrivilegeException("[" + SessionHandler.class.getName() + "] Defined parameter " - + XmlConstants.XML_PARAM_USERS_FILE + " is invalid"); - } - - // get users file - File usersFile = new File(PrivilegeContainer.getInstance().getBasePath() + "/" + usersFileName); - if (!usersFile.exists()) { - throw new PrivilegeException("[" + SessionHandler.class.getName() + "] Defined parameter " - + XmlConstants.XML_PARAM_USERS_FILE + " is invalid as users file does not exist at path " - + usersFile.getAbsolutePath()); - } - - // parse users xml file to XML document - Element usersRootElement = XmlHelper.parseDocument(usersFile).getRootElement(); - - // read users - readUsers(usersRootElement); - - logger.info("Read " + userMap.size() + " Users"); - logger.info("Read " + roleMap.size() + " Roles"); - } - - /** - * @param usersRootElement - */ - private void readUsers(Element usersRootElement) { - - List userElements = usersRootElement.elements(XmlConstants.XML_USER); - for (Element userElement : userElements) { - - String username = userElement.attributeValue(XmlConstants.XML_ATTR_USERNAME); - String password = userElement.attributeValue(XmlConstants.XML_ATTR_PASSWORD); - - String firstname = userElement.element(XmlConstants.XML_FIRSTNAME).getTextTrim(); - String surname = userElement.element(XmlConstants.XML_SURNAME).getTextTrim(); - - UserState userState = UserState.valueOf(userElement.element(XmlConstants.XML_STATE).getTextTrim()); - - // TODO better handling needed - String localeName = userElement.element(XmlConstants.XML_LOCALE).getTextTrim(); - Locale locale = new Locale(localeName); - - Element rolesElement = userElement.element(XmlConstants.XML_ROLES); - List rolesElementList = rolesElement.elements(XmlConstants.XML_ROLE); - List roleList = new LinkedList(); - for (Element roleElement : rolesElementList) { - String roleName = roleElement.getTextTrim(); - if (roleName.isEmpty()) { - logger.warn("User " + username + " has a role defined with no name, Skipped."); - } else { - roleList.add(roleName); - } - } - - // create user - User user = User.buildUser(username, password, firstname, surname, userState, roleList, locale); - - // put user in map - userMap.put(username, user); - } - } - - /** - * @param rolesRootElement - */ - private void readRoles(Element rolesRootElement) { - - List roleElements = rolesRootElement.elements(XmlConstants.XML_ROLE); - for (Element roleElement : roleElements) { - - String roleName = roleElement.attributeValue(XmlConstants.XML_ATTR_NAME); - - List privilegeElements = roleElement.elements(XmlConstants.XML_PRIVILEGE); - Map privilegeMap = new HashMap(); - for (Element privilegeElement : privilegeElements) { - - String privilegeName = privilegeElement.attributeValue(XmlConstants.XML_ATTR_NAME); - String privilegePolicy = privilegeElement.attributeValue(XmlConstants.XML_ATTR_POLICY); - - String allAllowedS = privilegeElement.element(XmlConstants.XML_ALL_ALLOWED).getTextTrim(); - boolean allAllowed = Boolean.valueOf(allAllowedS); - - List denyElements = privilegeElement.elements(XmlConstants.XML_DENY); - List denyList = new ArrayList(denyElements.size()); - for (Element denyElement : denyElements) { - String denyValue = denyElement.getTextTrim(); - if (denyValue.isEmpty()) { - logger.error("Role " + roleName + " has privilege " + privilegeName - + " with an empty deny value!"); - } else { - denyList.add(denyValue); - } - } - - List allowElements = privilegeElement.elements(XmlConstants.XML_ALLOW); - List allowList = new ArrayList(allowElements.size()); - for (Element allowElement : allowElements) { - String allowValue = allowElement.getTextTrim(); - if (allowValue.isEmpty()) { - logger.error("Role " + roleName + " has privilege " + privilegeName - + " with an empty allow value!"); - } else { - allowList.add(allowValue); - } - } - - Privilege privilege = new Privilege(privilegeName, privilegePolicy, allAllowed, denyList, allowList); - privilegeMap.put(privilegeName, privilege); - } - - Role role = new Role(roleName, privilegeMap); - roleMap.put(roleName, role); - } } private class CertificateSessionPair { diff --git a/src/ch/eitchnet/privilege/handler/PersistenceHandler.java b/src/ch/eitchnet/privilege/handler/PersistenceHandler.java index ba73ff7ea..5807f27d2 100644 --- a/src/ch/eitchnet/privilege/handler/PersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/PersistenceHandler.java @@ -10,20 +10,29 @@ package ch.eitchnet.privilege.handler; -import java.util.List; - +import ch.eitchnet.privilege.base.PrivilegeContainerObject; +import ch.eitchnet.privilege.model.Certificate; +import ch.eitchnet.privilege.model.internal.Privilege; +import ch.eitchnet.privilege.model.internal.Role; import ch.eitchnet.privilege.model.internal.User; -import ch.eitchnet.privilege.policy.RestrictionPolicy; /** * @author rvonburg * */ -public interface PersistenceHandler { +public interface PersistenceHandler extends PrivilegeContainerObject { - public List getAllUsers(); + public User getUser(String username); - public void saveUsers(List users); + public void addUser(Certificate certificate, User user); - public List getAllRestrictionPolicies(); + public Role getRole(String roleName); + + public void addRole(Certificate certificate, Role role); + + public Privilege getPrivilege(String privilegeName); + + public void addPrivilege(Certificate certificate, Privilege privilege); + + public void persist(Certificate certificate); } diff --git a/src/ch/eitchnet/privilege/model/internal/Role.java b/src/ch/eitchnet/privilege/model/internal/Role.java index 4032e9d8b..d4e2d57ad 100644 --- a/src/ch/eitchnet/privilege/model/internal/Role.java +++ b/src/ch/eitchnet/privilege/model/internal/Role.java @@ -11,7 +11,7 @@ package ch.eitchnet.privilege.model.internal; import java.util.Collections; -import java.util.Map; +import java.util.Set; /** * @author rvonburg @@ -20,14 +20,14 @@ import java.util.Map; public final class Role { private final String roleName; - private final Map privilegeMap; + private final Set privileges; /** * @param privilegeMap */ - public Role(String roleName, Map privilegeMap) { + public Role(String roleName, Set privileges) { this.roleName = roleName; - this.privilegeMap = Collections.unmodifiableMap(privilegeMap); + this.privileges = Collections.unmodifiableSet(privileges); } /** @@ -37,12 +37,18 @@ public final class Role { return roleName; } + /** + * @return + */ + public Set getPrivileges() { + return privileges; + } + /** * @param key * @return - * @see java.util.Map#get(java.lang.Object) */ - public Privilege getPrivilege(String key) { - return privilegeMap.get(key); + public boolean hasPrivilege(String key) { + return privileges.contains(key); } } diff --git a/src/ch/eitchnet/privilege/model/internal/User.java b/src/ch/eitchnet/privilege/model/internal/User.java index df66c3bea..d9c4fd634 100644 --- a/src/ch/eitchnet/privilege/model/internal/User.java +++ b/src/ch/eitchnet/privilege/model/internal/User.java @@ -11,8 +11,8 @@ package ch.eitchnet.privilege.model.internal; import java.util.Collections; -import java.util.List; import java.util.Locale; +import java.util.Set; import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.UserState; @@ -31,7 +31,7 @@ public final class User { private final UserState userState; - private final List roleList; + private final Set roles; private final Locale locale; @@ -39,7 +39,7 @@ public final class User { * The {@link User} constructor is private to ensure no unauthorized creation of {@link User} objects */ private User(String username, String password, String firstname, String surname, UserState userState, - List roleList, Locale locale) { + Set roles, Locale locale) { this.username = username; this.password = password; @@ -48,7 +48,7 @@ public final class User { this.firstname = firstname; this.surname = surname; - this.roleList = roleList; + this.roles = roles; this.locale = locale; } @@ -89,10 +89,10 @@ public final class User { } /** - * @return the roleList + * @return the roles */ - public List getRoleList() { - return roleList; + public Set getRoles() { + return roles; } /** @@ -106,14 +106,14 @@ public final class User { * @return a new {@link User} object which is authenticated on the current Java Virtual Machine */ public static User buildUser(String username, String password, String firstname, String surname, - UserState userState, List roleList, Locale locale) { + UserState userState, Set roles, Locale locale) { // set a default locale if (locale == null) locale = Locale.getDefault(); // TODO validate who is creating this User object - + if (username.length() < 3) { throw new PrivilegeException("The given username is shorter than 3 characters"); } @@ -126,7 +126,7 @@ public final class User { throw new PrivilegeException("The given firstname is empty"); } - User user = new User(username, password, firstname, surname, userState, Collections.unmodifiableList(roleList), + User user = new User(username, password, firstname, surname, userState, Collections.unmodifiableSet(roles), locale); return user; diff --git a/src/ch/eitchnet/privilege/policy/DefaultRestriction.java b/src/ch/eitchnet/privilege/policy/DefaultRestriction.java index 33ff8709d..77e7ba980 100644 --- a/src/ch/eitchnet/privilege/policy/DefaultRestriction.java +++ b/src/ch/eitchnet/privilege/policy/DefaultRestriction.java @@ -10,6 +10,7 @@ package ch.eitchnet.privilege.policy; +import ch.eitchnet.privilege.base.PrivilegeContainer; import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.Restrictable; import ch.eitchnet.privilege.model.internal.Privilege; @@ -39,7 +40,7 @@ public class DefaultRestriction implements RestrictionPolicy { } // get restriction object for users role - Privilege privilege = role.getPrivilege(restrictionKey); + Privilege privilege = PrivilegeContainer.getInstance().getPersistenceHandler().getPrivilege(restrictionKey); // no restriction object means no privilege // TODO should default deny/allow policy be configurable? From 0ca04da0533bb547a3add4468f760b225a0abb32 Mon Sep 17 00:00:00 2001 From: eitch Date: Thu, 3 Jun 2010 20:41:31 +0000 Subject: [PATCH 018/173] --- .../eitchnet/privilege/handler/DefaultPersistenceHandler.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java index f11f28956..65d1c8cbe 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java @@ -79,7 +79,7 @@ public class DefaultPersistenceHandler implements PersistenceHandler { */ @Override public void addUser(Certificate certificate, User user) { - // TODO Auto-generated method stub + // TODO validate who is doing this userMap.put(user.getUsername(), user); transientUserMap.put(user.getUsername(), user); @@ -115,7 +115,7 @@ public class DefaultPersistenceHandler implements PersistenceHandler { @Override public void persist(Certificate certificate) { - // TODO Auto-generated method stub + // TODO validate who is doing this // TODO Auto-generated method stub From dc414218e6546d1dd26e3323319b60ce8a8098ff Mon Sep 17 00:00:00 2001 From: eitch Date: Sat, 5 Jun 2010 21:06:03 +0000 Subject: [PATCH 019/173] --- config/PrivilegeUsers.xml | 1 + .../privilege/base/PrivilegeContainer.java | 6 + .../eitchnet/privilege/base/XmlConstants.java | 4 + .../handler/DefaultPersistenceHandler.java | 270 ++++++++++++++++-- .../handler/DefaultSessionHandler.java | 82 ++++-- .../privilege/handler/SessionHandler.java | 16 ++ .../privilege/helper/ConfigurationHelper.java | 8 +- .../privilege/helper/PrivilegeHelper.java | 45 +++ .../eitchnet/privilege/helper/XmlHelper.java | 49 +++- .../privilege/model/internal/Role.java | 16 +- .../privilege/model/internal/User.java | 10 + 11 files changed, 448 insertions(+), 59 deletions(-) create mode 100644 src/ch/eitchnet/privilege/helper/PrivilegeHelper.java diff --git a/config/PrivilegeUsers.xml b/config/PrivilegeUsers.xml index b0e7a7604..cc8867f25 100644 --- a/config/PrivilegeUsers.xml +++ b/config/PrivilegeUsers.xml @@ -8,6 +8,7 @@ en_GB admin + PrivilegeAdmin diff --git a/src/ch/eitchnet/privilege/base/PrivilegeContainer.java b/src/ch/eitchnet/privilege/base/PrivilegeContainer.java index 945721e83..42d17de2d 100644 --- a/src/ch/eitchnet/privilege/base/PrivilegeContainer.java +++ b/src/ch/eitchnet/privilege/base/PrivilegeContainer.java @@ -27,6 +27,12 @@ import ch.eitchnet.privilege.i18n.PrivilegeException; * @author rvonburg */ public class PrivilegeContainer { + + /** + * This is the role users must have, if they can modify the {@link PrivilegeContainer} and its objects + */ + public static final String PRIVILEGE_ADMIN_ROLE = "PrivilegeAdmin"; + private static final Logger logger = Logger.getLogger(PrivilegeContainer.class); private static final PrivilegeContainer instance; diff --git a/src/ch/eitchnet/privilege/base/XmlConstants.java b/src/ch/eitchnet/privilege/base/XmlConstants.java index b21a5322b..0377adcb7 100644 --- a/src/ch/eitchnet/privilege/base/XmlConstants.java +++ b/src/ch/eitchnet/privilege/base/XmlConstants.java @@ -22,10 +22,13 @@ public class XmlConstants { public static final String XML_ROLES = "Roles"; public static final String XML_ROLE = "Role"; + public static final String XML_USERS = "Users"; public static final String XML_USER = "User"; + public static final String XML_PRIVILEGES = "Privileges"; public static final String XML_PRIVILEGE = "Privilege"; public static final String XML_POLICY = "Policy"; public static final String XML_PARAMETERS = "Parameters"; + public static final String XML_PARAMETER = "Parameter"; public static final String XML_ALL_ALLOWED = "AllAllowed"; public static final String XML_DENY = "Deny"; public static final String XML_ALLOW = "Allow"; @@ -36,6 +39,7 @@ public class XmlConstants { public static final String XML_ATTR_CLASS = "class"; public static final String XML_ATTR_NAME = "name"; + public static final String XML_ATTR_VALUE = "value"; public static final String XML_ATTR_POLICY = "policy"; public static final String XML_ATTR_USERNAME = "username"; public static final String XML_ATTR_PASSWORD = "password"; diff --git a/src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java index 65d1c8cbe..4a7b31ab5 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java @@ -20,11 +20,13 @@ import java.util.Map; import java.util.Set; import org.apache.log4j.Logger; +import org.dom4j.DocumentFactory; import org.dom4j.Element; import ch.eitchnet.privilege.base.PrivilegeContainer; import ch.eitchnet.privilege.base.XmlConstants; import ch.eitchnet.privilege.helper.ConfigurationHelper; +import ch.eitchnet.privilege.helper.PrivilegeHelper; import ch.eitchnet.privilege.helper.XmlHelper; import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.Certificate; @@ -43,11 +45,13 @@ public class DefaultPersistenceHandler implements PersistenceHandler { private Map userMap; private Map roleMap; - private Map privilegesMap; + private Map privilegeMap; - private Map transientUserMap; - private Map transientRoleMap; - private Map transientPrivilegesMap; + private boolean userMapDirty; + private boolean roleMapDirty; + private boolean privilegeMapDirty; + + private Map parameterMap; /** * @see ch.eitchnet.privilege.handler.PersistenceHandler#addPrivilege(ch.eitchnet.privilege.model.Certificate, @@ -55,10 +59,12 @@ public class DefaultPersistenceHandler implements PersistenceHandler { */ @Override public void addPrivilege(Certificate certificate, Privilege privilege) { - // TODO validate who is doing this - privilegesMap.put(privilege.getName(), privilege); - transientPrivilegesMap.put(privilege.getName(), privilege); + // validate who is doing this + PrivilegeHelper.isUserPrivilegeAdmin(certificate); + + privilegeMap.put(privilege.getName(), privilege); + privilegeMapDirty = true; } /** @@ -67,10 +73,12 @@ public class DefaultPersistenceHandler implements PersistenceHandler { */ @Override public void addRole(Certificate certificate, Role role) { - // TODO validate who is doing this - roleMap.put(role.getRoleName(), role); - transientRoleMap.put(role.getRoleName(), role); + // validate who is doing this + PrivilegeHelper.isUserPrivilegeAdmin(certificate); + + roleMap.put(role.getName(), role); + roleMapDirty = true; } /** @@ -79,10 +87,12 @@ public class DefaultPersistenceHandler implements PersistenceHandler { */ @Override public void addUser(Certificate certificate, User user) { - // TODO validate who is doing this + + // validate who is doing this + PrivilegeHelper.isUserPrivilegeAdmin(certificate); userMap.put(user.getUsername(), user); - transientUserMap.put(user.getUsername(), user); + userMapDirty = true; } /** @@ -90,7 +100,7 @@ public class DefaultPersistenceHandler implements PersistenceHandler { */ @Override public Privilege getPrivilege(String privilegeName) { - return privilegesMap.get(privilegeName); + return privilegeMap.get(privilegeName); } /** @@ -115,10 +125,93 @@ public class DefaultPersistenceHandler implements PersistenceHandler { @Override public void persist(Certificate certificate) { - // TODO validate who is doing this + // validate who is doing this + PrivilegeHelper.isUserPrivilegeAdmin(certificate); - // TODO Auto-generated method stub + // USERS + if (!userMapDirty) { + logger.warn("No users unpersisted."); + } else { + logger.info("Persisting users..."); + // build XML DOM of users + List users = toDomUsers(); + Element rootElement = DocumentFactory.getInstance().createElement(XmlConstants.XML_USERS); + for (Element userElement : users) { + rootElement.add(userElement); + } + + // get users file name + String usersFileName = parameterMap.get(XmlConstants.XML_PARAM_USERS_FILE); + if (usersFileName == null || usersFileName.isEmpty()) { + throw new PrivilegeException("[" + SessionHandler.class.getName() + "] Defined parameter " + + XmlConstants.XML_PARAM_USERS_FILE + " is invalid"); + } + + // get users file + File usersFile = new File(PrivilegeContainer.getInstance().getBasePath() + "/" + usersFileName); + + // write DOM to file + XmlHelper.writeDocument(rootElement, usersFile); + } + + // ROLES + if (!roleMapDirty) { + logger.warn("No roles unpersisted."); + } else { + logger.info("Persisting roles..."); + + // build XML DOM of roles + List roles = toDomRoles(); + Element rootElement = DocumentFactory.getInstance().createElement(XmlConstants.XML_ROLES); + for (Element roleElement : roles) { + rootElement.add(roleElement); + } + + // get roles file name + String rolesFileName = parameterMap.get(XmlConstants.XML_PARAM_ROLES_FILE); + if (rolesFileName == null || rolesFileName.isEmpty()) { + throw new PrivilegeException("[" + SessionHandler.class.getName() + "] Defined parameter " + + XmlConstants.XML_PARAM_ROLES_FILE + " is invalid"); + } + + // get roles file + File rolesFile = new File(PrivilegeContainer.getInstance().getBasePath() + "/" + rolesFileName); + + // write DOM to file + XmlHelper.writeDocument(rootElement, rolesFile); + } + + // PRIVILEGES + if (!privilegeMapDirty) { + logger.warn("No privileges unpersisted."); + } else { + logger.info("Persisting privileges..."); + + // build XML DOM of privileges + List privileges = toDomPrivileges(); + Element rootElement = DocumentFactory.getInstance().createElement(XmlConstants.XML_PRIVILEGES); + for (Element privilegeElement : privileges) { + rootElement.add(privilegeElement); + } + + // get privileges file name + String privilegesFileName = parameterMap.get(XmlConstants.XML_PARAM_PRIVILEGES_FILE); + if (privilegesFileName == null || privilegesFileName.isEmpty()) { + throw new PrivilegeException("[" + SessionHandler.class.getName() + "] Defined parameter " + + XmlConstants.XML_PARAM_PRIVILEGES_FILE + " is invalid"); + } + + // get privileges file + File privilegesFile = new File(PrivilegeContainer.getInstance().getBasePath() + "/" + privilegesFileName); + + // write DOM to file + XmlHelper.writeDocument(rootElement, privilegesFile); + } + + userMapDirty = false; + roleMapDirty = false; + privilegeMapDirty = false; } /** @@ -129,11 +222,11 @@ public class DefaultPersistenceHandler implements PersistenceHandler { roleMap = new HashMap(); userMap = new HashMap(); - privilegesMap = new HashMap(); + privilegeMap = new HashMap(); // get parameters Element parameterElement = element.element(XmlConstants.XML_PARAMETERS); - Map parameterMap = ConfigurationHelper.convertToParameterMap(parameterElement); + parameterMap = ConfigurationHelper.convertToParameterMap(parameterElement); // get roles file name String rolesFileName = parameterMap.get(XmlConstants.XML_PARAM_ROLES_FILE); @@ -198,9 +291,26 @@ public class DefaultPersistenceHandler implements PersistenceHandler { // read privileges readPrivileges(privilegesRootElement); + userMapDirty = false; + roleMapDirty = false; + privilegeMapDirty = false; + logger.info("Read " + userMap.size() + " Users"); logger.info("Read " + roleMap.size() + " Roles"); - logger.info("Read " + privilegesMap.size() + " Privileges"); + logger.info("Read " + privilegeMap.size() + " Privileges"); + + // validate we have a user with PrivilegeAdmin access + boolean privilegeAdminExists = false; + for (String username : userMap.keySet()) { + User user = userMap.get(username); + if (user.hasRole(PrivilegeContainer.PRIVILEGE_ADMIN_ROLE)) { + privilegeAdminExists = true; + break; + } + } + if (!privilegeAdminExists) { + logger.warn("No User with PrivilegeAdmin role exists. Privilege modifications will not be possible!"); + } } /** @@ -229,7 +339,7 @@ public class DefaultPersistenceHandler implements PersistenceHandler { for (Element roleElement : rolesElementList) { String roleName = roleElement.getTextTrim(); if (roleName.isEmpty()) { - logger.warn("User " + username + " has a role defined with no name, Skipped."); + logger.error("User " + username + " has a role defined with no name, Skipped."); } else { roles.add(roleName); } @@ -303,7 +413,127 @@ public class DefaultPersistenceHandler implements PersistenceHandler { } Privilege privilege = new Privilege(privilegeName, privilegePolicy, allAllowed, denyList, allowList); - privilegesMap.put(privilegeName, privilege); + privilegeMap.put(privilegeName, privilege); } } + + private List toDomPrivileges() { + + List privilegesAsElements = new ArrayList(privilegeMap.size()); + + DocumentFactory documentFactory = DocumentFactory.getInstance(); + for (String privilegeName : privilegeMap.keySet()) { + + // get the privilege object + Privilege privilege = privilegeMap.get(privilegeName); + + // create the privilege element + Element privilegeElement = documentFactory.createElement(XmlConstants.XML_PRIVILEGE); + privilegeElement.addAttribute(XmlConstants.XML_ATTR_NAME, privilege.getName()); + privilegeElement.addAttribute(XmlConstants.XML_ATTR_POLICY, privilege.getPolicy()); + + // add the all allowed element + Element allAllowedElement = documentFactory.createElement(XmlConstants.XML_ALL_ALLOWED); + allAllowedElement.setText(Boolean.toString(privilege.isAllAllowed())); + privilegeElement.add(allAllowedElement); + + // add all the deny values + for (String denyValue : privilege.getDenyList()) { + Element denyValueElement = documentFactory.createElement(XmlConstants.XML_DENY); + denyValueElement.setText(denyValue); + privilegeElement.add(denyValueElement); + } + + // add all the allow values + for (String allowValue : privilege.getAllowList()) { + Element allowValueElement = documentFactory.createElement(XmlConstants.XML_ALLOW); + allowValueElement.setText(allowValue); + privilegeElement.add(allowValueElement); + } + + // add element to return list + privilegesAsElements.add(privilegeElement); + } + + return privilegesAsElements; + } + + private List toDomRoles() { + + List rolesAsElements = new ArrayList(roleMap.size()); + + DocumentFactory documentFactory = DocumentFactory.getInstance(); + for (String roleName : roleMap.keySet()) { + + // get the role object + Role role = roleMap.get(roleName); + + // create the role element + Element roleElement = documentFactory.createElement(XmlConstants.XML_ROLE); + roleElement.addAttribute(XmlConstants.XML_ATTR_NAME, role.getName()); + + // add all the privileges + for (String privilegeName : role.getPrivileges()) { + Element privilegeElement = documentFactory.createElement(XmlConstants.XML_PRIVILEGE); + privilegeElement.addAttribute(XmlConstants.XML_ATTR_NAME, privilegeName); + roleElement.add(privilegeElement); + } + + // add element to return list + rolesAsElements.add(roleElement); + } + + return rolesAsElements; + } + + private List toDomUsers() { + + List usersAsElements = new ArrayList(userMap.size()); + + DocumentFactory documentFactory = DocumentFactory.getInstance(); + for (String userName : userMap.keySet()) { + + // get the user object + User user = userMap.get(userName); + + // create the user element + Element userElement = documentFactory.createElement(XmlConstants.XML_USER); + userElement.addAttribute(XmlConstants.XML_ATTR_USERNAME, user.getUsername()); + userElement.addAttribute(XmlConstants.XML_ATTR_PASSWORD, user.getPassword()); + + // add first name element + Element firstnameElement = documentFactory.createElement(XmlConstants.XML_FIRSTNAME); + firstnameElement.setText(user.getFirstname()); + userElement.add(firstnameElement); + + // add surname element + Element surnameElement = documentFactory.createElement(XmlConstants.XML_SURNAME); + surnameElement.setText(user.getSurname()); + userElement.add(surnameElement); + + // add state element + Element stateElement = documentFactory.createElement(XmlConstants.XML_STATE); + stateElement.setText(user.getState().toString()); + userElement.add(stateElement); + + // add locale element + Element localeElement = documentFactory.createElement(XmlConstants.XML_LOCALE); + localeElement.setText(user.getLocale().toString()); + userElement.add(localeElement); + + // add all the role elements + Element rolesElement = documentFactory.createElement(XmlConstants.XML_ROLES); + userElement.add(rolesElement); + for (String roleName : user.getRoles()) { + Element roleElement = documentFactory.createElement(XmlConstants.XML_ROLE); + roleElement.setText(roleName); + rolesElement.add(roleElement); + } + + // add element to return list + usersAsElements.add(userElement); + } + + return usersAsElements; + } } diff --git a/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java b/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java index 807eceb4e..f63492ffb 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java @@ -51,11 +51,60 @@ public class DefaultSessionHandler implements SessionHandler { @Override public boolean actionAllowed(Certificate certificate, Restrictable restrictable) { - // certificate and restrictable must not be null + // first validate certificate + if (!isCertificateValid(certificate)) { + logger.info("Certificate is not valid, so action is not allowed: " + certificate + " for restrictable: " + + restrictable); + return false; + } + + // restrictable must not be null + if (restrictable == null) + throw new PrivilegeException("Restrictable may not be null!"); + + PrivilegeContainer privilegeContainer = PrivilegeContainer.getInstance(); + PersistenceHandler persistenceHandler = privilegeContainer.getPersistenceHandler(); + + // get user object + User user = persistenceHandler.getUser(certificate.getUsername()); + if (user == null) { + throw new PrivilegeException( + "Oh boy, how did this happen: No User in user map although the certificate is valid!"); + } + + // default is to not allow the action + // TODO should default deny/allow policy be configurable? + boolean actionAllowed = false; + + // now iterate roles and validate on policy handler + PolicyHandler policyHandler = privilegeContainer.getPolicyHandler(); + for (String roleName : user.getRoles()) { + + Role role = privilegeContainer.getPersistenceHandler().getRole(roleName); + if (role == null) { + logger.error("No role is defined with name " + roleName + " which is configured for user " + user); + continue; + } + + actionAllowed = policyHandler.actionAllowed(role, restrictable); + + // if action is allowed, then break iteration as a privilege match has been made + if (actionAllowed) + break; + } + + return actionAllowed; + } + + /** + * @see ch.eitchnet.privilege.handler.SessionHandler#isCertificateValid(ch.eitchnet.privilege.model.Certificate) + */ + @Override + public boolean isCertificateValid(Certificate certificate) { + + // certificate must not be null if (certificate == null) throw new PrivilegeException("Certificate may not be null!"); - else if (restrictable == null) - throw new PrivilegeException("Restrictable may not be null!"); // first see if a session exists for this certificate CertificateSessionPair certificateSessionPair = sessionMap.get(certificate.getSessionId()); @@ -77,33 +126,14 @@ public class DefaultSessionHandler implements SessionHandler { // get user object User user = PrivilegeContainer.getInstance().getPersistenceHandler().getUser( certificateSessionPair.session.getUsername()); + + // if user exists, then certificate is valid if (user == null) { throw new PrivilegeException( "Oh boy, how did this happen: No User in user map although the certificate is valid!"); + } else { + return true; } - - // default is to not allow the action - // TODO should default deny/allow policy be configurable? - boolean actionAllowed = false; - - // now iterate roles and validate on policy handler - PolicyHandler policyHandler = PrivilegeContainer.getInstance().getPolicyHandler(); - for (String roleName : user.getRoles()) { - - Role role = PrivilegeContainer.getInstance().getPersistenceHandler().getRole(roleName); - if (role == null) { - logger.error("No role is defined with name " + roleName + " which is configured for user " + user); - continue; - } - - actionAllowed = policyHandler.actionAllowed(role, restrictable); - - // if action is allowed, then break iteration as a privilege match has been made - if (actionAllowed) - break; - } - - return actionAllowed; } /** diff --git a/src/ch/eitchnet/privilege/handler/SessionHandler.java b/src/ch/eitchnet/privilege/handler/SessionHandler.java index 3c46a11f9..d019d0dbe 100644 --- a/src/ch/eitchnet/privilege/handler/SessionHandler.java +++ b/src/ch/eitchnet/privilege/handler/SessionHandler.java @@ -12,6 +12,7 @@ package ch.eitchnet.privilege.handler; import ch.eitchnet.privilege.base.PrivilegeContainerObject; import ch.eitchnet.privilege.i18n.AccessDeniedException; +import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.Certificate; import ch.eitchnet.privilege.model.Restrictable; import ch.eitchnet.privilege.model.internal.User; @@ -25,17 +26,32 @@ public interface SessionHandler extends PrivilegeContainerObject { /** * @param certificate * @param restrictable + * * @return * * @throws AccessDeniedException * if the {@link Certificate} is not for a currently logged in {@link User} or if the user may not * perform the action defined by the {@link Restrictable} implementation + * @throws PrivilegeException + * if there is anything wrong with this certificate */ public boolean actionAllowed(Certificate certificate, Restrictable restrictable); + /** + * @param certificate + * @return + * + * @throws AccessDeniedException + * if the {@link Certificate} is not for a currently logged in {@link User} + * @throws PrivilegeException + * if there is anything wrong with this certificate + */ + public boolean isCertificateValid(Certificate certificate); + /** * @param user * @param password + * * @return * * @throws AccessDeniedException diff --git a/src/ch/eitchnet/privilege/helper/ConfigurationHelper.java b/src/ch/eitchnet/privilege/helper/ConfigurationHelper.java index 177c5a3d7..34e6d642d 100644 --- a/src/ch/eitchnet/privilege/helper/ConfigurationHelper.java +++ b/src/ch/eitchnet/privilege/helper/ConfigurationHelper.java @@ -16,6 +16,8 @@ import java.util.Map; import org.dom4j.Element; +import ch.eitchnet.privilege.base.XmlConstants; + /** * @author rvonburg * @@ -27,10 +29,10 @@ public class ConfigurationHelper { Map parameterMap = new HashMap(); - List elements = element.elements("Parameter"); + List elements = element.elements(XmlConstants.XML_PARAMETER); for (Element parameter : elements) { - String name = parameter.attributeValue("name"); - String value = parameter.attributeValue("value"); + String name = parameter.attributeValue(XmlConstants.XML_ATTR_NAME); + String value = parameter.attributeValue(XmlConstants.XML_ATTR_VALUE); parameterMap.put(name, value); } diff --git a/src/ch/eitchnet/privilege/helper/PrivilegeHelper.java b/src/ch/eitchnet/privilege/helper/PrivilegeHelper.java new file mode 100644 index 000000000..720df2b4d --- /dev/null +++ b/src/ch/eitchnet/privilege/helper/PrivilegeHelper.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.helper; + +import ch.eitchnet.privilege.base.PrivilegeContainer; +import ch.eitchnet.privilege.i18n.AccessDeniedException; +import ch.eitchnet.privilege.i18n.PrivilegeException; +import ch.eitchnet.privilege.model.Certificate; +import ch.eitchnet.privilege.model.internal.User; + +/** + * @author rvonburg + * + */ +public class PrivilegeHelper { + + public static boolean isUserPrivilegeAdmin(Certificate certificate) { + // validate certificate + if (!PrivilegeContainer.getInstance().getSessionHandler().isCertificateValid(certificate)) { + throw new PrivilegeException("Certificate " + certificate + " is not valid!"); + } + + // get user object + User user = PrivilegeContainer.getInstance().getPersistenceHandler().getUser(certificate.getUsername()); + if (user == null) { + throw new PrivilegeException( + "Oh boy, how did this happen: No User in user map although the certificate is valid!"); + } + + // validate user has PrivilegeAdmin role + if (!user.hasRole(PrivilegeContainer.PRIVILEGE_ADMIN_ROLE)) { + throw new AccessDeniedException("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role!"); + } else { + return true; + } + } +} diff --git a/src/ch/eitchnet/privilege/helper/XmlHelper.java b/src/ch/eitchnet/privilege/helper/XmlHelper.java index 693cfee3b..05c75ac17 100644 --- a/src/ch/eitchnet/privilege/helper/XmlHelper.java +++ b/src/ch/eitchnet/privilege/helper/XmlHelper.java @@ -13,12 +13,19 @@ package ch.eitchnet.privilege.helper; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import org.apache.log4j.Logger; import org.dom4j.Document; import org.dom4j.DocumentException; +import org.dom4j.DocumentFactory; +import org.dom4j.Element; +import org.dom4j.io.OutputFormat; import org.dom4j.io.SAXReader; +import org.dom4j.io.XMLWriter; import ch.eitchnet.privilege.i18n.PrivilegeException; @@ -31,6 +38,7 @@ public class XmlHelper { private static final Logger logger = Logger.getLogger(XmlHelper.class); public static Document parseDocument(File xmlFile) { + try { InputStream inStream = new FileInputStream(xmlFile); @@ -38,13 +46,48 @@ public class XmlHelper { SAXReader reader = new SAXReader(); Document document = reader.read(inStream); - logger.info("Read Xml document " + document.getRootElement().getName()); + logger.info("Read XML document " + document.getRootElement().getName()); return document; } catch (FileNotFoundException e) { - throw new PrivilegeException("The Xml file does not exist or is not readable: " + xmlFile.getAbsolutePath()); + throw new PrivilegeException("The XML file does not exist or is not readable: " + xmlFile.getAbsolutePath()); } catch (DocumentException e) { - throw new PrivilegeException("the Xml file " + xmlFile.getAbsolutePath() + " is not parseable:", e); + throw new PrivilegeException("the XML file " + xmlFile.getAbsolutePath() + " is not parseable:", e); + } + } + + public static void writeDocument(Element rootElement, File file) { + + logger.info("Exporting root element " + rootElement.getName() + " to " + file.getAbsolutePath()); + + OutputStream fileOutputStream = null; + + try { + Document document = DocumentFactory.getInstance().createDocument(); + document.setRootElement(rootElement); + + fileOutputStream = new FileOutputStream(file); + + String aEncodingScheme = "UTF-8"; + OutputFormat outformat = OutputFormat.createPrettyPrint(); + outformat.setEncoding(aEncodingScheme); + XMLWriter writer = new XMLWriter(fileOutputStream, outformat); + writer.write(document); + writer.flush(); + + } catch (Exception e) { + + throw new PrivilegeException("Exception while exporting to file: " + e, e); + + } finally { + + if (fileOutputStream != null) { + try { + fileOutputStream.close(); + } catch (IOException e) { + logger.error("Could not close file output stream: " + e, e); + } + } } } } diff --git a/src/ch/eitchnet/privilege/model/internal/Role.java b/src/ch/eitchnet/privilege/model/internal/Role.java index d4e2d57ad..0dc6322d0 100644 --- a/src/ch/eitchnet/privilege/model/internal/Role.java +++ b/src/ch/eitchnet/privilege/model/internal/Role.java @@ -19,22 +19,24 @@ import java.util.Set; */ public final class Role { - private final String roleName; + private final String name; private final Set privileges; /** - * @param privilegeMap + * + * @param name + * @param privileges */ - public Role(String roleName, Set privileges) { - this.roleName = roleName; + public Role(String name, Set privileges) { + this.name = name; this.privileges = Collections.unmodifiableSet(privileges); } /** - * @return the roleName + * @return the name */ - public String getRoleName() { - return roleName; + public String getName() { + return name; } /** diff --git a/src/ch/eitchnet/privilege/model/internal/User.java b/src/ch/eitchnet/privilege/model/internal/User.java index d9c4fd634..9efc5bb2d 100644 --- a/src/ch/eitchnet/privilege/model/internal/User.java +++ b/src/ch/eitchnet/privilege/model/internal/User.java @@ -95,6 +95,16 @@ public final class User { return roles; } + /** + * Returns true if this user has the specified role + * + * @param role + * @return true if the this user has the specified role + */ + public boolean hasRole(String role) { + return roles.contains(role); + } + /** * @return the locale */ From aa28ab0fc31ba5d3b06ac5e0b107640b0c0cde08 Mon Sep 17 00:00:00 2001 From: eitch Date: Sat, 5 Jun 2010 21:33:30 +0000 Subject: [PATCH 020/173] --- .../handler/DefaultPersistenceHandler.java | 4 +- .../privilege/handler/PersistenceHandler.java | 6 +- .../helper/TestConfigurationHelper.java | 14 +- .../eitchnet/privilege/model/Certificate.java | 11 +- .../privilege/model/PrivilegeRep.java | 120 ++++++++++++++++++ src/ch/eitchnet/privilege/model/RoleRep.java | 35 +++++ src/ch/eitchnet/privilege/model/UserRep.java | 49 +++++++ .../privilege/model/internal/Privilege.java | 96 ++++++++++++-- .../privilege/model/internal/Role.java | 61 +++++++++ .../privilege/model/internal/Session.java | 11 +- .../privilege/model/internal/User.java | 30 +++++ 11 files changed, 423 insertions(+), 14 deletions(-) create mode 100644 src/ch/eitchnet/privilege/model/PrivilegeRep.java create mode 100644 src/ch/eitchnet/privilege/model/RoleRep.java create mode 100644 src/ch/eitchnet/privilege/model/UserRep.java diff --git a/src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java index 4a7b31ab5..fd07228eb 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java @@ -391,7 +391,7 @@ public class DefaultPersistenceHandler implements PersistenceHandler { boolean allAllowed = Boolean.valueOf(allAllowedS); List denyElements = privilegeElement.elements(XmlConstants.XML_DENY); - List denyList = new ArrayList(denyElements.size()); + Set denyList = new HashSet(denyElements.size()); for (Element denyElement : denyElements) { String denyValue = denyElement.getTextTrim(); if (denyValue.isEmpty()) { @@ -402,7 +402,7 @@ public class DefaultPersistenceHandler implements PersistenceHandler { } List allowElements = privilegeElement.elements(XmlConstants.XML_ALLOW); - List allowList = new ArrayList(allowElements.size()); + Set allowList = new HashSet(allowElements.size()); for (Element allowElement : allowElements) { String allowValue = allowElement.getTextTrim(); if (allowValue.isEmpty()) { diff --git a/src/ch/eitchnet/privilege/handler/PersistenceHandler.java b/src/ch/eitchnet/privilege/handler/PersistenceHandler.java index 5807f27d2..f0efacc8f 100644 --- a/src/ch/eitchnet/privilege/handler/PersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/PersistenceHandler.java @@ -10,6 +10,7 @@ package ch.eitchnet.privilege.handler; +import ch.eitchnet.privilege.base.PrivilegeContainer; import ch.eitchnet.privilege.base.PrivilegeContainerObject; import ch.eitchnet.privilege.model.Certificate; import ch.eitchnet.privilege.model.internal.Privilege; @@ -17,13 +18,16 @@ import ch.eitchnet.privilege.model.internal.Role; import ch.eitchnet.privilege.model.internal.User; /** + * TODO {@link PersistenceHandler} may not be freely accessible via {@link PrivilegeContainer} + * * @author rvonburg * */ public interface PersistenceHandler extends PrivilegeContainerObject { public User getUser(String username); - + // public void setUserPassword(String username, String password); + // public void setUserState(String username, UserState state); public void addUser(Certificate certificate, User user); public Role getRole(String roleName); diff --git a/src/ch/eitchnet/privilege/helper/TestConfigurationHelper.java b/src/ch/eitchnet/privilege/helper/TestConfigurationHelper.java index df0fbb9ae..3119acd76 100644 --- a/src/ch/eitchnet/privilege/helper/TestConfigurationHelper.java +++ b/src/ch/eitchnet/privilege/helper/TestConfigurationHelper.java @@ -19,6 +19,7 @@ import org.apache.log4j.Logger; import org.apache.log4j.PatternLayout; import ch.eitchnet.privilege.base.PrivilegeContainer; +import ch.eitchnet.privilege.handler.PersistenceHandler; import ch.eitchnet.privilege.model.Certificate; /** @@ -39,12 +40,23 @@ public class TestConfigurationHelper { // initialize container String pwd = System.getProperty("user.dir"); File privilegeContainerXml = new File(pwd + "/config/PrivilegeContainer.xml"); - PrivilegeContainer.getInstance().initialize(privilegeContainerXml); + PrivilegeContainer privilegeContainer = PrivilegeContainer.getInstance(); + privilegeContainer.initialize(privilegeContainerXml); + + PersistenceHandler persistenceHandler = privilegeContainer.getPersistenceHandler(); for (int i = 0; i < 10; i++) { // let's authenticate a session auth("eitch", "1234567890"); } + + // TODO let's add a user + // persistenceHandler.addUser(certificate, user); + + // TODO let's add a role + + // TODO let's add a privilege + } /** diff --git a/src/ch/eitchnet/privilege/model/Certificate.java b/src/ch/eitchnet/privilege/model/Certificate.java index d7269cde2..c7f3bbf85 100644 --- a/src/ch/eitchnet/privilege/model/Certificate.java +++ b/src/ch/eitchnet/privilege/model/Certificate.java @@ -161,6 +161,15 @@ public final class Certificate implements Serializable { */ @Override public String toString() { - return "Certificate [sessionId=" + sessionId + ", username=" + username + ", locale=" + locale + "]"; + StringBuilder builder = new StringBuilder(); + builder.append("Certificate [sessionId="); + builder.append(sessionId); + builder.append(", username="); + builder.append(username); + builder.append(", locale="); + builder.append(locale); + builder.append("]"); + return builder.toString(); } + } diff --git a/src/ch/eitchnet/privilege/model/PrivilegeRep.java b/src/ch/eitchnet/privilege/model/PrivilegeRep.java new file mode 100644 index 000000000..3ac250dda --- /dev/null +++ b/src/ch/eitchnet/privilege/model/PrivilegeRep.java @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.model; + +import java.io.Serializable; +import java.util.HashSet; +import java.util.Set; + +/** + * @author rvonburg + * + */ +public class PrivilegeRep implements Serializable { + + private static final long serialVersionUID = 1L; + + private String name; + private String policy; + private boolean allAllowed; + private Set denyList; + private Set allowList; + + /** + * @param name + * @param policy + * @param allAllowed + * @param denyList + * @param allowList + */ + public PrivilegeRep(String name, String policy, boolean allAllowed, Set denyList, Set allowList) { + this.name = name; + this.policy = policy; + this.allAllowed = allAllowed; + this.denyList = new HashSet(denyList); + this.allowList = new HashSet(allowList); + } + + /** + * @return the name + */ + public String getName() { + return name; + } + + /** + * @param name + * the name to set + */ + public void setName(String name) { + this.name = name; + } + + /** + * @return the policy + */ + public String getPolicy() { + return policy; + } + + /** + * @param policy + * the policy to set + */ + public void setPolicy(String policy) { + this.policy = policy; + } + + /** + * @return the allAllowed + */ + public boolean isAllAllowed() { + return allAllowed; + } + + /** + * @param allAllowed + * the allAllowed to set + */ + public void setAllAllowed(boolean allAllowed) { + this.allAllowed = allAllowed; + } + + /** + * @return the denyList + */ + public Set getDenyList() { + return denyList; + } + + /** + * @param denyList + * the denyList to set + */ + public void setDenyList(Set denyList) { + this.denyList = denyList; + } + + /** + * @return the allowList + */ + public Set getAllowList() { + return allowList; + } + + /** + * @param allowList + * the allowList to set + */ + public void setAllowList(Set allowList) { + this.allowList = allowList; + } +} diff --git a/src/ch/eitchnet/privilege/model/RoleRep.java b/src/ch/eitchnet/privilege/model/RoleRep.java new file mode 100644 index 000000000..92577e24e --- /dev/null +++ b/src/ch/eitchnet/privilege/model/RoleRep.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.model; + +import java.io.Serializable; +import java.util.Set; + +/** + * @author rvonburg + * + */ +public class RoleRep implements Serializable { + + private static final long serialVersionUID = 1L; + + public final String name; + public final Set privileges; + + /** + * @param name + * @param privileges + */ + public RoleRep(String name, Set privileges) { + this.name = name; + this.privileges = privileges; + } +} diff --git a/src/ch/eitchnet/privilege/model/UserRep.java b/src/ch/eitchnet/privilege/model/UserRep.java new file mode 100644 index 000000000..4e3575895 --- /dev/null +++ b/src/ch/eitchnet/privilege/model/UserRep.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.model; + +import java.io.Serializable; +import java.util.Locale; +import java.util.Set; + +/** + * @author rvonburg + * + */ +public class UserRep implements Serializable { + + private static final long serialVersionUID = 1L; + + public final String username; + public final String firstname; + public final String surname; + public final UserState userState; + public final Set roles; + public final Locale locale; + + /** + * @param username + * @param firstname + * @param surname + * @param userState + * @param roles + * @param locale + */ + public UserRep(String username, String firstname, String surname, UserState userState, Set roles, + Locale locale) { + this.username = username; + this.firstname = firstname; + this.surname = surname; + this.userState = userState; + this.roles = roles; + this.locale = locale; + } +} diff --git a/src/ch/eitchnet/privilege/model/internal/Privilege.java b/src/ch/eitchnet/privilege/model/internal/Privilege.java index ccb7a65c0..cf89a1f26 100644 --- a/src/ch/eitchnet/privilege/model/internal/Privilege.java +++ b/src/ch/eitchnet/privilege/model/internal/Privilege.java @@ -11,7 +11,9 @@ package ch.eitchnet.privilege.model.internal; import java.util.Collections; -import java.util.List; +import java.util.Set; + +import ch.eitchnet.privilege.model.PrivilegeRep; /** * @author rvonburg @@ -22,20 +24,20 @@ public final class Privilege { private final String name; private final String policy; private final boolean allAllowed; - private final List denyList; - private final List allowList; + private final Set denyList; + private final Set allowList; /** * @param allAllowed * @param denyList * @param allowList */ - public Privilege(String name, String policy, boolean allAllowed, List denyList, List allowList) { + public Privilege(String name, String policy, boolean allAllowed, Set denyList, Set allowList) { this.name = name; this.policy = policy; this.allAllowed = allAllowed; - this.denyList = Collections.unmodifiableList(denyList); - this.allowList = Collections.unmodifiableList(allowList); + this.denyList = Collections.unmodifiableSet(denyList); + this.allowList = Collections.unmodifiableSet(allowList); } /** @@ -62,15 +64,93 @@ public final class Privilege { /** * @return the allowList */ - public List getAllowList() { + public Set getAllowList() { return allowList; } /** * @return the denyList */ - public List getDenyList() { + public Set getDenyList() { return denyList; } + /** + * @return a {@link PrivilegeRep} which is a representation of this object used to serialize and view on clients + */ + public PrivilegeRep asPrivilegeRep() { + return new PrivilegeRep(name, policy, allAllowed, denyList, allowList); + } + + /** + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("Privilege [name="); + builder.append(name); + builder.append(", policy="); + builder.append(policy); + builder.append(", allAllowed="); + builder.append(allAllowed); + builder.append(", denyList="); + builder.append(denyList); + builder.append(", allowList="); + builder.append(allowList); + builder.append("]"); + return builder.toString(); + } + + /** + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (allAllowed ? 1231 : 1237); + result = prime * result + ((allowList == null) ? 0 : allowList.hashCode()); + result = prime * result + ((denyList == null) ? 0 : denyList.hashCode()); + result = prime * result + ((name == null) ? 0 : name.hashCode()); + result = prime * result + ((policy == null) ? 0 : policy.hashCode()); + return result; + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Privilege other = (Privilege) obj; + if (allAllowed != other.allAllowed) + return false; + if (allowList == null) { + if (other.allowList != null) + return false; + } else if (!allowList.equals(other.allowList)) + return false; + if (denyList == null) { + if (other.denyList != null) + return false; + } else if (!denyList.equals(other.denyList)) + return false; + if (name == null) { + if (other.name != null) + return false; + } else if (!name.equals(other.name)) + return false; + if (policy == null) { + if (other.policy != null) + return false; + } else if (!policy.equals(other.policy)) + return false; + return true; + } } diff --git a/src/ch/eitchnet/privilege/model/internal/Role.java b/src/ch/eitchnet/privilege/model/internal/Role.java index 0dc6322d0..17705febf 100644 --- a/src/ch/eitchnet/privilege/model/internal/Role.java +++ b/src/ch/eitchnet/privilege/model/internal/Role.java @@ -13,6 +13,8 @@ package ch.eitchnet.privilege.model.internal; import java.util.Collections; import java.util.Set; +import ch.eitchnet.privilege.model.RoleRep; + /** * @author rvonburg * @@ -53,4 +55,63 @@ public final class Role { public boolean hasPrivilege(String key) { return privileges.contains(key); } + + /** + * @return a {@link RoleRep} which is a representation of this object used to serialize and view on clients + */ + public RoleRep asRoleRep() { + return new RoleRep(name, privileges); + } + + /** + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("Role [name="); + builder.append(name); + builder.append(", privileges="); + builder.append(privileges); + builder.append("]"); + return builder.toString(); + } + + /** + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((name == null) ? 0 : name.hashCode()); + result = prime * result + ((privileges == null) ? 0 : privileges.hashCode()); + return result; + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + @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 (name == null) { + if (other.name != null) + return false; + } else if (!name.equals(other.name)) + return false; + if (privileges == null) { + if (other.privileges != null) + return false; + } else if (!privileges.equals(other.privileges)) + return false; + return true; + } + } diff --git a/src/ch/eitchnet/privilege/model/internal/Session.java b/src/ch/eitchnet/privilege/model/internal/Session.java index cdcd1cda5..4d4ff4355 100644 --- a/src/ch/eitchnet/privilege/model/internal/Session.java +++ b/src/ch/eitchnet/privilege/model/internal/Session.java @@ -125,6 +125,15 @@ public final class Session { */ @Override public String toString() { - return "Session [username=" + username + ", sessionId=" + sessionId + ", loginTime=" + loginTime + "]"; + StringBuilder builder = new StringBuilder(); + builder.append("Session [sessionId="); + builder.append(sessionId); + builder.append(", username="); + builder.append(username); + builder.append(", loginTime="); + builder.append(loginTime); + builder.append("]"); + return builder.toString(); } + } diff --git a/src/ch/eitchnet/privilege/model/internal/User.java b/src/ch/eitchnet/privilege/model/internal/User.java index 9efc5bb2d..1ff0d208f 100644 --- a/src/ch/eitchnet/privilege/model/internal/User.java +++ b/src/ch/eitchnet/privilege/model/internal/User.java @@ -15,6 +15,7 @@ import java.util.Locale; import java.util.Set; import ch.eitchnet.privilege.i18n.PrivilegeException; +import ch.eitchnet.privilege.model.UserRep; import ch.eitchnet.privilege.model.UserState; /** @@ -112,6 +113,35 @@ public final class User { return locale; } + /** + * @return a {@link UserRep} which is a representation of this object used to serialize and view on clients + */ + public UserRep asUserRep() { + return new UserRep(username, firstname, surname, userState, roles, locale); + } + + /** + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("User [username="); + builder.append(username); + builder.append(", firstname="); + builder.append(firstname); + builder.append(", surname="); + builder.append(surname); + builder.append(", locale="); + builder.append(locale); + builder.append(", userState="); + builder.append(userState); + builder.append(", roles="); + builder.append(roles); + builder.append("]"); + return builder.toString(); + } + /** * @return a new {@link User} object which is authenticated on the current Java Virtual Machine */ From 672768e0b246215133d68f91afcc86fd5d01d4fa Mon Sep 17 00:00:00 2001 From: eitch Date: Sat, 5 Jun 2010 21:34:02 +0000 Subject: [PATCH 021/173] --- src/ch/eitchnet/privilege/base/PrivilegeContainer.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ch/eitchnet/privilege/base/PrivilegeContainer.java b/src/ch/eitchnet/privilege/base/PrivilegeContainer.java index 42d17de2d..608593d11 100644 --- a/src/ch/eitchnet/privilege/base/PrivilegeContainer.java +++ b/src/ch/eitchnet/privilege/base/PrivilegeContainer.java @@ -24,6 +24,8 @@ import ch.eitchnet.privilege.helper.XmlHelper; import ch.eitchnet.privilege.i18n.PrivilegeException; /** + * TODO make persistence handle not accessible + * * @author rvonburg */ public class PrivilegeContainer { From c653edd6797457f01fd08ba3dcab6fe0302558a9 Mon Sep 17 00:00:00 2001 From: eitch Date: Sun, 6 Jun 2010 19:11:36 +0000 Subject: [PATCH 022/173] --- .../privilege/base/PrivilegeContainer.java | 35 ++++++++-------- .../eitchnet/privilege/base/XmlConstants.java | 1 + .../handler/DefaultPersistenceHandler.java | 13 +++--- .../handler/DefaultPolicyHandler.java | 1 + .../handler/DefaultSessionHandler.java | 9 ++-- .../handler/ModificationHandler.java | 41 +++++++++++++++++++ .../privilege/handler/PersistenceHandler.java | 9 ++-- 7 files changed, 77 insertions(+), 32 deletions(-) create mode 100644 src/ch/eitchnet/privilege/handler/ModificationHandler.java diff --git a/src/ch/eitchnet/privilege/base/PrivilegeContainer.java b/src/ch/eitchnet/privilege/base/PrivilegeContainer.java index 608593d11..efc490dd7 100644 --- a/src/ch/eitchnet/privilege/base/PrivilegeContainer.java +++ b/src/ch/eitchnet/privilege/base/PrivilegeContainer.java @@ -16,6 +16,7 @@ import org.apache.log4j.Logger; import org.dom4j.Element; import ch.eitchnet.privilege.handler.EncryptionHandler; +import ch.eitchnet.privilege.handler.ModificationHandler; import ch.eitchnet.privilege.handler.PersistenceHandler; import ch.eitchnet.privilege.handler.PolicyHandler; import ch.eitchnet.privilege.handler.SessionHandler; @@ -46,9 +47,7 @@ public class PrivilegeContainer { private SessionHandler sessionHandler; private PolicyHandler policyHandler; private EncryptionHandler encryptionHandler; - private PersistenceHandler persistenceHandler; - - private String basePath; + private ModificationHandler modificationHandler; public static PrivilegeContainer getInstance() { return instance; @@ -83,17 +82,10 @@ public class PrivilegeContainer { } /** - * @return the persistenceHandler + * @return the modificationHandler */ - public PersistenceHandler getPersistenceHandler() { - return persistenceHandler; - } - - /** - * @return the basePath - */ - public String getBasePath() { - return basePath; + public ModificationHandler getModificationHandler() { + return modificationHandler; } public void initialize(File privilegeContainerXml) { @@ -105,7 +97,7 @@ public class PrivilegeContainer { } // set base path from privilege container xml - basePath = privilegeContainerXml.getParentFile().getAbsolutePath(); + String basePath = privilegeContainerXml.getParentFile().getAbsolutePath(); // parse container xml file to XML document Element containerRootElement = XmlHelper.parseDocument(privilegeContainerXml).getRootElement(); @@ -129,9 +121,14 @@ public class PrivilegeContainer { Element policyHandlerElement = containerRootElement.element(XmlConstants.XML_HANDLER_POLICY); String policyHandlerClassName = policyHandlerElement.attributeValue(XmlConstants.XML_ATTR_CLASS); PolicyHandler policyHandler = ClassHelper.instantiateClass(policyHandlerClassName); + + // instantiate modification handler + Element modificationHandlerElement = containerRootElement.element(XmlConstants.XML_HANDLER_MODIFICATION); + String modificationHandlerClassName = modificationHandlerElement.attributeValue(XmlConstants.XML_ATTR_CLASS); + ModificationHandler modificationHandler = ClassHelper.instantiateClass(policyHandlerClassName); try { - persistenceHandler.initialize(persistenceHandlerElement); + persistenceHandler.initialize(basePath, persistenceHandlerElement); } catch (Exception e) { logger.error(e, e); throw new PrivilegeException("PersistenceHandler " + persistenceHandlerElement @@ -156,9 +153,15 @@ public class PrivilegeContainer { logger.error(e, e); throw new PrivilegeException("PolicyHandler " + policyHandlerClassName + " could not be initialized"); } + try { + modificationHandler.initialize(modificationHandlerElement); + } catch (Exception e) { + logger.error(e, e); + throw new PrivilegeException("ModificationHandler " + modificationHandlerClassName + " could not be initialized"); + } // keep references to the handlers - this.persistenceHandler = persistenceHandler; + this.modificationHandler = modificationHandler; this.sessionHandler = sessionHandler; this.encryptionHandler = encryptionHandler; this.policyHandler = policyHandler; diff --git a/src/ch/eitchnet/privilege/base/XmlConstants.java b/src/ch/eitchnet/privilege/base/XmlConstants.java index 0377adcb7..cc7904f3e 100644 --- a/src/ch/eitchnet/privilege/base/XmlConstants.java +++ b/src/ch/eitchnet/privilege/base/XmlConstants.java @@ -19,6 +19,7 @@ public class XmlConstants { public static final String XML_HANDLER_ENCRYPTION = "EncryptionHandler"; public static final String XML_HANDLER_SESSION = "SessionHandler"; public static final String XML_HANDLER_POLICY = "PolicyHandler"; + public static final String XML_HANDLER_MODIFICATION = "ModificationHandler"; public static final String XML_ROLES = "Roles"; public static final String XML_ROLE = "Role"; diff --git a/src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java index fd07228eb..a49094302 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java @@ -51,14 +51,15 @@ public class DefaultPersistenceHandler implements PersistenceHandler { private boolean roleMapDirty; private boolean privilegeMapDirty; + private PersistenceHandler persistenceHandler; private Map parameterMap; /** - * @see ch.eitchnet.privilege.handler.PersistenceHandler#addPrivilege(ch.eitchnet.privilege.model.Certificate, + * @see ch.eitchnet.privilege.handler.PersistenceHandler#addOrReplacePrivilege(ch.eitchnet.privilege.model.Certificate, * ch.eitchnet.privilege.model.internal.Privilege) */ @Override - public void addPrivilege(Certificate certificate, Privilege privilege) { + public void addOrReplacePrivilege(Certificate certificate, Privilege privilege) { // validate who is doing this PrivilegeHelper.isUserPrivilegeAdmin(certificate); @@ -68,11 +69,11 @@ public class DefaultPersistenceHandler implements PersistenceHandler { } /** - * @see ch.eitchnet.privilege.handler.PersistenceHandler#addRole(ch.eitchnet.privilege.model.Certificate, + * @see ch.eitchnet.privilege.handler.PersistenceHandler#addOrReplaceRole(ch.eitchnet.privilege.model.Certificate, * ch.eitchnet.privilege.model.internal.Role) */ @Override - public void addRole(Certificate certificate, Role role) { + public void addOrReplaceRole(Certificate certificate, Role role) { // validate who is doing this PrivilegeHelper.isUserPrivilegeAdmin(certificate); @@ -82,11 +83,11 @@ public class DefaultPersistenceHandler implements PersistenceHandler { } /** - * @see ch.eitchnet.privilege.handler.PersistenceHandler#addUser(ch.eitchnet.privilege.model.Certificate, + * @see ch.eitchnet.privilege.handler.PersistenceHandler#addOrReplaceUser(ch.eitchnet.privilege.model.Certificate, * ch.eitchnet.privilege.model.internal.User) */ @Override - public void addUser(Certificate certificate, User user) { + public void addOrReplaceUser(Certificate certificate, User user) { // validate who is doing this PrivilegeHelper.isUserPrivilegeAdmin(certificate); diff --git a/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java index a72f5c996..d789197f9 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java @@ -33,6 +33,7 @@ import ch.eitchnet.privilege.policy.RestrictionPolicy; */ public class DefaultPolicyHandler implements PolicyHandler { + private PersistenceHandler persistenceHandler; private Map> policyMap; /** diff --git a/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java b/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java index f63492ffb..c074e6154 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java @@ -36,6 +36,7 @@ public class DefaultSessionHandler implements SessionHandler { private static long lastSessionId; + private PersistenceHandler persistenceHandler; private Map sessionMap; /** @@ -63,7 +64,6 @@ public class DefaultSessionHandler implements SessionHandler { throw new PrivilegeException("Restrictable may not be null!"); PrivilegeContainer privilegeContainer = PrivilegeContainer.getInstance(); - PersistenceHandler persistenceHandler = privilegeContainer.getPersistenceHandler(); // get user object User user = persistenceHandler.getUser(certificate.getUsername()); @@ -80,7 +80,7 @@ public class DefaultSessionHandler implements SessionHandler { PolicyHandler policyHandler = privilegeContainer.getPolicyHandler(); for (String roleName : user.getRoles()) { - Role role = privilegeContainer.getPersistenceHandler().getRole(roleName); + Role role = persistenceHandler.getRole(roleName); if (role == null) { logger.error("No role is defined with name " + roleName + " which is configured for user " + user); continue; @@ -124,8 +124,7 @@ public class DefaultSessionHandler implements SessionHandler { + certificate.getSessionId()); // get user object - User user = PrivilegeContainer.getInstance().getPersistenceHandler().getUser( - certificateSessionPair.session.getUsername()); + User user = persistenceHandler.getUser(certificateSessionPair.session.getUsername()); // if user exists, then certificate is valid if (user == null) { @@ -157,7 +156,7 @@ public class DefaultSessionHandler implements SessionHandler { String passwordHash = encryptionHandler.convertToHash(password); // get user object - User user = PrivilegeContainer.getInstance().getPersistenceHandler().getUser(username); + User user = persistenceHandler.getUser(username); // no user means no authentication if (user == null) throw new AccessDeniedException("There is no user defined with the credentials: " + username + " / ***..."); diff --git a/src/ch/eitchnet/privilege/handler/ModificationHandler.java b/src/ch/eitchnet/privilege/handler/ModificationHandler.java new file mode 100644 index 000000000..b5574c734 --- /dev/null +++ b/src/ch/eitchnet/privilege/handler/ModificationHandler.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.handler; + +import ch.eitchnet.privilege.base.PrivilegeContainerObject; +import ch.eitchnet.privilege.model.Certificate; +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.Privilege; + +/** + * @author rvonburg + * + */ +public interface ModificationHandler extends PrivilegeContainerObject { + + public void addOrReplaceUser(Certificate certificate, UserRep userRep); + + public void setUserPassword(Certificate certificate, String username, String password); + + public void setUserState(Certificate certificate, String username, UserState state); + + public void addOrReplaceRole(Certificate certificate, RoleRep roleRep); + + public void addRoleToUser(Certificate certificate, String username, String role); + + public void addOrReplacePrivilege(Certificate certificate, Privilege privilege); + + public void addPrivilegeToRole(Certificate certificate, String roleName, PrivilegeRep privilegeRep); + +} diff --git a/src/ch/eitchnet/privilege/handler/PersistenceHandler.java b/src/ch/eitchnet/privilege/handler/PersistenceHandler.java index f0efacc8f..a8bb169c0 100644 --- a/src/ch/eitchnet/privilege/handler/PersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/PersistenceHandler.java @@ -26,17 +26,16 @@ import ch.eitchnet.privilege.model.internal.User; public interface PersistenceHandler extends PrivilegeContainerObject { public User getUser(String username); - // public void setUserPassword(String username, String password); - // public void setUserState(String username, UserState state); - public void addUser(Certificate certificate, User user); + + public void addOrReplaceUser(Certificate certificate, User user); public Role getRole(String roleName); - public void addRole(Certificate certificate, Role role); + public void addOrReplaceRole(Certificate certificate, Role role); public Privilege getPrivilege(String privilegeName); - public void addPrivilege(Certificate certificate, Privilege privilege); + public void addOrReplacePrivilege(Certificate certificate, Privilege privilege); public void persist(Certificate certificate); } From 9daaccd31865353b1e660bac912c5ee5f2e37329 Mon Sep 17 00:00:00 2001 From: eitch Date: Sun, 20 Jun 2010 20:11:53 +0000 Subject: [PATCH 023/173] --- .../privilege/base/PrivilegeContainer.java | 36 +- .../eitchnet/privilege/base/XmlConstants.java | 2 +- .../handler/DefaultModelHandler.java | 345 ++++++++++++++++++ .../handler/DefaultPersistenceHandler.java | 160 ++++---- .../handler/DefaultSessionHandler.java | 12 +- .../privilege/handler/ModelHandler.java | 77 ++++ .../handler/ModificationHandler.java | 41 --- .../privilege/handler/PersistenceHandler.java | 14 +- .../privilege/helper/PrivilegeHelper.java | 4 +- .../helper/TestConfigurationHelper.java | 4 +- .../privilege/model/internal/User.java | 16 +- .../privilege/policy/DefaultRestriction.java | 2 +- 12 files changed, 577 insertions(+), 136 deletions(-) create mode 100644 src/ch/eitchnet/privilege/handler/DefaultModelHandler.java create mode 100644 src/ch/eitchnet/privilege/handler/ModelHandler.java delete mode 100644 src/ch/eitchnet/privilege/handler/ModificationHandler.java diff --git a/src/ch/eitchnet/privilege/base/PrivilegeContainer.java b/src/ch/eitchnet/privilege/base/PrivilegeContainer.java index efc490dd7..1bece4bb5 100644 --- a/src/ch/eitchnet/privilege/base/PrivilegeContainer.java +++ b/src/ch/eitchnet/privilege/base/PrivilegeContainer.java @@ -16,7 +16,7 @@ import org.apache.log4j.Logger; import org.dom4j.Element; import ch.eitchnet.privilege.handler.EncryptionHandler; -import ch.eitchnet.privilege.handler.ModificationHandler; +import ch.eitchnet.privilege.handler.ModelHandler; import ch.eitchnet.privilege.handler.PersistenceHandler; import ch.eitchnet.privilege.handler.PolicyHandler; import ch.eitchnet.privilege.handler.SessionHandler; @@ -47,7 +47,9 @@ public class PrivilegeContainer { private SessionHandler sessionHandler; private PolicyHandler policyHandler; private EncryptionHandler encryptionHandler; - private ModificationHandler modificationHandler; + private ModelHandler modelHandler; + + private String basePath; public static PrivilegeContainer getInstance() { return instance; @@ -82,10 +84,17 @@ public class PrivilegeContainer { } /** - * @return the modificationHandler + * @return the modelHandler */ - public ModificationHandler getModificationHandler() { - return modificationHandler; + public ModelHandler getModelHandler() { + return modelHandler; + } + + /** + * @return the basePath + */ + public String getBasePath() { + return basePath; } public void initialize(File privilegeContainerXml) { @@ -97,7 +106,7 @@ public class PrivilegeContainer { } // set base path from privilege container xml - String basePath = privilegeContainerXml.getParentFile().getAbsolutePath(); + basePath = privilegeContainerXml.getParentFile().getAbsolutePath(); // parse container xml file to XML document Element containerRootElement = XmlHelper.parseDocument(privilegeContainerXml).getRootElement(); @@ -121,14 +130,14 @@ public class PrivilegeContainer { Element policyHandlerElement = containerRootElement.element(XmlConstants.XML_HANDLER_POLICY); String policyHandlerClassName = policyHandlerElement.attributeValue(XmlConstants.XML_ATTR_CLASS); PolicyHandler policyHandler = ClassHelper.instantiateClass(policyHandlerClassName); - + // instantiate modification handler - Element modificationHandlerElement = containerRootElement.element(XmlConstants.XML_HANDLER_MODIFICATION); + Element modificationHandlerElement = containerRootElement.element(XmlConstants.XML_HANDLER_MODEL); String modificationHandlerClassName = modificationHandlerElement.attributeValue(XmlConstants.XML_ATTR_CLASS); - ModificationHandler modificationHandler = ClassHelper.instantiateClass(policyHandlerClassName); + ModelHandler modelHandler = ClassHelper.instantiateClass(modificationHandlerClassName); try { - persistenceHandler.initialize(basePath, persistenceHandlerElement); + persistenceHandler.initialize(persistenceHandlerElement); } catch (Exception e) { logger.error(e, e); throw new PrivilegeException("PersistenceHandler " + persistenceHandlerElement @@ -154,14 +163,15 @@ public class PrivilegeContainer { throw new PrivilegeException("PolicyHandler " + policyHandlerClassName + " could not be initialized"); } try { - modificationHandler.initialize(modificationHandlerElement); + modelHandler.initialize(modificationHandlerElement); } catch (Exception e) { logger.error(e, e); - throw new PrivilegeException("ModificationHandler " + modificationHandlerClassName + " could not be initialized"); + throw new PrivilegeException("ModificationHandler " + modificationHandlerClassName + + " could not be initialized"); } // keep references to the handlers - this.modificationHandler = modificationHandler; + this.modelHandler = modelHandler; this.sessionHandler = sessionHandler; this.encryptionHandler = encryptionHandler; this.policyHandler = policyHandler; diff --git a/src/ch/eitchnet/privilege/base/XmlConstants.java b/src/ch/eitchnet/privilege/base/XmlConstants.java index cc7904f3e..95566e5bb 100644 --- a/src/ch/eitchnet/privilege/base/XmlConstants.java +++ b/src/ch/eitchnet/privilege/base/XmlConstants.java @@ -19,7 +19,7 @@ public class XmlConstants { public static final String XML_HANDLER_ENCRYPTION = "EncryptionHandler"; public static final String XML_HANDLER_SESSION = "SessionHandler"; public static final String XML_HANDLER_POLICY = "PolicyHandler"; - public static final String XML_HANDLER_MODIFICATION = "ModificationHandler"; + public static final String XML_HANDLER_MODEL = "ModificationHandler"; public static final String XML_ROLES = "Roles"; public static final String XML_ROLE = "Role"; diff --git a/src/ch/eitchnet/privilege/handler/DefaultModelHandler.java b/src/ch/eitchnet/privilege/handler/DefaultModelHandler.java new file mode 100644 index 000000000..f4bfacc5c --- /dev/null +++ b/src/ch/eitchnet/privilege/handler/DefaultModelHandler.java @@ -0,0 +1,345 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.handler; + +import java.util.List; +import java.util.Locale; + +import org.dom4j.Element; + +import ch.eitchnet.privilege.helper.PrivilegeHelper; +import ch.eitchnet.privilege.model.Certificate; +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.Privilege; +import ch.eitchnet.privilege.model.internal.Role; +import ch.eitchnet.privilege.model.internal.User; + +/** + * @author rvonburg + * + */ +public class DefaultModelHandler implements ModelHandler { + + private PersistenceHandler persistenceHandler; + + /** + * @see ch.eitchnet.privilege.handler.SessionHandler#setPersistenceHandler(ch.eitchnet.privilege.handler.PersistenceHandler) + */ + public void setPersistenceHandler(PersistenceHandler persistenceHandler) { + this.persistenceHandler = persistenceHandler; + } + + /** + * @see ch.eitchnet.privilege.handler.ModelHandler#addOrReplacePrivilege(ch.eitchnet.privilege.model.Certificate, + * ch.eitchnet.privilege.model.PrivilegeRep) + */ + @Override + public void addOrReplacePrivilege(Certificate certificate, PrivilegeRep privilegeRep) { + + // validate who is doing this + PrivilegeHelper.isUserPrivilegeAdmin(certificate); + + // TODO Auto-generated method stub + + } + + /** + * @see ch.eitchnet.privilege.handler.ModelHandler#addOrReplaceRole(ch.eitchnet.privilege.model.Certificate, + * ch.eitchnet.privilege.model.RoleRep) + */ + @Override + public void addOrReplaceRole(Certificate certificate, RoleRep roleRep) { + + // validate who is doing this + PrivilegeHelper.isUserPrivilegeAdmin(certificate); + + // TODO Auto-generated method stub + + } + + /** + * @see ch.eitchnet.privilege.handler.ModelHandler#addOrReplaceUser(ch.eitchnet.privilege.model.Certificate, + * ch.eitchnet.privilege.model.UserRep) + */ + @Override + public void addOrReplaceUser(Certificate certificate, UserRep userRep) { + + // validate who is doing this + PrivilegeHelper.isUserPrivilegeAdmin(certificate); + + // TODO Auto-generated method stub + + } + + /** + * @see ch.eitchnet.privilege.handler.ModelHandler#addPrivilegeToRole(ch.eitchnet.privilege.model.Certificate, + * java.lang.String, java.lang.String) + */ + @Override + public void addPrivilegeToRole(Certificate certificate, String roleName, String privilegeName) { + + // validate who is doing this + PrivilegeHelper.isUserPrivilegeAdmin(certificate); + + // TODO Auto-generated method stub + + } + + /** + * @see ch.eitchnet.privilege.handler.ModelHandler#addRoleToUser(ch.eitchnet.privilege.model.Certificate, + * java.lang.String, java.lang.String) + */ + @Override + public void addRoleToUser(Certificate certificate, String username, String rolename) { + + // validate who is doing this + PrivilegeHelper.isUserPrivilegeAdmin(certificate); + + // TODO Auto-generated method stub + + } + + /** + * @see ch.eitchnet.privilege.handler.ModelHandler#persist(ch.eitchnet.privilege.model.Certificate) + */ + @Override + public boolean persist(Certificate certificate) { + + // validate who is doing this + PrivilegeHelper.isUserPrivilegeAdmin(certificate); + + // TODO Auto-generated method stub + + return false; + } + + /** + * @see ch.eitchnet.privilege.handler.ModelHandler#removePrivilege(ch.eitchnet.privilege.model.Certificate, + * java.lang.String) + */ + @Override + public PrivilegeRep removePrivilege(Certificate certificate, String privilegeName) { + + // validate who is doing this + PrivilegeHelper.isUserPrivilegeAdmin(certificate); + + // TODO Auto-generated method stub + + return null; + } + + /** + * @see ch.eitchnet.privilege.handler.ModelHandler#removePrivilegeFromRole(ch.eitchnet.privilege.model.Certificate, + * java.lang.String, java.lang.String) + */ + @Override + public void removePrivilegeFromRole(Certificate certificate, String roleName, String privilegeName) { + + // validate who is doing this + PrivilegeHelper.isUserPrivilegeAdmin(certificate); + + // TODO Auto-generated method stub + + } + + /** + * @see ch.eitchnet.privilege.handler.ModelHandler#removeRole(ch.eitchnet.privilege.model.Certificate, + * java.lang.String) + */ + @Override + public RoleRep removeRole(Certificate certificate, String roleName) { + + // validate who is doing this + PrivilegeHelper.isUserPrivilegeAdmin(certificate); + + // TODO Auto-generated method stub + + return null; + } + + /** + * @see ch.eitchnet.privilege.handler.ModelHandler#removeRoleFromUser(ch.eitchnet.privilege.model.Certificate, + * java.lang.String, java.lang.String) + */ + @Override + public void removeRoleFromUser(Certificate certificate, String username, String rolename) { + + // validate who is doing this + PrivilegeHelper.isUserPrivilegeAdmin(certificate); + + // TODO Auto-generated method stub + + } + + /** + * @see ch.eitchnet.privilege.handler.ModelHandler#removeUser(ch.eitchnet.privilege.model.Certificate, + * java.lang.String) + */ + @Override + public UserRep removeUser(Certificate certificate, String username) { + + // validate who is doing this + PrivilegeHelper.isUserPrivilegeAdmin(certificate); + + // TODO Auto-generated method stub + + return null; + } + + /** + * @see ch.eitchnet.privilege.handler.ModelHandler#setPrivilegeAllAllowed(ch.eitchnet.privilege.model.Certificate, + * java.lang.String, boolean) + */ + @Override + public void setPrivilegeAllAllowed(Certificate certificate, String privilegeName, boolean allAllowed) { + + // validate who is doing this + PrivilegeHelper.isUserPrivilegeAdmin(certificate); + + // TODO Auto-generated method stub + + } + + /** + * @see ch.eitchnet.privilege.handler.ModelHandler#setPrivilegeAllowList(ch.eitchnet.privilege.model.Certificate, + * java.lang.String, java.util.List) + */ + @Override + public void setPrivilegeAllowList(Certificate certificate, String privilegeName, List allowList) { + + // validate who is doing this + PrivilegeHelper.isUserPrivilegeAdmin(certificate); + + // TODO Auto-generated method stub + + } + + /** + * @see ch.eitchnet.privilege.handler.ModelHandler#setPrivilegeDenyList(ch.eitchnet.privilege.model.Certificate, + * java.lang.String, java.util.List) + */ + @Override + public void setPrivilegeDenyList(Certificate certificate, String privilegeName, List denyList) { + + // validate who is doing this + PrivilegeHelper.isUserPrivilegeAdmin(certificate); + + // TODO Auto-generated method stub + + } + + /** + * @see ch.eitchnet.privilege.handler.ModelHandler#setPrivilegePolicy(ch.eitchnet.privilege.model.Certificate, + * java.lang.String, java.lang.String) + */ + @Override + public void setPrivilegePolicy(Certificate certificate, String privilegeName, String policyName) { + + // validate who is doing this + PrivilegeHelper.isUserPrivilegeAdmin(certificate); + + // TODO Auto-generated method stub + + } + + /** + * @see ch.eitchnet.privilege.handler.ModelHandler#setUserLocaleState(ch.eitchnet.privilege.model.Certificate, + * java.lang.String, java.util.Locale) + */ + @Override + public void setUserLocaleState(Certificate certificate, String username, Locale locale) { + + // validate who is doing this + PrivilegeHelper.isUserPrivilegeAdmin(certificate); + + // TODO Auto-generated method stub + + } + + /** + * @see ch.eitchnet.privilege.handler.ModelHandler#setUserNamePassword(ch.eitchnet.privilege.model.Certificate, + * java.lang.String, java.lang.String, java.lang.String) + */ + @Override + public void setUserNamePassword(Certificate certificate, String username, String firstname, String surname) { + + // validate who is doing this + PrivilegeHelper.isUserPrivilegeAdmin(certificate); + + // TODO Auto-generated method stub + + } + + /** + * @see ch.eitchnet.privilege.handler.ModelHandler#setUserPassword(ch.eitchnet.privilege.model.Certificate, + * java.lang.String, java.lang.String) + */ + @Override + public void setUserPassword(Certificate certificate, String username, String password) { + + // validate who is doing this + PrivilegeHelper.isUserPrivilegeAdmin(certificate); + + // TODO Auto-generated method stub + + } + + /** + * @see ch.eitchnet.privilege.handler.ModelHandler#setUserState(ch.eitchnet.privilege.model.Certificate, + * java.lang.String, ch.eitchnet.privilege.model.UserState) + */ + @Override + public void setUserState(Certificate certificate, String username, UserState state) { + + // validate who is doing this + PrivilegeHelper.isUserPrivilegeAdmin(certificate); + + // TODO Auto-generated method stub + + } + + /** + * @see ch.eitchnet.privilege.base.PrivilegeContainerObject#initialize(org.dom4j.Element) + */ + @Override + public void initialize(Element element) { + // TODO Auto-generated method stub + + } + + /** + * @see ch.eitchnet.privilege.handler.ModelHandler#getPrivilege(java.lang.String) + */ + @Override + public Privilege getPrivilege(String privilegeName) { + return persistenceHandler.getPrivilege(privilegeName); + } + + /** + * @see ch.eitchnet.privilege.handler.ModelHandler#getRole(java.lang.String) + */ + @Override + public Role getRole(String roleName) { + return persistenceHandler.getRole(roleName); + } + + /** + * @see ch.eitchnet.privilege.handler.ModelHandler#getUser(java.lang.String) + */ + @Override + public User getUser(String username) { + return persistenceHandler.getUser(username); + } + +} diff --git a/src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java index a49094302..820a7b1a9 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java @@ -26,7 +26,6 @@ import org.dom4j.Element; import ch.eitchnet.privilege.base.PrivilegeContainer; import ch.eitchnet.privilege.base.XmlConstants; import ch.eitchnet.privilege.helper.ConfigurationHelper; -import ch.eitchnet.privilege.helper.PrivilegeHelper; import ch.eitchnet.privilege.helper.XmlHelper; import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.Certificate; @@ -47,11 +46,13 @@ public class DefaultPersistenceHandler implements PersistenceHandler { private Map roleMap; private Map privilegeMap; + private long usersFileDate; private boolean userMapDirty; + private long rolesFileDate; private boolean roleMapDirty; + private long privilegesFileDate; private boolean privilegeMapDirty; - private PersistenceHandler persistenceHandler; private Map parameterMap; /** @@ -59,43 +60,61 @@ public class DefaultPersistenceHandler implements PersistenceHandler { * ch.eitchnet.privilege.model.internal.Privilege) */ @Override - public void addOrReplacePrivilege(Certificate certificate, Privilege privilege) { - - // validate who is doing this - PrivilegeHelper.isUserPrivilegeAdmin(certificate); - + public void addOrReplacePrivilege(Privilege privilege) { privilegeMap.put(privilege.getName(), privilege); privilegeMapDirty = true; } + /** + * @see ch.eitchnet.privilege.handler.PersistenceHandler#removePrivilege(java.lang.String) + */ + @Override + public Privilege removePrivilege(String privilegeName) { + Privilege privilege = privilegeMap.remove(privilegeName); + privilegeMapDirty = privilege != null; + return privilege; + } + /** * @see ch.eitchnet.privilege.handler.PersistenceHandler#addOrReplaceRole(ch.eitchnet.privilege.model.Certificate, * ch.eitchnet.privilege.model.internal.Role) */ @Override - public void addOrReplaceRole(Certificate certificate, Role role) { - - // validate who is doing this - PrivilegeHelper.isUserPrivilegeAdmin(certificate); - + public void addOrReplaceRole(Role role) { roleMap.put(role.getName(), role); roleMapDirty = true; } + /** + * @see ch.eitchnet.privilege.handler.PersistenceHandler#removeRole(java.lang.String) + */ + @Override + public Role removeRole(String roleName) { + Role role = roleMap.remove(roleName); + roleMapDirty = role != null; + return role; + } + /** * @see ch.eitchnet.privilege.handler.PersistenceHandler#addOrReplaceUser(ch.eitchnet.privilege.model.Certificate, * ch.eitchnet.privilege.model.internal.User) */ @Override - public void addOrReplaceUser(Certificate certificate, User user) { - - // validate who is doing this - PrivilegeHelper.isUserPrivilegeAdmin(certificate); - + public void addOrReplaceUser(User user) { userMap.put(user.getUsername(), user); userMapDirty = true; } + /** + * @see ch.eitchnet.privilege.handler.PersistenceHandler#removeUser(java.lang.String) + */ + @Override + public User removeUser(String username) { + User user = userMap.remove(username); + userMapDirty = user != null; + return user; + } + /** * @see ch.eitchnet.privilege.handler.PersistenceHandler#getPrivilege(java.lang.String) */ @@ -121,44 +140,50 @@ public class DefaultPersistenceHandler implements PersistenceHandler { } /** - * @see ch.eitchnet.privilege.handler.PersistenceHandler#persist() + * @see ch.eitchnet.privilege.handler.PersistenceHandler#persist(ch.eitchnet.privilege.model.Certificate) */ @Override - public void persist(Certificate certificate) { - - // validate who is doing this - PrivilegeHelper.isUserPrivilegeAdmin(certificate); + public boolean persist(Certificate certificate) { // USERS - if (!userMapDirty) { - logger.warn("No users unpersisted."); + // get users file name + String usersFileName = parameterMap.get(XmlConstants.XML_PARAM_USERS_FILE); + if (usersFileName == null || usersFileName.isEmpty()) { + throw new PrivilegeException("[" + SessionHandler.class.getName() + "] Defined parameter " + + XmlConstants.XML_PARAM_USERS_FILE + " is invalid"); + } + // get users file + File usersFile = new File(PrivilegeContainer.getInstance().getBasePath() + "/" + usersFileName); + boolean usersFileUnchanged = usersFile.exists() && usersFile.lastModified() == usersFileDate; + if (!userMapDirty && usersFileUnchanged) { + logger.warn("No users unpersisted and user file unchanged on file system"); } else { logger.info("Persisting users..."); // build XML DOM of users - List users = toDomUsers(); + List users = toDomUsers(certificate); Element rootElement = DocumentFactory.getInstance().createElement(XmlConstants.XML_USERS); for (Element userElement : users) { rootElement.add(userElement); } - // get users file name - String usersFileName = parameterMap.get(XmlConstants.XML_PARAM_USERS_FILE); - if (usersFileName == null || usersFileName.isEmpty()) { - throw new PrivilegeException("[" + SessionHandler.class.getName() + "] Defined parameter " - + XmlConstants.XML_PARAM_USERS_FILE + " is invalid"); - } - - // get users file - File usersFile = new File(PrivilegeContainer.getInstance().getBasePath() + "/" + usersFileName); - // write DOM to file XmlHelper.writeDocument(rootElement, usersFile); + userMapDirty = true; } // ROLES - if (!roleMapDirty) { - logger.warn("No roles unpersisted."); + // get roles file name + String rolesFileName = parameterMap.get(XmlConstants.XML_PARAM_ROLES_FILE); + if (rolesFileName == null || rolesFileName.isEmpty()) { + throw new PrivilegeException("[" + SessionHandler.class.getName() + "] Defined parameter " + + XmlConstants.XML_PARAM_ROLES_FILE + " is invalid"); + } + // get roles file + File rolesFile = new File(PrivilegeContainer.getInstance().getBasePath() + "/" + rolesFileName); + boolean rolesFileUnchanged = rolesFile.exists() && rolesFile.lastModified() == rolesFileDate; + if (!roleMapDirty && rolesFileUnchanged) { + logger.warn("No roles unpersisted and roles file unchanged on file system"); } else { logger.info("Persisting roles..."); @@ -169,23 +194,24 @@ public class DefaultPersistenceHandler implements PersistenceHandler { rootElement.add(roleElement); } - // get roles file name - String rolesFileName = parameterMap.get(XmlConstants.XML_PARAM_ROLES_FILE); - if (rolesFileName == null || rolesFileName.isEmpty()) { - throw new PrivilegeException("[" + SessionHandler.class.getName() + "] Defined parameter " - + XmlConstants.XML_PARAM_ROLES_FILE + " is invalid"); - } - - // get roles file - File rolesFile = new File(PrivilegeContainer.getInstance().getBasePath() + "/" + rolesFileName); - // write DOM to file XmlHelper.writeDocument(rootElement, rolesFile); + roleMapDirty = true; } // PRIVILEGES - if (!privilegeMapDirty) { - logger.warn("No privileges unpersisted."); + // get privileges file name + String privilegesFileName = parameterMap.get(XmlConstants.XML_PARAM_PRIVILEGES_FILE); + if (privilegesFileName == null || privilegesFileName.isEmpty()) { + throw new PrivilegeException("[" + SessionHandler.class.getName() + "] Defined parameter " + + XmlConstants.XML_PARAM_PRIVILEGES_FILE + " is invalid"); + } + // get privileges file + File privilegesFile = new File(PrivilegeContainer.getInstance().getBasePath() + "/" + privilegesFileName); + boolean privilegesFileUnchanged = privilegesFile.exists() + && privilegesFile.lastModified() == privilegesFileDate; + if (!privilegeMapDirty && privilegesFileUnchanged) { + logger.warn("No privileges unpersisted and privileges file unchanged on file system"); } else { logger.info("Persisting privileges..."); @@ -196,23 +222,26 @@ public class DefaultPersistenceHandler implements PersistenceHandler { rootElement.add(privilegeElement); } - // get privileges file name - String privilegesFileName = parameterMap.get(XmlConstants.XML_PARAM_PRIVILEGES_FILE); - if (privilegesFileName == null || privilegesFileName.isEmpty()) { - throw new PrivilegeException("[" + SessionHandler.class.getName() + "] Defined parameter " - + XmlConstants.XML_PARAM_PRIVILEGES_FILE + " is invalid"); - } - - // get privileges file - File privilegesFile = new File(PrivilegeContainer.getInstance().getBasePath() + "/" + privilegesFileName); - // write DOM to file XmlHelper.writeDocument(rootElement, privilegesFile); + privilegeMapDirty = true; } - userMapDirty = false; - roleMapDirty = false; - privilegeMapDirty = false; + // reset dirty states and return if something was dirty, false otherwise + if (userMapDirty || roleMapDirty || privilegeMapDirty) { + userMapDirty = false; + roleMapDirty = false; + privilegeMapDirty = false; + + return true; + + } else { + userMapDirty = false; + roleMapDirty = false; + privilegeMapDirty = false; + + return false; + } } /** @@ -249,6 +278,7 @@ public class DefaultPersistenceHandler implements PersistenceHandler { // read roles readRoles(rolesRootElement); + rolesFileDate = rolesFile.lastModified(); // get users file name String usersFileName = parameterMap.get(XmlConstants.XML_PARAM_USERS_FILE); @@ -270,6 +300,7 @@ public class DefaultPersistenceHandler implements PersistenceHandler { // read users readUsers(usersRootElement); + usersFileDate = usersFile.lastModified(); // get privileges file name String privilegesFileName = parameterMap.get(XmlConstants.XML_PARAM_PRIVILEGES_FILE); @@ -291,6 +322,7 @@ public class DefaultPersistenceHandler implements PersistenceHandler { // read privileges readPrivileges(privilegesRootElement); + privilegesFileDate = privilegesFile.lastModified(); userMapDirty = false; roleMapDirty = false; @@ -487,7 +519,7 @@ public class DefaultPersistenceHandler implements PersistenceHandler { return rolesAsElements; } - private List toDomUsers() { + private List toDomUsers(Certificate certificate) { List usersAsElements = new ArrayList(userMap.size()); @@ -500,7 +532,7 @@ public class DefaultPersistenceHandler implements PersistenceHandler { // create the user element Element userElement = documentFactory.createElement(XmlConstants.XML_USER); userElement.addAttribute(XmlConstants.XML_ATTR_USERNAME, user.getUsername()); - userElement.addAttribute(XmlConstants.XML_ATTR_PASSWORD, user.getPassword()); + userElement.addAttribute(XmlConstants.XML_ATTR_PASSWORD, user.getPassword(certificate)); // add first name element Element firstnameElement = documentFactory.createElement(XmlConstants.XML_FIRSTNAME); diff --git a/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java b/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java index c074e6154..4e38fc69a 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java @@ -36,7 +36,6 @@ public class DefaultSessionHandler implements SessionHandler { private static long lastSessionId; - private PersistenceHandler persistenceHandler; private Map sessionMap; /** @@ -66,7 +65,7 @@ public class DefaultSessionHandler implements SessionHandler { PrivilegeContainer privilegeContainer = PrivilegeContainer.getInstance(); // get user object - User user = persistenceHandler.getUser(certificate.getUsername()); + User user = PrivilegeContainer.getInstance().getModelHandler().getUser(certificate.getUsername()); if (user == null) { throw new PrivilegeException( "Oh boy, how did this happen: No User in user map although the certificate is valid!"); @@ -80,7 +79,7 @@ public class DefaultSessionHandler implements SessionHandler { PolicyHandler policyHandler = privilegeContainer.getPolicyHandler(); for (String roleName : user.getRoles()) { - Role role = persistenceHandler.getRole(roleName); + Role role = PrivilegeContainer.getInstance().getModelHandler().getRole(roleName); if (role == null) { logger.error("No role is defined with name " + roleName + " which is configured for user " + user); continue; @@ -124,7 +123,8 @@ public class DefaultSessionHandler implements SessionHandler { + certificate.getSessionId()); // get user object - User user = persistenceHandler.getUser(certificateSessionPair.session.getUsername()); + User user = PrivilegeContainer.getInstance().getModelHandler().getUser( + certificateSessionPair.session.getUsername()); // if user exists, then certificate is valid if (user == null) { @@ -156,13 +156,13 @@ public class DefaultSessionHandler implements SessionHandler { String passwordHash = encryptionHandler.convertToHash(password); // get user object - User user = persistenceHandler.getUser(username); + User user = PrivilegeContainer.getInstance().getModelHandler().getUser(username); // no user means no authentication if (user == null) throw new AccessDeniedException("There is no user defined with the credentials: " + username + " / ***..."); // validate password - if (!user.getPassword().equals(passwordHash)) + if (!user.isPassword(passwordHash)) throw new AccessDeniedException("Password is incorrect for " + username + " / ***..."); // validate if user is allowed to login diff --git a/src/ch/eitchnet/privilege/handler/ModelHandler.java b/src/ch/eitchnet/privilege/handler/ModelHandler.java new file mode 100644 index 000000000..c464ebb2f --- /dev/null +++ b/src/ch/eitchnet/privilege/handler/ModelHandler.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.handler; + +import java.util.List; +import java.util.Locale; + +import ch.eitchnet.privilege.base.PrivilegeContainerObject; +import ch.eitchnet.privilege.model.Certificate; +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.Privilege; +import ch.eitchnet.privilege.model.internal.Role; +import ch.eitchnet.privilege.model.internal.User; + +/** + * @author rvonburg + * + */ +public interface ModelHandler extends PrivilegeContainerObject { + + public void setPersistenceHandler(PersistenceHandler persistenceHandler); + + public User getUser(String username); + + public void addOrReplaceUser(Certificate certificate, UserRep userRep); + + public UserRep removeUser(Certificate certificate, String username); + + public void setUserPassword(Certificate certificate, String username, String password); + + public void setUserNamePassword(Certificate certificate, String username, String firstname, String surname); + + public void setUserState(Certificate certificate, String username, UserState state); + + public void setUserLocaleState(Certificate certificate, String username, Locale locale); + + public void addRoleToUser(Certificate certificate, String username, String rolename); + + public void removeRoleFromUser(Certificate certificate, String username, String rolename); + + public void addOrReplaceRole(Certificate certificate, RoleRep roleRep); + + public Role getRole(String roleName); + + public RoleRep removeRole(Certificate certificate, String roleName); + + public void addPrivilegeToRole(Certificate certificate, String roleName, String privilegeName); + + public void removePrivilegeFromRole(Certificate certificate, String roleName, String privilegeName); + + public Privilege getPrivilege(String privilegeName); + + public void addOrReplacePrivilege(Certificate certificate, PrivilegeRep privilegeRep); + + public PrivilegeRep removePrivilege(Certificate certificate, String privilegeName); + + public void setPrivilegePolicy(Certificate certificate, String privilegeName, String policyName); + + public void setPrivilegeAllAllowed(Certificate certificate, String privilegeName, boolean allAllowed); + + public void setPrivilegeDenyList(Certificate certificate, String privilegeName, List denyList); + + public void setPrivilegeAllowList(Certificate certificate, String privilegeName, List allowList); + + public boolean persist(Certificate certificate); +} diff --git a/src/ch/eitchnet/privilege/handler/ModificationHandler.java b/src/ch/eitchnet/privilege/handler/ModificationHandler.java deleted file mode 100644 index b5574c734..000000000 --- a/src/ch/eitchnet/privilege/handler/ModificationHandler.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2010 - * - * Robert von Burg - * eitch@eitchnet.ch - * - * All rights reserved. - * - */ - -package ch.eitchnet.privilege.handler; - -import ch.eitchnet.privilege.base.PrivilegeContainerObject; -import ch.eitchnet.privilege.model.Certificate; -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.Privilege; - -/** - * @author rvonburg - * - */ -public interface ModificationHandler extends PrivilegeContainerObject { - - public void addOrReplaceUser(Certificate certificate, UserRep userRep); - - public void setUserPassword(Certificate certificate, String username, String password); - - public void setUserState(Certificate certificate, String username, UserState state); - - public void addOrReplaceRole(Certificate certificate, RoleRep roleRep); - - public void addRoleToUser(Certificate certificate, String username, String role); - - public void addOrReplacePrivilege(Certificate certificate, Privilege privilege); - - public void addPrivilegeToRole(Certificate certificate, String roleName, PrivilegeRep privilegeRep); - -} diff --git a/src/ch/eitchnet/privilege/handler/PersistenceHandler.java b/src/ch/eitchnet/privilege/handler/PersistenceHandler.java index a8bb169c0..ded4736ed 100644 --- a/src/ch/eitchnet/privilege/handler/PersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/PersistenceHandler.java @@ -27,15 +27,21 @@ public interface PersistenceHandler extends PrivilegeContainerObject { public User getUser(String username); - public void addOrReplaceUser(Certificate certificate, User user); + public void addOrReplaceUser(User user); + + public User removeUser(String username); public Role getRole(String roleName); - public void addOrReplaceRole(Certificate certificate, Role role); + public void addOrReplaceRole(Role role); + + public Role removeRole(String roleName); public Privilege getPrivilege(String privilegeName); - public void addOrReplacePrivilege(Certificate certificate, Privilege privilege); + public void addOrReplacePrivilege(Privilege privilege); - public void persist(Certificate certificate); + public Privilege removePrivilege(String privilegeName); + + public boolean persist(Certificate certificate); } diff --git a/src/ch/eitchnet/privilege/helper/PrivilegeHelper.java b/src/ch/eitchnet/privilege/helper/PrivilegeHelper.java index 720df2b4d..fff3c1114 100644 --- a/src/ch/eitchnet/privilege/helper/PrivilegeHelper.java +++ b/src/ch/eitchnet/privilege/helper/PrivilegeHelper.java @@ -18,7 +18,7 @@ import ch.eitchnet.privilege.model.internal.User; /** * @author rvonburg - * + * */ public class PrivilegeHelper { @@ -29,7 +29,7 @@ public class PrivilegeHelper { } // get user object - User user = PrivilegeContainer.getInstance().getPersistenceHandler().getUser(certificate.getUsername()); + User user = PrivilegeContainer.getInstance().getModelHandler().getUser(certificate.getUsername()); if (user == null) { throw new PrivilegeException( "Oh boy, how did this happen: No User in user map although the certificate is valid!"); diff --git a/src/ch/eitchnet/privilege/helper/TestConfigurationHelper.java b/src/ch/eitchnet/privilege/helper/TestConfigurationHelper.java index 3119acd76..41662774c 100644 --- a/src/ch/eitchnet/privilege/helper/TestConfigurationHelper.java +++ b/src/ch/eitchnet/privilege/helper/TestConfigurationHelper.java @@ -19,7 +19,7 @@ import org.apache.log4j.Logger; import org.apache.log4j.PatternLayout; import ch.eitchnet.privilege.base.PrivilegeContainer; -import ch.eitchnet.privilege.handler.PersistenceHandler; +import ch.eitchnet.privilege.handler.ModelHandler; import ch.eitchnet.privilege.model.Certificate; /** @@ -43,7 +43,7 @@ public class TestConfigurationHelper { PrivilegeContainer privilegeContainer = PrivilegeContainer.getInstance(); privilegeContainer.initialize(privilegeContainerXml); - PersistenceHandler persistenceHandler = privilegeContainer.getPersistenceHandler(); + ModelHandler modelHandler = privilegeContainer.getModelHandler(); for (int i = 0; i < 10; i++) { // let's authenticate a session diff --git a/src/ch/eitchnet/privilege/model/internal/User.java b/src/ch/eitchnet/privilege/model/internal/User.java index 1ff0d208f..3edc0575a 100644 --- a/src/ch/eitchnet/privilege/model/internal/User.java +++ b/src/ch/eitchnet/privilege/model/internal/User.java @@ -14,7 +14,9 @@ import java.util.Collections; import java.util.Locale; import java.util.Set; +import ch.eitchnet.privilege.helper.PrivilegeHelper; import ch.eitchnet.privilege.i18n.PrivilegeException; +import ch.eitchnet.privilege.model.Certificate; import ch.eitchnet.privilege.model.UserRep; import ch.eitchnet.privilege.model.UserState; @@ -64,8 +66,18 @@ public final class User { /** * @return the password */ - public String getPassword() { - return password; + public String getPassword(Certificate certificate) { + if (PrivilegeHelper.isUserPrivilegeAdmin(certificate)) + return password; + else + return null; + } + + /** + * @return the password + */ + public boolean isPassword(String password) { + return this.password.equals(password); } /** diff --git a/src/ch/eitchnet/privilege/policy/DefaultRestriction.java b/src/ch/eitchnet/privilege/policy/DefaultRestriction.java index 77e7ba980..d0d27d1e8 100644 --- a/src/ch/eitchnet/privilege/policy/DefaultRestriction.java +++ b/src/ch/eitchnet/privilege/policy/DefaultRestriction.java @@ -40,7 +40,7 @@ public class DefaultRestriction implements RestrictionPolicy { } // get restriction object for users role - Privilege privilege = PrivilegeContainer.getInstance().getPersistenceHandler().getPrivilege(restrictionKey); + Privilege privilege = PrivilegeContainer.getInstance().getModelHandler().getPrivilege(restrictionKey); // no restriction object means no privilege // TODO should default deny/allow policy be configurable? From bfb7082a47f1411cec1afb240ae2f3ee8ef2c753 Mon Sep 17 00:00:00 2001 From: eitch Date: Sun, 20 Jun 2010 20:12:19 +0000 Subject: [PATCH 024/173] --- src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java index d789197f9..a72f5c996 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java @@ -33,7 +33,6 @@ import ch.eitchnet.privilege.policy.RestrictionPolicy; */ public class DefaultPolicyHandler implements PolicyHandler { - private PersistenceHandler persistenceHandler; private Map> policyMap; /** From f491dd7293311600fb83ec62a2231f0060f04aff Mon Sep 17 00:00:00 2001 From: eitch Date: Sun, 20 Jun 2010 20:13:58 +0000 Subject: [PATCH 025/173] --- config/PrivilegeContainer.xml | 1 + src/ch/eitchnet/privilege/base/PrivilegeContainer.java | 1 + src/ch/eitchnet/privilege/base/XmlConstants.java | 2 +- src/ch/eitchnet/privilege/helper/TestConfigurationHelper.java | 2 +- 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/config/PrivilegeContainer.xml b/config/PrivilegeContainer.xml index dad421977..0c64f7a94 100644 --- a/config/PrivilegeContainer.xml +++ b/config/PrivilegeContainer.xml @@ -9,6 +9,7 @@ + diff --git a/src/ch/eitchnet/privilege/base/PrivilegeContainer.java b/src/ch/eitchnet/privilege/base/PrivilegeContainer.java index 1bece4bb5..582fa2579 100644 --- a/src/ch/eitchnet/privilege/base/PrivilegeContainer.java +++ b/src/ch/eitchnet/privilege/base/PrivilegeContainer.java @@ -164,6 +164,7 @@ public class PrivilegeContainer { } try { modelHandler.initialize(modificationHandlerElement); + modelHandler.setPersistenceHandler(persistenceHandler); } catch (Exception e) { logger.error(e, e); throw new PrivilegeException("ModificationHandler " + modificationHandlerClassName diff --git a/src/ch/eitchnet/privilege/base/XmlConstants.java b/src/ch/eitchnet/privilege/base/XmlConstants.java index 95566e5bb..fbf3e9dda 100644 --- a/src/ch/eitchnet/privilege/base/XmlConstants.java +++ b/src/ch/eitchnet/privilege/base/XmlConstants.java @@ -19,7 +19,7 @@ public class XmlConstants { public static final String XML_HANDLER_ENCRYPTION = "EncryptionHandler"; public static final String XML_HANDLER_SESSION = "SessionHandler"; public static final String XML_HANDLER_POLICY = "PolicyHandler"; - public static final String XML_HANDLER_MODEL = "ModificationHandler"; + public static final String XML_HANDLER_MODEL = "ModelHandler"; public static final String XML_ROLES = "Roles"; public static final String XML_ROLE = "Role"; diff --git a/src/ch/eitchnet/privilege/helper/TestConfigurationHelper.java b/src/ch/eitchnet/privilege/helper/TestConfigurationHelper.java index 41662774c..0d987bed0 100644 --- a/src/ch/eitchnet/privilege/helper/TestConfigurationHelper.java +++ b/src/ch/eitchnet/privilege/helper/TestConfigurationHelper.java @@ -43,7 +43,7 @@ public class TestConfigurationHelper { PrivilegeContainer privilegeContainer = PrivilegeContainer.getInstance(); privilegeContainer.initialize(privilegeContainerXml); - ModelHandler modelHandler = privilegeContainer.getModelHandler(); + // ModelHandler modelHandler = privilegeContainer.getModelHandler(); for (int i = 0; i < 10; i++) { // let's authenticate a session From b523f680f9ad59047534410d72642292912752b9 Mon Sep 17 00:00:00 2001 From: eitch Date: Mon, 21 Jun 2010 21:45:55 +0000 Subject: [PATCH 026/173] - implemented default model handler - added JUnit 4 test case --- .classpath | 2 + .../privilege/base/PrivilegeContainer.java | 2 - .../handler/DefaultModelHandler.java | 402 +++++++++++++++--- .../handler/DefaultPersistenceHandler.java | 4 +- .../privilege/handler/ModelHandler.java | 14 +- .../privilege/handler/PersistenceHandler.java | 3 - .../privilege/helper/PrivilegeHelper.java | 6 +- .../helper/TestConfigurationHelper.java | 16 +- src/ch/eitchnet/privilege/model/RoleRep.java | 34 +- src/ch/eitchnet/privilege/model/UserRep.java | 96 ++++- .../privilege/model/internal/User.java | 43 +- .../privilege/test/PrivilegeTest.java | 136 ++++++ 12 files changed, 640 insertions(+), 118 deletions(-) create mode 100644 test/ch/eitchnet/privilege/test/PrivilegeTest.java diff --git a/.classpath b/.classpath index bba4c498c..f52b649dc 100644 --- a/.classpath +++ b/.classpath @@ -1,8 +1,10 @@ + + diff --git a/src/ch/eitchnet/privilege/base/PrivilegeContainer.java b/src/ch/eitchnet/privilege/base/PrivilegeContainer.java index 582fa2579..48ca8f866 100644 --- a/src/ch/eitchnet/privilege/base/PrivilegeContainer.java +++ b/src/ch/eitchnet/privilege/base/PrivilegeContainer.java @@ -25,8 +25,6 @@ import ch.eitchnet.privilege.helper.XmlHelper; import ch.eitchnet.privilege.i18n.PrivilegeException; /** - * TODO make persistence handle not accessible - * * @author rvonburg */ public class PrivilegeContainer { diff --git a/src/ch/eitchnet/privilege/handler/DefaultModelHandler.java b/src/ch/eitchnet/privilege/handler/DefaultModelHandler.java index f4bfacc5c..9dfd0b806 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultModelHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultModelHandler.java @@ -10,12 +10,16 @@ package ch.eitchnet.privilege.handler; -import java.util.List; +import java.util.HashSet; import java.util.Locale; +import java.util.Set; +import org.apache.log4j.Logger; import org.dom4j.Element; +import ch.eitchnet.privilege.base.PrivilegeContainer; import ch.eitchnet.privilege.helper.PrivilegeHelper; +import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.Certificate; import ch.eitchnet.privilege.model.PrivilegeRep; import ch.eitchnet.privilege.model.RoleRep; @@ -31,6 +35,8 @@ import ch.eitchnet.privilege.model.internal.User; */ public class DefaultModelHandler implements ModelHandler { + private static final Logger logger = Logger.getLogger(DefaultModelHandler.class); + private PersistenceHandler persistenceHandler; /** @@ -48,10 +54,18 @@ public class DefaultModelHandler implements ModelHandler { public void addOrReplacePrivilege(Certificate certificate, PrivilegeRep privilegeRep) { // validate who is doing this - PrivilegeHelper.isUserPrivilegeAdmin(certificate); + if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { + logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " + + certificate); + return; + } - // TODO Auto-generated method stub + // create a new privilege + Privilege privilege = new Privilege(privilegeRep.getName(), privilegeRep.getPolicy(), privilegeRep + .isAllAllowed(), privilegeRep.getDenyList(), privilegeRep.getAllowList()); + // delegate to persistence handler + persistenceHandler.addOrReplacePrivilege(privilege); } /** @@ -62,24 +76,46 @@ public class DefaultModelHandler implements ModelHandler { public void addOrReplaceRole(Certificate certificate, RoleRep roleRep) { // validate who is doing this - PrivilegeHelper.isUserPrivilegeAdmin(certificate); + if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { + logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " + + certificate); + return; + } - // TODO Auto-generated method stub + // create new role + Role role = new Role(roleRep.getName(), roleRep.getPrivileges()); + // delegate to persistence handler + persistenceHandler.addOrReplaceRole(role); } /** * @see ch.eitchnet.privilege.handler.ModelHandler#addOrReplaceUser(ch.eitchnet.privilege.model.Certificate, - * ch.eitchnet.privilege.model.UserRep) + * ch.eitchnet.privilege.model.UserRep, java.lang.String) */ @Override - public void addOrReplaceUser(Certificate certificate, UserRep userRep) { + public void addOrReplaceUser(Certificate certificate, UserRep userRep, String password) { // validate who is doing this - PrivilegeHelper.isUserPrivilegeAdmin(certificate); + if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { + logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " + + certificate); + return; + } - // TODO Auto-generated method stub + // hash password + String passwordHash; + if (password == null) + passwordHash = null; + else + passwordHash = PrivilegeContainer.getInstance().getEncryptionHandler().convertToHash(password); + // create new user + User user = new User(userRep.getUsername(), passwordHash, userRep.getFirstname(), userRep.getSurname(), userRep + .getUserState(), userRep.getRoles(), userRep.getLocale()); + + // delegate to persistence handler + persistenceHandler.addOrReplaceUser(user); } /** @@ -90,10 +126,39 @@ public class DefaultModelHandler implements ModelHandler { public void addPrivilegeToRole(Certificate certificate, String roleName, String privilegeName) { // validate who is doing this - PrivilegeHelper.isUserPrivilegeAdmin(certificate); + if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { + logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " + + certificate); + return; + } - // TODO Auto-generated method stub + // get role + Role role = getRole(roleName); + if (role == null) { + throw new PrivilegeException("Role " + roleName + " does not exist!"); + } + // ignore if role already has this privilege + Set currentPrivileges = role.getPrivileges(); + if (currentPrivileges.contains(roleName)) { + logger.error("Role " + roleName + " already has privilege " + privilegeName); + return; + } + + // validate that privilege exists + if (getPrivilege(privilegeName) == null) { + throw new PrivilegeException("Privilege " + privilegeName + " does not exist and can not be added to role " + + roleName); + } + + // create new role with the additional privilege + Set newPrivileges = new HashSet(currentPrivileges); + newPrivileges.add(roleName); + + Role newRole = new Role(role.getName(), newPrivileges); + + // delegate role replacement to persistence handler + persistenceHandler.addOrReplaceRole(newRole); } /** @@ -101,13 +166,42 @@ public class DefaultModelHandler implements ModelHandler { * java.lang.String, java.lang.String) */ @Override - public void addRoleToUser(Certificate certificate, String username, String rolename) { + public void addRoleToUser(Certificate certificate, String username, String roleName) { // validate who is doing this - PrivilegeHelper.isUserPrivilegeAdmin(certificate); + if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { + logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " + + certificate); + return; + } - // TODO Auto-generated method stub + // get user + User user = getUser(username); + if (user == null) { + throw new PrivilegeException("User " + username + " does not exist!"); + } + // ignore if user already has role + Set currentRoles = user.getRoles(); + if (currentRoles.contains(roleName)) { + logger.error("User " + username + " already has role " + roleName); + return; + } + + // validate that role exists + if (getRole(roleName) == null) { + throw new PrivilegeException("Role " + roleName + " doest not exist!"); + } + + // create new user + Set newRoles = new HashSet(currentRoles); + currentRoles.add(roleName); + + User newUser = new User(user.getUsername(), user.getPassword(certificate), user.getFirstname(), user + .getSurname(), user.getState(), newRoles, user.getLocale()); + + // delegate user replacement to persistence handler + persistenceHandler.addOrReplaceUser(newUser); } /** @@ -117,11 +211,13 @@ public class DefaultModelHandler implements ModelHandler { public boolean persist(Certificate certificate) { // validate who is doing this - PrivilegeHelper.isUserPrivilegeAdmin(certificate); + if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { + logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " + + certificate); + return false; + } - // TODO Auto-generated method stub - - return false; + return persistenceHandler.persist(certificate); } /** @@ -132,11 +228,20 @@ public class DefaultModelHandler implements ModelHandler { public PrivilegeRep removePrivilege(Certificate certificate, String privilegeName) { // validate who is doing this - PrivilegeHelper.isUserPrivilegeAdmin(certificate); + if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { + logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " + + certificate); + return null; + } - // TODO Auto-generated method stub + // delegate privilege removal to persistence handler + Privilege removedPrivilege = persistenceHandler.removePrivilege(privilegeName); - return null; + // return privilege rep if it was removed + if (removedPrivilege != null) + return removedPrivilege.asPrivilegeRep(); + else + return null; } /** @@ -147,10 +252,32 @@ public class DefaultModelHandler implements ModelHandler { public void removePrivilegeFromRole(Certificate certificate, String roleName, String privilegeName) { // validate who is doing this - PrivilegeHelper.isUserPrivilegeAdmin(certificate); + if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { + logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " + + certificate); + return; + } - // TODO Auto-generated method stub + // get role + Role role = getRole(roleName); + if (role == null) { + throw new PrivilegeException("Role " + roleName + " does not exist!"); + } + // ignore if role does not have privilege + Set currentPrivileges = role.getPrivileges(); + if (!currentPrivileges.contains(privilegeName)) { + logger.error("Role " + roleName + " doest not have privilege " + privilegeName); + return; + } + + // create new role + Set newPrivileges = new HashSet(currentPrivileges); + newPrivileges.remove(privilegeName); + Role newRole = new Role(role.getName(), newPrivileges); + + // delegate user replacement to persistence handler + persistenceHandler.addOrReplaceRole(newRole); } /** @@ -161,11 +288,20 @@ public class DefaultModelHandler implements ModelHandler { public RoleRep removeRole(Certificate certificate, String roleName) { // validate who is doing this - PrivilegeHelper.isUserPrivilegeAdmin(certificate); + if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { + logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " + + certificate); + return null; + } - // TODO Auto-generated method stub + // delegate role removal to persistence handler + Role removedRole = persistenceHandler.removeRole(roleName); - return null; + // return role rep if it was removed + if (removedRole != null) + return removedRole.asRoleRep(); + else + return null; } /** @@ -173,13 +309,36 @@ public class DefaultModelHandler implements ModelHandler { * java.lang.String, java.lang.String) */ @Override - public void removeRoleFromUser(Certificate certificate, String username, String rolename) { + public void removeRoleFromUser(Certificate certificate, String username, String roleName) { // validate who is doing this - PrivilegeHelper.isUserPrivilegeAdmin(certificate); + if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { + logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " + + certificate); + return; + } - // TODO Auto-generated method stub + // get User + User user = getUser(username); + if (user == null) { + throw new PrivilegeException("User " + username + " does not exist!"); + } + // ignore if user does not have role + Set currentRoles = user.getRoles(); + if (!currentRoles.contains(roleName)) { + logger.error("User " + user + " does not have role " + roleName); + return; + } + + // create new user + Set newRoles = new HashSet(currentRoles); + newRoles.remove(roleName); + User newUser = new User(user.getUsername(), user.getPassword(certificate), user.getFirstname(), user + .getSurname(), user.getState(), newRoles, user.getLocale()); + + // delegate user replacement to persistence handler + persistenceHandler.addOrReplaceUser(newUser); } /** @@ -190,11 +349,20 @@ public class DefaultModelHandler implements ModelHandler { public UserRep removeUser(Certificate certificate, String username) { // validate who is doing this - PrivilegeHelper.isUserPrivilegeAdmin(certificate); + if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { + logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " + + certificate); + return null; + } - // TODO Auto-generated method stub + // delegate user removal to persistence handler + User removedUser = persistenceHandler.removeUser(username); - return null; + // return user rep if it was removed + if (removedUser != null) + return removedUser.asUserRep(); + else + return null; } /** @@ -205,38 +373,87 @@ public class DefaultModelHandler implements ModelHandler { public void setPrivilegeAllAllowed(Certificate certificate, String privilegeName, boolean allAllowed) { // validate who is doing this - PrivilegeHelper.isUserPrivilegeAdmin(certificate); + if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { + logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " + + certificate); + return; + } - // TODO Auto-generated method stub + // get Privilege + Privilege privilege = getPrivilege(privilegeName); + if (privilege == null) { + throw new PrivilegeException("Privilege " + privilegeName + " does not exist!"); + } + // ignore if privilege is already set to argument + if (privilege.isAllAllowed() == allAllowed) { + logger.error("Privilege " + privilegeName + " is already set to " + + (allAllowed ? "all allowed" : "not all allowed")); + return; + } + + // create new privilege + Privilege newPrivilege = new Privilege(privilege.getName(), privilege.getPolicy(), allAllowed, privilege + .getDenyList(), privilege.getAllowList()); + + // delegate privilege replacement to persistence handler + persistenceHandler.addOrReplacePrivilege(newPrivilege); } /** * @see ch.eitchnet.privilege.handler.ModelHandler#setPrivilegeAllowList(ch.eitchnet.privilege.model.Certificate, - * java.lang.String, java.util.List) + * java.lang.String, java.util.Set) */ @Override - public void setPrivilegeAllowList(Certificate certificate, String privilegeName, List allowList) { + public void setPrivilegeAllowList(Certificate certificate, String privilegeName, Set allowList) { // validate who is doing this - PrivilegeHelper.isUserPrivilegeAdmin(certificate); + if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { + logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " + + certificate); + return; + } - // TODO Auto-generated method stub + // get Privilege + Privilege privilege = getPrivilege(privilegeName); + if (privilege == null) { + throw new PrivilegeException("Privilege " + privilegeName + " does not exist!"); + } + // create new privilege + Privilege newPrivilege = new Privilege(privilege.getName(), privilege.getPolicy(), privilege.isAllAllowed(), + privilege.getDenyList(), allowList); + + // delegate privilege replacement to persistence handler + persistenceHandler.addOrReplacePrivilege(newPrivilege); } /** * @see ch.eitchnet.privilege.handler.ModelHandler#setPrivilegeDenyList(ch.eitchnet.privilege.model.Certificate, - * java.lang.String, java.util.List) + * java.lang.String, java.util.Set) */ @Override - public void setPrivilegeDenyList(Certificate certificate, String privilegeName, List denyList) { + public void setPrivilegeDenyList(Certificate certificate, String privilegeName, Set denyList) { // validate who is doing this - PrivilegeHelper.isUserPrivilegeAdmin(certificate); + if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { + logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " + + certificate); + return; + } - // TODO Auto-generated method stub + // get Privilege + Privilege privilege = getPrivilege(privilegeName); + if (privilege == null) { + throw new PrivilegeException("Privilege " + privilegeName + " does not exist!"); + } + // create new privilege + Privilege newPrivilege = new Privilege(privilege.getName(), privilege.getPolicy(), privilege.isAllAllowed(), + denyList, privilege.getAllowList()); + + // delegate privilege replacement to persistence handler + persistenceHandler.addOrReplacePrivilege(newPrivilege); } /** @@ -247,10 +464,24 @@ public class DefaultModelHandler implements ModelHandler { public void setPrivilegePolicy(Certificate certificate, String privilegeName, String policyName) { // validate who is doing this - PrivilegeHelper.isUserPrivilegeAdmin(certificate); + if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { + logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " + + certificate); + return; + } - // TODO Auto-generated method stub + // get Privilege + Privilege privilege = getPrivilege(privilegeName); + if (privilege == null) { + throw new PrivilegeException("Privilege " + privilegeName + " does not exist!"); + } + // create new privilege + Privilege newPrivilege = new Privilege(privilege.getName(), policyName, privilege.isAllAllowed(), privilege + .getDenyList(), privilege.getAllowList()); + + // delegate privilege replacement to persistence handler + persistenceHandler.addOrReplacePrivilege(newPrivilege); } /** @@ -261,24 +492,52 @@ public class DefaultModelHandler implements ModelHandler { public void setUserLocaleState(Certificate certificate, String username, Locale locale) { // validate who is doing this - PrivilegeHelper.isUserPrivilegeAdmin(certificate); + if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { + logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " + + certificate); + return; + } - // TODO Auto-generated method stub + // get User + User user = getUser(username); + if (user == null) { + throw new PrivilegeException("User " + username + " does not exist!"); + } + // create new user + User newUser = new User(user.getUsername(), user.getPassword(certificate), user.getFirstname(), user + .getSurname(), user.getState(), user.getRoles(), locale); + + // delegate user replacement to persistence handler + persistenceHandler.addOrReplaceUser(newUser); } /** - * @see ch.eitchnet.privilege.handler.ModelHandler#setUserNamePassword(ch.eitchnet.privilege.model.Certificate, + * @see ch.eitchnet.privilege.handler.ModelHandler#setUserName(ch.eitchnet.privilege.model.Certificate, * java.lang.String, java.lang.String, java.lang.String) */ @Override - public void setUserNamePassword(Certificate certificate, String username, String firstname, String surname) { + public void setUserName(Certificate certificate, String username, String firstname, String surname) { // validate who is doing this - PrivilegeHelper.isUserPrivilegeAdmin(certificate); + if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { + logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " + + certificate); + return; + } - // TODO Auto-generated method stub + // get User + User user = getUser(username); + if (user == null) { + throw new PrivilegeException("User " + username + " does not exist!"); + } + // create new user + User newUser = new User(user.getUsername(), user.getPassword(certificate), firstname, surname, user.getState(), + user.getRoles(), user.getLocale()); + + // delegate user replacement to persistence handler + persistenceHandler.addOrReplaceUser(newUser); } /** @@ -289,10 +548,27 @@ public class DefaultModelHandler implements ModelHandler { public void setUserPassword(Certificate certificate, String username, String password) { // validate who is doing this - PrivilegeHelper.isUserPrivilegeAdmin(certificate); + if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { + logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " + + certificate); + return; + } - // TODO Auto-generated method stub + // get User + User user = getUser(username); + if (user == null) { + throw new PrivilegeException("User " + username + " does not exist!"); + } + // hash password + String passwordHash = PrivilegeContainer.getInstance().getEncryptionHandler().convertToHash(password); + + // create new user + User newUser = new User(user.getUsername(), passwordHash, user.getFirstname(), user.getSurname(), user + .getState(), user.getRoles(), user.getLocale()); + + // delegate user replacement to persistence handler + persistenceHandler.addOrReplaceUser(newUser); } /** @@ -303,10 +579,24 @@ public class DefaultModelHandler implements ModelHandler { public void setUserState(Certificate certificate, String username, UserState state) { // validate who is doing this - PrivilegeHelper.isUserPrivilegeAdmin(certificate); + if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { + logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " + + certificate); + return; + } - // TODO Auto-generated method stub + // get User + User user = getUser(username); + if (user == null) { + throw new PrivilegeException("User " + username + " does not exist!"); + } + // create new user + User newUser = new User(user.getUsername(), user.getPassword(certificate), user.getFirstname(), user + .getSurname(), state, user.getRoles(), user.getLocale()); + + // delegate user replacement to persistence handler + persistenceHandler.addOrReplaceUser(newUser); } /** @@ -314,8 +604,7 @@ public class DefaultModelHandler implements ModelHandler { */ @Override public void initialize(Element element) { - // TODO Auto-generated method stub - + // nothing to initialize } /** @@ -341,5 +630,4 @@ public class DefaultModelHandler implements ModelHandler { public User getUser(String username) { return persistenceHandler.getUser(username); } - } diff --git a/src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java index 820a7b1a9..427c6322d 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java @@ -12,6 +12,7 @@ package ch.eitchnet.privilege.handler; import java.io.File; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -379,7 +380,8 @@ public class DefaultPersistenceHandler implements PersistenceHandler { } // create user - User user = User.buildUser(username, password, firstname, surname, userState, roles, locale); + User user = new User(username, password, firstname, surname, userState, Collections.unmodifiableSet(roles), + locale); // put user in map userMap.put(username, user); diff --git a/src/ch/eitchnet/privilege/handler/ModelHandler.java b/src/ch/eitchnet/privilege/handler/ModelHandler.java index c464ebb2f..e8fa1256e 100644 --- a/src/ch/eitchnet/privilege/handler/ModelHandler.java +++ b/src/ch/eitchnet/privilege/handler/ModelHandler.java @@ -10,8 +10,8 @@ package ch.eitchnet.privilege.handler; -import java.util.List; import java.util.Locale; +import java.util.Set; import ch.eitchnet.privilege.base.PrivilegeContainerObject; import ch.eitchnet.privilege.model.Certificate; @@ -33,21 +33,21 @@ public interface ModelHandler extends PrivilegeContainerObject { public User getUser(String username); - public void addOrReplaceUser(Certificate certificate, UserRep userRep); + public void addOrReplaceUser(Certificate certificate, UserRep userRep, String password); public UserRep removeUser(Certificate certificate, String username); public void setUserPassword(Certificate certificate, String username, String password); - public void setUserNamePassword(Certificate certificate, String username, String firstname, String surname); + public void setUserName(Certificate certificate, String username, String firstname, String surname); public void setUserState(Certificate certificate, String username, UserState state); public void setUserLocaleState(Certificate certificate, String username, Locale locale); - public void addRoleToUser(Certificate certificate, String username, String rolename); + public void addRoleToUser(Certificate certificate, String username, String roleName); - public void removeRoleFromUser(Certificate certificate, String username, String rolename); + public void removeRoleFromUser(Certificate certificate, String username, String roleName); public void addOrReplaceRole(Certificate certificate, RoleRep roleRep); @@ -69,9 +69,9 @@ public interface ModelHandler extends PrivilegeContainerObject { public void setPrivilegeAllAllowed(Certificate certificate, String privilegeName, boolean allAllowed); - public void setPrivilegeDenyList(Certificate certificate, String privilegeName, List denyList); + public void setPrivilegeDenyList(Certificate certificate, String privilegeName, Set denyList); - public void setPrivilegeAllowList(Certificate certificate, String privilegeName, List allowList); + public void setPrivilegeAllowList(Certificate certificate, String privilegeName, Set allowList); public boolean persist(Certificate certificate); } diff --git a/src/ch/eitchnet/privilege/handler/PersistenceHandler.java b/src/ch/eitchnet/privilege/handler/PersistenceHandler.java index ded4736ed..117130490 100644 --- a/src/ch/eitchnet/privilege/handler/PersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/PersistenceHandler.java @@ -10,7 +10,6 @@ package ch.eitchnet.privilege.handler; -import ch.eitchnet.privilege.base.PrivilegeContainer; import ch.eitchnet.privilege.base.PrivilegeContainerObject; import ch.eitchnet.privilege.model.Certificate; import ch.eitchnet.privilege.model.internal.Privilege; @@ -18,8 +17,6 @@ import ch.eitchnet.privilege.model.internal.Role; import ch.eitchnet.privilege.model.internal.User; /** - * TODO {@link PersistenceHandler} may not be freely accessible via {@link PrivilegeContainer} - * * @author rvonburg * */ diff --git a/src/ch/eitchnet/privilege/helper/PrivilegeHelper.java b/src/ch/eitchnet/privilege/helper/PrivilegeHelper.java index fff3c1114..32be4f883 100644 --- a/src/ch/eitchnet/privilege/helper/PrivilegeHelper.java +++ b/src/ch/eitchnet/privilege/helper/PrivilegeHelper.java @@ -32,12 +32,14 @@ public class PrivilegeHelper { User user = PrivilegeContainer.getInstance().getModelHandler().getUser(certificate.getUsername()); if (user == null) { throw new PrivilegeException( - "Oh boy, how did this happen: No User in user map although the certificate is valid!"); + "Oh boy, how did this happen: No User in user map although the certificate is valid! Certificate: " + + certificate); } // validate user has PrivilegeAdmin role if (!user.hasRole(PrivilegeContainer.PRIVILEGE_ADMIN_ROLE)) { - throw new AccessDeniedException("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role!"); + throw new AccessDeniedException("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + + " role! Certificate: " + certificate); } else { return true; } diff --git a/src/ch/eitchnet/privilege/helper/TestConfigurationHelper.java b/src/ch/eitchnet/privilege/helper/TestConfigurationHelper.java index 0d987bed0..8b0b4d80c 100644 --- a/src/ch/eitchnet/privilege/helper/TestConfigurationHelper.java +++ b/src/ch/eitchnet/privilege/helper/TestConfigurationHelper.java @@ -11,6 +11,7 @@ package ch.eitchnet.privilege.helper; import java.io.File; +import java.util.HashSet; import org.apache.log4j.BasicConfigurator; import org.apache.log4j.ConsoleAppender; @@ -21,6 +22,8 @@ import org.apache.log4j.PatternLayout; import ch.eitchnet.privilege.base.PrivilegeContainer; import ch.eitchnet.privilege.handler.ModelHandler; import ch.eitchnet.privilege.model.Certificate; +import ch.eitchnet.privilege.model.UserRep; +import ch.eitchnet.privilege.model.UserState; /** * @author rvonburg @@ -43,15 +46,19 @@ public class TestConfigurationHelper { PrivilegeContainer privilegeContainer = PrivilegeContainer.getInstance(); privilegeContainer.initialize(privilegeContainerXml); - // ModelHandler modelHandler = privilegeContainer.getModelHandler(); + ModelHandler modelHandler = privilegeContainer.getModelHandler(); + + Certificate certificate = auth("eitch", "1234567890"); for (int i = 0; i < 10; i++) { // let's authenticate a session auth("eitch", "1234567890"); } - // TODO let's add a user - // persistenceHandler.addUser(certificate, user); + // let's add a new user bob + UserRep userRep = new UserRep("bob", "Bob", "Newman", UserState.NEW, new HashSet(), null); + modelHandler.addOrReplaceUser(certificate, userRep, null); + logger.info("Added user bob"); // TODO let's add a role @@ -62,10 +69,11 @@ public class TestConfigurationHelper { /** * */ - private static void auth(String username, String password) { + private static Certificate auth(String username, String password) { long start = System.currentTimeMillis(); Certificate certificate = PrivilegeContainer.getInstance().getSessionHandler().authenticate(username, password); logger.info("Auth took " + (System.currentTimeMillis() - start)); logger.info("Authenticated with certificate: " + certificate); + return certificate; } } diff --git a/src/ch/eitchnet/privilege/model/RoleRep.java b/src/ch/eitchnet/privilege/model/RoleRep.java index 92577e24e..12c3d755d 100644 --- a/src/ch/eitchnet/privilege/model/RoleRep.java +++ b/src/ch/eitchnet/privilege/model/RoleRep.java @@ -21,8 +21,8 @@ public class RoleRep implements Serializable { private static final long serialVersionUID = 1L; - public final String name; - public final Set privileges; + private String name; + private Set privileges; /** * @param name @@ -32,4 +32,34 @@ public class RoleRep implements Serializable { this.name = name; this.privileges = privileges; } + + /** + * @return the name + */ + public String getName() { + return name; + } + + /** + * @param name + * the name to set + */ + public void setName(String name) { + this.name = name; + } + + /** + * @return the privileges + */ + public Set getPrivileges() { + return privileges; + } + + /** + * @param privileges + * the privileges to set + */ + public void setPrivileges(Set privileges) { + this.privileges = privileges; + } } diff --git a/src/ch/eitchnet/privilege/model/UserRep.java b/src/ch/eitchnet/privilege/model/UserRep.java index 4e3575895..d2222f423 100644 --- a/src/ch/eitchnet/privilege/model/UserRep.java +++ b/src/ch/eitchnet/privilege/model/UserRep.java @@ -22,12 +22,12 @@ public class UserRep implements Serializable { private static final long serialVersionUID = 1L; - public final String username; - public final String firstname; - public final String surname; - public final UserState userState; - public final Set roles; - public final Locale locale; + private String username; + private String firstname; + private String surname; + private UserState userState; + private Set roles; + private Locale locale; /** * @param username @@ -46,4 +46,88 @@ public class UserRep implements Serializable { this.roles = roles; this.locale = locale; } + + /** + * @return the username + */ + public String getUsername() { + return username; + } + + /** + * @param username the username to set + */ + public void setUsername(String username) { + this.username = username; + } + + /** + * @return the firstname + */ + public String getFirstname() { + return firstname; + } + + /** + * @param firstname the firstname to set + */ + public void setFirstname(String firstname) { + this.firstname = firstname; + } + + /** + * @return the surname + */ + public String getSurname() { + return surname; + } + + /** + * @param surname the surname to set + */ + public void setSurname(String surname) { + this.surname = surname; + } + + /** + * @return the userState + */ + public UserState getUserState() { + return userState; + } + + /** + * @param userState the userState to set + */ + public void setUserState(UserState userState) { + this.userState = userState; + } + + /** + * @return the roles + */ + public Set getRoles() { + return roles; + } + + /** + * @param roles the roles to set + */ + public void setRoles(Set roles) { + this.roles = roles; + } + + /** + * @return the locale + */ + public Locale getLocale() { + return locale; + } + + /** + * @param locale the locale to set + */ + public void setLocale(Locale locale) { + this.locale = locale; + } } diff --git a/src/ch/eitchnet/privilege/model/internal/User.java b/src/ch/eitchnet/privilege/model/internal/User.java index 3edc0575a..e2f0b9ea0 100644 --- a/src/ch/eitchnet/privilege/model/internal/User.java +++ b/src/ch/eitchnet/privilege/model/internal/User.java @@ -10,12 +10,10 @@ package ch.eitchnet.privilege.model.internal; -import java.util.Collections; import java.util.Locale; import java.util.Set; import ch.eitchnet.privilege.helper.PrivilegeHelper; -import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.Certificate; import ch.eitchnet.privilege.model.UserRep; import ch.eitchnet.privilege.model.UserState; @@ -39,9 +37,16 @@ public final class User { private final Locale locale; /** - * The {@link User} constructor is private to ensure no unauthorized creation of {@link User} objects + * + * @param username + * @param password + * @param firstname + * @param surname + * @param userState + * @param roles + * @param locale */ - private User(String username, String password, String firstname, String surname, UserState userState, + public User(String username, String password, String firstname, String surname, UserState userState, Set roles, Locale locale) { this.username = username; @@ -153,34 +158,4 @@ public final class User { builder.append("]"); return builder.toString(); } - - /** - * @return a new {@link User} object which is authenticated on the current Java Virtual Machine - */ - public static User buildUser(String username, String password, String firstname, String surname, - UserState userState, Set roles, Locale locale) { - - // set a default locale - if (locale == null) - locale = Locale.getDefault(); - - // TODO validate who is creating this User object - - if (username.length() < 3) { - throw new PrivilegeException("The given username is shorter than 3 characters"); - } - - if (firstname.isEmpty()) { - throw new PrivilegeException("The given firstname is empty"); - } - - if (surname.isEmpty()) { - throw new PrivilegeException("The given firstname is empty"); - } - - User user = new User(username, password, firstname, surname, userState, Collections.unmodifiableSet(roles), - locale); - - return user; - } } diff --git a/test/ch/eitchnet/privilege/test/PrivilegeTest.java b/test/ch/eitchnet/privilege/test/PrivilegeTest.java new file mode 100644 index 000000000..6dcba0a80 --- /dev/null +++ b/test/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.test; + +import java.io.File; +import java.util.HashSet; + +import org.apache.log4j.BasicConfigurator; +import org.apache.log4j.ConsoleAppender; +import org.apache.log4j.Level; +import org.apache.log4j.Logger; +import org.apache.log4j.PatternLayout; +import org.junit.Before; +import org.junit.Test; + +import ch.eitchnet.privilege.base.PrivilegeContainer; +import ch.eitchnet.privilege.handler.ModelHandler; +import ch.eitchnet.privilege.i18n.AccessDeniedException; +import ch.eitchnet.privilege.i18n.PrivilegeException; +import ch.eitchnet.privilege.model.Certificate; +import ch.eitchnet.privilege.model.UserRep; +import ch.eitchnet.privilege.model.UserState; + +/** + * @author rvonburg + * + */ +public class PrivilegeTest { + + private static final Logger logger = Logger.getLogger(PrivilegeTest.class); + + /** + * @throws java.lang.Exception + */ + @Before + public void setUp() throws Exception { + + // set up log4j + BasicConfigurator.resetConfiguration(); + BasicConfigurator.configure(new ConsoleAppender(new PatternLayout("%d %5p [%t] %C{1} %M - %m%n"))); + Logger.getRootLogger().setLevel(Level.INFO); + + // initialize container + String pwd = System.getProperty("user.dir"); + File privilegeContainerXml = new File(pwd + "/config/PrivilegeContainer.xml"); + PrivilegeContainer privilegeContainer = PrivilegeContainer.getInstance(); + privilegeContainer.initialize(privilegeContainerXml); + } + + @Test + public void testAuthenticationOk() throws Exception { + + Certificate certificate = PrivilegeContainer.getInstance().getSessionHandler().authenticate("eitch", + "1234567890"); + org.junit.Assert.assertTrue("Certificate is null!", certificate != null); + } + + @Test(expected = AccessDeniedException.class) + public void testAuthenticationNOk() throws Exception { + + Certificate certificate = PrivilegeContainer.getInstance().getSessionHandler().authenticate("eitch", "123"); + org.junit.Assert.assertTrue("Certificate is null!", certificate != null); + } + + @Test(expected = PrivilegeException.class) + public void testAuthenticationPWNull() throws Exception { + + Certificate certificate = PrivilegeContainer.getInstance().getSessionHandler().authenticate("eitch", null); + org.junit.Assert.assertTrue("Certificate is null!", certificate != null); + } + + @Test + public void testAddUserBobWithPW() throws Exception { + + Certificate certificate = PrivilegeContainer.getInstance().getSessionHandler().authenticate("eitch", + "1234567890"); + + ModelHandler modelHandler = PrivilegeContainer.getInstance().getModelHandler(); + + // let's add a new user bob + UserRep userRep = new UserRep("bob", "Bob", "Newman", UserState.NEW, new HashSet(), null); + modelHandler.addOrReplaceUser(certificate, userRep, null); + logger.info("Added user bob"); + + // set bob's password + modelHandler.setUserPassword(certificate, "bob", "12345678901"); + logger.info("Set Bob's password"); + } + + /** + * Will fail because user bob is not yet enabled + * + * @throws Exception + */ + @Test(expected = AccessDeniedException.class) + public void testAuthAsBob() throws Exception { + + PrivilegeContainer.getInstance().getSessionHandler().authenticate("bob", "12345678901"); + } + + @Test + public void testEnableUserBob() throws Exception { + + Certificate certificate = PrivilegeContainer.getInstance().getSessionHandler().authenticate("eitch", + "1234567890"); + + ModelHandler modelHandler = PrivilegeContainer.getInstance().getModelHandler(); + modelHandler.setUserState(certificate, "bob", UserState.ENABLED); + } + + /** + * Will fail because user bob does not have admin rights + * + * @throws Exception + */ + @Test(expected = AccessDeniedException.class) + public void testAddUserTedAsBob() throws Exception { + + Certificate certificate = PrivilegeContainer.getInstance().getSessionHandler().authenticate("bob", + "12345678901"); + org.junit.Assert.assertTrue("Certificate is null!", certificate != null); + + // let's add a new user bob + UserRep userRep = new UserRep("bob", "Bob", "Newman", UserState.NEW, new HashSet(), null); + PrivilegeContainer.getInstance().getModelHandler().addOrReplaceUser(certificate, userRep, null); + logger.info("Added user bob"); + } +} From 57f0164e38f464a871d3a295ac04e76e86dd085c Mon Sep 17 00:00:00 2001 From: eitch Date: Wed, 14 Jul 2010 21:26:52 +0000 Subject: [PATCH 027/173] --- config/PrivilegeRoles.xml | 2 + .../privilege/base/PrivilegeContainer.java | 1 + .../eitchnet/privilege/base/XmlConstants.java | 6 + .../handler/DefaultModelHandler.java | 2 +- .../helper/BootstrapConfigurationHelper.java | 179 ++++++++++++++++++ .../helper/TestConfigurationHelper.java | 79 -------- .../eitchnet/privilege/helper/XmlHelper.java | 22 ++- .../privilege/model/internal/User.java | 3 +- .../privilege/test/PrivilegeTest.java | 66 ++++++- 9 files changed, 267 insertions(+), 93 deletions(-) create mode 100644 src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java delete mode 100644 src/ch/eitchnet/privilege/helper/TestConfigurationHelper.java diff --git a/config/PrivilegeRoles.xml b/config/PrivilegeRoles.xml index c78b0ca4e..a09f1a393 100644 --- a/config/PrivilegeRoles.xml +++ b/config/PrivilegeRoles.xml @@ -1,8 +1,10 @@ + + \ No newline at end of file diff --git a/src/ch/eitchnet/privilege/base/PrivilegeContainer.java b/src/ch/eitchnet/privilege/base/PrivilegeContainer.java index 48ca8f866..1ffc36d3b 100644 --- a/src/ch/eitchnet/privilege/base/PrivilegeContainer.java +++ b/src/ch/eitchnet/privilege/base/PrivilegeContainer.java @@ -33,6 +33,7 @@ public class PrivilegeContainer { * This is the role users must have, if they can modify the {@link PrivilegeContainer} and its objects */ public static final String PRIVILEGE_ADMIN_ROLE = "PrivilegeAdmin"; + public static final String PRIVILEGE_CONTAINER_FILE = "PrivilegeContainer.xml"; private static final Logger logger = Logger.getLogger(PrivilegeContainer.class); diff --git a/src/ch/eitchnet/privilege/base/XmlConstants.java b/src/ch/eitchnet/privilege/base/XmlConstants.java index fbf3e9dda..727841b53 100644 --- a/src/ch/eitchnet/privilege/base/XmlConstants.java +++ b/src/ch/eitchnet/privilege/base/XmlConstants.java @@ -15,6 +15,12 @@ package ch.eitchnet.privilege.base; * */ public class XmlConstants { + public static final String XML_ROOT_PRIVILEGE_CONTAINER = "PrivilegeContainer"; + public static final String XML_ROOT_PRIVILEGE_ROLES = "PrivilegeRoles"; + public static final String XML_ROOT_PRIVILEGES = "Privileges"; + public static final String XML_ROOT_PRIVILEGE_USERS = "PrivilegesUsers"; + public static final String XML_ROOT_RESTRICTION_POLICIES = "RestrictionPolicies"; + public static final String XML_HANDLER_PERSISTENCE = "PersistenceHandler"; public static final String XML_HANDLER_ENCRYPTION = "EncryptionHandler"; public static final String XML_HANDLER_SESSION = "SessionHandler"; diff --git a/src/ch/eitchnet/privilege/handler/DefaultModelHandler.java b/src/ch/eitchnet/privilege/handler/DefaultModelHandler.java index 9dfd0b806..cbbeface2 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultModelHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultModelHandler.java @@ -195,7 +195,7 @@ public class DefaultModelHandler implements ModelHandler { // create new user Set newRoles = new HashSet(currentRoles); - currentRoles.add(roleName); + newRoles.add(roleName); User newUser = new User(user.getUsername(), user.getPassword(certificate), user.getFirstname(), user .getSurname(), user.getState(), newRoles, user.getLocale()); diff --git a/src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java b/src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java new file mode 100644 index 000000000..14001dc08 --- /dev/null +++ b/src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.helper; + +import java.io.File; + +import org.apache.log4j.BasicConfigurator; +import org.apache.log4j.ConsoleAppender; +import org.apache.log4j.Level; +import org.apache.log4j.Logger; +import org.apache.log4j.PatternLayout; +import org.dom4j.Document; +import org.dom4j.DocumentFactory; +import org.dom4j.Element; + +import ch.eitchnet.privilege.base.PrivilegeContainer; +import ch.eitchnet.privilege.base.XmlConstants; + +/** + *

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

    + * + *

    + * 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 PrivilegeContainer} + *

    + * + * @author rvonburg + * + */ +public class BootstrapConfigurationHelper { +// private static final Logger logger = Logger.getLogger(BootstrapConfigurationHelper.class); + + private static String path; + + private static String usersFileName = "PrivilegeUsers.xml"; + private static String rolesFileName = "PrivilegeRoles.xml"; + private static String privilegesFileName = "Privileges.xml"; + + private static String hashAlgorithm = "SHA-256"; + + private static String policyXmlFile = "RestrictionPolicies.xml"; + + private static String defaultPersistenceHandler = "ch.eitchnet.privilege.handler.DefaultPersistenceHandler"; + private static String defaultSessionHandler = "ch.eitchnet.privilege.handler.DefaultSessionHandler"; + private static String defaultEncryptionHandler = "ch.eitchnet.privilege.handler.DefaultEncryptionHandler"; + private static String defaultPolicyHandler = "ch.eitchnet.privilege.handler.DefaultPolicyHandler"; + + /** + * @param args + */ + public static void main(String[] args) { + BasicConfigurator.resetConfiguration(); + BasicConfigurator.configure(new ConsoleAppender(new PatternLayout("%d %5p [%t] %C{1} %M - %m%n"))); + Logger.getRootLogger().setLevel(Level.INFO); + + // get current directory + path = System.getProperty("user.dir") + "/newConfig"; + + // ask user where to save configuration, default is pwd/newConfig/.... + + // see if path already exists + File pathF = new File(path); + if (pathF.exists()) { + throw new RuntimeException("Path already exists: " + pathF.getAbsolutePath()); + } else { + if (!pathF.mkdirs()) { + throw new RuntimeException("Could not create path " + pathF.getAbsolutePath()); + } + } + + // ask other questions... + + // now perform work: + createXmlPrivilegeContainer(); + createPolicyConfiguration(); + createModel(); + } + + /** + * + */ + private static void createModel() { + // TODO Auto-generated method stub + + } + + /** + * + */ + private static void createPolicyConfiguration() { + // TODO Auto-generated method stub + + } + + /** + * + */ + private static void createXmlPrivilegeContainer() { + + // create document root + DocumentFactory factory = DocumentFactory.getInstance(); + Document doc = factory.createDocument(XmlHelper.DEFAULT_ENCODING); + doc.setName(XmlConstants.XML_ROOT_PRIVILEGE_CONTAINER); + Element rootElement = factory.createElement(XmlConstants.XML_ROOT_PRIVILEGE_CONTAINER); + doc.setRootElement(rootElement); + + Element parameterElement; + Element parametersElement; + + // create PersistenceHandler + Element persistenceHandlerElem = factory.createElement(XmlConstants.XML_HANDLER_PERSISTENCE); + rootElement.add(persistenceHandlerElem); + persistenceHandlerElem.addAttribute(XmlConstants.XML_ATTR_CLASS, defaultPersistenceHandler); + parametersElement = factory.createElement(XmlConstants.XML_PARAMETERS); + persistenceHandlerElem.add(parametersElement); + // Parameter usersXmlFile + parameterElement = factory.createElement(XmlConstants.XML_PARAMETER); + parameterElement.addAttribute(XmlConstants.XML_ATTR_NAME, XmlConstants.XML_PARAM_USERS_FILE); + parameterElement.addAttribute(XmlConstants.XML_ATTR_VALUE, usersFileName); + parametersElement.add(parameterElement); + // Parameter rolesXmlFile + parameterElement = factory.createElement(XmlConstants.XML_PARAMETER); + parameterElement.addAttribute(XmlConstants.XML_ATTR_NAME, XmlConstants.XML_PARAM_ROLES_FILE); + parameterElement.addAttribute(XmlConstants.XML_ATTR_VALUE, rolesFileName); + parametersElement.add(parameterElement); + // Parameter privilegesXmlFile + parameterElement = factory.createElement(XmlConstants.XML_PARAMETER); + parameterElement.addAttribute(XmlConstants.XML_ATTR_NAME, XmlConstants.XML_PARAM_PRIVILEGES_FILE); + parameterElement.addAttribute(XmlConstants.XML_ATTR_VALUE, privilegesFileName); + parametersElement.add(parameterElement); + + // create SessionHandler + Element sessionHandlerElem = factory.createElement(XmlConstants.XML_HANDLER_SESSION); + sessionHandlerElem.addAttribute(XmlConstants.XML_ATTR_CLASS, defaultSessionHandler); + + // create ModelHandler + Element modelHandlerElem = factory.createElement(XmlConstants.XML_HANDLER_MODEL); + rootElement.add(modelHandlerElem); + modelHandlerElem.addAttribute(XmlConstants.XML_ATTR_CLASS, "ch.eitchnet.privilege.handler.DefaultModelHandler"); + + // create EncryptionHandler + Element encryptionHandlerElem = factory.createElement(XmlConstants.XML_HANDLER_ENCRYPTION); + rootElement.add(encryptionHandlerElem); + encryptionHandlerElem.addAttribute(XmlConstants.XML_ATTR_CLASS, defaultEncryptionHandler); + parametersElement = factory.createElement(XmlConstants.XML_PARAMETERS); + encryptionHandlerElem.add(parametersElement); + // Parameter hashAlgorithm + parameterElement = factory.createElement(XmlConstants.XML_PARAMETER); + parameterElement.addAttribute(XmlConstants.XML_ATTR_NAME, XmlConstants.XML_PARAM_HASH_ALGORITHM); + parameterElement.addAttribute(XmlConstants.XML_ATTR_VALUE, hashAlgorithm); + parametersElement.add(parameterElement); + + // create PolicyHandler + Element policyHandlerElem = factory.createElement(XmlConstants.XML_HANDLER_POLICY); + rootElement.add(policyHandlerElem); + policyHandlerElem.addAttribute(XmlConstants.XML_ATTR_CLASS, defaultPolicyHandler); + parametersElement = factory.createElement(XmlConstants.XML_PARAMETERS); + policyHandlerElem.add(parametersElement); + // Parameter policyXmlFile + parameterElement = factory.createElement(XmlConstants.XML_PARAMETER); + parameterElement.addAttribute(XmlConstants.XML_ATTR_NAME, XmlConstants.XML_PARAM_POLICY_FILE); + parameterElement.addAttribute(XmlConstants.XML_ATTR_VALUE, policyXmlFile); + parametersElement.add(parameterElement); + + File privilegeContainerFile = new File(path + "/" + PrivilegeContainer.PRIVILEGE_CONTAINER_FILE); + XmlHelper.writeDocument(doc, privilegeContainerFile); + } +} diff --git a/src/ch/eitchnet/privilege/helper/TestConfigurationHelper.java b/src/ch/eitchnet/privilege/helper/TestConfigurationHelper.java deleted file mode 100644 index 8b0b4d80c..000000000 --- a/src/ch/eitchnet/privilege/helper/TestConfigurationHelper.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (c) 2010 - * - * Robert von Burg - * eitch@eitchnet.ch - * - * All rights reserved. - * - */ - -package ch.eitchnet.privilege.helper; - -import java.io.File; -import java.util.HashSet; - -import org.apache.log4j.BasicConfigurator; -import org.apache.log4j.ConsoleAppender; -import org.apache.log4j.Level; -import org.apache.log4j.Logger; -import org.apache.log4j.PatternLayout; - -import ch.eitchnet.privilege.base.PrivilegeContainer; -import ch.eitchnet.privilege.handler.ModelHandler; -import ch.eitchnet.privilege.model.Certificate; -import ch.eitchnet.privilege.model.UserRep; -import ch.eitchnet.privilege.model.UserState; - -/** - * @author rvonburg - * - */ -public class TestConfigurationHelper { - private static final Logger logger = Logger.getLogger(TestConfigurationHelper.class); - - /** - * @param args - */ - public static void main(String[] args) { - BasicConfigurator.resetConfiguration(); - BasicConfigurator.configure(new ConsoleAppender(new PatternLayout("%d %5p [%t] %C{1} %M - %m%n"))); - Logger.getRootLogger().setLevel(Level.INFO); - - // initialize container - String pwd = System.getProperty("user.dir"); - File privilegeContainerXml = new File(pwd + "/config/PrivilegeContainer.xml"); - PrivilegeContainer privilegeContainer = PrivilegeContainer.getInstance(); - privilegeContainer.initialize(privilegeContainerXml); - - ModelHandler modelHandler = privilegeContainer.getModelHandler(); - - Certificate certificate = auth("eitch", "1234567890"); - - for (int i = 0; i < 10; i++) { - // let's authenticate a session - auth("eitch", "1234567890"); - } - - // let's add a new user bob - UserRep userRep = new UserRep("bob", "Bob", "Newman", UserState.NEW, new HashSet(), null); - modelHandler.addOrReplaceUser(certificate, userRep, null); - logger.info("Added user bob"); - - // TODO let's add a role - - // TODO let's add a privilege - - } - - /** - * - */ - private static Certificate auth(String username, String password) { - long start = System.currentTimeMillis(); - Certificate certificate = PrivilegeContainer.getInstance().getSessionHandler().authenticate(username, password); - logger.info("Auth took " + (System.currentTimeMillis() - start)); - logger.info("Authenticated with certificate: " + certificate); - return certificate; - } -} diff --git a/src/ch/eitchnet/privilege/helper/XmlHelper.java b/src/ch/eitchnet/privilege/helper/XmlHelper.java index 05c75ac17..f4f57a87a 100644 --- a/src/ch/eitchnet/privilege/helper/XmlHelper.java +++ b/src/ch/eitchnet/privilege/helper/XmlHelper.java @@ -35,6 +35,8 @@ import ch.eitchnet.privilege.i18n.PrivilegeException; */ public class XmlHelper { + public static final String DEFAULT_ENCODING = "UTF-8"; + private static final Logger logger = Logger.getLogger(XmlHelper.class); public static Document parseDocument(File xmlFile) { @@ -56,19 +58,20 @@ public class XmlHelper { } } - public static void writeDocument(Element rootElement, File file) { + public static void writeDocument(Document document, File file) { - logger.info("Exporting root element " + rootElement.getName() + " to " + file.getAbsolutePath()); + logger.info("Exporting document element " + document.getName() + " to " + file.getAbsolutePath()); OutputStream fileOutputStream = null; try { - Document document = DocumentFactory.getInstance().createDocument(); - document.setRootElement(rootElement); fileOutputStream = new FileOutputStream(file); - String aEncodingScheme = "UTF-8"; + String aEncodingScheme = document.getXMLEncoding(); + if (aEncodingScheme == null || aEncodingScheme.isEmpty()) { + aEncodingScheme = DEFAULT_ENCODING; + } OutputFormat outformat = OutputFormat.createPrettyPrint(); outformat.setEncoding(aEncodingScheme); XMLWriter writer = new XMLWriter(fileOutputStream, outformat); @@ -90,4 +93,13 @@ public class XmlHelper { } } } + + public static void writeDocument(Element rootElement, File file) { + + Document document = DocumentFactory.getInstance().createDocument(DEFAULT_ENCODING); + document.setRootElement(rootElement); + document.setName(rootElement.getName()); + + writeDocument(document, file); + } } diff --git a/src/ch/eitchnet/privilege/model/internal/User.java b/src/ch/eitchnet/privilege/model/internal/User.java index e2f0b9ea0..b14dad127 100644 --- a/src/ch/eitchnet/privilege/model/internal/User.java +++ b/src/ch/eitchnet/privilege/model/internal/User.java @@ -10,6 +10,7 @@ package ch.eitchnet.privilege.model.internal; +import java.util.Collections; import java.util.Locale; import java.util.Set; @@ -56,7 +57,7 @@ public final class User { this.firstname = firstname; this.surname = surname; - this.roles = roles; + this.roles = Collections.unmodifiableSet(roles); this.locale = locale; } diff --git a/test/ch/eitchnet/privilege/test/PrivilegeTest.java b/test/ch/eitchnet/privilege/test/PrivilegeTest.java index 6dcba0a80..d7a6e748f 100644 --- a/test/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/test/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -18,7 +18,7 @@ import org.apache.log4j.ConsoleAppender; import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.apache.log4j.PatternLayout; -import org.junit.Before; +import org.junit.BeforeClass; import org.junit.Test; import ch.eitchnet.privilege.base.PrivilegeContainer; @@ -40,8 +40,8 @@ public class PrivilegeTest { /** * @throws java.lang.Exception */ - @Before - public void setUp() throws Exception { + @BeforeClass + public static void init() throws Exception { // set up log4j BasicConfigurator.resetConfiguration(); @@ -64,14 +64,14 @@ public class PrivilegeTest { } @Test(expected = AccessDeniedException.class) - public void testAuthenticationNOk() throws Exception { + public void testFailAuthenticationNOk() throws Exception { Certificate certificate = PrivilegeContainer.getInstance().getSessionHandler().authenticate("eitch", "123"); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); } @Test(expected = PrivilegeException.class) - public void testAuthenticationPWNull() throws Exception { + public void testFailAuthenticationPWNull() throws Exception { Certificate certificate = PrivilegeContainer.getInstance().getSessionHandler().authenticate("eitch", null); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); @@ -101,7 +101,7 @@ public class PrivilegeTest { * @throws Exception */ @Test(expected = AccessDeniedException.class) - public void testAuthAsBob() throws Exception { + public void testFailAuthAsBob() throws Exception { PrivilegeContainer.getInstance().getSessionHandler().authenticate("bob", "12345678901"); } @@ -116,13 +116,42 @@ public class PrivilegeTest { modelHandler.setUserState(certificate, "bob", UserState.ENABLED); } + /** + * Will fail as user bob has no role + * + * @throws Exception + */ + @Test(expected = PrivilegeException.class) + public void testFailAuthUserBob() throws Exception { + + Certificate certificate = PrivilegeContainer.getInstance().getSessionHandler().authenticate("bob", + "12345678901"); + org.junit.Assert.assertTrue("Certificate is null!", certificate != null); + } + + @Test + public void testAddUserRoleToBob() throws Exception { + + Certificate certificate = PrivilegeContainer.getInstance().getSessionHandler().authenticate("eitch", + "1234567890"); + + ModelHandler modelHandler = PrivilegeContainer.getInstance().getModelHandler(); + modelHandler.addRoleToUser(certificate, "bob", "user"); + } + + @Test + public void testAuthAsBob() throws Exception { + + PrivilegeContainer.getInstance().getSessionHandler().authenticate("bob", "12345678901"); + } + /** * Will fail because user bob does not have admin rights * * @throws Exception */ @Test(expected = AccessDeniedException.class) - public void testAddUserTedAsBob() throws Exception { + public void testFailAddUserTedAsBob() throws Exception { Certificate certificate = PrivilegeContainer.getInstance().getSessionHandler().authenticate("bob", "12345678901"); @@ -133,4 +162,27 @@ public class PrivilegeTest { PrivilegeContainer.getInstance().getModelHandler().addOrReplaceUser(certificate, userRep, null); logger.info("Added user bob"); } + + @Test + public void testAddAdminRoleToBob() throws Exception { + + Certificate certificate = PrivilegeContainer.getInstance().getSessionHandler().authenticate("eitch", + "1234567890"); + + ModelHandler modelHandler = PrivilegeContainer.getInstance().getModelHandler(); + modelHandler.addRoleToUser(certificate, "bob", PrivilegeContainer.PRIVILEGE_ADMIN_ROLE); + } + + @Test + public void testAddUserTedAsBob() throws Exception { + + Certificate certificate = PrivilegeContainer.getInstance().getSessionHandler().authenticate("bob", + "12345678901"); + org.junit.Assert.assertTrue("Certificate is null!", certificate != null); + + // let's add a new user ted + UserRep userRep = new UserRep("ted", "Ted", "Newman", UserState.NEW, new HashSet(), null); + PrivilegeContainer.getInstance().getModelHandler().addOrReplaceUser(certificate, userRep, null); + logger.info("Added user bob"); + } } From 28f1e4a66292ad75f09d63af9a017dca46a3c870 Mon Sep 17 00:00:00 2001 From: eitch Date: Mon, 2 Aug 2010 23:20:54 +0000 Subject: [PATCH 028/173] --- config/PrivilegeContainer.xml | 2 +- config/PrivilegePolicies.xml | 6 ++ config/PrivilegeRoles.xml | 5 +- config/PrivilegeUsers.xml | 3 +- config/Privileges.xml | 8 +- config/RestrictionPolicies.xml | 6 -- docs/PrivilegeHandlers.dia | Bin 0 -> 2034 bytes docs/PrivilegeModelPrivilege.dia | Bin 0 -> 2063 bytes docs/PrivilegeModelUser.dia | Bin 0 -> 1752 bytes .../privilege/base/PrivilegeContainer.java | 12 +-- .../eitchnet/privilege/base/XmlConstants.java | 2 +- .../handler/DefaultPolicyHandler.java | 47 ++++++---- .../privilege/handler/SessionHandler.java | 4 +- .../helper/BootstrapConfigurationHelper.java | 2 +- .../privilege/model/Restrictable.java | 4 +- .../privilege/policy/DefaultPrivilege.java | 71 ++++++++++++++++ .../privilege/policy/DefaultRestriction.java | 80 ------------------ ...ictionPolicy.java => PrivilegePolicy.java} | 5 +- .../privilege/test/PrivilegeTest.java | 15 ++++ .../privilege/test/TestRestrictable.java | 35 ++++++++ 20 files changed, 186 insertions(+), 121 deletions(-) create mode 100644 config/PrivilegePolicies.xml delete mode 100644 config/RestrictionPolicies.xml create mode 100644 docs/PrivilegeHandlers.dia create mode 100644 docs/PrivilegeModelPrivilege.dia create mode 100644 docs/PrivilegeModelUser.dia create mode 100644 src/ch/eitchnet/privilege/policy/DefaultPrivilege.java delete mode 100644 src/ch/eitchnet/privilege/policy/DefaultRestriction.java rename src/ch/eitchnet/privilege/policy/{RestrictionPolicy.java => PrivilegePolicy.java} (60%) create mode 100644 test/ch/eitchnet/privilege/test/TestRestrictable.java diff --git a/config/PrivilegeContainer.xml b/config/PrivilegeContainer.xml index 0c64f7a94..e87acad3c 100644 --- a/config/PrivilegeContainer.xml +++ b/config/PrivilegeContainer.xml @@ -17,7 +17,7 @@ - + diff --git a/config/PrivilegePolicies.xml b/config/PrivilegePolicies.xml new file mode 100644 index 000000000..2b2c6f486 --- /dev/null +++ b/config/PrivilegePolicies.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/config/PrivilegeRoles.xml b/config/PrivilegeRoles.xml index a09f1a393..89724934b 100644 --- a/config/PrivilegeRoles.xml +++ b/config/PrivilegeRoles.xml @@ -3,8 +3,11 @@ - + + + + \ No newline at end of file diff --git a/config/PrivilegeUsers.xml b/config/PrivilegeUsers.xml index cc8867f25..f03340020 100644 --- a/config/PrivilegeUsers.xml +++ b/config/PrivilegeUsers.xml @@ -7,8 +7,9 @@ ENABLED en_GB - admin PrivilegeAdmin + admin + serviceExecutor diff --git a/config/Privileges.xml b/config/Privileges.xml index e1fb1de4e..baec28f6c 100644 --- a/config/Privileges.xml +++ b/config/Privileges.xml @@ -1,10 +1,16 @@ - + true + + + false + + ch.eitchnet.privilege.test.TestRestrictable + \ No newline at end of file diff --git a/config/RestrictionPolicies.xml b/config/RestrictionPolicies.xml deleted file mode 100644 index acda375fe..000000000 --- a/config/RestrictionPolicies.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/docs/PrivilegeHandlers.dia b/docs/PrivilegeHandlers.dia new file mode 100644 index 0000000000000000000000000000000000000000..4f8da83b765831d300996f1c1f64ca25a1fdaabb GIT binary patch literal 2034 zcmaKqc{~#iAIA&1N^_>)k`0mHOha=H&3zPdOHq_%axcP6IdT_~99whW{2bG$jSRUm z=ah_=4o9vW)11FwJ%2o}=XpJ^*Yo|nUf)07f4u*CgHrep{XQ=4Wj8m>q?R~tweCi* zlB!028MB16ykrfU^egw<$`T=~O!B`~N-3c(1zi6_Dn@9wbD~5&AvYb_rI!Jn#)PD$ zv#9&3-(K)Rj1>0pue@^DK=$bwH5R3kh5LJB@~_veICi(!*B1wCXL8?dp50T42(iys zl%V=+fIuK2eL#}v=qF!PT+=pah5!#KMD1@m){_0(<&%w9->iz3o;gJquid_78Sq{1 z^Ay%@AhNj)Dv^!hk!;*Ta;)}!s9_9zLoTXg@Hq4E+Pt}4aK7IpBaH@5TX@jl^Z^Yv zEK8{fueUwZX}w8xQaC5^-LUN*mi+``@@>4)bqznXMRwE>Gq+0G%z2$ux|;w)6noue z#?FtBmi(E%Y^KeAusLYNOS_0WX#xNMx_y0lJQWeT6_N3hayFpII6lj4wyN)knZ@*rCVg1n4)@UyiDP~* zZnr@O_NXT}7OF`jBKak<22DSR^NyoJk&)xIx zfwC+`wzy(4wX+(w?+rP)rN9E0f_%Dcmu42mUGo!1E--9^Zm#O%YTVv`#E8bT=fK7Z z;DOf4`x=>gS^ju($@5R9&PP1eiLTG%v;23;zVtTg0ZXkex?w(J3Ei{I?gFL?dn{m? zJOYBhGA+jg(t>qP>lp6f*9>HquS{I$q|r6>wjW22tmZ7Hy%TT|>!&95^DdsWfRZu+ zPzhyu&(aH#W`o?NJq*6j6kR!A9bRQdgQUdB1igdtKDl2tXui)S;<0sQUE>35vW@8u zDdJh#;n0RvTb%k)E{o2PhFq(3frCp*M;%Mx3P!xbb&&F6(}@TzqL} z5wm`kgp|3dCy+20=c_fArL>&0z0}hRXV|Kq4*rhCOS82J3;FSdiF5L?tA?=NA_1%x z4PH}%*Qgb1&+u^Uo@5S$dv&L7?DO|^(SrDs4=#Tr(5vcrbr{HW^KXctyBGqpdGV?#(U=OsU{>R=;i(?fED2XsU(H*L;dCRP9w zJg(?gmC2;iK(4K`UOhZ*ssOeq$fiLw=h40 z;pcrV%K<3+HV8Er{Q-Z+T3IcU9>#j3$38DQh-ifO)D#@QI`uW0$+zjZMp0=ofzq|? zH~b11`Bq;(ML{~JLQgtnTMUvb+h&w-#d+nW*wylAbl#H%DUJ46QH452;q_Z)^YrJB=;yaYv8C)Ag| zsGO(X&qj^(l{+BFl=-Z`Ozz4l7maD?I5m5)k5PQpi=2r-524i1I;?_vhuWhkwIZ1N zBaW!hpH{Au@&=C|NFCPZ3g-FB-z$nqY&1tq zUbwlyZ%v{aP|^=tfoYhsF4_=5+we#7 z7J9Z~yaYWdZQ(y+WPw4`L+eUHE$(&>xjY6Bz)0a6G#-xZ*L%*qDiy2i6*i-lVFfPR z(HvwXSifcud0*1xzO>_Y_EHVxr^KX2AgBc}Rc?fuE_vOK`_v}rrB{6^=9dJ_Uw?xu zQe=JC^|!{3MEp-h_A~aL><8{IjB4oN6D(vs*hLr`hLzBzLodrz)LU8%_hV!$gdO-H z(AZxs^1gl=e_VmeASk|j2sTQW=@niyte=r5dDQFL)>J`H?t%irTJ_0NVQXeSLIT@z@^Ehs1?4pf9+_AVGk7DgL#Kji&iU?mV9m|*#) zJ8yjaZ3&H)otOsI+62z4pRBwvr@7(MGP`@AK#&PFT8f_#YB=Sosq)#2=%HaRHoZ&X zW55OEd)*kLe<;5J#5aFD|msue6SJHKsb% zd4wFJEY+6D6OVX)Fnz1@{tCFYi8Bq0s$MudWB2$`*@SoU|9RMe=1@sg4ITofi%)8J zs}0{O=tE{23{upZfp7fCG~6fsK#)I4XrHqN7#@Qd zPSuI9AEM#IDyda=;9uQ2o2*n%v>IpEY3JI9@A_;T{vGz}WpmvDUq8B5uVMbsp??7W CZ2*h_ literal 0 HcmV?d00001 diff --git a/docs/PrivilegeModelPrivilege.dia b/docs/PrivilegeModelPrivilege.dia new file mode 100644 index 0000000000000000000000000000000000000000..f46a49ea6d6e8ef2aef5ea370e9d46035577ac02 GIT binary patch literal 2063 zcmZ{Yc{mde1IJM!#$34@epZIujmBI#W`+1(v^Lf5fshlkTjP2AW+9`cXlkdS$o$5P-B1xw#B`NC0 z8Dz>!gBxJ8!uR4$#(ln?U(44*rxTBNZ4!a#H_Fo+~iJpu>YvVa92-B7xHZInR@}B&oBmgPKL7 zY0l(LUsEwUTsms9-RV)CzrOJ3^&XgPbeb!CRWwFXxTbG1#2K+I{;P>HUa)Vh$xjL; zAaVG)Rb*jnj*3L&*3kgs(&-f~>y`yzI+LmG!U?udB7N?=jXWAQ6C=3u5ITJBTD-#f zKjDaOw@oZ`6M1q|RH`A1~Z{rCdAY9Az;iL#e0pcqZA3a;V~)8u8jK_75d;eBU6G9-eNP1Tahro;)g(gXVw= zpMsxbOznKa9Ir2agXl6}%|ZlpUsPNOrUosw2_Z%hw6Pyf%>u;$h21-xmEybrGL}K{x+NBR1${o{Y6R9nzlI#!CZ{^s?h$pK+YKg^ee1Hohy&yy zjc$K|BL~p%gC$4OCb&4FU4C>rx3u}->m!J#%s{ZaiglL{2)lx+lhdCVu{iS~!nA(X zJ!VzSTTH8yuO~EHEv#BlfOkIb_axLPfGnGc`Lb3ymsB%}f5u+2t$sC2ikKk%GrS}W z0Ltz*(RO+LY8I%serY&mcm^F#hOg9RDs{@cZNyQ`6HGIcfWDn)S9X5>j=uUFWO1=l z`CZfy@t2p?m)X#?$bmI~@?I2j!&%onoM`Q_AuY;t{BHW36!Ii&jeN`dLB}>L_w7=Y z29F0ZEd1jBj2EGha>s7N{Eb{%RM2f*5m`%gVVUIA>*vrY-K9tMH-VaMKb056Yifh- z0xP&%A1NS4m#SCxtFI(kNF8e_MtS0wk;>}i%0{D~F|0SR2{+|w=du%v0TlW?ePV72 z;5)d`obXilKb4(a4+F0zQHdX1`9$?<&dp7-JMaFRc?8}EW%lkk zx|hQGd_}9g+>O#BS4xy;A4AbO_oF#|#!4sP|5m*eMA&kk5LVxsdIV*w_e9d2P5 z(2Lac<4s}Sp0-a|oG;+qX_O($zX^ph99D_$|6tK_UcAMiB|fp@jfQ?(1tj$G*tnqO z1u@lj8O2Z&8+w7S1S2os7D$(G51Rs=j@XmUlge!UsE9;Q{CoRH?-?M~+#A%-od%N5FIN9*> ziM68bJq}E{0=iOR6n0(pnucdVxmp74wMP{eQ*12isH=g!TMQevDD|+qe8Y=C_*&cx z_PI!-m`fU7>BdnUpt87jtY(#S-mx&gw(bi4P2G^>W@(Q{feU?RG!$}+S^bci_Tt)$lbaa zZ}cska`}v8-sGLA2)rbwXZ>(jj26ey3rF9r_UP8JlU2TDic3^PS+?j-lFF}S6$rr zO#HK`Zq7SzU0XgCHaP6ou=xswb;*un8yIBfZ@UKAn}h@!O#tLa550}podjL9@5Dn8 z=`Yw}-k!sl6#mtY>vn>YgP#r+8c|8Nn_xV_Iiso$#))~C9L|F1P`rYY!`ZDR&c@QW zdaY0dEo4lCKx@_fQ7Z57Wv!MtbLCz8lB-Uo@Jv%x6QE(>fQ8E*03{1EdWe+JLFi>z8Nf$!NT%4t55!) literal 0 HcmV?d00001 diff --git a/docs/PrivilegeModelUser.dia b/docs/PrivilegeModelUser.dia new file mode 100644 index 0000000000000000000000000000000000000000..86d5b1149545da120c81855694cad0df40d5ba97 GIT binary patch literal 1752 zcmV;}1}FI+iwFP!000021MOW~Z`(E)eb28jT%b>hEX9^DO_O3PFl<1E7Hjit&@wG^ zrA3vb;`(L3eU#+oHtvs^AMzEVUBX>k@=igx}X zoRf|z)M?+GwOUUDOo=A(xy@OkwPce>(^fzh^twGE-Zx*0D44dBQk!y~;6k?U3BPW? znXQj@XA`EAit|kDkoa^WDfyNk+BU~%R3vmrWj5?06pEQ5dKK;z$%zyHeo|&rN|{1| zfAi*+YE9*vq*|Mk+j#pbn+v2DMEWe)#WMZFlg_xb-0mno`p(ep+LwdwL%*C3H)$4* zHw!15g=1L`aUA6lZid)%&2oxIpE0e)f%?w~SI0CUIsM4E zQ`zfFHhrGFsS-I+mTao$;~zD3R}y_yQ}lbL*o4!4wP%5@Ec~jP@Y$=gp24?oFF6{L zXt_(obVyz5p1wr*5t~wVV3?)L5T9(>;}Kn~7v5!JV!O z#dbW|=NH{<+jnPZ;*Y83%GvPtEEHsh_fo?A*1*58^n3Yi&tj=eeAnKvJ2&7NH>{-&44CIzNrP6K~; z?%dAIgK`*}_h!ui^~LBuGh!k0O$c%3}BrYeJD442PL2 zT>L8d^wWg1??uTR3cgwh8P1tks7Bcem8y&vr=;;e4_8GIGrv97;_=e_PWe66%p>cb zDS3qoS7*4tI_3IiGh>}tmED?@s>Kub9LzLrYnYkXdew~4=8#ilZ4Nd)@DAcSm5%ee z;GPcPBf7Fbs$`pzpG0OQfM~%V3-78W2le92-XStuptwwqX8erMlD9IZH$pprU)Fys zgELnldKRxTq}@$eeOT*6oN=aC``~C0E%r!ar^D>o_Z=y-7U;g1ykq5w{llX$++NA5 zA@YQ#NlPK8AP>j`@|;MX8Iww5=>dDdo&xMq5z;nr2iz&T=lP7l_P>cAka@E zkTi8x%`H7tzvcQWS%KdA`U)fhiC#X5xbO&XZV9@r77o+_b@ox`Bp21z0nj;cw{;O3 z>e0Gxe?P=mY13X}Ml0q{b1HDB_3JjLx#riEGqMJw^HKfQw0?cgG}DA$>W0>3jn1^L z)vv?{@tia5FOf4>!g{!K4%(kZ$ce|uw2BI zCfPmX)WY{F%~Vb<32-eNFHPJSw#gi6w{xBEcZ= z>J0+j3O_H57-0t6kyM1e9a9A1dBk1`4nT?16y- u1OkCzpn!n_1_~G`V4(O328!f0Co3v%&XO1A$2ZNrIr|qZGas^Rs{jBt5M;ps literal 0 HcmV?d00001 diff --git a/src/ch/eitchnet/privilege/base/PrivilegeContainer.java b/src/ch/eitchnet/privilege/base/PrivilegeContainer.java index 1ffc36d3b..acd6ee315 100644 --- a/src/ch/eitchnet/privilege/base/PrivilegeContainer.java +++ b/src/ch/eitchnet/privilege/base/PrivilegeContainer.java @@ -130,10 +130,10 @@ public class PrivilegeContainer { String policyHandlerClassName = policyHandlerElement.attributeValue(XmlConstants.XML_ATTR_CLASS); PolicyHandler policyHandler = ClassHelper.instantiateClass(policyHandlerClassName); - // instantiate modification handler - Element modificationHandlerElement = containerRootElement.element(XmlConstants.XML_HANDLER_MODEL); - String modificationHandlerClassName = modificationHandlerElement.attributeValue(XmlConstants.XML_ATTR_CLASS); - ModelHandler modelHandler = ClassHelper.instantiateClass(modificationHandlerClassName); + // instantiate model handler + Element modelHandlerElement = containerRootElement.element(XmlConstants.XML_HANDLER_MODEL); + String modelHandlerClassName = modelHandlerElement.attributeValue(XmlConstants.XML_ATTR_CLASS); + ModelHandler modelHandler = ClassHelper.instantiateClass(modelHandlerClassName); try { persistenceHandler.initialize(persistenceHandlerElement); @@ -162,11 +162,11 @@ public class PrivilegeContainer { throw new PrivilegeException("PolicyHandler " + policyHandlerClassName + " could not be initialized"); } try { - modelHandler.initialize(modificationHandlerElement); + modelHandler.initialize(modelHandlerElement); modelHandler.setPersistenceHandler(persistenceHandler); } catch (Exception e) { logger.error(e, e); - throw new PrivilegeException("ModificationHandler " + modificationHandlerClassName + throw new PrivilegeException("ModificationHandler " + modelHandlerClassName + " could not be initialized"); } diff --git a/src/ch/eitchnet/privilege/base/XmlConstants.java b/src/ch/eitchnet/privilege/base/XmlConstants.java index 727841b53..d2c7400e4 100644 --- a/src/ch/eitchnet/privilege/base/XmlConstants.java +++ b/src/ch/eitchnet/privilege/base/XmlConstants.java @@ -19,7 +19,7 @@ public class XmlConstants { public static final String XML_ROOT_PRIVILEGE_ROLES = "PrivilegeRoles"; public static final String XML_ROOT_PRIVILEGES = "Privileges"; public static final String XML_ROOT_PRIVILEGE_USERS = "PrivilegesUsers"; - public static final String XML_ROOT_RESTRICTION_POLICIES = "RestrictionPolicies"; + public static final String XML_ROOT_PRIVILEGE_POLICIES = "PrivilegePolicies"; public static final String XML_HANDLER_PERSISTENCE = "PersistenceHandler"; public static final String XML_HANDLER_ENCRYPTION = "EncryptionHandler"; diff --git a/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java index a72f5c996..27dff956d 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java @@ -24,8 +24,9 @@ import ch.eitchnet.privilege.helper.ConfigurationHelper; import ch.eitchnet.privilege.helper.XmlHelper; import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.Restrictable; +import ch.eitchnet.privilege.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.Role; -import ch.eitchnet.privilege.policy.RestrictionPolicy; +import ch.eitchnet.privilege.policy.PrivilegePolicy; /** * @author rvonburg @@ -33,7 +34,7 @@ import ch.eitchnet.privilege.policy.RestrictionPolicy; */ public class DefaultPolicyHandler implements PolicyHandler { - private Map> policyMap; + private Map> policyMap; /** * @see ch.eitchnet.privilege.handler.PolicyHandler#actionAllowed(ch.eitchnet.privilege.model.internal.Role, @@ -48,26 +49,38 @@ public class DefaultPolicyHandler implements PolicyHandler { else if (restrictable == null) throw new PrivilegeException("Restrictable may not be null!"); - // validate restriction key for this restrictable - String restrictionKey = restrictable.getRestrictionKey(); - if (restrictionKey == null || restrictionKey.length() < 3) { + // validate PrivilegeName for this restrictable + String privilegeName = restrictable.getPrivilegeName(); + if (privilegeName == null || privilegeName.length() < 3) { throw new PrivilegeException( - "The RestrictionKey may not be shorter than 3 characters. Invalid Restrictable " + "The PrivilegeName may not be shorter than 3 characters. Invalid Restrictable " + restrictable.getClass().getName()); } - // get restriction policy class - Class policyClazz = policyMap.get(restrictionKey); - if (policyClazz == null) { - throw new PrivilegeException("No RestrictionPolicy exists for the RestrictionKey " + restrictionKey - + " for Restrictable " + restrictable.getClass().getName()); + // If the role does not have this privilege, then stop as another role might have this privilege + if (!role.hasPrivilege(privilegeName)) { + return false; } - // instantiate policy - RestrictionPolicy policy = ClassHelper.instantiateClass(policyClazz); + // get the privilege for this restrictable + Privilege privilege = PrivilegeContainer.getInstance().getModelHandler().getPrivilege(privilegeName); + if (privilege == null) { + throw new PrivilegeException("No Privilege exists with the name " + privilegeName + " for Restrictable " + + restrictable.getClass().getName()); + } - // delegate checking to restriction policy - return policy.actionAllowed(role, restrictable); + // get the policy class configured for this privilege + Class policyClazz = policyMap.get(privilege.getPolicy()); + if (policyClazz == null) { + throw new PrivilegeException("PrivilegePolicy " + privilege.getPolicy() + " does not exist for Privilege " + + privilegeName); + } + + // instantiate the policy + PrivilegePolicy policy = ClassHelper.instantiateClass(policyClazz); + + // delegate checking to privilege policy + return policy.actionAllowed(role, privilege, restrictable); } /** @@ -95,7 +108,7 @@ public class DefaultPolicyHandler implements PolicyHandler { + policyFile.getAbsolutePath()); } - policyMap = new HashMap>(); + policyMap = new HashMap>(); // parse policy xml file to XML document Element containerRootElement = XmlHelper.parseDocument(policyFile).getRootElement(); @@ -105,7 +118,7 @@ public class DefaultPolicyHandler implements PolicyHandler { String policyName = policyElement.attributeValue(XmlConstants.XML_ATTR_NAME); String policyClass = policyElement.attributeValue(XmlConstants.XML_ATTR_CLASS); - Class clazz = ClassHelper.loadClass(policyClass); + Class clazz = ClassHelper.loadClass(policyClass); policyMap.put(policyName, clazz); } diff --git a/src/ch/eitchnet/privilege/handler/SessionHandler.java b/src/ch/eitchnet/privilege/handler/SessionHandler.java index d019d0dbe..10e0b4fa4 100644 --- a/src/ch/eitchnet/privilege/handler/SessionHandler.java +++ b/src/ch/eitchnet/privilege/handler/SessionHandler.java @@ -49,7 +49,7 @@ public interface SessionHandler extends PrivilegeContainerObject { public boolean isCertificateValid(Certificate certificate); /** - * @param user + * @param username * @param password * * @return @@ -57,5 +57,5 @@ public interface SessionHandler extends PrivilegeContainerObject { * @throws AccessDeniedException * if the user credentials are not valid */ - public Certificate authenticate(String user, String password); + public Certificate authenticate(String username, String password); } diff --git a/src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java b/src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java index 14001dc08..db99ea0be 100644 --- a/src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java +++ b/src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java @@ -49,7 +49,7 @@ public class BootstrapConfigurationHelper { private static String hashAlgorithm = "SHA-256"; - private static String policyXmlFile = "RestrictionPolicies.xml"; + private static String policyXmlFile = "PrivilegePolicies.xml"; private static String defaultPersistenceHandler = "ch.eitchnet.privilege.handler.DefaultPersistenceHandler"; private static String defaultSessionHandler = "ch.eitchnet.privilege.handler.DefaultSessionHandler"; diff --git a/src/ch/eitchnet/privilege/model/Restrictable.java b/src/ch/eitchnet/privilege/model/Restrictable.java index 9a2952fac..155876e23 100644 --- a/src/ch/eitchnet/privilege/model/Restrictable.java +++ b/src/ch/eitchnet/privilege/model/Restrictable.java @@ -16,7 +16,7 @@ package ch.eitchnet.privilege.model; */ public interface Restrictable { - public String getRestrictionKey(); + public String getPrivilegeName(); - public Object getRestrictionValue(); + public Object getPrivilegeValue(); } diff --git a/src/ch/eitchnet/privilege/policy/DefaultPrivilege.java b/src/ch/eitchnet/privilege/policy/DefaultPrivilege.java new file mode 100644 index 000000000..097214169 --- /dev/null +++ b/src/ch/eitchnet/privilege/policy/DefaultPrivilege.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.policy; + +import ch.eitchnet.privilege.i18n.PrivilegeException; +import ch.eitchnet.privilege.model.Restrictable; +import ch.eitchnet.privilege.model.internal.Privilege; +import ch.eitchnet.privilege.model.internal.Role; + +/** + * @author rvonburg + * + */ +public class DefaultPrivilege implements PrivilegePolicy { + + /** + * @see ch.eitchnet.privilege.policy.PrivilegePolicy#actionAllowed(ch.eitchnet.privilege.model.internal.Role, + * ch.eitchnet.privilege.model.internal.Privilege, ch.eitchnet.privilege.model.Restrictable) + */ + @Override + public boolean actionAllowed(Role role, Privilege privilege, Restrictable restrictable) { + + // validate user is not null + if (role == null) + throw new PrivilegeException("Role may not be null!"); + + // get the PrivilegeName + String privilegeName = restrictable.getPrivilegeName(); + if (privilegeName == null || privilegeName.isEmpty()) { + throw new PrivilegeException("The PrivilegeName for the Restrictable is null or empty: " + restrictable); + } + + // does this role have privilege for any values? + if (privilege.isAllAllowed()) + return true; + + // 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)) { + throw new PrivilegeException(Restrictable.class.getName() + " " + restrictable.getClass().getSimpleName() + + " has returned a non-string privilege value!"); + } + + String privilegeValue = (String) object; + + // first check values not allowed + for (String denied : privilege.getDenyList()) { + if (denied.equals(privilegeValue)) + return false; + } + + // now check values allowed + for (String allowed : privilege.getAllowList()) { + if (allowed.equals(privilegeValue)) + return true; + } + + // default is not allowed + return false; + } +} diff --git a/src/ch/eitchnet/privilege/policy/DefaultRestriction.java b/src/ch/eitchnet/privilege/policy/DefaultRestriction.java deleted file mode 100644 index d0d27d1e8..000000000 --- a/src/ch/eitchnet/privilege/policy/DefaultRestriction.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (c) 2010 - * - * Robert von Burg - * eitch@eitchnet.ch - * - * All rights reserved. - * - */ - -package ch.eitchnet.privilege.policy; - -import ch.eitchnet.privilege.base.PrivilegeContainer; -import ch.eitchnet.privilege.i18n.PrivilegeException; -import ch.eitchnet.privilege.model.Restrictable; -import ch.eitchnet.privilege.model.internal.Privilege; -import ch.eitchnet.privilege.model.internal.Role; - -/** - * @author rvonburg - * - */ -public class DefaultRestriction implements RestrictionPolicy { - - /** - * @see ch.eitchnet.privilege.policy.RestrictionPolicy#actionAllowed(java.lang.String, - * ch.eitchnet.privilege.model.internal.Role, ch.eitchnet.privilege.model.Restrictable) - */ - @Override - public boolean actionAllowed(Role role, Restrictable restrictable) { - - // validate user is not null - if (role == null) - throw new PrivilegeException("Role may not be null!"); - - // get the restriction key - String restrictionKey = restrictable.getRestrictionKey(); - if (restrictionKey == null || restrictionKey.isEmpty()) { - throw new PrivilegeException("The restriction key for the Restrictable is null or empty: " + restrictable); - } - - // get restriction object for users role - Privilege privilege = PrivilegeContainer.getInstance().getModelHandler().getPrivilege(restrictionKey); - - // no restriction object means no privilege - // TODO should default deny/allow policy be configurable? - if (privilege == null) - return false; - - // does this role have privilege for any values? - if (privilege.isAllAllowed()) - return true; - - // get the value on which the action is to be performed - Object object = restrictable.getRestrictionValue(); - - // DefaultRestriction policy expects the restriction value to be a string - if (!(object instanceof String)) { - throw new PrivilegeException(Restrictable.class.getName() + " " + restrictable.getClass().getSimpleName() - + " has returned a non-string restriction value!"); - } - - String restrictionValue = (String) object; - - // first check values not allowed - for (String denied : privilege.getDenyList()) { - if (denied.equals(restrictionValue)) - return false; - } - - // now check values allowed - for (String allowed : privilege.getAllowList()) { - if (allowed.equals(restrictionValue)) - return true; - } - - // default is not allowed - return false; - } -} diff --git a/src/ch/eitchnet/privilege/policy/RestrictionPolicy.java b/src/ch/eitchnet/privilege/policy/PrivilegePolicy.java similarity index 60% rename from src/ch/eitchnet/privilege/policy/RestrictionPolicy.java rename to src/ch/eitchnet/privilege/policy/PrivilegePolicy.java index 093ca1468..44c679e5b 100644 --- a/src/ch/eitchnet/privilege/policy/RestrictionPolicy.java +++ b/src/ch/eitchnet/privilege/policy/PrivilegePolicy.java @@ -11,13 +11,14 @@ package ch.eitchnet.privilege.policy; import ch.eitchnet.privilege.model.Restrictable; +import ch.eitchnet.privilege.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.Role; /** * @author rvonburg * */ -public interface RestrictionPolicy { +public interface PrivilegePolicy { - public boolean actionAllowed(Role role, Restrictable restrictable); + public boolean actionAllowed(Role role, Privilege privilege, Restrictable restrictable); } diff --git a/test/ch/eitchnet/privilege/test/PrivilegeTest.java b/test/ch/eitchnet/privilege/test/PrivilegeTest.java index d7a6e748f..bfa8a7beb 100644 --- a/test/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/test/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -26,6 +26,7 @@ import ch.eitchnet.privilege.handler.ModelHandler; import ch.eitchnet.privilege.i18n.AccessDeniedException; import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.Certificate; +import ch.eitchnet.privilege.model.Restrictable; import ch.eitchnet.privilege.model.UserRep; import ch.eitchnet.privilege.model.UserState; @@ -185,4 +186,18 @@ public class PrivilegeTest { PrivilegeContainer.getInstance().getModelHandler().addOrReplaceUser(certificate, userRep, null); logger.info("Added user bob"); } + + @Test + public void testPerformRestrictable() throws Exception { + + Certificate certificate = PrivilegeContainer.getInstance().getSessionHandler().authenticate("eitch", + "1234567890"); + org.junit.Assert.assertTrue("Certificate is null!", certificate != null); + + // see if eitch can perform restrictable + Restrictable restrictable = new TestRestrictable(); + boolean actionAllowed = PrivilegeContainer.getInstance().getSessionHandler().actionAllowed(certificate, + restrictable); + org.junit.Assert.assertTrue("eitch may not perform restrictable!", actionAllowed); + } } diff --git a/test/ch/eitchnet/privilege/test/TestRestrictable.java b/test/ch/eitchnet/privilege/test/TestRestrictable.java new file mode 100644 index 000000000..1f65beef6 --- /dev/null +++ b/test/ch/eitchnet/privilege/test/TestRestrictable.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2010 + * + * Robert von Burg + * eitch@eitchnet.ch + * + * All rights reserved. + * + */ + +package ch.eitchnet.privilege.test; + +import ch.eitchnet.privilege.model.Restrictable; + +/** + * @author rvonburg + * + */ +public class TestRestrictable implements Restrictable { + + /**@see ch.eitchnet.privilege.model.Restrictable#getPrivilegeName() + */ + @Override + public String getPrivilegeName() { + return "Service"; + } + + /**@see ch.eitchnet.privilege.model.Restrictable#getPrivilegeValue() + */ + @Override + public Object getPrivilegeValue() { + return TestRestrictable.class.getName(); + } + +} From 9a6637429fc0ad2e38d3c7a9b228c293f3bf4918 Mon Sep 17 00:00:00 2001 From: eitch Date: Mon, 2 Aug 2010 23:30:39 +0000 Subject: [PATCH 029/173] --- docs/PrivilegeModelPrivilege.dia | Bin 2063 -> 2230 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/PrivilegeModelPrivilege.dia b/docs/PrivilegeModelPrivilege.dia index f46a49ea6d6e8ef2aef5ea370e9d46035577ac02..85fc14ffb44e591e416dc0963e1dfbbad13aad08 100644 GIT binary patch literal 2230 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 From 55679fc62d2eea4c664fafccb895e2e0c5bf0199 Mon Sep 17 00:00:00 2001 From: eitch Date: Sun, 8 Aug 2010 20:13:36 +0000 Subject: [PATCH 030/173] --- .../privilege/base/PrivilegeContainer.java | 56 +--- .../base/PrivilegeContainerObject.java | 22 -- .../handler/DefaultPersistenceHandler.java | 77 ++++- .../handler/DefaultPolicyHandler.java | 126 -------- ...dler.java => DefaultPrivilegeHandler.java} | 295 ++++++++++++++++-- .../handler/DefaultSessionHandler.java | 222 ------------- .../privilege/handler/EncryptionHandler.java | 6 +- .../privilege/handler/PersistenceHandler.java | 10 +- .../privilege/handler/PolicyHandler.java | 24 -- ...odelHandler.java => PrivilegeHandler.java} | 62 +++- .../privilege/handler/SessionHandler.java | 61 ---- .../privilege/helper/PrivilegeHelper.java | 2 +- .../privilege/test/PrivilegeTest.java | 38 +-- 13 files changed, 429 insertions(+), 572 deletions(-) delete mode 100644 src/ch/eitchnet/privilege/base/PrivilegeContainerObject.java delete mode 100644 src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java rename src/ch/eitchnet/privilege/handler/{DefaultModelHandler.java => DefaultPrivilegeHandler.java} (60%) delete mode 100644 src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java delete mode 100644 src/ch/eitchnet/privilege/handler/PolicyHandler.java rename src/ch/eitchnet/privilege/handler/{ModelHandler.java => PrivilegeHandler.java} (56%) delete mode 100644 src/ch/eitchnet/privilege/handler/SessionHandler.java diff --git a/src/ch/eitchnet/privilege/base/PrivilegeContainer.java b/src/ch/eitchnet/privilege/base/PrivilegeContainer.java index acd6ee315..27ae0e4b0 100644 --- a/src/ch/eitchnet/privilege/base/PrivilegeContainer.java +++ b/src/ch/eitchnet/privilege/base/PrivilegeContainer.java @@ -16,10 +16,8 @@ import org.apache.log4j.Logger; import org.dom4j.Element; import ch.eitchnet.privilege.handler.EncryptionHandler; -import ch.eitchnet.privilege.handler.ModelHandler; import ch.eitchnet.privilege.handler.PersistenceHandler; -import ch.eitchnet.privilege.handler.PolicyHandler; -import ch.eitchnet.privilege.handler.SessionHandler; +import ch.eitchnet.privilege.handler.PrivilegeHandler; import ch.eitchnet.privilege.helper.ClassHelper; import ch.eitchnet.privilege.helper.XmlHelper; import ch.eitchnet.privilege.i18n.PrivilegeException; @@ -43,10 +41,8 @@ public class PrivilegeContainer { instance = new PrivilegeContainer(); } - private SessionHandler sessionHandler; - private PolicyHandler policyHandler; private EncryptionHandler encryptionHandler; - private ModelHandler modelHandler; + private PrivilegeHandler modelHandler; private String basePath; @@ -61,20 +57,6 @@ public class PrivilegeContainer { // private constructor } - /** - * @return the sessionHandler - */ - public SessionHandler getSessionHandler() { - return sessionHandler; - } - - /** - * @return the policyHandler - */ - public PolicyHandler getPolicyHandler() { - return policyHandler; - } - /** * @return the encryptionHandler */ @@ -85,7 +67,7 @@ public class PrivilegeContainer { /** * @return the modelHandler */ - public ModelHandler getModelHandler() { + public PrivilegeHandler getModelHandler() { return modelHandler; } @@ -115,25 +97,15 @@ public class PrivilegeContainer { String persistenceHandlerClassName = persistenceHandlerElement.attributeValue(XmlConstants.XML_ATTR_CLASS); PersistenceHandler persistenceHandler = ClassHelper.instantiateClass(persistenceHandlerClassName); - // instantiate session handler - Element sessionHandlerElement = containerRootElement.element(XmlConstants.XML_HANDLER_SESSION); - String sessionHandlerClassName = sessionHandlerElement.attributeValue(XmlConstants.XML_ATTR_CLASS); - SessionHandler sessionHandler = ClassHelper.instantiateClass(sessionHandlerClassName); - // instantiate encryption handler Element encryptionHandlerElement = containerRootElement.element(XmlConstants.XML_HANDLER_ENCRYPTION); String encryptionHandlerClassName = encryptionHandlerElement.attributeValue(XmlConstants.XML_ATTR_CLASS); EncryptionHandler encryptionHandler = ClassHelper.instantiateClass(encryptionHandlerClassName); - // instantiate policy handler - Element policyHandlerElement = containerRootElement.element(XmlConstants.XML_HANDLER_POLICY); - String policyHandlerClassName = policyHandlerElement.attributeValue(XmlConstants.XML_ATTR_CLASS); - PolicyHandler policyHandler = ClassHelper.instantiateClass(policyHandlerClassName); - - // instantiate model handler + // instantiate privilege handler Element modelHandlerElement = containerRootElement.element(XmlConstants.XML_HANDLER_MODEL); String modelHandlerClassName = modelHandlerElement.attributeValue(XmlConstants.XML_ATTR_CLASS); - ModelHandler modelHandler = ClassHelper.instantiateClass(modelHandlerClassName); + PrivilegeHandler modelHandler = ClassHelper.instantiateClass(modelHandlerClassName); try { persistenceHandler.initialize(persistenceHandlerElement); @@ -142,12 +114,6 @@ public class PrivilegeContainer { throw new PrivilegeException("PersistenceHandler " + persistenceHandlerElement + " could not be initialized"); } - try { - sessionHandler.initialize(sessionHandlerElement); - } catch (Exception e) { - logger.error(e, e); - throw new PrivilegeException("SessionHandler " + sessionHandlerClassName + " could not be initialized"); - } try { encryptionHandler.initialize(encryptionHandlerElement); } catch (Exception e) { @@ -155,25 +121,15 @@ public class PrivilegeContainer { throw new PrivilegeException("EncryptionHandler " + encryptionHandlerClassName + " could not be initialized"); } - try { - policyHandler.initialize(policyHandlerElement); - } catch (Exception e) { - logger.error(e, e); - throw new PrivilegeException("PolicyHandler " + policyHandlerClassName + " could not be initialized"); - } try { modelHandler.initialize(modelHandlerElement); - modelHandler.setPersistenceHandler(persistenceHandler); } catch (Exception e) { logger.error(e, e); - throw new PrivilegeException("ModificationHandler " + modelHandlerClassName - + " could not be initialized"); + throw new PrivilegeException("ModificationHandler " + modelHandlerClassName + " could not be initialized"); } // keep references to the handlers this.modelHandler = modelHandler; - this.sessionHandler = sessionHandler; this.encryptionHandler = encryptionHandler; - this.policyHandler = policyHandler; } } diff --git a/src/ch/eitchnet/privilege/base/PrivilegeContainerObject.java b/src/ch/eitchnet/privilege/base/PrivilegeContainerObject.java deleted file mode 100644 index 88a8b2c2f..000000000 --- a/src/ch/eitchnet/privilege/base/PrivilegeContainerObject.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (c) 2010 - * - * Robert von Burg - * eitch@eitchnet.ch - * - * All rights reserved. - * - */ - -package ch.eitchnet.privilege.base; - -import org.dom4j.Element; - -/** - * @author rvonburg - * - */ -public interface PrivilegeContainerObject { - - public void initialize(Element element); -} diff --git a/src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java index 427c6322d..5ea040e1c 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java @@ -26,6 +26,7 @@ import org.dom4j.Element; import ch.eitchnet.privilege.base.PrivilegeContainer; import ch.eitchnet.privilege.base.XmlConstants; +import ch.eitchnet.privilege.helper.ClassHelper; import ch.eitchnet.privilege.helper.ConfigurationHelper; import ch.eitchnet.privilege.helper.XmlHelper; import ch.eitchnet.privilege.i18n.PrivilegeException; @@ -34,6 +35,7 @@ import ch.eitchnet.privilege.model.UserState; import ch.eitchnet.privilege.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.Role; import ch.eitchnet.privilege.model.internal.User; +import ch.eitchnet.privilege.policy.PrivilegePolicy; /** * @author rvonburg @@ -46,6 +48,7 @@ public class DefaultPersistenceHandler implements PersistenceHandler { private Map userMap; private Map roleMap; private Map privilegeMap; + private Map> policyMap; private long usersFileDate; private boolean userMapDirty; @@ -140,6 +143,24 @@ public class DefaultPersistenceHandler implements PersistenceHandler { return userMap.get(username); } + /** + * @see ch.eitchnet.privilege.handler.PersistenceHandler#getPolicy(java.lang.String) + */ + @Override + public PrivilegePolicy getPolicy(String policyName) { + + // get the policies class + Class policyClazz = policyMap.get(policyName); + if (policyClazz == null) { + return null; + } + + // instantiate the policy + PrivilegePolicy policy = ClassHelper.instantiateClass(policyClazz); + + return policy; + } + /** * @see ch.eitchnet.privilege.handler.PersistenceHandler#persist(ch.eitchnet.privilege.model.Certificate) */ @@ -150,7 +171,7 @@ public class DefaultPersistenceHandler implements PersistenceHandler { // get users file name String usersFileName = parameterMap.get(XmlConstants.XML_PARAM_USERS_FILE); if (usersFileName == null || usersFileName.isEmpty()) { - throw new PrivilegeException("[" + SessionHandler.class.getName() + "] Defined parameter " + throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " + XmlConstants.XML_PARAM_USERS_FILE + " is invalid"); } // get users file @@ -177,7 +198,7 @@ public class DefaultPersistenceHandler implements PersistenceHandler { // get roles file name String rolesFileName = parameterMap.get(XmlConstants.XML_PARAM_ROLES_FILE); if (rolesFileName == null || rolesFileName.isEmpty()) { - throw new PrivilegeException("[" + SessionHandler.class.getName() + "] Defined parameter " + throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " + XmlConstants.XML_PARAM_ROLES_FILE + " is invalid"); } // get roles file @@ -204,7 +225,7 @@ public class DefaultPersistenceHandler implements PersistenceHandler { // get privileges file name String privilegesFileName = parameterMap.get(XmlConstants.XML_PARAM_PRIVILEGES_FILE); if (privilegesFileName == null || privilegesFileName.isEmpty()) { - throw new PrivilegeException("[" + SessionHandler.class.getName() + "] Defined parameter " + throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " + XmlConstants.XML_PARAM_PRIVILEGES_FILE + " is invalid"); } // get privileges file @@ -254,6 +275,7 @@ public class DefaultPersistenceHandler implements PersistenceHandler { roleMap = new HashMap(); userMap = new HashMap(); privilegeMap = new HashMap(); + policyMap = new HashMap>(); // get parameters Element parameterElement = element.element(XmlConstants.XML_PARAMETERS); @@ -262,14 +284,14 @@ public class DefaultPersistenceHandler implements PersistenceHandler { // get roles file name String rolesFileName = parameterMap.get(XmlConstants.XML_PARAM_ROLES_FILE); if (rolesFileName == null || rolesFileName.isEmpty()) { - throw new PrivilegeException("[" + SessionHandler.class.getName() + "] Defined parameter " + throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " + XmlConstants.XML_PARAM_ROLES_FILE + " is invalid"); } // get roles file File rolesFile = new File(PrivilegeContainer.getInstance().getBasePath() + "/" + rolesFileName); if (!rolesFile.exists()) { - throw new PrivilegeException("[" + SessionHandler.class.getName() + "] Defined parameter " + throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " + XmlConstants.XML_PARAM_ROLES_FILE + " is invalid as roles file does not exist at path " + rolesFile.getAbsolutePath()); } @@ -284,14 +306,14 @@ public class DefaultPersistenceHandler implements PersistenceHandler { // get users file name String usersFileName = parameterMap.get(XmlConstants.XML_PARAM_USERS_FILE); if (usersFileName == null || usersFileName.isEmpty()) { - throw new PrivilegeException("[" + SessionHandler.class.getName() + "] Defined parameter " + throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " + XmlConstants.XML_PARAM_USERS_FILE + " is invalid"); } // get users file File usersFile = new File(PrivilegeContainer.getInstance().getBasePath() + "/" + usersFileName); if (!usersFile.exists()) { - throw new PrivilegeException("[" + SessionHandler.class.getName() + "] Defined parameter " + throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " + XmlConstants.XML_PARAM_USERS_FILE + " is invalid as users file does not exist at path " + usersFile.getAbsolutePath()); } @@ -306,14 +328,14 @@ public class DefaultPersistenceHandler implements PersistenceHandler { // get privileges file name String privilegesFileName = parameterMap.get(XmlConstants.XML_PARAM_PRIVILEGES_FILE); if (privilegesFileName == null || privilegesFileName.isEmpty()) { - throw new PrivilegeException("[" + SessionHandler.class.getName() + "] Defined parameter " + throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " + XmlConstants.XML_PARAM_PRIVILEGES_FILE + " is invalid"); } // get privileges file File privilegesFile = new File(PrivilegeContainer.getInstance().getBasePath() + "/" + privilegesFileName); if (!privilegesFile.exists()) { - throw new PrivilegeException("[" + SessionHandler.class.getName() + "] Defined parameter " + throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " + XmlConstants.XML_PARAM_PRIVILEGES_FILE + " is invalid as privileges file does not exist at path " + privilegesFile.getAbsolutePath()); } @@ -325,6 +347,27 @@ public class DefaultPersistenceHandler implements PersistenceHandler { readPrivileges(privilegesRootElement); privilegesFileDate = privilegesFile.lastModified(); + // get policy file name + String policyFileName = parameterMap.get(XmlConstants.XML_PARAM_POLICY_FILE); + if (policyFileName == null || policyFileName.isEmpty()) { + throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " + + XmlConstants.XML_PARAM_POLICY_FILE + " is invalid"); + } + + // get policy file + File policyFile = new File(PrivilegeContainer.getInstance().getBasePath() + "/" + policyFileName); + if (!policyFile.exists()) { + throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " + + XmlConstants.XML_PARAM_POLICY_FILE + " is invalid as policy file does not exist at path " + + policyFile.getAbsolutePath()); + } + + // parse policy xml file to XML document + Element policiesRootElement = XmlHelper.parseDocument(policyFile).getRootElement(); + + // read policies + readPolicies(policiesRootElement); + userMapDirty = false; roleMapDirty = false; privilegeMapDirty = false; @@ -452,6 +495,22 @@ public class DefaultPersistenceHandler implements PersistenceHandler { } } + /** + * @param policiesRootElement + */ + private void readPolicies(Element policiesRootElement) { + + List policyElements = policiesRootElement.elements(XmlConstants.XML_POLICY); + for (Element policyElement : policyElements) { + String policyName = policyElement.attributeValue(XmlConstants.XML_ATTR_NAME); + String policyClass = policyElement.attributeValue(XmlConstants.XML_ATTR_CLASS); + + Class clazz = ClassHelper.loadClass(policyClass); + + policyMap.put(policyName, clazz); + } + } + private List toDomPrivileges() { List privilegesAsElements = new ArrayList(privilegeMap.size()); diff --git a/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java deleted file mode 100644 index 27dff956d..000000000 --- a/src/ch/eitchnet/privilege/handler/DefaultPolicyHandler.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (c) 2010 - * - * Robert von Burg - * eitch@eitchnet.ch - * - * All rights reserved. - * - */ - -package ch.eitchnet.privilege.handler; - -import java.io.File; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.dom4j.Element; - -import ch.eitchnet.privilege.base.PrivilegeContainer; -import ch.eitchnet.privilege.base.XmlConstants; -import ch.eitchnet.privilege.helper.ClassHelper; -import ch.eitchnet.privilege.helper.ConfigurationHelper; -import ch.eitchnet.privilege.helper.XmlHelper; -import ch.eitchnet.privilege.i18n.PrivilegeException; -import ch.eitchnet.privilege.model.Restrictable; -import ch.eitchnet.privilege.model.internal.Privilege; -import ch.eitchnet.privilege.model.internal.Role; -import ch.eitchnet.privilege.policy.PrivilegePolicy; - -/** - * @author rvonburg - * - */ -public class DefaultPolicyHandler implements PolicyHandler { - - private Map> policyMap; - - /** - * @see ch.eitchnet.privilege.handler.PolicyHandler#actionAllowed(ch.eitchnet.privilege.model.internal.Role, - * ch.eitchnet.privilege.model.Restrictable) - */ - @Override - public boolean actionAllowed(Role role, Restrictable restrictable) { - - // user and restrictable must not be null - if (role == null) - throw new PrivilegeException("Role may not be null!"); - else if (restrictable == null) - throw new PrivilegeException("Restrictable may not be null!"); - - // validate PrivilegeName for this restrictable - String privilegeName = restrictable.getPrivilegeName(); - if (privilegeName == null || privilegeName.length() < 3) { - throw new PrivilegeException( - "The PrivilegeName may not be shorter than 3 characters. Invalid Restrictable " - + restrictable.getClass().getName()); - } - - // If the role does not have this privilege, then stop as another role might have this privilege - if (!role.hasPrivilege(privilegeName)) { - return false; - } - - // get the privilege for this restrictable - Privilege privilege = PrivilegeContainer.getInstance().getModelHandler().getPrivilege(privilegeName); - if (privilege == null) { - throw new PrivilegeException("No Privilege exists with the name " + privilegeName + " for Restrictable " - + restrictable.getClass().getName()); - } - - // get the policy class configured for this privilege - Class policyClazz = policyMap.get(privilege.getPolicy()); - if (policyClazz == null) { - throw new PrivilegeException("PrivilegePolicy " + privilege.getPolicy() + " does not exist for Privilege " - + privilegeName); - } - - // instantiate the policy - PrivilegePolicy policy = ClassHelper.instantiateClass(policyClazz); - - // delegate checking to privilege policy - return policy.actionAllowed(role, privilege, restrictable); - } - - /** - * @see ch.eitchnet.privilege.base.PrivilegeContainerObject#initialize(org.dom4j.Element) - */ - @SuppressWarnings("unchecked") - public void initialize(Element element) { - - // get parameters - Element parameterElement = element.element(XmlConstants.XML_PARAMETERS); - Map parameterMap = ConfigurationHelper.convertToParameterMap(parameterElement); - - // get policy file name - String policyFileName = parameterMap.get(XmlConstants.XML_PARAM_POLICY_FILE); - if (policyFileName == null || policyFileName.isEmpty()) { - throw new PrivilegeException("[" + PolicyHandler.class.getName() + "] Defined parameter " - + XmlConstants.XML_PARAM_POLICY_FILE + " is invalid"); - } - - // get policy file - File policyFile = new File(PrivilegeContainer.getInstance().getBasePath() + "/" + policyFileName); - if (!policyFile.exists()) { - throw new PrivilegeException("[" + PolicyHandler.class.getName() + "] Defined parameter " - + XmlConstants.XML_PARAM_POLICY_FILE + " is invalid as policy file does not exist at path " - + policyFile.getAbsolutePath()); - } - - policyMap = new HashMap>(); - - // parse policy xml file to XML document - Element containerRootElement = XmlHelper.parseDocument(policyFile).getRootElement(); - - List policyElements = containerRootElement.elements(XmlConstants.XML_POLICY); - for (Element policyElement : policyElements) { - String policyName = policyElement.attributeValue(XmlConstants.XML_ATTR_NAME); - String policyClass = policyElement.attributeValue(XmlConstants.XML_ATTR_CLASS); - - Class clazz = ClassHelper.loadClass(policyClass); - - policyMap.put(policyName, clazz); - } - } -} diff --git a/src/ch/eitchnet/privilege/handler/DefaultModelHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java similarity index 60% rename from src/ch/eitchnet/privilege/handler/DefaultModelHandler.java rename to src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index cbbeface2..2fca99624 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultModelHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -10,8 +10,10 @@ package ch.eitchnet.privilege.handler; +import java.util.HashMap; import java.util.HashSet; import java.util.Locale; +import java.util.Map; import java.util.Set; import org.apache.log4j.Logger; @@ -19,35 +21,234 @@ import org.dom4j.Element; import ch.eitchnet.privilege.base.PrivilegeContainer; import ch.eitchnet.privilege.helper.PrivilegeHelper; +import ch.eitchnet.privilege.i18n.AccessDeniedException; import ch.eitchnet.privilege.i18n.PrivilegeException; 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.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.Role; +import ch.eitchnet.privilege.model.internal.Session; import ch.eitchnet.privilege.model.internal.User; +import ch.eitchnet.privilege.policy.PrivilegePolicy; /** * @author rvonburg * */ -public class DefaultModelHandler implements ModelHandler { +public class DefaultPrivilegeHandler implements PrivilegeHandler { - private static final Logger logger = Logger.getLogger(DefaultModelHandler.class); + private static final Logger logger = Logger.getLogger(DefaultPrivilegeHandler.class); + + private static long lastSessionId; + + private Map sessionMap; private PersistenceHandler persistenceHandler; + private EncryptionHandler encryptionHandler; + private PrivilegeHandler modelHandler; /** - * @see ch.eitchnet.privilege.handler.SessionHandler#setPersistenceHandler(ch.eitchnet.privilege.handler.PersistenceHandler) + * TODO What is better, validate from {@link Restrictable} to {@link User} or the opposite direction? + * + * @see ch.eitchnet.privilege.handler.SessionHandler#actionAllowed(ch.eitchnet.privilege.model.Certificate, + * ch.eitchnet.privilege.model.Restrictable) + * + * @throws AccessDeniedException + * if the {@link Certificate} is not for a currently logged in {@link User} or if the user may not + * perform the action defined by the {@link Restrictable} implementation */ - public void setPersistenceHandler(PersistenceHandler persistenceHandler) { - this.persistenceHandler = persistenceHandler; + @Override + public boolean actionAllowed(Certificate certificate, Restrictable restrictable) { + + // first validate certificate + if (!isCertificateValid(certificate)) { + logger.info("Certificate is not valid, so action is not allowed: " + certificate + " for restrictable: " + + restrictable); + return false; + } + + // restrictable must not be null + if (restrictable == null) + throw new PrivilegeException("Restrictable may not be null!"); + + // get user object + User user = modelHandler.getUser(certificate.getUsername()); + if (user == null) { + throw new PrivilegeException( + "Oh boy, how did this happen: No User in user map although the certificate is valid!"); + } + + // default is to not allow the action + // TODO should default deny/allow policy be configurable? + boolean actionAllowed = false; + + // now iterate roles and validate on policies + for (String roleName : user.getRoles()) { + + Role role = modelHandler.getRole(roleName); + if (role == null) { + logger.error("No role is defined with name " + roleName + " which is configured for user " + user); + continue; + } + + actionAllowed = actionAllowed(role, restrictable); + + // if action is allowed, then break iteration as a privilege match has been made + if (actionAllowed) + break; + } + + return actionAllowed; } /** - * @see ch.eitchnet.privilege.handler.ModelHandler#addOrReplacePrivilege(ch.eitchnet.privilege.model.Certificate, + * @see ch.eitchnet.privilege.handler.PolicyHandler#actionAllowed(ch.eitchnet.privilege.model.internal.Role, + * ch.eitchnet.privilege.model.Restrictable) + */ + @Override + public boolean actionAllowed(Role role, Restrictable restrictable) { + + // user and restrictable must not be null + if (role == null) + throw new PrivilegeException("Role may not be null!"); + else if (restrictable == null) + throw new PrivilegeException("Restrictable may not be null!"); + + // validate PrivilegeName for this restrictable + String privilegeName = restrictable.getPrivilegeName(); + if (privilegeName == null || privilegeName.length() < 3) { + throw new PrivilegeException( + "The PrivilegeName may not be shorter than 3 characters. Invalid Restrictable " + + restrictable.getClass().getName()); + } + + // If the role does not have this privilege, then stop as another role might have this privilege + if (!role.hasPrivilege(privilegeName)) { + return false; + } + + // get the privilege for this restrictable + Privilege privilege = modelHandler.getPrivilege(privilegeName); + if (privilege == null) { + throw new PrivilegeException("No Privilege exists with the name " + privilegeName + " for Restrictable " + + restrictable.getClass().getName()); + } + + // get the policy configured for this privilege + PrivilegePolicy policy = modelHandler.getPolicy(privilege.getPolicy()); + if (policy == null) { + throw new PrivilegeException("PrivilegePolicy " + privilege.getPolicy() + " does not exist for Privilege " + + privilegeName); + } + + // delegate checking to privilege policy + return policy.actionAllowed(role, privilege, restrictable); + } + + /** + * @see ch.eitchnet.privilege.handler.SessionHandler#isCertificateValid(ch.eitchnet.privilege.model.Certificate) + */ + @Override + public boolean isCertificateValid(Certificate certificate) { + + // certificate must not be null + if (certificate == null) + throw new PrivilegeException("Certificate may not be null!"); + + // first see if a session exists for this certificate + CertificateSessionPair certificateSessionPair = sessionMap.get(certificate.getSessionId()); + if (certificateSessionPair == null) + throw new AccessDeniedException("There is no session information for " + certificate.toString()); + + // validate certificate has not been tampered with + Certificate sessionCertificate = certificateSessionPair.certificate; + if (!sessionCertificate.equals(certificate)) + throw new PrivilegeException("Received illegal certificate for session id " + certificate.getSessionId()); + + // TODO is validating authToken overkill since the two certificates have already been checked on equality? + // validate authToken from certificate using the sessions authPassword + String authToken = certificate.getAuthToken(certificateSessionPair.session.getAuthPassword()); + if (authToken == null || !authToken.equals(certificateSessionPair.session.getAuthToken())) + throw new PrivilegeException("Received illegal certificate data for session id " + + certificate.getSessionId()); + + // get user object + User user = modelHandler.getUser(certificateSessionPair.session.getUsername()); + + // if user exists, then certificate is valid + if (user == null) { + throw new PrivilegeException( + "Oh boy, how did this happen: No User in user map although the certificate is valid!"); + } else { + return true; + } + } + + /** + * @see ch.eitchnet.privilege.handler.SessionHandler#authenticate(java.lang.String, java.lang.String) + * + * @throws AccessDeniedException + * if the user credentials are not valid + */ + @Override + public Certificate authenticate(String username, String password) { + + // both username and password must at least have 3 characters in length + if (username == null || username.length() < 3) + throw new PrivilegeException("The given username is shorter than 3 characters"); + else if (password == null || password.length() < 3) + throw new PrivilegeException("The given password is shorter than 3 characters"); + + // we only work with hashed passwords + String passwordHash = encryptionHandler.convertToHash(password); + + // get user object + User user = modelHandler.getUser(username); + // no user means no authentication + if (user == null) + throw new AccessDeniedException("There is no user defined with the credentials: " + username + " / ***..."); + + // validate password + if (!user.isPassword(passwordHash)) + throw new AccessDeniedException("Password is incorrect for " + username + " / ***..."); + + // validate if user is allowed to login + if (user.getState() != UserState.ENABLED) + throw new AccessDeniedException("User " + username + " is not ENABLED. State is: " + user.getState()); + + // validate user has at least one role + if (user.getRoles().isEmpty()) { + throw new PrivilegeException("User " + username + " does not have any roles defined!"); + } + + // get 2 auth tokens + String authToken = encryptionHandler.nextToken(); + String authPassword = encryptionHandler.nextToken(); + + // get next session id + String sessionId = nextSessionId(); + + // create certificate + Certificate certificate = new Certificate(sessionId, username, authToken, authPassword, user.getLocale()); + + // create and save a new session + Session session = new Session(sessionId, authToken, authPassword, user.getUsername(), System + .currentTimeMillis()); + sessionMap.put(sessionId, new CertificateSessionPair(session, certificate)); + + // log + logger.info("Authenticated: " + session); + + // return the certificate + return certificate; + } + + /** + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#addOrReplacePrivilege(ch.eitchnet.privilege.model.Certificate, * ch.eitchnet.privilege.model.PrivilegeRep) */ @Override @@ -69,7 +270,7 @@ public class DefaultModelHandler implements ModelHandler { } /** - * @see ch.eitchnet.privilege.handler.ModelHandler#addOrReplaceRole(ch.eitchnet.privilege.model.Certificate, + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#addOrReplaceRole(ch.eitchnet.privilege.model.Certificate, * ch.eitchnet.privilege.model.RoleRep) */ @Override @@ -90,7 +291,7 @@ public class DefaultModelHandler implements ModelHandler { } /** - * @see ch.eitchnet.privilege.handler.ModelHandler#addOrReplaceUser(ch.eitchnet.privilege.model.Certificate, + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#addOrReplaceUser(ch.eitchnet.privilege.model.Certificate, * ch.eitchnet.privilege.model.UserRep, java.lang.String) */ @Override @@ -108,7 +309,7 @@ public class DefaultModelHandler implements ModelHandler { if (password == null) passwordHash = null; else - passwordHash = PrivilegeContainer.getInstance().getEncryptionHandler().convertToHash(password); + passwordHash = encryptionHandler.convertToHash(password); // create new user User user = new User(userRep.getUsername(), passwordHash, userRep.getFirstname(), userRep.getSurname(), userRep @@ -119,7 +320,7 @@ public class DefaultModelHandler implements ModelHandler { } /** - * @see ch.eitchnet.privilege.handler.ModelHandler#addPrivilegeToRole(ch.eitchnet.privilege.model.Certificate, + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#addPrivilegeToRole(ch.eitchnet.privilege.model.Certificate, * java.lang.String, java.lang.String) */ @Override @@ -162,7 +363,7 @@ public class DefaultModelHandler implements ModelHandler { } /** - * @see ch.eitchnet.privilege.handler.ModelHandler#addRoleToUser(ch.eitchnet.privilege.model.Certificate, + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#addRoleToUser(ch.eitchnet.privilege.model.Certificate, * java.lang.String, java.lang.String) */ @Override @@ -205,7 +406,7 @@ public class DefaultModelHandler implements ModelHandler { } /** - * @see ch.eitchnet.privilege.handler.ModelHandler#persist(ch.eitchnet.privilege.model.Certificate) + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#persist(ch.eitchnet.privilege.model.Certificate) */ @Override public boolean persist(Certificate certificate) { @@ -221,7 +422,7 @@ public class DefaultModelHandler implements ModelHandler { } /** - * @see ch.eitchnet.privilege.handler.ModelHandler#removePrivilege(ch.eitchnet.privilege.model.Certificate, + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#removePrivilege(ch.eitchnet.privilege.model.Certificate, * java.lang.String) */ @Override @@ -245,7 +446,7 @@ public class DefaultModelHandler implements ModelHandler { } /** - * @see ch.eitchnet.privilege.handler.ModelHandler#removePrivilegeFromRole(ch.eitchnet.privilege.model.Certificate, + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#removePrivilegeFromRole(ch.eitchnet.privilege.model.Certificate, * java.lang.String, java.lang.String) */ @Override @@ -281,7 +482,7 @@ public class DefaultModelHandler implements ModelHandler { } /** - * @see ch.eitchnet.privilege.handler.ModelHandler#removeRole(ch.eitchnet.privilege.model.Certificate, + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#removeRole(ch.eitchnet.privilege.model.Certificate, * java.lang.String) */ @Override @@ -305,7 +506,7 @@ public class DefaultModelHandler implements ModelHandler { } /** - * @see ch.eitchnet.privilege.handler.ModelHandler#removeRoleFromUser(ch.eitchnet.privilege.model.Certificate, + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#removeRoleFromUser(ch.eitchnet.privilege.model.Certificate, * java.lang.String, java.lang.String) */ @Override @@ -342,7 +543,7 @@ public class DefaultModelHandler implements ModelHandler { } /** - * @see ch.eitchnet.privilege.handler.ModelHandler#removeUser(ch.eitchnet.privilege.model.Certificate, + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#removeUser(ch.eitchnet.privilege.model.Certificate, * java.lang.String) */ @Override @@ -366,7 +567,7 @@ public class DefaultModelHandler implements ModelHandler { } /** - * @see ch.eitchnet.privilege.handler.ModelHandler#setPrivilegeAllAllowed(ch.eitchnet.privilege.model.Certificate, + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setPrivilegeAllAllowed(ch.eitchnet.privilege.model.Certificate, * java.lang.String, boolean) */ @Override @@ -401,7 +602,7 @@ public class DefaultModelHandler implements ModelHandler { } /** - * @see ch.eitchnet.privilege.handler.ModelHandler#setPrivilegeAllowList(ch.eitchnet.privilege.model.Certificate, + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setPrivilegeAllowList(ch.eitchnet.privilege.model.Certificate, * java.lang.String, java.util.Set) */ @Override @@ -429,7 +630,7 @@ public class DefaultModelHandler implements ModelHandler { } /** - * @see ch.eitchnet.privilege.handler.ModelHandler#setPrivilegeDenyList(ch.eitchnet.privilege.model.Certificate, + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setPrivilegeDenyList(ch.eitchnet.privilege.model.Certificate, * java.lang.String, java.util.Set) */ @Override @@ -457,7 +658,7 @@ public class DefaultModelHandler implements ModelHandler { } /** - * @see ch.eitchnet.privilege.handler.ModelHandler#setPrivilegePolicy(ch.eitchnet.privilege.model.Certificate, + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setPrivilegePolicy(ch.eitchnet.privilege.model.Certificate, * java.lang.String, java.lang.String) */ @Override @@ -485,7 +686,7 @@ public class DefaultModelHandler implements ModelHandler { } /** - * @see ch.eitchnet.privilege.handler.ModelHandler#setUserLocaleState(ch.eitchnet.privilege.model.Certificate, + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setUserLocaleState(ch.eitchnet.privilege.model.Certificate, * java.lang.String, java.util.Locale) */ @Override @@ -513,7 +714,7 @@ public class DefaultModelHandler implements ModelHandler { } /** - * @see ch.eitchnet.privilege.handler.ModelHandler#setUserName(ch.eitchnet.privilege.model.Certificate, + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setUserName(ch.eitchnet.privilege.model.Certificate, * java.lang.String, java.lang.String, java.lang.String) */ @Override @@ -541,7 +742,7 @@ public class DefaultModelHandler implements ModelHandler { } /** - * @see ch.eitchnet.privilege.handler.ModelHandler#setUserPassword(ch.eitchnet.privilege.model.Certificate, + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setUserPassword(ch.eitchnet.privilege.model.Certificate, * java.lang.String, java.lang.String) */ @Override @@ -561,7 +762,7 @@ public class DefaultModelHandler implements ModelHandler { } // hash password - String passwordHash = PrivilegeContainer.getInstance().getEncryptionHandler().convertToHash(password); + String passwordHash = encryptionHandler.convertToHash(password); // create new user User newUser = new User(user.getUsername(), passwordHash, user.getFirstname(), user.getSurname(), user @@ -572,7 +773,7 @@ public class DefaultModelHandler implements ModelHandler { } /** - * @see ch.eitchnet.privilege.handler.ModelHandler#setUserState(ch.eitchnet.privilege.model.Certificate, + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setUserState(ch.eitchnet.privilege.model.Certificate, * java.lang.String, ch.eitchnet.privilege.model.UserState) */ @Override @@ -600,15 +801,17 @@ public class DefaultModelHandler implements ModelHandler { } /** - * @see ch.eitchnet.privilege.base.PrivilegeContainerObject#initialize(org.dom4j.Element) + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#initialize(org.dom4j.Element) */ @Override public void initialize(Element element) { - // nothing to initialize + + lastSessionId = 0l; + sessionMap = new HashMap(); } /** - * @see ch.eitchnet.privilege.handler.ModelHandler#getPrivilege(java.lang.String) + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#getPrivilege(java.lang.String) */ @Override public Privilege getPrivilege(String privilegeName) { @@ -616,7 +819,7 @@ public class DefaultModelHandler implements ModelHandler { } /** - * @see ch.eitchnet.privilege.handler.ModelHandler#getRole(java.lang.String) + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#getRole(java.lang.String) */ @Override public Role getRole(String roleName) { @@ -624,10 +827,40 @@ public class DefaultModelHandler implements ModelHandler { } /** - * @see ch.eitchnet.privilege.handler.ModelHandler#getUser(java.lang.String) + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#getUser(java.lang.String) */ @Override public User getUser(String username) { return persistenceHandler.getUser(username); } + + /** + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#getPolicy(java.lang.String) + */ + @Override + public PrivilegePolicy getPolicy(String policyName) { + return persistenceHandler.getPolicy(policyName); + } + + /** + * @return a new session id + */ + private synchronized String nextSessionId() { + return Long.toString(++lastSessionId % Long.MAX_VALUE); + } + + /** + * An internal class used to keep a record of sessions with the certificate + * + * @author rvonburg + */ + private class CertificateSessionPair { + private Session session; + private Certificate certificate; + + public CertificateSessionPair(Session session, Certificate certificate) { + this.session = session; + this.certificate = certificate; + } + } } diff --git a/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java b/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java deleted file mode 100644 index 4e38fc69a..000000000 --- a/src/ch/eitchnet/privilege/handler/DefaultSessionHandler.java +++ /dev/null @@ -1,222 +0,0 @@ -/* - * Copyright (c) 2010 - * - * Robert von Burg - * eitch@eitchnet.ch - * - * All rights reserved. - * - */ - -package ch.eitchnet.privilege.handler; - -import java.util.HashMap; -import java.util.Map; - -import org.apache.log4j.Logger; -import org.dom4j.Element; - -import ch.eitchnet.privilege.base.PrivilegeContainer; -import ch.eitchnet.privilege.i18n.AccessDeniedException; -import ch.eitchnet.privilege.i18n.PrivilegeException; -import ch.eitchnet.privilege.model.Certificate; -import ch.eitchnet.privilege.model.Restrictable; -import ch.eitchnet.privilege.model.UserState; -import ch.eitchnet.privilege.model.internal.Role; -import ch.eitchnet.privilege.model.internal.Session; -import ch.eitchnet.privilege.model.internal.User; - -/** - * @author rvonburg - * - */ -public class DefaultSessionHandler implements SessionHandler { - - private static final Logger logger = Logger.getLogger(DefaultSessionHandler.class); - - private static long lastSessionId; - - private Map sessionMap; - - /** - * TODO What is better, validate from {@link Restrictable} to {@link User} or the opposite direction? - * - * @see ch.eitchnet.privilege.handler.SessionHandler#actionAllowed(ch.eitchnet.privilege.model.Certificate, - * ch.eitchnet.privilege.model.Restrictable) - * - * @throws AccessDeniedException - * if the {@link Certificate} is not for a currently logged in {@link User} or if the user may not - * perform the action defined by the {@link Restrictable} implementation - */ - @Override - public boolean actionAllowed(Certificate certificate, Restrictable restrictable) { - - // first validate certificate - if (!isCertificateValid(certificate)) { - logger.info("Certificate is not valid, so action is not allowed: " + certificate + " for restrictable: " - + restrictable); - return false; - } - - // restrictable must not be null - if (restrictable == null) - throw new PrivilegeException("Restrictable may not be null!"); - - PrivilegeContainer privilegeContainer = PrivilegeContainer.getInstance(); - - // get user object - User user = PrivilegeContainer.getInstance().getModelHandler().getUser(certificate.getUsername()); - if (user == null) { - throw new PrivilegeException( - "Oh boy, how did this happen: No User in user map although the certificate is valid!"); - } - - // default is to not allow the action - // TODO should default deny/allow policy be configurable? - boolean actionAllowed = false; - - // now iterate roles and validate on policy handler - PolicyHandler policyHandler = privilegeContainer.getPolicyHandler(); - for (String roleName : user.getRoles()) { - - Role role = PrivilegeContainer.getInstance().getModelHandler().getRole(roleName); - if (role == null) { - logger.error("No role is defined with name " + roleName + " which is configured for user " + user); - continue; - } - - actionAllowed = policyHandler.actionAllowed(role, restrictable); - - // if action is allowed, then break iteration as a privilege match has been made - if (actionAllowed) - break; - } - - return actionAllowed; - } - - /** - * @see ch.eitchnet.privilege.handler.SessionHandler#isCertificateValid(ch.eitchnet.privilege.model.Certificate) - */ - @Override - public boolean isCertificateValid(Certificate certificate) { - - // certificate must not be null - if (certificate == null) - throw new PrivilegeException("Certificate may not be null!"); - - // first see if a session exists for this certificate - CertificateSessionPair certificateSessionPair = sessionMap.get(certificate.getSessionId()); - if (certificateSessionPair == null) - throw new AccessDeniedException("There is no session information for " + certificate.toString()); - - // validate certificate has not been tampered with - Certificate sessionCertificate = certificateSessionPair.certificate; - if (!sessionCertificate.equals(certificate)) - throw new PrivilegeException("Received illegal certificate for session id " + certificate.getSessionId()); - - // TODO is validating authToken overkill since the two certificates have already been checked on equality? - // validate authToken from certificate using the sessions authPassword - String authToken = certificate.getAuthToken(certificateSessionPair.session.getAuthPassword()); - if (authToken == null || !authToken.equals(certificateSessionPair.session.getAuthToken())) - throw new PrivilegeException("Received illegal certificate data for session id " - + certificate.getSessionId()); - - // get user object - User user = PrivilegeContainer.getInstance().getModelHandler().getUser( - certificateSessionPair.session.getUsername()); - - // if user exists, then certificate is valid - if (user == null) { - throw new PrivilegeException( - "Oh boy, how did this happen: No User in user map although the certificate is valid!"); - } else { - return true; - } - } - - /** - * @see ch.eitchnet.privilege.handler.SessionHandler#authenticate(java.lang.String, java.lang.String) - * - * @throws AccessDeniedException - * if the user credentials are not valid - */ - @Override - public Certificate authenticate(String username, String password) { - - // both username and password must at least have 3 characters in length - if (username == null || username.length() < 3) - throw new PrivilegeException("The given username is shorter than 3 characters"); - else if (password == null || password.length() < 3) - throw new PrivilegeException("The given password is shorter than 3 characters"); - - EncryptionHandler encryptionHandler = PrivilegeContainer.getInstance().getEncryptionHandler(); - - // we only work with hashed passwords - String passwordHash = encryptionHandler.convertToHash(password); - - // get user object - User user = PrivilegeContainer.getInstance().getModelHandler().getUser(username); - // no user means no authentication - if (user == null) - throw new AccessDeniedException("There is no user defined with the credentials: " + username + " / ***..."); - - // validate password - if (!user.isPassword(passwordHash)) - throw new AccessDeniedException("Password is incorrect for " + username + " / ***..."); - - // validate if user is allowed to login - if (user.getState() != UserState.ENABLED) - throw new AccessDeniedException("User " + username + " is not ENABLED. State is: " + user.getState()); - - // validate user has at least one role - if (user.getRoles().isEmpty()) { - throw new PrivilegeException("User " + username + " does not have any roles defined!"); - } - - // get 2 auth tokens - String authToken = encryptionHandler.nextToken(); - String authPassword = encryptionHandler.nextToken(); - - // get next session id - String sessionId = nextSessionId(); - - // create certificate - Certificate certificate = new Certificate(sessionId, username, authToken, authPassword, user.getLocale()); - - // create and save a new session - Session session = new Session(sessionId, authToken, authPassword, user.getUsername(), System - .currentTimeMillis()); - sessionMap.put(sessionId, new CertificateSessionPair(session, certificate)); - - // log - logger.info("Authenticated: " + session); - - // return the certificate - return certificate; - } - - private synchronized String nextSessionId() { - return Long.toString(++lastSessionId % Long.MAX_VALUE); - } - - /** - * @see ch.eitchnet.privilege.base.PrivilegeContainerObject#initialize(org.dom4j.Element) - */ - public void initialize(Element element) { - - lastSessionId = 0l; - sessionMap = new HashMap(); - - } - - private class CertificateSessionPair { - private Session session; - private Certificate certificate; - - public CertificateSessionPair(Session session, Certificate certificate) { - this.session = session; - this.certificate = certificate; - } - } -} diff --git a/src/ch/eitchnet/privilege/handler/EncryptionHandler.java b/src/ch/eitchnet/privilege/handler/EncryptionHandler.java index c26be38a4..83df25437 100644 --- a/src/ch/eitchnet/privilege/handler/EncryptionHandler.java +++ b/src/ch/eitchnet/privilege/handler/EncryptionHandler.java @@ -10,15 +10,17 @@ package ch.eitchnet.privilege.handler; -import ch.eitchnet.privilege.base.PrivilegeContainerObject; +import org.dom4j.Element; /** * @author rvonburg * */ -public interface EncryptionHandler extends PrivilegeContainerObject{ +public interface EncryptionHandler { public String nextToken(); public String convertToHash(String string); + + public void initialize(Element element); } diff --git a/src/ch/eitchnet/privilege/handler/PersistenceHandler.java b/src/ch/eitchnet/privilege/handler/PersistenceHandler.java index 117130490..9f7e2d08b 100644 --- a/src/ch/eitchnet/privilege/handler/PersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/PersistenceHandler.java @@ -10,17 +10,19 @@ package ch.eitchnet.privilege.handler; -import ch.eitchnet.privilege.base.PrivilegeContainerObject; +import org.dom4j.Element; + import ch.eitchnet.privilege.model.Certificate; import ch.eitchnet.privilege.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.Role; import ch.eitchnet.privilege.model.internal.User; +import ch.eitchnet.privilege.policy.PrivilegePolicy; /** * @author rvonburg * */ -public interface PersistenceHandler extends PrivilegeContainerObject { +public interface PersistenceHandler { public User getUser(String username); @@ -40,5 +42,9 @@ public interface PersistenceHandler extends PrivilegeContainerObject { public Privilege removePrivilege(String privilegeName); + public PrivilegePolicy getPolicy(String policyName); + public boolean persist(Certificate certificate); + + public void initialize(Element element); } diff --git a/src/ch/eitchnet/privilege/handler/PolicyHandler.java b/src/ch/eitchnet/privilege/handler/PolicyHandler.java deleted file mode 100644 index 5c55ad7a6..000000000 --- a/src/ch/eitchnet/privilege/handler/PolicyHandler.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2010 - * - * Robert von Burg - * eitch@eitchnet.ch - * - * All rights reserved. - * - */ - -package ch.eitchnet.privilege.handler; - -import ch.eitchnet.privilege.base.PrivilegeContainerObject; -import ch.eitchnet.privilege.model.Restrictable; -import ch.eitchnet.privilege.model.internal.Role; - -/** - * @author rvonburg - * - */ -public interface PolicyHandler extends PrivilegeContainerObject { - - public boolean actionAllowed(Role role, Restrictable restrictable); -} diff --git a/src/ch/eitchnet/privilege/handler/ModelHandler.java b/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java similarity index 56% rename from src/ch/eitchnet/privilege/handler/ModelHandler.java rename to src/ch/eitchnet/privilege/handler/PrivilegeHandler.java index e8fa1256e..ea27acac8 100644 --- a/src/ch/eitchnet/privilege/handler/ModelHandler.java +++ b/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java @@ -13,23 +13,75 @@ package ch.eitchnet.privilege.handler; import java.util.Locale; import java.util.Set; -import ch.eitchnet.privilege.base.PrivilegeContainerObject; +import org.dom4j.Element; + +import ch.eitchnet.privilege.i18n.AccessDeniedException; +import ch.eitchnet.privilege.i18n.PrivilegeException; 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.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.Role; import ch.eitchnet.privilege.model.internal.User; +import ch.eitchnet.privilege.policy.PrivilegePolicy; /** * @author rvonburg * */ -public interface ModelHandler extends PrivilegeContainerObject { +public interface PrivilegeHandler { - public void setPersistenceHandler(PersistenceHandler persistenceHandler); + /** + * @param certificate + * @param restrictable + * + * @return + * + * @throws AccessDeniedException + * if the {@link Certificate} is not for a currently logged in {@link User} or if the user may not + * perform the action defined by the {@link Restrictable} implementation + * @throws PrivilegeException + * if there is anything wrong with this certificate + */ + public boolean actionAllowed(Certificate certificate, Restrictable restrictable); + + /** + * @param role + * @param restrictable + * @return + * + * @throws AccessDeniedException + * if the {@link Certificate} is not for a currently logged in {@link User} or if the user may not + * perform the action defined by the {@link Restrictable} implementation + * @throws PrivilegeException + * if there is anything wrong with this certificate + */ + public boolean actionAllowed(Role role, Restrictable restrictable); + + /** + * @param certificate + * @return + * + * @throws AccessDeniedException + * if the {@link Certificate} is not for a currently logged in {@link User} + * @throws PrivilegeException + * if there is anything wrong with this certificate + */ + public boolean isCertificateValid(Certificate certificate); + + /** + * @param username + * @param password + * + * @return + * + * @throws AccessDeniedException + * if the user credentials are not valid + */ + public Certificate authenticate(String username, String password); public User getUser(String username); @@ -73,5 +125,9 @@ public interface ModelHandler extends PrivilegeContainerObject { public void setPrivilegeAllowList(Certificate certificate, String privilegeName, Set allowList); + public PrivilegePolicy getPolicy(String policyName); + public boolean persist(Certificate certificate); + + public void initialize(Element element); } diff --git a/src/ch/eitchnet/privilege/handler/SessionHandler.java b/src/ch/eitchnet/privilege/handler/SessionHandler.java deleted file mode 100644 index 10e0b4fa4..000000000 --- a/src/ch/eitchnet/privilege/handler/SessionHandler.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2010 - * - * Robert von Burg - * eitch@eitchnet.ch - * - * All rights reserved. - * - */ - -package ch.eitchnet.privilege.handler; - -import ch.eitchnet.privilege.base.PrivilegeContainerObject; -import ch.eitchnet.privilege.i18n.AccessDeniedException; -import ch.eitchnet.privilege.i18n.PrivilegeException; -import ch.eitchnet.privilege.model.Certificate; -import ch.eitchnet.privilege.model.Restrictable; -import ch.eitchnet.privilege.model.internal.User; - -/** - * @author rvonburg - * - */ -public interface SessionHandler extends PrivilegeContainerObject { - - /** - * @param certificate - * @param restrictable - * - * @return - * - * @throws AccessDeniedException - * if the {@link Certificate} is not for a currently logged in {@link User} or if the user may not - * perform the action defined by the {@link Restrictable} implementation - * @throws PrivilegeException - * if there is anything wrong with this certificate - */ - public boolean actionAllowed(Certificate certificate, Restrictable restrictable); - - /** - * @param certificate - * @return - * - * @throws AccessDeniedException - * if the {@link Certificate} is not for a currently logged in {@link User} - * @throws PrivilegeException - * if there is anything wrong with this certificate - */ - public boolean isCertificateValid(Certificate certificate); - - /** - * @param username - * @param password - * - * @return - * - * @throws AccessDeniedException - * if the user credentials are not valid - */ - public Certificate authenticate(String username, String password); -} diff --git a/src/ch/eitchnet/privilege/helper/PrivilegeHelper.java b/src/ch/eitchnet/privilege/helper/PrivilegeHelper.java index 32be4f883..06029806e 100644 --- a/src/ch/eitchnet/privilege/helper/PrivilegeHelper.java +++ b/src/ch/eitchnet/privilege/helper/PrivilegeHelper.java @@ -24,7 +24,7 @@ public class PrivilegeHelper { public static boolean isUserPrivilegeAdmin(Certificate certificate) { // validate certificate - if (!PrivilegeContainer.getInstance().getSessionHandler().isCertificateValid(certificate)) { + if (!PrivilegeContainer.getInstance().getModelHandler().isCertificateValid(certificate)) { throw new PrivilegeException("Certificate " + certificate + " is not valid!"); } diff --git a/test/ch/eitchnet/privilege/test/PrivilegeTest.java b/test/ch/eitchnet/privilege/test/PrivilegeTest.java index bfa8a7beb..2f52de634 100644 --- a/test/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/test/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -22,7 +22,7 @@ import org.junit.BeforeClass; import org.junit.Test; import ch.eitchnet.privilege.base.PrivilegeContainer; -import ch.eitchnet.privilege.handler.ModelHandler; +import ch.eitchnet.privilege.handler.PrivilegeHandler; import ch.eitchnet.privilege.i18n.AccessDeniedException; import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.Certificate; @@ -59,7 +59,7 @@ public class PrivilegeTest { @Test public void testAuthenticationOk() throws Exception { - Certificate certificate = PrivilegeContainer.getInstance().getSessionHandler().authenticate("eitch", + Certificate certificate = PrivilegeContainer.getInstance().getModelHandler().authenticate("eitch", "1234567890"); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); } @@ -67,24 +67,24 @@ public class PrivilegeTest { @Test(expected = AccessDeniedException.class) public void testFailAuthenticationNOk() throws Exception { - Certificate certificate = PrivilegeContainer.getInstance().getSessionHandler().authenticate("eitch", "123"); + Certificate certificate = PrivilegeContainer.getInstance().getModelHandler().authenticate("eitch", "123"); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); } @Test(expected = PrivilegeException.class) public void testFailAuthenticationPWNull() throws Exception { - Certificate certificate = PrivilegeContainer.getInstance().getSessionHandler().authenticate("eitch", null); + Certificate certificate = PrivilegeContainer.getInstance().getModelHandler().authenticate("eitch", null); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); } @Test public void testAddUserBobWithPW() throws Exception { - Certificate certificate = PrivilegeContainer.getInstance().getSessionHandler().authenticate("eitch", + Certificate certificate = PrivilegeContainer.getInstance().getModelHandler().authenticate("eitch", "1234567890"); - ModelHandler modelHandler = PrivilegeContainer.getInstance().getModelHandler(); + PrivilegeHandler modelHandler = PrivilegeContainer.getInstance().getModelHandler(); // let's add a new user bob UserRep userRep = new UserRep("bob", "Bob", "Newman", UserState.NEW, new HashSet(), null); @@ -104,16 +104,16 @@ public class PrivilegeTest { @Test(expected = AccessDeniedException.class) public void testFailAuthAsBob() throws Exception { - PrivilegeContainer.getInstance().getSessionHandler().authenticate("bob", "12345678901"); + PrivilegeContainer.getInstance().getModelHandler().authenticate("bob", "12345678901"); } @Test public void testEnableUserBob() throws Exception { - Certificate certificate = PrivilegeContainer.getInstance().getSessionHandler().authenticate("eitch", + Certificate certificate = PrivilegeContainer.getInstance().getModelHandler().authenticate("eitch", "1234567890"); - ModelHandler modelHandler = PrivilegeContainer.getInstance().getModelHandler(); + PrivilegeHandler modelHandler = PrivilegeContainer.getInstance().getModelHandler(); modelHandler.setUserState(certificate, "bob", UserState.ENABLED); } @@ -125,7 +125,7 @@ public class PrivilegeTest { @Test(expected = PrivilegeException.class) public void testFailAuthUserBob() throws Exception { - Certificate certificate = PrivilegeContainer.getInstance().getSessionHandler().authenticate("bob", + Certificate certificate = PrivilegeContainer.getInstance().getModelHandler().authenticate("bob", "12345678901"); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); } @@ -133,17 +133,17 @@ public class PrivilegeTest { @Test public void testAddUserRoleToBob() throws Exception { - Certificate certificate = PrivilegeContainer.getInstance().getSessionHandler().authenticate("eitch", + Certificate certificate = PrivilegeContainer.getInstance().getModelHandler().authenticate("eitch", "1234567890"); - ModelHandler modelHandler = PrivilegeContainer.getInstance().getModelHandler(); + PrivilegeHandler modelHandler = PrivilegeContainer.getInstance().getModelHandler(); modelHandler.addRoleToUser(certificate, "bob", "user"); } @Test public void testAuthAsBob() throws Exception { - PrivilegeContainer.getInstance().getSessionHandler().authenticate("bob", "12345678901"); + PrivilegeContainer.getInstance().getModelHandler().authenticate("bob", "12345678901"); } /** @@ -154,7 +154,7 @@ public class PrivilegeTest { @Test(expected = AccessDeniedException.class) public void testFailAddUserTedAsBob() throws Exception { - Certificate certificate = PrivilegeContainer.getInstance().getSessionHandler().authenticate("bob", + Certificate certificate = PrivilegeContainer.getInstance().getModelHandler().authenticate("bob", "12345678901"); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); @@ -167,17 +167,17 @@ public class PrivilegeTest { @Test public void testAddAdminRoleToBob() throws Exception { - Certificate certificate = PrivilegeContainer.getInstance().getSessionHandler().authenticate("eitch", + Certificate certificate = PrivilegeContainer.getInstance().getModelHandler().authenticate("eitch", "1234567890"); - ModelHandler modelHandler = PrivilegeContainer.getInstance().getModelHandler(); + PrivilegeHandler modelHandler = PrivilegeContainer.getInstance().getModelHandler(); modelHandler.addRoleToUser(certificate, "bob", PrivilegeContainer.PRIVILEGE_ADMIN_ROLE); } @Test public void testAddUserTedAsBob() throws Exception { - Certificate certificate = PrivilegeContainer.getInstance().getSessionHandler().authenticate("bob", + Certificate certificate = PrivilegeContainer.getInstance().getModelHandler().authenticate("bob", "12345678901"); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); @@ -190,13 +190,13 @@ public class PrivilegeTest { @Test public void testPerformRestrictable() throws Exception { - Certificate certificate = PrivilegeContainer.getInstance().getSessionHandler().authenticate("eitch", + Certificate certificate = PrivilegeContainer.getInstance().getModelHandler().authenticate("eitch", "1234567890"); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); // see if eitch can perform restrictable Restrictable restrictable = new TestRestrictable(); - boolean actionAllowed = PrivilegeContainer.getInstance().getSessionHandler().actionAllowed(certificate, + boolean actionAllowed = PrivilegeContainer.getInstance().getModelHandler().actionAllowed(certificate, restrictable); org.junit.Assert.assertTrue("eitch may not perform restrictable!", actionAllowed); } From 7c0c86fe66487ba6473bd9fb5f1b52d68366e9ac Mon Sep 17 00:00:00 2001 From: eitch Date: Sat, 18 Sep 2010 20:00:20 +0000 Subject: [PATCH 031/173] --- .../privilege/base/PrivilegeContainer.java | 135 -- .../handler/DefaultEncryptionHandler.java | 13 +- .../handler/DefaultPrivilegeHandler.java | 1224 ++++++++--------- .../privilege/handler/EncryptionHandler.java | 14 +- .../privilege/handler/PersistenceHandler.java | 50 +- .../privilege/handler/PrivilegeHandler.java | 269 +++- ...andler.java => XmlPersistenceHandler.java} | 52 +- .../helper/BootstrapConfigurationHelper.java | 43 +- .../privilege/helper/ConfigurationHelper.java | 87 +- .../privilege/helper/PrivilegeHelper.java | 47 - .../{base => helper}/XmlConstants.java | 7 +- .../privilege/model/PrivilegeRep.java | 5 +- src/ch/eitchnet/privilege/model/UserRep.java | 18 +- .../eitchnet/privilege/model/UserState.java | 2 +- .../privilege/model/internal/Privilege.java | 3 +- .../privilege/model/internal/Role.java | 3 +- .../privilege/model/internal/User.java | 23 +- 17 files changed, 1041 insertions(+), 954 deletions(-) delete mode 100644 src/ch/eitchnet/privilege/base/PrivilegeContainer.java rename src/ch/eitchnet/privilege/handler/{DefaultPersistenceHandler.java => XmlPersistenceHandler.java} (92%) delete mode 100644 src/ch/eitchnet/privilege/helper/PrivilegeHelper.java rename src/ch/eitchnet/privilege/{base => helper}/XmlConstants.java (90%) diff --git a/src/ch/eitchnet/privilege/base/PrivilegeContainer.java b/src/ch/eitchnet/privilege/base/PrivilegeContainer.java deleted file mode 100644 index 27ae0e4b0..000000000 --- a/src/ch/eitchnet/privilege/base/PrivilegeContainer.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (c) 2010 - * - * Robert von Burg - * eitch@eitchnet.ch - * - * All rights reserved. - * - */ - -package ch.eitchnet.privilege.base; - -import java.io.File; - -import org.apache.log4j.Logger; -import org.dom4j.Element; - -import ch.eitchnet.privilege.handler.EncryptionHandler; -import ch.eitchnet.privilege.handler.PersistenceHandler; -import ch.eitchnet.privilege.handler.PrivilegeHandler; -import ch.eitchnet.privilege.helper.ClassHelper; -import ch.eitchnet.privilege.helper.XmlHelper; -import ch.eitchnet.privilege.i18n.PrivilegeException; - -/** - * @author rvonburg - */ -public class PrivilegeContainer { - - /** - * This is the role users must have, if they can modify the {@link PrivilegeContainer} and its objects - */ - public static final String PRIVILEGE_ADMIN_ROLE = "PrivilegeAdmin"; - public static final String PRIVILEGE_CONTAINER_FILE = "PrivilegeContainer.xml"; - - private static final Logger logger = Logger.getLogger(PrivilegeContainer.class); - - private static final PrivilegeContainer instance; - - static { - instance = new PrivilegeContainer(); - } - - private EncryptionHandler encryptionHandler; - private PrivilegeHandler modelHandler; - - private String basePath; - - public static PrivilegeContainer getInstance() { - return instance; - } - - /** - * private constructor to force singleton - */ - private PrivilegeContainer() { - // private constructor - } - - /** - * @return the encryptionHandler - */ - public EncryptionHandler getEncryptionHandler() { - return encryptionHandler; - } - - /** - * @return the modelHandler - */ - public PrivilegeHandler getModelHandler() { - return modelHandler; - } - - /** - * @return the basePath - */ - public String getBasePath() { - return basePath; - } - - public void initialize(File privilegeContainerXml) { - - // make sure file exists - if (!privilegeContainerXml.exists()) { - throw new PrivilegeException("Privilige file does not exist at path " - + privilegeContainerXml.getAbsolutePath()); - } - - // set base path from privilege container xml - basePath = privilegeContainerXml.getParentFile().getAbsolutePath(); - - // parse container xml file to XML document - Element containerRootElement = XmlHelper.parseDocument(privilegeContainerXml).getRootElement(); - - // instantiate persistence handler - Element persistenceHandlerElement = containerRootElement.element(XmlConstants.XML_HANDLER_PERSISTENCE); - String persistenceHandlerClassName = persistenceHandlerElement.attributeValue(XmlConstants.XML_ATTR_CLASS); - PersistenceHandler persistenceHandler = ClassHelper.instantiateClass(persistenceHandlerClassName); - - // instantiate encryption handler - Element encryptionHandlerElement = containerRootElement.element(XmlConstants.XML_HANDLER_ENCRYPTION); - String encryptionHandlerClassName = encryptionHandlerElement.attributeValue(XmlConstants.XML_ATTR_CLASS); - EncryptionHandler encryptionHandler = ClassHelper.instantiateClass(encryptionHandlerClassName); - - // instantiate privilege handler - Element modelHandlerElement = containerRootElement.element(XmlConstants.XML_HANDLER_MODEL); - String modelHandlerClassName = modelHandlerElement.attributeValue(XmlConstants.XML_ATTR_CLASS); - PrivilegeHandler modelHandler = ClassHelper.instantiateClass(modelHandlerClassName); - - try { - persistenceHandler.initialize(persistenceHandlerElement); - } catch (Exception e) { - logger.error(e, e); - throw new PrivilegeException("PersistenceHandler " + persistenceHandlerElement - + " could not be initialized"); - } - try { - encryptionHandler.initialize(encryptionHandlerElement); - } catch (Exception e) { - logger.error(e, e); - throw new PrivilegeException("EncryptionHandler " + encryptionHandlerClassName - + " could not be initialized"); - } - try { - modelHandler.initialize(modelHandlerElement); - } catch (Exception e) { - logger.error(e, e); - throw new PrivilegeException("ModificationHandler " + modelHandlerClassName + " could not be initialized"); - } - - // keep references to the handlers - this.modelHandler = modelHandler; - this.encryptionHandler = encryptionHandler; - } -} diff --git a/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java b/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java index 8cb679885..5fd3c4664 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java @@ -16,11 +16,9 @@ import java.security.SecureRandom; import java.util.Map; import org.apache.log4j.Logger; -import org.dom4j.Element; -import ch.eitchnet.privilege.base.XmlConstants; -import ch.eitchnet.privilege.helper.ConfigurationHelper; import ch.eitchnet.privilege.helper.EncryptionHelper; +import ch.eitchnet.privilege.helper.XmlConstants; import ch.eitchnet.privilege.i18n.PrivilegeException; /** @@ -62,16 +60,13 @@ public class DefaultEncryptionHandler implements EncryptionHandler { } /** - * @see ch.eitchnet.privilege.base.PrivilegeContainerObject#initialize(org.dom4j.Element) + * @see ch.eitchnet.privilege.handler.EncryptionHandler#initialize(java.util.Map) */ - public void initialize(Element element) { + @Override + public void initialize(Map parameterMap) { secureRandom = new SecureRandom(); - // get parameters - Element parameterElement = element.element(XmlConstants.XML_PARAMETERS); - Map parameterMap = ConfigurationHelper.convertToParameterMap(parameterElement); - // get hash algorithm parameters hashAlgorithm = parameterMap.get(XmlConstants.XML_PARAM_HASH_ALGORITHM); if (hashAlgorithm == null || hashAlgorithm.isEmpty()) { diff --git a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 2fca99624..1119e8aff 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -17,10 +17,7 @@ import java.util.Map; import java.util.Set; import org.apache.log4j.Logger; -import org.dom4j.Element; -import ch.eitchnet.privilege.base.PrivilegeContainer; -import ch.eitchnet.privilege.helper.PrivilegeHelper; import ch.eitchnet.privilege.i18n.AccessDeniedException; import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.Certificate; @@ -49,7 +46,565 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { private PersistenceHandler persistenceHandler; private EncryptionHandler encryptionHandler; - private PrivilegeHandler modelHandler; + + /** + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#getPrivilege(java.lang.String) + */ + @Override + public PrivilegeRep getPrivilege(String privilegeName) { + return persistenceHandler.getPrivilege(privilegeName).asPrivilegeRep(); + } + + /** + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#getRole(java.lang.String) + */ + @Override + public RoleRep getRole(String roleName) { + return persistenceHandler.getRole(roleName).asRoleRep(); + } + + /** + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#getUser(java.lang.String) + */ + @Override + public UserRep getUser(String username) { + return persistenceHandler.getUser(username).asUserRep(); + } + + /** + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#getPolicy(java.lang.String) + */ + @Override + public PrivilegePolicy getPolicy(String policyName) { + return persistenceHandler.getPolicy(policyName); + } + + /** + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#addOrReplacePrivilege(ch.eitchnet.privilege.model.Certificate, + * ch.eitchnet.privilege.model.PrivilegeRep) + */ + @Override + public void addOrReplacePrivilege(Certificate certificate, PrivilegeRep privilegeRep) { + + // validate who is doing this + validateIsPrivilegeAdmin(certificate); + + // create a new privilege + Privilege privilege = new Privilege(privilegeRep.getName(), privilegeRep.getPolicy(), privilegeRep + .isAllAllowed(), privilegeRep.getDenyList(), privilegeRep.getAllowList()); + + // delegate to persistence handler + persistenceHandler.addOrReplacePrivilege(privilege); + } + + /** + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#addOrReplaceRole(ch.eitchnet.privilege.model.Certificate, + * ch.eitchnet.privilege.model.RoleRep) + */ + @Override + public void addOrReplaceRole(Certificate certificate, RoleRep roleRep) { + + // validate who is doing this + validateIsPrivilegeAdmin(certificate); + + // create new role + Role role = new Role(roleRep.getName(), roleRep.getPrivileges()); + + // delegate to persistence handler + persistenceHandler.addOrReplaceRole(role); + } + + /** + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#addOrReplaceUser(ch.eitchnet.privilege.model.Certificate, + * ch.eitchnet.privilege.model.UserRep, java.lang.String) + */ + @Override + public void addOrReplaceUser(Certificate certificate, UserRep userRep, String password) { + + // validate who is doing this + validateIsPrivilegeAdmin(certificate); + + // validate password meets basic requirements + validatePassword(password); + + // hash password + String passwordHash; + if (password == null) + passwordHash = null; + else + passwordHash = encryptionHandler.convertToHash(password); + + // create new user + User user = new User(userRep.getUsername(), passwordHash, userRep.getFirstname(), userRep.getSurname(), userRep + .getUserState(), userRep.getRoles(), userRep.getLocale()); + + // delegate to persistence handler + persistenceHandler.addOrReplaceUser(user); + } + + /** + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#addPrivilegeToRole(ch.eitchnet.privilege.model.Certificate, + * java.lang.String, java.lang.String) + */ + @Override + public void addPrivilegeToRole(Certificate certificate, String roleName, String privilegeName) { + + // validate who is doing this + validateIsPrivilegeAdmin(certificate); + + // get role + Role role = persistenceHandler.getRole(roleName); + if (role == null) { + throw new PrivilegeException("Role " + roleName + " does not exist!"); + } + + // ignore if role already has this privilege + Set currentPrivileges = role.getPrivileges(); + if (currentPrivileges.contains(roleName)) { + logger.error("Role " + roleName + " already has privilege " + privilegeName); + return; + } + + // validate that privilege exists + if (getPrivilege(privilegeName) == null) { + throw new PrivilegeException("Privilege " + privilegeName + " does not exist and can not be added to role " + + roleName); + } + + // create new role with the additional privilege + Set newPrivileges = new HashSet(currentPrivileges); + newPrivileges.add(roleName); + + Role newRole = new Role(role.getName(), newPrivileges); + + // delegate role replacement to persistence handler + persistenceHandler.addOrReplaceRole(newRole); + } + + /** + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#addRoleToUser(ch.eitchnet.privilege.model.Certificate, + * java.lang.String, java.lang.String) + */ + @Override + public void addRoleToUser(Certificate certificate, String username, String roleName) { + + // validate who is doing this + validateIsPrivilegeAdmin(certificate); + + // get user + User user = persistenceHandler.getUser(username); + if (user == null) { + throw new PrivilegeException("User " + username + " does not exist!"); + } + + // ignore if user already has role + Set currentRoles = user.getRoles(); + if (currentRoles.contains(roleName)) { + logger.error("User " + username + " already has role " + roleName); + return; + } + + // validate that role exists + if (getRole(roleName) == null) { + throw new PrivilegeException("Role " + roleName + " doest not exist!"); + } + + // create new user + Set newRoles = new HashSet(currentRoles); + newRoles.add(roleName); + + User newUser = new User(user.getUsername(), user.getPassword(), user.getFirstname(), user.getSurname(), user + .getState(), newRoles, user.getLocale()); + + // delegate user replacement to persistence handler + persistenceHandler.addOrReplaceUser(newUser); + } + + /** + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#removePrivilege(ch.eitchnet.privilege.model.Certificate, + * java.lang.String) + */ + @Override + public PrivilegeRep removePrivilege(Certificate certificate, String privilegeName) { + + // validate who is doing this + validateIsPrivilegeAdmin(certificate); + + // delegate privilege removal to persistence handler + Privilege removedPrivilege = persistenceHandler.removePrivilege(privilegeName); + + // return privilege rep if it was removed + if (removedPrivilege != null) + return removedPrivilege.asPrivilegeRep(); + else + return null; + } + + /** + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#removePrivilegeFromRole(ch.eitchnet.privilege.model.Certificate, + * java.lang.String, java.lang.String) + */ + @Override + public void removePrivilegeFromRole(Certificate certificate, String roleName, String privilegeName) { + + // validate who is doing this + validateIsPrivilegeAdmin(certificate); + + // get role + Role role = persistenceHandler.getRole(roleName); + if (role == null) { + throw new PrivilegeException("Role " + roleName + " does not exist!"); + } + + // ignore if role does not have privilege + Set currentPrivileges = role.getPrivileges(); + if (!currentPrivileges.contains(privilegeName)) { + logger.error("Role " + roleName + " doest not have privilege " + privilegeName); + return; + } + + // create new role + Set newPrivileges = new HashSet(currentPrivileges); + newPrivileges.remove(privilegeName); + Role newRole = new Role(role.getName(), newPrivileges); + + // delegate user replacement to persistence handler + persistenceHandler.addOrReplaceRole(newRole); + } + + /** + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#removeRole(ch.eitchnet.privilege.model.Certificate, + * java.lang.String) + */ + @Override + public RoleRep removeRole(Certificate certificate, String roleName) { + + // validate who is doing this + validateIsPrivilegeAdmin(certificate); + + // delegate role removal to persistence handler + Role removedRole = persistenceHandler.removeRole(roleName); + + // return role rep if it was removed + if (removedRole != null) + return removedRole.asRoleRep(); + else + return null; + } + + /** + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#removeRoleFromUser(ch.eitchnet.privilege.model.Certificate, + * java.lang.String, java.lang.String) + */ + @Override + public void removeRoleFromUser(Certificate certificate, String username, String roleName) { + + // validate who is doing this + validateIsPrivilegeAdmin(certificate); + + // get User + User user = persistenceHandler.getUser(username); + if (user == null) { + throw new PrivilegeException("User " + username + " does not exist!"); + } + + // ignore if user does not have role + Set currentRoles = user.getRoles(); + if (!currentRoles.contains(roleName)) { + logger.error("User " + user + " does not have role " + roleName); + return; + } + + // create new user + Set newRoles = new HashSet(currentRoles); + newRoles.remove(roleName); + User newUser = new User(user.getUsername(), user.getPassword(), user.getFirstname(), user.getSurname(), user + .getState(), newRoles, user.getLocale()); + + // delegate user replacement to persistence handler + persistenceHandler.addOrReplaceUser(newUser); + } + + /** + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#removeUser(ch.eitchnet.privilege.model.Certificate, + * java.lang.String) + */ + @Override + public UserRep removeUser(Certificate certificate, String username) { + + // validate who is doing this + validateIsPrivilegeAdmin(certificate); + + // delegate user removal to persistence handler + User removedUser = persistenceHandler.removeUser(username); + + // return user rep if it was removed + if (removedUser != null) + return removedUser.asUserRep(); + else + return null; + } + + /** + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setPrivilegeAllAllowed(ch.eitchnet.privilege.model.Certificate, + * java.lang.String, boolean) + */ + @Override + public void setPrivilegeAllAllowed(Certificate certificate, String privilegeName, boolean allAllowed) { + + // validate who is doing this + validateIsPrivilegeAdmin(certificate); + + // get Privilege + Privilege privilege = persistenceHandler.getPrivilege(privilegeName); + if (privilege == null) { + throw new PrivilegeException("Privilege " + privilegeName + " does not exist!"); + } + + // ignore if privilege is already set to argument + if (privilege.isAllAllowed() == allAllowed) { + logger.error("Privilege " + privilegeName + " is already set to " + + (allAllowed ? "all allowed" : "not all allowed")); + return; + } + + // create new privilege + Privilege newPrivilege = new Privilege(privilege.getName(), privilege.getPolicy(), allAllowed, privilege + .getDenyList(), privilege.getAllowList()); + + // delegate privilege replacement to persistence handler + persistenceHandler.addOrReplacePrivilege(newPrivilege); + } + + /** + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setPrivilegeAllowList(ch.eitchnet.privilege.model.Certificate, + * java.lang.String, java.util.Set) + */ + @Override + public void setPrivilegeAllowList(Certificate certificate, String privilegeName, Set allowList) { + + // validate who is doing this + validateIsPrivilegeAdmin(certificate); + + // get Privilege + Privilege privilege = persistenceHandler.getPrivilege(privilegeName); + if (privilege == null) { + throw new PrivilegeException("Privilege " + privilegeName + " does not exist!"); + } + + // create new privilege + Privilege newPrivilege = new Privilege(privilege.getName(), privilege.getPolicy(), privilege.isAllAllowed(), + privilege.getDenyList(), allowList); + + // delegate privilege replacement to persistence handler + persistenceHandler.addOrReplacePrivilege(newPrivilege); + } + + /** + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setPrivilegeDenyList(ch.eitchnet.privilege.model.Certificate, + * java.lang.String, java.util.Set) + */ + @Override + public void setPrivilegeDenyList(Certificate certificate, String privilegeName, Set denyList) { + + // validate who is doing this + validateIsPrivilegeAdmin(certificate); + + // get Privilege + Privilege privilege = persistenceHandler.getPrivilege(privilegeName); + if (privilege == null) { + throw new PrivilegeException("Privilege " + privilegeName + " does not exist!"); + } + + // create new privilege + Privilege newPrivilege = new Privilege(privilege.getName(), privilege.getPolicy(), privilege.isAllAllowed(), + denyList, privilege.getAllowList()); + + // delegate privilege replacement to persistence handler + persistenceHandler.addOrReplacePrivilege(newPrivilege); + } + + /** + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setPrivilegePolicy(ch.eitchnet.privilege.model.Certificate, + * java.lang.String, java.lang.String) + */ + @Override + public void setPrivilegePolicy(Certificate certificate, String privilegeName, String policyName) { + + // validate who is doing this + validateIsPrivilegeAdmin(certificate); + + // get Privilege + Privilege privilege = persistenceHandler.getPrivilege(privilegeName); + if (privilege == null) { + throw new PrivilegeException("Privilege " + privilegeName + " does not exist!"); + } + + // create new privilege + Privilege newPrivilege = new Privilege(privilege.getName(), policyName, privilege.isAllAllowed(), privilege + .getDenyList(), privilege.getAllowList()); + + // delegate privilege replacement to persistence handler + persistenceHandler.addOrReplacePrivilege(newPrivilege); + } + + /** + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setUserLocaleState(ch.eitchnet.privilege.model.Certificate, + * java.lang.String, java.util.Locale) + */ + @Override + public void setUserLocaleState(Certificate certificate, String username, Locale locale) { + + // validate who is doing this + validateIsPrivilegeAdmin(certificate); + + // get User + User user = persistenceHandler.getUser(username); + if (user == null) { + throw new PrivilegeException("User " + username + " does not exist!"); + } + + // create new user + User newUser = new User(user.getUsername(), user.getPassword(), user.getFirstname(), user.getSurname(), user + .getState(), user.getRoles(), locale); + + // delegate user replacement to persistence handler + persistenceHandler.addOrReplaceUser(newUser); + } + + /** + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setUserName(ch.eitchnet.privilege.model.Certificate, + * java.lang.String, java.lang.String, java.lang.String) + */ + @Override + public void setUserName(Certificate certificate, String username, String firstname, String surname) { + + // validate who is doing this + validateIsPrivilegeAdmin(certificate); + + // get User + User user = persistenceHandler.getUser(username); + if (user == null) { + throw new PrivilegeException("User " + username + " does not exist!"); + } + + // create new user + User newUser = new User(user.getUsername(), user.getPassword(), firstname, surname, user.getState(), user + .getRoles(), user.getLocale()); + + // delegate user replacement to persistence handler + persistenceHandler.addOrReplaceUser(newUser); + } + + /** + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setUserPassword(ch.eitchnet.privilege.model.Certificate, + * java.lang.String, java.lang.String) + */ + @Override + public void setUserPassword(Certificate certificate, String username, String password) { + + // validate who is doing this + validateIsPrivilegeAdmin(certificate); + + // get User + User user = persistenceHandler.getUser(username); + if (user == null) { + throw new PrivilegeException("User " + username + " does not exist!"); + } + + // hash password + String passwordHash = encryptionHandler.convertToHash(password); + + // create new user + User newUser = new User(user.getUsername(), passwordHash, user.getFirstname(), user.getSurname(), user + .getState(), user.getRoles(), user.getLocale()); + + // delegate user replacement to persistence handler + persistenceHandler.addOrReplaceUser(newUser); + } + + /** + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setUserState(ch.eitchnet.privilege.model.Certificate, + * java.lang.String, ch.eitchnet.privilege.model.UserState) + */ + @Override + public void setUserState(Certificate certificate, String username, UserState state) { + + // validate who is doing this + validateIsPrivilegeAdmin(certificate); + + // get User + User user = persistenceHandler.getUser(username); + if (user == null) { + throw new PrivilegeException("User " + username + " does not exist!"); + } + + // create new user + User newUser = new User(user.getUsername(), user.getPassword(), user.getFirstname(), user.getSurname(), state, + user.getRoles(), user.getLocale()); + + // delegate user replacement to persistence handler + persistenceHandler.addOrReplaceUser(newUser); + } + + /** + * @see ch.eitchnet.privilege.handler.SessionHandler#authenticate(java.lang.String, java.lang.String) + * + * @throws AccessDeniedException + * if the user credentials are not valid + */ + @Override + public Certificate authenticate(String username, String password) { + + // both username and password must at least have 3 characters in length + if (username == null || username.length() < 3) + throw new PrivilegeException("The given username is shorter than 3 characters"); + else if (password == null || password.length() < 3) + throw new PrivilegeException("The given password is shorter than 3 characters"); + + // we only work with hashed passwords + String passwordHash = encryptionHandler.convertToHash(password); + + // get user object + User user = persistenceHandler.getUser(username); + // no user means no authentication + if (user == null) + throw new AccessDeniedException("There is no user defined with the credentials: " + username + " / ***..."); + + // validate password + if (!user.isPassword(passwordHash)) + throw new AccessDeniedException("Password is incorrect for " + username + " / ***..."); + + // validate if user is allowed to login + if (user.getState() != UserState.ENABLED) + throw new AccessDeniedException("User " + username + " is not ENABLED. State is: " + user.getState()); + + // validate user has at least one role + if (user.getRoles().isEmpty()) { + throw new PrivilegeException("User " + username + " does not have any roles defined!"); + } + + // get 2 auth tokens + String authToken = encryptionHandler.nextToken(); + String authPassword = encryptionHandler.nextToken(); + + // get next session id + String sessionId = nextSessionId(); + + // create certificate + Certificate certificate = new Certificate(sessionId, username, authToken, authPassword, user.getLocale()); + + // create and save a new session + Session session = new Session(sessionId, authToken, authPassword, user.getUsername(), System + .currentTimeMillis()); + sessionMap.put(sessionId, new CertificateSessionPair(session, certificate)); + + // log + logger.info("Authenticated: " + session); + + // return the certificate + return certificate; + } /** * TODO What is better, validate from {@link Restrictable} to {@link User} or the opposite direction? @@ -76,7 +631,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { throw new PrivilegeException("Restrictable may not be null!"); // get user object - User user = modelHandler.getUser(certificate.getUsername()); + User user = persistenceHandler.getUser(certificate.getUsername()); if (user == null) { throw new PrivilegeException( "Oh boy, how did this happen: No User in user map although the certificate is valid!"); @@ -89,7 +644,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // now iterate roles and validate on policies for (String roleName : user.getRoles()) { - Role role = modelHandler.getRole(roleName); + Role role = persistenceHandler.getRole(roleName); if (role == null) { logger.error("No role is defined with name " + roleName + " which is configured for user " + user); continue; @@ -132,14 +687,14 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } // get the privilege for this restrictable - Privilege privilege = modelHandler.getPrivilege(privilegeName); + Privilege privilege = persistenceHandler.getPrivilege(privilegeName); if (privilege == null) { throw new PrivilegeException("No Privilege exists with the name " + privilegeName + " for Restrictable " + restrictable.getClass().getName()); } // get the policy configured for this privilege - PrivilegePolicy policy = modelHandler.getPolicy(privilege.getPolicy()); + PrivilegePolicy policy = this.getPolicy(privilege.getPolicy()); if (policy == null) { throw new PrivilegeException("PrivilegePolicy " + privilege.getPolicy() + " does not exist for Privilege " + privilegeName); @@ -177,7 +732,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { + certificate.getSessionId()); // get user object - User user = modelHandler.getUser(certificateSessionPair.session.getUsername()); + User user = persistenceHandler.getUser(certificateSessionPair.session.getUsername()); // if user exists, then certificate is valid if (user == null) { @@ -189,220 +744,40 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } /** - * @see ch.eitchnet.privilege.handler.SessionHandler#authenticate(java.lang.String, java.lang.String) - * - * @throws AccessDeniedException - * if the user credentials are not valid + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#validateIsPrivilegeAdmin(ch.eitchnet.privilege.model.Certificate) */ @Override - public Certificate authenticate(String username, String password) { + public void validateIsPrivilegeAdmin(Certificate certificate) throws PrivilegeException { - // both username and password must at least have 3 characters in length - if (username == null || username.length() < 3) - throw new PrivilegeException("The given username is shorter than 3 characters"); - else if (password == null || password.length() < 3) - throw new PrivilegeException("The given password is shorter than 3 characters"); - - // we only work with hashed passwords - String passwordHash = encryptionHandler.convertToHash(password); + // validate certificate + if (!this.isCertificateValid(certificate)) { + throw new PrivilegeException("Certificate " + certificate + " is not valid!"); + } // get user object - User user = modelHandler.getUser(username); - // no user means no authentication - if (user == null) - throw new AccessDeniedException("There is no user defined with the credentials: " + username + " / ***..."); - - // validate password - if (!user.isPassword(passwordHash)) - throw new AccessDeniedException("Password is incorrect for " + username + " / ***..."); - - // validate if user is allowed to login - if (user.getState() != UserState.ENABLED) - throw new AccessDeniedException("User " + username + " is not ENABLED. State is: " + user.getState()); - - // validate user has at least one role - if (user.getRoles().isEmpty()) { - throw new PrivilegeException("User " + username + " does not have any roles defined!"); - } - - // get 2 auth tokens - String authToken = encryptionHandler.nextToken(); - String authPassword = encryptionHandler.nextToken(); - - // get next session id - String sessionId = nextSessionId(); - - // create certificate - Certificate certificate = new Certificate(sessionId, username, authToken, authPassword, user.getLocale()); - - // create and save a new session - Session session = new Session(sessionId, authToken, authPassword, user.getUsername(), System - .currentTimeMillis()); - sessionMap.put(sessionId, new CertificateSessionPair(session, certificate)); - - // log - logger.info("Authenticated: " + session); - - // return the certificate - return certificate; - } - - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#addOrReplacePrivilege(ch.eitchnet.privilege.model.Certificate, - * ch.eitchnet.privilege.model.PrivilegeRep) - */ - @Override - public void addOrReplacePrivilege(Certificate certificate, PrivilegeRep privilegeRep) { - - // validate who is doing this - if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { - logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " - + certificate); - return; - } - - // create a new privilege - Privilege privilege = new Privilege(privilegeRep.getName(), privilegeRep.getPolicy(), privilegeRep - .isAllAllowed(), privilegeRep.getDenyList(), privilegeRep.getAllowList()); - - // delegate to persistence handler - persistenceHandler.addOrReplacePrivilege(privilege); - } - - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#addOrReplaceRole(ch.eitchnet.privilege.model.Certificate, - * ch.eitchnet.privilege.model.RoleRep) - */ - @Override - public void addOrReplaceRole(Certificate certificate, RoleRep roleRep) { - - // validate who is doing this - if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { - logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " - + certificate); - return; - } - - // create new role - Role role = new Role(roleRep.getName(), roleRep.getPrivileges()); - - // delegate to persistence handler - persistenceHandler.addOrReplaceRole(role); - } - - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#addOrReplaceUser(ch.eitchnet.privilege.model.Certificate, - * ch.eitchnet.privilege.model.UserRep, java.lang.String) - */ - @Override - public void addOrReplaceUser(Certificate certificate, UserRep userRep, String password) { - - // validate who is doing this - if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { - logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " - + certificate); - return; - } - - // hash password - String passwordHash; - if (password == null) - passwordHash = null; - else - passwordHash = encryptionHandler.convertToHash(password); - - // create new user - User user = new User(userRep.getUsername(), passwordHash, userRep.getFirstname(), userRep.getSurname(), userRep - .getUserState(), userRep.getRoles(), userRep.getLocale()); - - // delegate to persistence handler - persistenceHandler.addOrReplaceUser(user); - } - - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#addPrivilegeToRole(ch.eitchnet.privilege.model.Certificate, - * java.lang.String, java.lang.String) - */ - @Override - public void addPrivilegeToRole(Certificate certificate, String roleName, String privilegeName) { - - // validate who is doing this - if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { - logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " - + certificate); - return; - } - - // get role - Role role = getRole(roleName); - if (role == null) { - throw new PrivilegeException("Role " + roleName + " does not exist!"); - } - - // ignore if role already has this privilege - Set currentPrivileges = role.getPrivileges(); - if (currentPrivileges.contains(roleName)) { - logger.error("Role " + roleName + " already has privilege " + privilegeName); - return; - } - - // validate that privilege exists - if (getPrivilege(privilegeName) == null) { - throw new PrivilegeException("Privilege " + privilegeName + " does not exist and can not be added to role " - + roleName); - } - - // create new role with the additional privilege - Set newPrivileges = new HashSet(currentPrivileges); - newPrivileges.add(roleName); - - Role newRole = new Role(role.getName(), newPrivileges); - - // delegate role replacement to persistence handler - persistenceHandler.addOrReplaceRole(newRole); - } - - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#addRoleToUser(ch.eitchnet.privilege.model.Certificate, - * java.lang.String, java.lang.String) - */ - @Override - public void addRoleToUser(Certificate certificate, String username, String roleName) { - - // validate who is doing this - if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { - logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " - + certificate); - return; - } - - // get user - User user = getUser(username); + User user = persistenceHandler.getUser(certificate.getUsername()); if (user == null) { - throw new PrivilegeException("User " + username + " does not exist!"); + throw new PrivilegeException( + "Oh boy, how did this happen: No User in user map although the certificate is valid! Certificate: " + + certificate); } - // ignore if user already has role - Set currentRoles = user.getRoles(); - if (currentRoles.contains(roleName)) { - logger.error("User " + username + " already has role " + roleName); - return; + // validate user has PrivilegeAdmin role + if (!user.hasRole(PrivilegeHandler.PRIVILEGE_ADMIN_ROLE)) { + throw new AccessDeniedException("User does not have " + PrivilegeHandler.PRIVILEGE_ADMIN_ROLE + + " role! Certificate: " + certificate); } + } - // validate that role exists - if (getRole(roleName) == null) { - throw new PrivilegeException("Role " + roleName + " doest not exist!"); + /** + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#validatePassword(java.lang.String) + */ + @Override + public void validatePassword(String password) throws PrivilegeException { + + if (password == null || password.isEmpty()) { + throw new PrivilegeException("A password may not be empty!"); } - - // create new user - Set newRoles = new HashSet(currentRoles); - newRoles.add(roleName); - - User newUser = new User(user.getUsername(), user.getPassword(certificate), user.getFirstname(), user - .getSurname(), user.getState(), newRoles, user.getLocale()); - - // delegate user replacement to persistence handler - persistenceHandler.addOrReplaceUser(newUser); } /** @@ -412,436 +787,26 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { public boolean persist(Certificate certificate) { // validate who is doing this - if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { - logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " - + certificate); - return false; - } + validateIsPrivilegeAdmin(certificate); return persistenceHandler.persist(certificate); } /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#removePrivilege(ch.eitchnet.privilege.model.Certificate, - * java.lang.String) + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#initialize(java.util.Map, + * ch.eitchnet.privilege.handler.EncryptionHandler, ch.eitchnet.privilege.handler.PersistenceHandler) */ @Override - public PrivilegeRep removePrivilege(Certificate certificate, String privilegeName) { + public void initialize(Map parameterMap, EncryptionHandler encryptionHandler, + PersistenceHandler persistenceHandler) { - // validate who is doing this - if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { - logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " - + certificate); - return null; - } - - // delegate privilege removal to persistence handler - Privilege removedPrivilege = persistenceHandler.removePrivilege(privilegeName); - - // return privilege rep if it was removed - if (removedPrivilege != null) - return removedPrivilege.asPrivilegeRep(); - else - return null; - } - - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#removePrivilegeFromRole(ch.eitchnet.privilege.model.Certificate, - * java.lang.String, java.lang.String) - */ - @Override - public void removePrivilegeFromRole(Certificate certificate, String roleName, String privilegeName) { - - // validate who is doing this - if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { - logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " - + certificate); - return; - } - - // get role - Role role = getRole(roleName); - if (role == null) { - throw new PrivilegeException("Role " + roleName + " does not exist!"); - } - - // ignore if role does not have privilege - Set currentPrivileges = role.getPrivileges(); - if (!currentPrivileges.contains(privilegeName)) { - logger.error("Role " + roleName + " doest not have privilege " + privilegeName); - return; - } - - // create new role - Set newPrivileges = new HashSet(currentPrivileges); - newPrivileges.remove(privilegeName); - Role newRole = new Role(role.getName(), newPrivileges); - - // delegate user replacement to persistence handler - persistenceHandler.addOrReplaceRole(newRole); - } - - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#removeRole(ch.eitchnet.privilege.model.Certificate, - * java.lang.String) - */ - @Override - public RoleRep removeRole(Certificate certificate, String roleName) { - - // validate who is doing this - if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { - logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " - + certificate); - return null; - } - - // delegate role removal to persistence handler - Role removedRole = persistenceHandler.removeRole(roleName); - - // return role rep if it was removed - if (removedRole != null) - return removedRole.asRoleRep(); - else - return null; - } - - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#removeRoleFromUser(ch.eitchnet.privilege.model.Certificate, - * java.lang.String, java.lang.String) - */ - @Override - public void removeRoleFromUser(Certificate certificate, String username, String roleName) { - - // validate who is doing this - if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { - logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " - + certificate); - return; - } - - // get User - User user = getUser(username); - if (user == null) { - throw new PrivilegeException("User " + username + " does not exist!"); - } - - // ignore if user does not have role - Set currentRoles = user.getRoles(); - if (!currentRoles.contains(roleName)) { - logger.error("User " + user + " does not have role " + roleName); - return; - } - - // create new user - Set newRoles = new HashSet(currentRoles); - newRoles.remove(roleName); - User newUser = new User(user.getUsername(), user.getPassword(certificate), user.getFirstname(), user - .getSurname(), user.getState(), newRoles, user.getLocale()); - - // delegate user replacement to persistence handler - persistenceHandler.addOrReplaceUser(newUser); - } - - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#removeUser(ch.eitchnet.privilege.model.Certificate, - * java.lang.String) - */ - @Override - public UserRep removeUser(Certificate certificate, String username) { - - // validate who is doing this - if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { - logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " - + certificate); - return null; - } - - // delegate user removal to persistence handler - User removedUser = persistenceHandler.removeUser(username); - - // return user rep if it was removed - if (removedUser != null) - return removedUser.asUserRep(); - else - return null; - } - - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setPrivilegeAllAllowed(ch.eitchnet.privilege.model.Certificate, - * java.lang.String, boolean) - */ - @Override - public void setPrivilegeAllAllowed(Certificate certificate, String privilegeName, boolean allAllowed) { - - // validate who is doing this - if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { - logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " - + certificate); - return; - } - - // get Privilege - Privilege privilege = getPrivilege(privilegeName); - if (privilege == null) { - throw new PrivilegeException("Privilege " + privilegeName + " does not exist!"); - } - - // ignore if privilege is already set to argument - if (privilege.isAllAllowed() == allAllowed) { - logger.error("Privilege " + privilegeName + " is already set to " - + (allAllowed ? "all allowed" : "not all allowed")); - return; - } - - // create new privilege - Privilege newPrivilege = new Privilege(privilege.getName(), privilege.getPolicy(), allAllowed, privilege - .getDenyList(), privilege.getAllowList()); - - // delegate privilege replacement to persistence handler - persistenceHandler.addOrReplacePrivilege(newPrivilege); - } - - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setPrivilegeAllowList(ch.eitchnet.privilege.model.Certificate, - * java.lang.String, java.util.Set) - */ - @Override - public void setPrivilegeAllowList(Certificate certificate, String privilegeName, Set allowList) { - - // validate who is doing this - if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { - logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " - + certificate); - return; - } - - // get Privilege - Privilege privilege = getPrivilege(privilegeName); - if (privilege == null) { - throw new PrivilegeException("Privilege " + privilegeName + " does not exist!"); - } - - // create new privilege - Privilege newPrivilege = new Privilege(privilege.getName(), privilege.getPolicy(), privilege.isAllAllowed(), - privilege.getDenyList(), allowList); - - // delegate privilege replacement to persistence handler - persistenceHandler.addOrReplacePrivilege(newPrivilege); - } - - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setPrivilegeDenyList(ch.eitchnet.privilege.model.Certificate, - * java.lang.String, java.util.Set) - */ - @Override - public void setPrivilegeDenyList(Certificate certificate, String privilegeName, Set denyList) { - - // validate who is doing this - if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { - logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " - + certificate); - return; - } - - // get Privilege - Privilege privilege = getPrivilege(privilegeName); - if (privilege == null) { - throw new PrivilegeException("Privilege " + privilegeName + " does not exist!"); - } - - // create new privilege - Privilege newPrivilege = new Privilege(privilege.getName(), privilege.getPolicy(), privilege.isAllAllowed(), - denyList, privilege.getAllowList()); - - // delegate privilege replacement to persistence handler - persistenceHandler.addOrReplacePrivilege(newPrivilege); - } - - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setPrivilegePolicy(ch.eitchnet.privilege.model.Certificate, - * java.lang.String, java.lang.String) - */ - @Override - public void setPrivilegePolicy(Certificate certificate, String privilegeName, String policyName) { - - // validate who is doing this - if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { - logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " - + certificate); - return; - } - - // get Privilege - Privilege privilege = getPrivilege(privilegeName); - if (privilege == null) { - throw new PrivilegeException("Privilege " + privilegeName + " does not exist!"); - } - - // create new privilege - Privilege newPrivilege = new Privilege(privilege.getName(), policyName, privilege.isAllAllowed(), privilege - .getDenyList(), privilege.getAllowList()); - - // delegate privilege replacement to persistence handler - persistenceHandler.addOrReplacePrivilege(newPrivilege); - } - - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setUserLocaleState(ch.eitchnet.privilege.model.Certificate, - * java.lang.String, java.util.Locale) - */ - @Override - public void setUserLocaleState(Certificate certificate, String username, Locale locale) { - - // validate who is doing this - if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { - logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " - + certificate); - return; - } - - // get User - User user = getUser(username); - if (user == null) { - throw new PrivilegeException("User " + username + " does not exist!"); - } - - // create new user - User newUser = new User(user.getUsername(), user.getPassword(certificate), user.getFirstname(), user - .getSurname(), user.getState(), user.getRoles(), locale); - - // delegate user replacement to persistence handler - persistenceHandler.addOrReplaceUser(newUser); - } - - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setUserName(ch.eitchnet.privilege.model.Certificate, - * java.lang.String, java.lang.String, java.lang.String) - */ - @Override - public void setUserName(Certificate certificate, String username, String firstname, String surname) { - - // validate who is doing this - if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { - logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " - + certificate); - return; - } - - // get User - User user = getUser(username); - if (user == null) { - throw new PrivilegeException("User " + username + " does not exist!"); - } - - // create new user - User newUser = new User(user.getUsername(), user.getPassword(certificate), firstname, surname, user.getState(), - user.getRoles(), user.getLocale()); - - // delegate user replacement to persistence handler - persistenceHandler.addOrReplaceUser(newUser); - } - - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setUserPassword(ch.eitchnet.privilege.model.Certificate, - * java.lang.String, java.lang.String) - */ - @Override - public void setUserPassword(Certificate certificate, String username, String password) { - - // validate who is doing this - if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { - logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " - + certificate); - return; - } - - // get User - User user = getUser(username); - if (user == null) { - throw new PrivilegeException("User " + username + " does not exist!"); - } - - // hash password - String passwordHash = encryptionHandler.convertToHash(password); - - // create new user - User newUser = new User(user.getUsername(), passwordHash, user.getFirstname(), user.getSurname(), user - .getState(), user.getRoles(), user.getLocale()); - - // delegate user replacement to persistence handler - persistenceHandler.addOrReplaceUser(newUser); - } - - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setUserState(ch.eitchnet.privilege.model.Certificate, - * java.lang.String, ch.eitchnet.privilege.model.UserState) - */ - @Override - public void setUserState(Certificate certificate, String username, UserState state) { - - // validate who is doing this - if (!PrivilegeHelper.isUserPrivilegeAdmin(certificate)) { - logger.error("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE + " role! Certificate: " - + certificate); - return; - } - - // get User - User user = getUser(username); - if (user == null) { - throw new PrivilegeException("User " + username + " does not exist!"); - } - - // create new user - User newUser = new User(user.getUsername(), user.getPassword(certificate), user.getFirstname(), user - .getSurname(), state, user.getRoles(), user.getLocale()); - - // delegate user replacement to persistence handler - persistenceHandler.addOrReplaceUser(newUser); - } - - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#initialize(org.dom4j.Element) - */ - @Override - public void initialize(Element element) { + this.encryptionHandler = encryptionHandler; + this.persistenceHandler = persistenceHandler; lastSessionId = 0l; sessionMap = new HashMap(); } - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#getPrivilege(java.lang.String) - */ - @Override - public Privilege getPrivilege(String privilegeName) { - return persistenceHandler.getPrivilege(privilegeName); - } - - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#getRole(java.lang.String) - */ - @Override - public Role getRole(String roleName) { - return persistenceHandler.getRole(roleName); - } - - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#getUser(java.lang.String) - */ - @Override - public User getUser(String username) { - return persistenceHandler.getUser(username); - } - - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#getPolicy(java.lang.String) - */ - @Override - public PrivilegePolicy getPolicy(String policyName) { - return persistenceHandler.getPolicy(policyName); - } - /** * @return a new session id */ @@ -863,4 +828,5 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { this.certificate = certificate; } } + } diff --git a/src/ch/eitchnet/privilege/handler/EncryptionHandler.java b/src/ch/eitchnet/privilege/handler/EncryptionHandler.java index 83df25437..9efa5f337 100644 --- a/src/ch/eitchnet/privilege/handler/EncryptionHandler.java +++ b/src/ch/eitchnet/privilege/handler/EncryptionHandler.java @@ -10,7 +10,7 @@ package ch.eitchnet.privilege.handler; -import org.dom4j.Element; +import java.util.Map; /** * @author rvonburg @@ -18,9 +18,19 @@ import org.dom4j.Element; */ public interface EncryptionHandler { + /** + * @return + */ public String nextToken(); + /** + * @param string + * @return + */ public String convertToHash(String string); - public void initialize(Element element); + /** + * @param parameterMap + */ + public void initialize(Map parameterMap); } diff --git a/src/ch/eitchnet/privilege/handler/PersistenceHandler.java b/src/ch/eitchnet/privilege/handler/PersistenceHandler.java index 9f7e2d08b..5fcd9c7a6 100644 --- a/src/ch/eitchnet/privilege/handler/PersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/PersistenceHandler.java @@ -10,7 +10,7 @@ package ch.eitchnet.privilege.handler; -import org.dom4j.Element; +import java.util.Map; import ch.eitchnet.privilege.model.Certificate; import ch.eitchnet.privilege.model.internal.Privilege; @@ -24,27 +24,71 @@ import ch.eitchnet.privilege.policy.PrivilegePolicy; */ public interface PersistenceHandler { + /** + * @param username + * @return + */ public User getUser(String username); + /** + * @param user + */ public void addOrReplaceUser(User user); + /** + * @param username + * @return + */ public User removeUser(String username); + /** + * @param roleName + * @return + */ public Role getRole(String roleName); + /** + * @param role + */ public void addOrReplaceRole(Role role); + /** + * @param roleName + * @return + */ public Role removeRole(String roleName); + /** + * @param privilegeName + * @return + */ public Privilege getPrivilege(String privilegeName); + /** + * @param privilege + */ public void addOrReplacePrivilege(Privilege privilege); + /** + * @param privilegeName + * @return + */ public Privilege removePrivilege(String privilegeName); + /** + * @param policyName + * @return + */ public PrivilegePolicy getPolicy(String policyName); + /** + * @param certificate + * @return + */ public boolean persist(Certificate certificate); - - public void initialize(Element element); + + /** + * @param parameterMap + */ + public void initialize(Map parameterMap); } diff --git a/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java b/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java index ea27acac8..74f27bd70 100644 --- a/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java +++ b/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java @@ -11,11 +11,9 @@ package ch.eitchnet.privilege.handler; import java.util.Locale; +import java.util.Map; import java.util.Set; -import org.dom4j.Element; - -import ch.eitchnet.privilege.i18n.AccessDeniedException; import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.Certificate; import ch.eitchnet.privilege.model.PrivilegeRep; @@ -23,9 +21,7 @@ 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.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.Role; -import ch.eitchnet.privilege.model.internal.User; import ch.eitchnet.privilege.policy.PrivilegePolicy; /** @@ -34,6 +30,178 @@ import ch.eitchnet.privilege.policy.PrivilegePolicy; */ public interface PrivilegeHandler { + /** + * This is the role users must have, if they are allowed to modify objects + */ + public static final String PRIVILEGE_ADMIN_ROLE = "PrivilegeAdmin"; + + /** + * @param username + * + * @return + */ + public UserRep getUser(String username); + + /** + * @param roleName + * + * @return + */ + public RoleRep getRole(String roleName); + + /** + * @param privilegeName + * + * @return + */ + public PrivilegeRep getPrivilege(String privilegeName); + + /** + * @param policyName + * + * @return + */ + public PrivilegePolicy getPolicy(String policyName); + + /** + * @param certificate + * @param username + * + * @return + */ + public UserRep removeUser(Certificate certificate, String username); + + /** + * @param certificate + * @param username + * @param roleName + */ + public void removeRoleFromUser(Certificate certificate, String username, String roleName); + + /** + * @param certificate + * @param roleName + * + * @return + */ + public RoleRep removeRole(Certificate certificate, String roleName); + + /** + * @param certificate + * @param roleName + * @param privilegeName + */ + public void removePrivilegeFromRole(Certificate certificate, String roleName, String privilegeName); + + /** + * @param certificate + * @param privilegeName + * + * @return + */ + public PrivilegeRep removePrivilege(Certificate certificate, String privilegeName); + + /** + * @param certificate + * @param userRep + * @param password + */ + public void addOrReplaceUser(Certificate certificate, UserRep userRep, String password); + + /** + * @param certificate + * @param roleRep + */ + public void addOrReplaceRole(Certificate certificate, RoleRep roleRep); + + /** + * @param certificate + * @param privilegeRep + */ + public void addOrReplacePrivilege(Certificate certificate, PrivilegeRep privilegeRep); + + /** + * @param certificate + * @param username + * @param roleName + */ + public void addRoleToUser(Certificate certificate, String username, String roleName); + + /** + * @param certificate + * @param roleName + * @param privilegeName + */ + public void addPrivilegeToRole(Certificate certificate, String roleName, String privilegeName); + + /** + * @param certificate + * @param username + * @param password + */ + public void setUserPassword(Certificate certificate, String username, String password); + + /** + * @param certificate + * @param username + * @param firstname + * @param surname + */ + public void setUserName(Certificate certificate, String username, String firstname, String surname); + + /** + * @param certificate + * @param username + * @param state + */ + public void setUserState(Certificate certificate, String username, UserState state); + + /** + * @param certificate + * @param username + * @param locale + */ + public void setUserLocaleState(Certificate certificate, String username, Locale locale); + + /** + * @param certificate + * @param privilegeName + * @param policyName + */ + public void setPrivilegePolicy(Certificate certificate, String privilegeName, String policyName); + + /** + * @param certificate + * @param privilegeName + * @param allAllowed + */ + public void setPrivilegeAllAllowed(Certificate certificate, String privilegeName, boolean allAllowed); + + /** + * @param certificate + * @param privilegeName + * @param denyList + */ + public void setPrivilegeDenyList(Certificate certificate, String privilegeName, Set denyList); + + /** + * @param certificate + * @param privilegeName + * @param allowList + */ + public void setPrivilegeAllowList(Certificate certificate, String privilegeName, Set allowList); + + /** + * @param username + * @param password + * + * @return + * + * @throws AccessDeniedException + * if the user credentials are not valid + */ + public Certificate authenticate(String username, String password); + /** * @param certificate * @param restrictable @@ -73,61 +241,50 @@ public interface PrivilegeHandler { public boolean isCertificateValid(Certificate certificate); /** - * @param username + *

    + * Validates if this {@link Certificate} is for a {@link ch.eitchnet.privilege.model.internal.User} with + * {@link Role} with name {@link PrivilegeHandler#PRIVILEGE_ADMIN_ROLE} + *

    + * + *

    + * In other words, this method checks if the given certificate is for a user who has the rights to change objects + *

    + * + *

    + * If the user is not the administrator, then a {@link ch.eitchnet.privilege.i18n.PrivilegeException} is thrown + *

    + * + * @param certificate + * the {@link Certificate} for which the role should be validated against + * + * @throws ch.eitchnet.privilege.i18n.PrivilegeException + * if the user does not not have admin privileges + */ + public void validateIsPrivilegeAdmin(Certificate certificate) throws PrivilegeException; + + /** + * Validate that the given password meets any requirements. What these requirements are is a decision made by the + * concrete implementation + * * @param password * - * @return - * - * @throws AccessDeniedException - * if the user credentials are not valid + * @throws PrivilegeException */ - public Certificate authenticate(String username, String password); - - public User getUser(String username); - - public void addOrReplaceUser(Certificate certificate, UserRep userRep, String password); - - public UserRep removeUser(Certificate certificate, String username); - - public void setUserPassword(Certificate certificate, String username, String password); - - public void setUserName(Certificate certificate, String username, String firstname, String surname); - - public void setUserState(Certificate certificate, String username, UserState state); - - public void setUserLocaleState(Certificate certificate, String username, Locale locale); - - public void addRoleToUser(Certificate certificate, String username, String roleName); - - public void removeRoleFromUser(Certificate certificate, String username, String roleName); - - public void addOrReplaceRole(Certificate certificate, RoleRep roleRep); - - public Role getRole(String roleName); - - public RoleRep removeRole(Certificate certificate, String roleName); - - public void addPrivilegeToRole(Certificate certificate, String roleName, String privilegeName); - - public void removePrivilegeFromRole(Certificate certificate, String roleName, String privilegeName); - - public Privilege getPrivilege(String privilegeName); - - public void addOrReplacePrivilege(Certificate certificate, PrivilegeRep privilegeRep); - - public PrivilegeRep removePrivilege(Certificate certificate, String privilegeName); - - public void setPrivilegePolicy(Certificate certificate, String privilegeName, String policyName); - - public void setPrivilegeAllAllowed(Certificate certificate, String privilegeName, boolean allAllowed); - - public void setPrivilegeDenyList(Certificate certificate, String privilegeName, Set denyList); - - public void setPrivilegeAllowList(Certificate certificate, String privilegeName, Set allowList); - - public PrivilegePolicy getPolicy(String policyName); + public void validatePassword(String password) throws PrivilegeException; + /** + * @param certificate + * + * @return + */ public boolean persist(Certificate certificate); - public void initialize(Element element); + /** + * + * @param parameterMap + * @param encryptionHandler + * @param persistenceHandler + */ + public void initialize(Map parameterMap, EncryptionHandler encryptionHandler, + PersistenceHandler persistenceHandler); } diff --git a/src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java b/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java similarity index 92% rename from src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java rename to src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java index 5ea040e1c..9f97b4377 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultPersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java @@ -24,10 +24,8 @@ import org.apache.log4j.Logger; import org.dom4j.DocumentFactory; import org.dom4j.Element; -import ch.eitchnet.privilege.base.PrivilegeContainer; -import ch.eitchnet.privilege.base.XmlConstants; import ch.eitchnet.privilege.helper.ClassHelper; -import ch.eitchnet.privilege.helper.ConfigurationHelper; +import ch.eitchnet.privilege.helper.XmlConstants; import ch.eitchnet.privilege.helper.XmlHelper; import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.Certificate; @@ -41,9 +39,9 @@ import ch.eitchnet.privilege.policy.PrivilegePolicy; * @author rvonburg * */ -public class DefaultPersistenceHandler implements PersistenceHandler { +public class XmlPersistenceHandler implements PersistenceHandler { - private static final Logger logger = Logger.getLogger(DefaultPersistenceHandler.class); + private static final Logger logger = Logger.getLogger(XmlPersistenceHandler.class); private Map userMap; private Map roleMap; @@ -59,6 +57,8 @@ public class DefaultPersistenceHandler implements PersistenceHandler { private Map parameterMap; + private String basePath; + /** * @see ch.eitchnet.privilege.handler.PersistenceHandler#addOrReplacePrivilege(ch.eitchnet.privilege.model.Certificate, * ch.eitchnet.privilege.model.internal.Privilege) @@ -175,7 +175,7 @@ public class DefaultPersistenceHandler implements PersistenceHandler { + XmlConstants.XML_PARAM_USERS_FILE + " is invalid"); } // get users file - File usersFile = new File(PrivilegeContainer.getInstance().getBasePath() + "/" + usersFileName); + File usersFile = new File(basePath + "/" + usersFileName); boolean usersFileUnchanged = usersFile.exists() && usersFile.lastModified() == usersFileDate; if (!userMapDirty && usersFileUnchanged) { logger.warn("No users unpersisted and user file unchanged on file system"); @@ -202,7 +202,7 @@ public class DefaultPersistenceHandler implements PersistenceHandler { + XmlConstants.XML_PARAM_ROLES_FILE + " is invalid"); } // get roles file - File rolesFile = new File(PrivilegeContainer.getInstance().getBasePath() + "/" + rolesFileName); + File rolesFile = new File(basePath + "/" + rolesFileName); boolean rolesFileUnchanged = rolesFile.exists() && rolesFile.lastModified() == rolesFileDate; if (!roleMapDirty && rolesFileUnchanged) { logger.warn("No roles unpersisted and roles file unchanged on file system"); @@ -229,7 +229,7 @@ public class DefaultPersistenceHandler implements PersistenceHandler { + XmlConstants.XML_PARAM_PRIVILEGES_FILE + " is invalid"); } // get privileges file - File privilegesFile = new File(PrivilegeContainer.getInstance().getBasePath() + "/" + privilegesFileName); + File privilegesFile = new File(basePath + "/" + privilegesFileName); boolean privilegesFileUnchanged = privilegesFile.exists() && privilegesFile.lastModified() == privilegesFileDate; if (!privilegeMapDirty && privilegesFileUnchanged) { @@ -267,19 +267,23 @@ public class DefaultPersistenceHandler implements PersistenceHandler { } /** - * @see ch.eitchnet.privilege.base.PrivilegeContainerObject#initialize(org.dom4j.Element) + * @see ch.eitchnet.privilege.handler.EncryptionHandler#initialize(java.util.Map) */ @Override - public void initialize(Element element) { + public void initialize(Map parameterMap) { roleMap = new HashMap(); userMap = new HashMap(); privilegeMap = new HashMap(); policyMap = new HashMap>(); - // get parameters - Element parameterElement = element.element(XmlConstants.XML_PARAMETERS); - parameterMap = ConfigurationHelper.convertToParameterMap(parameterElement); + // get and validate base bath + basePath = parameterMap.get(XmlConstants.XML_PARAM_BASE_PATH); + File basePathF = new File(basePath); + if (!basePathF.exists() && !basePathF.isDirectory()) { + throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " + + XmlConstants.XML_PARAM_BASE_PATH + " is invalid"); + } // get roles file name String rolesFileName = parameterMap.get(XmlConstants.XML_PARAM_ROLES_FILE); @@ -289,7 +293,7 @@ public class DefaultPersistenceHandler implements PersistenceHandler { } // get roles file - File rolesFile = new File(PrivilegeContainer.getInstance().getBasePath() + "/" + rolesFileName); + File rolesFile = new File(basePath + "/" + rolesFileName); if (!rolesFile.exists()) { throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " + XmlConstants.XML_PARAM_ROLES_FILE + " is invalid as roles file does not exist at path " @@ -311,7 +315,7 @@ public class DefaultPersistenceHandler implements PersistenceHandler { } // get users file - File usersFile = new File(PrivilegeContainer.getInstance().getBasePath() + "/" + usersFileName); + File usersFile = new File(basePath + "/" + usersFileName); if (!usersFile.exists()) { throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " + XmlConstants.XML_PARAM_USERS_FILE + " is invalid as users file does not exist at path " @@ -333,7 +337,7 @@ public class DefaultPersistenceHandler implements PersistenceHandler { } // get privileges file - File privilegesFile = new File(PrivilegeContainer.getInstance().getBasePath() + "/" + privilegesFileName); + File privilegesFile = new File(basePath + "/" + privilegesFileName); if (!privilegesFile.exists()) { throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " + XmlConstants.XML_PARAM_PRIVILEGES_FILE + " is invalid as privileges file does not exist at path " @@ -355,7 +359,7 @@ public class DefaultPersistenceHandler implements PersistenceHandler { } // get policy file - File policyFile = new File(PrivilegeContainer.getInstance().getBasePath() + "/" + policyFileName); + File policyFile = new File(basePath + "/" + policyFileName); if (!policyFile.exists()) { throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " + XmlConstants.XML_PARAM_POLICY_FILE + " is invalid as policy file does not exist at path " @@ -380,7 +384,7 @@ public class DefaultPersistenceHandler implements PersistenceHandler { boolean privilegeAdminExists = false; for (String username : userMap.keySet()) { User user = userMap.get(username); - if (user.hasRole(PrivilegeContainer.PRIVILEGE_ADMIN_ROLE)) { + if (user.hasRole(PrivilegeHandler.PRIVILEGE_ADMIN_ROLE)) { privilegeAdminExists = true; break; } @@ -395,6 +399,7 @@ public class DefaultPersistenceHandler implements PersistenceHandler { */ private void readUsers(Element usersRootElement) { + @SuppressWarnings("unchecked") List userElements = usersRootElement.elements(XmlConstants.XML_USER); for (Element userElement : userElements) { @@ -406,11 +411,12 @@ public class DefaultPersistenceHandler implements PersistenceHandler { UserState userState = UserState.valueOf(userElement.element(XmlConstants.XML_STATE).getTextTrim()); - // TODO better handling needed + // TODO better parsing needed String localeName = userElement.element(XmlConstants.XML_LOCALE).getTextTrim(); Locale locale = new Locale(localeName); Element rolesElement = userElement.element(XmlConstants.XML_ROLES); + @SuppressWarnings("unchecked") List rolesElementList = rolesElement.elements(XmlConstants.XML_ROLE); Set roles = new HashSet(); for (Element roleElement : rolesElementList) { @@ -436,11 +442,13 @@ public class DefaultPersistenceHandler implements PersistenceHandler { */ private void readRoles(Element rolesRootElement) { + @SuppressWarnings("unchecked") List roleElements = rolesRootElement.elements(XmlConstants.XML_ROLE); for (Element roleElement : roleElements) { String roleName = roleElement.attributeValue(XmlConstants.XML_ATTR_NAME); + @SuppressWarnings("unchecked") List privilegeElements = roleElement.elements(XmlConstants.XML_PRIVILEGE); Set privileges = new HashSet(); for (Element privilegeElement : privilegeElements) { @@ -459,6 +467,7 @@ public class DefaultPersistenceHandler implements PersistenceHandler { */ private void readPrivileges(Element privilegesRootElement) { + @SuppressWarnings("unchecked") List privilegeElements = privilegesRootElement.elements(XmlConstants.XML_PRIVILEGE); for (Element privilegeElement : privilegeElements) { @@ -468,6 +477,7 @@ public class DefaultPersistenceHandler implements PersistenceHandler { String allAllowedS = privilegeElement.element(XmlConstants.XML_ALL_ALLOWED).getTextTrim(); boolean allAllowed = Boolean.valueOf(allAllowedS); + @SuppressWarnings("unchecked") List denyElements = privilegeElement.elements(XmlConstants.XML_DENY); Set denyList = new HashSet(denyElements.size()); for (Element denyElement : denyElements) { @@ -479,6 +489,7 @@ public class DefaultPersistenceHandler implements PersistenceHandler { } } + @SuppressWarnings("unchecked") List allowElements = privilegeElement.elements(XmlConstants.XML_ALLOW); Set allowList = new HashSet(allowElements.size()); for (Element allowElement : allowElements) { @@ -500,6 +511,7 @@ public class DefaultPersistenceHandler implements PersistenceHandler { */ private void readPolicies(Element policiesRootElement) { + @SuppressWarnings("unchecked") List policyElements = policiesRootElement.elements(XmlConstants.XML_POLICY); for (Element policyElement : policyElements) { String policyName = policyElement.attributeValue(XmlConstants.XML_ATTR_NAME); @@ -593,7 +605,7 @@ public class DefaultPersistenceHandler implements PersistenceHandler { // create the user element Element userElement = documentFactory.createElement(XmlConstants.XML_USER); userElement.addAttribute(XmlConstants.XML_ATTR_USERNAME, user.getUsername()); - userElement.addAttribute(XmlConstants.XML_ATTR_PASSWORD, user.getPassword(certificate)); + userElement.addAttribute(XmlConstants.XML_ATTR_PASSWORD, user.getPassword()); // add first name element Element firstnameElement = documentFactory.createElement(XmlConstants.XML_FIRSTNAME); diff --git a/src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java b/src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java index db99ea0be..4c8a7f7dc 100644 --- a/src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java +++ b/src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java @@ -21,8 +21,6 @@ import org.dom4j.Document; import org.dom4j.DocumentFactory; import org.dom4j.Element; -import ch.eitchnet.privilege.base.PrivilegeContainer; -import ch.eitchnet.privilege.base.XmlConstants; /** *

    @@ -43,6 +41,8 @@ public class BootstrapConfigurationHelper { private static String path; + private static String defaultPrivilegeContainerXmlFile = "PrivilegeContainer.xml"; + private static String usersFileName = "PrivilegeUsers.xml"; private static String rolesFileName = "PrivilegeRoles.xml"; private static String privilegesFileName = "Privileges.xml"; @@ -51,10 +51,9 @@ public class BootstrapConfigurationHelper { private static String policyXmlFile = "PrivilegePolicies.xml"; + private static String defaultPrivilegeHandler = "ch.eitchnet.privilege.handler.DefaultPrivilegeHandler"; private static String defaultPersistenceHandler = "ch.eitchnet.privilege.handler.DefaultPersistenceHandler"; - private static String defaultSessionHandler = "ch.eitchnet.privilege.handler.DefaultSessionHandler"; private static String defaultEncryptionHandler = "ch.eitchnet.privilege.handler.DefaultEncryptionHandler"; - private static String defaultPolicyHandler = "ch.eitchnet.privilege.handler.DefaultPolicyHandler"; /** * @param args @@ -67,7 +66,7 @@ public class BootstrapConfigurationHelper { // get current directory path = System.getProperty("user.dir") + "/newConfig"; - // ask user where to save configuration, default is pwd/newConfig/.... + // TODO ask user where to save configuration, default is pwd/newConfig/.... // see if path already exists File pathF = new File(path); @@ -79,7 +78,7 @@ public class BootstrapConfigurationHelper { } } - // ask other questions... + // TODO ask other questions... // now perform work: createXmlPrivilegeContainer(); @@ -139,15 +138,16 @@ public class BootstrapConfigurationHelper { parameterElement.addAttribute(XmlConstants.XML_ATTR_NAME, XmlConstants.XML_PARAM_PRIVILEGES_FILE); parameterElement.addAttribute(XmlConstants.XML_ATTR_VALUE, privilegesFileName); parametersElement.add(parameterElement); + // Parameter policyXmlFile + parameterElement = factory.createElement(XmlConstants.XML_PARAMETER); + parameterElement.addAttribute(XmlConstants.XML_ATTR_NAME, XmlConstants.XML_PARAM_POLICY_FILE); + parameterElement.addAttribute(XmlConstants.XML_ATTR_VALUE, policyXmlFile); + parametersElement.add(parameterElement); - // create SessionHandler - Element sessionHandlerElem = factory.createElement(XmlConstants.XML_HANDLER_SESSION); - sessionHandlerElem.addAttribute(XmlConstants.XML_ATTR_CLASS, defaultSessionHandler); - - // create ModelHandler - Element modelHandlerElem = factory.createElement(XmlConstants.XML_HANDLER_MODEL); - rootElement.add(modelHandlerElem); - modelHandlerElem.addAttribute(XmlConstants.XML_ATTR_CLASS, "ch.eitchnet.privilege.handler.DefaultModelHandler"); + // create PrivilegeHandler + Element privilegeHandlerElem = factory.createElement(XmlConstants.XML_HANDLER_PRIVILEGE); + rootElement.add(privilegeHandlerElem); + privilegeHandlerElem.addAttribute(XmlConstants.XML_ATTR_CLASS, defaultPrivilegeHandler); // create EncryptionHandler Element encryptionHandlerElem = factory.createElement(XmlConstants.XML_HANDLER_ENCRYPTION); @@ -161,19 +161,8 @@ public class BootstrapConfigurationHelper { parameterElement.addAttribute(XmlConstants.XML_ATTR_VALUE, hashAlgorithm); parametersElement.add(parameterElement); - // create PolicyHandler - Element policyHandlerElem = factory.createElement(XmlConstants.XML_HANDLER_POLICY); - rootElement.add(policyHandlerElem); - policyHandlerElem.addAttribute(XmlConstants.XML_ATTR_CLASS, defaultPolicyHandler); - parametersElement = factory.createElement(XmlConstants.XML_PARAMETERS); - policyHandlerElem.add(parametersElement); - // Parameter policyXmlFile - parameterElement = factory.createElement(XmlConstants.XML_PARAMETER); - parameterElement.addAttribute(XmlConstants.XML_ATTR_NAME, XmlConstants.XML_PARAM_POLICY_FILE); - parameterElement.addAttribute(XmlConstants.XML_ATTR_VALUE, policyXmlFile); - parametersElement.add(parameterElement); - - File privilegeContainerFile = new File(path + "/" + PrivilegeContainer.PRIVILEGE_CONTAINER_FILE); + // write the container file to disk + File privilegeContainerFile = new File(path + "/" + defaultPrivilegeContainerXmlFile); XmlHelper.writeDocument(doc, privilegeContainerFile); } } diff --git a/src/ch/eitchnet/privilege/helper/ConfigurationHelper.java b/src/ch/eitchnet/privilege/helper/ConfigurationHelper.java index 34e6d642d..8fc6fda6e 100644 --- a/src/ch/eitchnet/privilege/helper/ConfigurationHelper.java +++ b/src/ch/eitchnet/privilege/helper/ConfigurationHelper.java @@ -10,13 +10,18 @@ package ch.eitchnet.privilege.helper; +import java.io.File; import java.util.HashMap; import java.util.List; import java.util.Map; +import org.apache.log4j.Logger; import org.dom4j.Element; -import ch.eitchnet.privilege.base.XmlConstants; +import ch.eitchnet.privilege.handler.EncryptionHandler; +import ch.eitchnet.privilege.handler.PersistenceHandler; +import ch.eitchnet.privilege.handler.PrivilegeHandler; +import ch.eitchnet.privilege.i18n.PrivilegeException; /** * @author rvonburg @@ -24,6 +29,86 @@ import ch.eitchnet.privilege.base.XmlConstants; */ public class ConfigurationHelper { + private static final Logger logger = Logger.getLogger(ConfigurationHelper.class); + + /** + * @param privilegeContainerXmlFile + */ + public static void initializeFromXml(File privilegeContainerXmlFile) { + + // make sure file exists + if (!privilegeContainerXmlFile.exists()) { + throw new PrivilegeException("Privilige file does not exist at path " + + privilegeContainerXmlFile.getAbsolutePath()); + } + + // parse container xml file to XML document + Element containerRootElement = XmlHelper.parseDocument(privilegeContainerXmlFile).getRootElement(); + + // instantiate encryption handler + Element encryptionHandlerElement = containerRootElement.element(XmlConstants.XML_HANDLER_ENCRYPTION); + String encryptionHandlerClassName = encryptionHandlerElement.attributeValue(XmlConstants.XML_ATTR_CLASS); + EncryptionHandler encryptionHandler = ClassHelper.instantiateClass(encryptionHandlerClassName); + + // instantiate persistence handler + Element persistenceHandlerElement = containerRootElement.element(XmlConstants.XML_HANDLER_PERSISTENCE); + String persistenceHandlerClassName = persistenceHandlerElement.attributeValue(XmlConstants.XML_ATTR_CLASS); + PersistenceHandler persistenceHandler = ClassHelper.instantiateClass(persistenceHandlerClassName); + + // instantiate privilege handler + Element privilegeHandlerElement = containerRootElement.element(XmlConstants.XML_HANDLER_PRIVILEGE); + String privilegeHandlerClassName = privilegeHandlerElement.attributeValue(XmlConstants.XML_ATTR_CLASS); + PrivilegeHandler privilegeHandler = ClassHelper.instantiateClass(privilegeHandlerClassName); + + try { + + // get parameters + Element parameterElement = encryptionHandlerElement.element(XmlConstants.XML_PARAMETERS); + Map parameterMap = convertToParameterMap(parameterElement); + + // initialize encryption handler + encryptionHandler.initialize(parameterMap); + + } catch (Exception e) { + logger.error(e, e); + throw new PrivilegeException("EncryptionHandler " + encryptionHandlerClassName + + " could not be initialized"); + } + + try { + + // get parameters + Element parameterElement = persistenceHandlerElement.element(XmlConstants.XML_PARAMETERS); + Map parameterMap = convertToParameterMap(parameterElement); + + // initialize persistence handler + persistenceHandler.initialize(parameterMap); + + } catch (Exception e) { + logger.error(e, e); + throw new PrivilegeException("PersistenceHandler " + persistenceHandlerElement + + " could not be initialized"); + } + + try { + + // get parameters + Element parameterElement = privilegeHandlerElement.element(XmlConstants.XML_PARAMETERS); + Map parameterMap = convertToParameterMap(parameterElement); + + // initialize privilege handler + privilegeHandler.initialize(parameterMap, encryptionHandler, persistenceHandler); + + } catch (Exception e) { + logger.error(e, e); + throw new PrivilegeException("PrivilegeHandler " + privilegeHandlerClassName + " could not be initialized"); + } + } + + /** + * @param element + * @return + */ @SuppressWarnings("unchecked") public static Map convertToParameterMap(Element element) { diff --git a/src/ch/eitchnet/privilege/helper/PrivilegeHelper.java b/src/ch/eitchnet/privilege/helper/PrivilegeHelper.java deleted file mode 100644 index 06029806e..000000000 --- a/src/ch/eitchnet/privilege/helper/PrivilegeHelper.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2010 - * - * Robert von Burg - * eitch@eitchnet.ch - * - * All rights reserved. - * - */ - -package ch.eitchnet.privilege.helper; - -import ch.eitchnet.privilege.base.PrivilegeContainer; -import ch.eitchnet.privilege.i18n.AccessDeniedException; -import ch.eitchnet.privilege.i18n.PrivilegeException; -import ch.eitchnet.privilege.model.Certificate; -import ch.eitchnet.privilege.model.internal.User; - -/** - * @author rvonburg - * - */ -public class PrivilegeHelper { - - public static boolean isUserPrivilegeAdmin(Certificate certificate) { - // validate certificate - if (!PrivilegeContainer.getInstance().getModelHandler().isCertificateValid(certificate)) { - throw new PrivilegeException("Certificate " + certificate + " is not valid!"); - } - - // get user object - User user = PrivilegeContainer.getInstance().getModelHandler().getUser(certificate.getUsername()); - if (user == null) { - throw new PrivilegeException( - "Oh boy, how did this happen: No User in user map although the certificate is valid! Certificate: " - + certificate); - } - - // validate user has PrivilegeAdmin role - if (!user.hasRole(PrivilegeContainer.PRIVILEGE_ADMIN_ROLE)) { - throw new AccessDeniedException("User does not have " + PrivilegeContainer.PRIVILEGE_ADMIN_ROLE - + " role! Certificate: " + certificate); - } else { - return true; - } - } -} diff --git a/src/ch/eitchnet/privilege/base/XmlConstants.java b/src/ch/eitchnet/privilege/helper/XmlConstants.java similarity index 90% rename from src/ch/eitchnet/privilege/base/XmlConstants.java rename to src/ch/eitchnet/privilege/helper/XmlConstants.java index d2c7400e4..5a42dbe34 100644 --- a/src/ch/eitchnet/privilege/base/XmlConstants.java +++ b/src/ch/eitchnet/privilege/helper/XmlConstants.java @@ -8,7 +8,7 @@ * */ -package ch.eitchnet.privilege.base; +package ch.eitchnet.privilege.helper; /** * @author rvonburg @@ -23,9 +23,7 @@ public class XmlConstants { public static final String XML_HANDLER_PERSISTENCE = "PersistenceHandler"; public static final String XML_HANDLER_ENCRYPTION = "EncryptionHandler"; - public static final String XML_HANDLER_SESSION = "SessionHandler"; - public static final String XML_HANDLER_POLICY = "PolicyHandler"; - public static final String XML_HANDLER_MODEL = "ModelHandler"; + public static final String XML_HANDLER_PRIVILEGE = "PrivilegeHandler"; public static final String XML_ROLES = "Roles"; public static final String XML_ROLE = "Role"; @@ -56,4 +54,5 @@ public class XmlConstants { public static final String XML_PARAM_ROLES_FILE = "rolesXmlFile"; public static final String XML_PARAM_USERS_FILE = "usersXmlFile"; public static final String XML_PARAM_PRIVILEGES_FILE = "privilegesXmlFile"; + public static final String XML_PARAM_BASE_PATH = "basePath"; } diff --git a/src/ch/eitchnet/privilege/model/PrivilegeRep.java b/src/ch/eitchnet/privilege/model/PrivilegeRep.java index 3ac250dda..bce742671 100644 --- a/src/ch/eitchnet/privilege/model/PrivilegeRep.java +++ b/src/ch/eitchnet/privilege/model/PrivilegeRep.java @@ -11,7 +11,6 @@ package ch.eitchnet.privilege.model; import java.io.Serializable; -import java.util.HashSet; import java.util.Set; /** @@ -39,8 +38,8 @@ public class PrivilegeRep implements Serializable { this.name = name; this.policy = policy; this.allAllowed = allAllowed; - this.denyList = new HashSet(denyList); - this.allowList = new HashSet(allowList); + this.denyList = denyList; + this.allowList = allowList; } /** diff --git a/src/ch/eitchnet/privilege/model/UserRep.java b/src/ch/eitchnet/privilege/model/UserRep.java index d2222f423..c7310d750 100644 --- a/src/ch/eitchnet/privilege/model/UserRep.java +++ b/src/ch/eitchnet/privilege/model/UserRep.java @@ -55,7 +55,8 @@ public class UserRep implements Serializable { } /** - * @param username the username to set + * @param username + * the username to set */ public void setUsername(String username) { this.username = username; @@ -69,7 +70,8 @@ public class UserRep implements Serializable { } /** - * @param firstname the firstname to set + * @param firstname + * the firstname to set */ public void setFirstname(String firstname) { this.firstname = firstname; @@ -83,7 +85,8 @@ public class UserRep implements Serializable { } /** - * @param surname the surname to set + * @param surname + * the surname to set */ public void setSurname(String surname) { this.surname = surname; @@ -97,7 +100,8 @@ public class UserRep implements Serializable { } /** - * @param userState the userState to set + * @param userState + * the userState to set */ public void setUserState(UserState userState) { this.userState = userState; @@ -111,7 +115,8 @@ public class UserRep implements Serializable { } /** - * @param roles the roles to set + * @param roles + * the roles to set */ public void setRoles(Set roles) { this.roles = roles; @@ -125,7 +130,8 @@ public class UserRep implements Serializable { } /** - * @param locale the locale to set + * @param locale + * the locale to set */ public void setLocale(Locale locale) { this.locale = locale; diff --git a/src/ch/eitchnet/privilege/model/UserState.java b/src/ch/eitchnet/privilege/model/UserState.java index 24cb3128e..955bdefaf 100644 --- a/src/ch/eitchnet/privilege/model/UserState.java +++ b/src/ch/eitchnet/privilege/model/UserState.java @@ -18,5 +18,5 @@ public enum UserState { NEW, ENABLED, DISABLED, - DEACTIVATED; + EXPIRED; } diff --git a/src/ch/eitchnet/privilege/model/internal/Privilege.java b/src/ch/eitchnet/privilege/model/internal/Privilege.java index cf89a1f26..da188f9d8 100644 --- a/src/ch/eitchnet/privilege/model/internal/Privilege.java +++ b/src/ch/eitchnet/privilege/model/internal/Privilege.java @@ -11,6 +11,7 @@ package ch.eitchnet.privilege.model.internal; import java.util.Collections; +import java.util.HashSet; import java.util.Set; import ch.eitchnet.privilege.model.PrivilegeRep; @@ -79,7 +80,7 @@ public final class Privilege { * @return a {@link PrivilegeRep} which is a representation of this object used to serialize and view on clients */ public PrivilegeRep asPrivilegeRep() { - return new PrivilegeRep(name, policy, allAllowed, denyList, allowList); + return new PrivilegeRep(name, policy, allAllowed, new HashSet(denyList), new HashSet(allowList)); } /** diff --git a/src/ch/eitchnet/privilege/model/internal/Role.java b/src/ch/eitchnet/privilege/model/internal/Role.java index 17705febf..a5390bea5 100644 --- a/src/ch/eitchnet/privilege/model/internal/Role.java +++ b/src/ch/eitchnet/privilege/model/internal/Role.java @@ -11,6 +11,7 @@ package ch.eitchnet.privilege.model.internal; import java.util.Collections; +import java.util.HashSet; import java.util.Set; import ch.eitchnet.privilege.model.RoleRep; @@ -60,7 +61,7 @@ public final class Role { * @return a {@link RoleRep} which is a representation of this object used to serialize and view on clients */ public RoleRep asRoleRep() { - return new RoleRep(name, privileges); + return new RoleRep(name, new HashSet(privileges)); } /** diff --git a/src/ch/eitchnet/privilege/model/internal/User.java b/src/ch/eitchnet/privilege/model/internal/User.java index b14dad127..787a6d8c6 100644 --- a/src/ch/eitchnet/privilege/model/internal/User.java +++ b/src/ch/eitchnet/privilege/model/internal/User.java @@ -11,11 +11,10 @@ package ch.eitchnet.privilege.model.internal; import java.util.Collections; +import java.util.HashSet; import java.util.Locale; import java.util.Set; -import ch.eitchnet.privilege.helper.PrivilegeHelper; -import ch.eitchnet.privilege.model.Certificate; import ch.eitchnet.privilege.model.UserRep; import ch.eitchnet.privilege.model.UserState; @@ -70,13 +69,19 @@ public final class User { } /** - * @return the password + * + * @param privilegeHandler + * @param certificate + * + * @return */ - public String getPassword(Certificate certificate) { - if (PrivilegeHelper.isUserPrivilegeAdmin(certificate)) - return password; - else - return null; + public String getPassword() { + + // TODO is it possible that there is a hidden way of accessing this + // field even though? The User object should be private, but maybe I + // forgot something? + + return password; } /** @@ -135,7 +140,7 @@ public final class User { * @return a {@link UserRep} which is a representation of this object used to serialize and view on clients */ public UserRep asUserRep() { - return new UserRep(username, firstname, surname, userState, roles, locale); + return new UserRep(username, firstname, surname, userState, new HashSet(roles), locale); } /** From c5fe322c5f7a7e86882cc03577db1006fab8f89d Mon Sep 17 00:00:00 2001 From: eitch Date: Sat, 18 Sep 2010 20:03:09 +0000 Subject: [PATCH 032/173] --- config/PrivilegeContainer.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/config/PrivilegeContainer.xml b/config/PrivilegeContainer.xml index e87acad3c..c0dd61837 100644 --- a/config/PrivilegeContainer.xml +++ b/config/PrivilegeContainer.xml @@ -3,22 +3,22 @@ + + + + + - - - - - \ No newline at end of file From bef8474114b2f1cc1042f74e696bc76eb4b04939 Mon Sep 17 00:00:00 2001 From: eitch Date: Sat, 18 Sep 2010 20:03:29 +0000 Subject: [PATCH 033/173] --- src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java b/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java index 9f97b4377..6d6ecb46f 100644 --- a/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java @@ -285,6 +285,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { + XmlConstants.XML_PARAM_BASE_PATH + " is invalid"); } + // ROLES // get roles file name String rolesFileName = parameterMap.get(XmlConstants.XML_PARAM_ROLES_FILE); if (rolesFileName == null || rolesFileName.isEmpty()) { @@ -307,6 +308,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { readRoles(rolesRootElement); rolesFileDate = rolesFile.lastModified(); + // USERS // get users file name String usersFileName = parameterMap.get(XmlConstants.XML_PARAM_USERS_FILE); if (usersFileName == null || usersFileName.isEmpty()) { @@ -329,6 +331,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { readUsers(usersRootElement); usersFileDate = usersFile.lastModified(); + // PRIVILEGES // get privileges file name String privilegesFileName = parameterMap.get(XmlConstants.XML_PARAM_PRIVILEGES_FILE); if (privilegesFileName == null || privilegesFileName.isEmpty()) { @@ -351,6 +354,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { readPrivileges(privilegesRootElement); privilegesFileDate = privilegesFile.lastModified(); + // POLICIES // get policy file name String policyFileName = parameterMap.get(XmlConstants.XML_PARAM_POLICY_FILE); if (policyFileName == null || policyFileName.isEmpty()) { From 1d410a6edeafda32efe9ee11f3ff23f72ac6af81 Mon Sep 17 00:00:00 2001 From: eitch Date: Sat, 18 Sep 2010 20:13:31 +0000 Subject: [PATCH 034/173] --- config/PrivilegeContainer.xml | 20 ++--- ...nHelper.java => InitializationHelper.java} | 17 +++- .../privilege/test/PrivilegeTest.java | 87 ++++++++----------- 3 files changed, 61 insertions(+), 63 deletions(-) rename src/ch/eitchnet/privilege/helper/{ConfigurationHelper.java => InitializationHelper.java} (88%) diff --git a/config/PrivilegeContainer.xml b/config/PrivilegeContainer.xml index c0dd61837..20b2be359 100644 --- a/config/PrivilegeContainer.xml +++ b/config/PrivilegeContainer.xml @@ -1,7 +1,15 @@ - + + + + + + + + + @@ -11,14 +19,4 @@ - - - - - - - - - - \ No newline at end of file diff --git a/src/ch/eitchnet/privilege/helper/ConfigurationHelper.java b/src/ch/eitchnet/privilege/helper/InitializationHelper.java similarity index 88% rename from src/ch/eitchnet/privilege/helper/ConfigurationHelper.java rename to src/ch/eitchnet/privilege/helper/InitializationHelper.java index 8fc6fda6e..d61e36d04 100644 --- a/src/ch/eitchnet/privilege/helper/ConfigurationHelper.java +++ b/src/ch/eitchnet/privilege/helper/InitializationHelper.java @@ -27,14 +27,14 @@ import ch.eitchnet.privilege.i18n.PrivilegeException; * @author rvonburg * */ -public class ConfigurationHelper { +public class InitializationHelper { - private static final Logger logger = Logger.getLogger(ConfigurationHelper.class); + private static final Logger logger = Logger.getLogger(InitializationHelper.class); /** * @param privilegeContainerXmlFile */ - public static void initializeFromXml(File privilegeContainerXmlFile) { + public static PrivilegeHandler initializeFromXml(File privilegeContainerXmlFile) { // make sure file exists if (!privilegeContainerXmlFile.exists()) { @@ -103,6 +103,8 @@ public class ConfigurationHelper { logger.error(e, e); throw new PrivilegeException("PrivilegeHandler " + privilegeHandlerClassName + " could not be initialized"); } + + return privilegeHandler; } /** @@ -114,7 +116,16 @@ public class ConfigurationHelper { Map parameterMap = new HashMap(); + // if element is null then there are no parameters, so return empty map + if (element == null) + return parameterMap; + List elements = element.elements(XmlConstants.XML_PARAMETER); + + // if elements is null or empty then there are no parameters, so return empty map + if (elements == null || elements.isEmpty()) + return parameterMap; + for (Element parameter : elements) { String name = parameter.attributeValue(XmlConstants.XML_ATTR_NAME); String value = parameter.attributeValue(XmlConstants.XML_ATTR_VALUE); diff --git a/test/ch/eitchnet/privilege/test/PrivilegeTest.java b/test/ch/eitchnet/privilege/test/PrivilegeTest.java index 2f52de634..90960ab95 100644 --- a/test/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/test/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -21,8 +21,8 @@ import org.apache.log4j.PatternLayout; import org.junit.BeforeClass; import org.junit.Test; -import ch.eitchnet.privilege.base.PrivilegeContainer; import ch.eitchnet.privilege.handler.PrivilegeHandler; +import ch.eitchnet.privilege.helper.InitializationHelper; import ch.eitchnet.privilege.i18n.AccessDeniedException; import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.Certificate; @@ -38,61 +38,64 @@ public class PrivilegeTest { private static final Logger logger = Logger.getLogger(PrivilegeTest.class); + private static PrivilegeHandler privilegeHandler; + /** * @throws java.lang.Exception */ @BeforeClass public static void init() throws Exception { - // set up log4j - BasicConfigurator.resetConfiguration(); - BasicConfigurator.configure(new ConsoleAppender(new PatternLayout("%d %5p [%t] %C{1} %M - %m%n"))); - Logger.getRootLogger().setLevel(Level.INFO); + try { + // set up log4j + BasicConfigurator.resetConfiguration(); + BasicConfigurator.configure(new ConsoleAppender(new PatternLayout("%d %5p [%t] %C{1} %M - %m%n"))); + Logger.getRootLogger().setLevel(Level.INFO); - // initialize container - String pwd = System.getProperty("user.dir"); - File privilegeContainerXml = new File(pwd + "/config/PrivilegeContainer.xml"); - PrivilegeContainer privilegeContainer = PrivilegeContainer.getInstance(); - privilegeContainer.initialize(privilegeContainerXml); + // initialize container + String pwd = System.getProperty("user.dir"); + File privilegeContainerXmlFile = new File(pwd + "/config/PrivilegeContainer.xml"); + privilegeHandler = InitializationHelper.initializeFromXml(privilegeContainerXmlFile); + } catch (Exception e) { + logger.error(e, e); + + throw new RuntimeException("Initialization failed: " + e.getLocalizedMessage(), e); + } } @Test public void testAuthenticationOk() throws Exception { - Certificate certificate = PrivilegeContainer.getInstance().getModelHandler().authenticate("eitch", - "1234567890"); + Certificate certificate = privilegeHandler.authenticate("eitch", "1234567890"); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); } @Test(expected = AccessDeniedException.class) public void testFailAuthenticationNOk() throws Exception { - Certificate certificate = PrivilegeContainer.getInstance().getModelHandler().authenticate("eitch", "123"); + Certificate certificate = privilegeHandler.authenticate("eitch", "123"); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); } @Test(expected = PrivilegeException.class) public void testFailAuthenticationPWNull() throws Exception { - Certificate certificate = PrivilegeContainer.getInstance().getModelHandler().authenticate("eitch", null); + Certificate certificate = privilegeHandler.authenticate("eitch", null); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); } @Test public void testAddUserBobWithPW() throws Exception { - Certificate certificate = PrivilegeContainer.getInstance().getModelHandler().authenticate("eitch", - "1234567890"); - - PrivilegeHandler modelHandler = PrivilegeContainer.getInstance().getModelHandler(); + Certificate certificate = privilegeHandler.authenticate("eitch", "1234567890"); // let's add a new user bob UserRep userRep = new UserRep("bob", "Bob", "Newman", UserState.NEW, new HashSet(), null); - modelHandler.addOrReplaceUser(certificate, userRep, null); + privilegeHandler.addOrReplaceUser(certificate, userRep, null); logger.info("Added user bob"); // set bob's password - modelHandler.setUserPassword(certificate, "bob", "12345678901"); + privilegeHandler.setUserPassword(certificate, "bob", "12345678901"); logger.info("Set Bob's password"); } @@ -104,17 +107,14 @@ public class PrivilegeTest { @Test(expected = AccessDeniedException.class) public void testFailAuthAsBob() throws Exception { - PrivilegeContainer.getInstance().getModelHandler().authenticate("bob", "12345678901"); + privilegeHandler.authenticate("bob", "12345678901"); } @Test public void testEnableUserBob() throws Exception { - Certificate certificate = PrivilegeContainer.getInstance().getModelHandler().authenticate("eitch", - "1234567890"); - - PrivilegeHandler modelHandler = PrivilegeContainer.getInstance().getModelHandler(); - modelHandler.setUserState(certificate, "bob", UserState.ENABLED); + Certificate certificate = privilegeHandler.authenticate("eitch", "1234567890"); + privilegeHandler.setUserState(certificate, "bob", UserState.ENABLED); } /** @@ -125,25 +125,21 @@ public class PrivilegeTest { @Test(expected = PrivilegeException.class) public void testFailAuthUserBob() throws Exception { - Certificate certificate = PrivilegeContainer.getInstance().getModelHandler().authenticate("bob", - "12345678901"); + Certificate certificate = privilegeHandler.authenticate("bob", "12345678901"); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); } @Test public void testAddUserRoleToBob() throws Exception { - Certificate certificate = PrivilegeContainer.getInstance().getModelHandler().authenticate("eitch", - "1234567890"); - - PrivilegeHandler modelHandler = PrivilegeContainer.getInstance().getModelHandler(); - modelHandler.addRoleToUser(certificate, "bob", "user"); + Certificate certificate = privilegeHandler.authenticate("eitch", "1234567890"); + privilegeHandler.addRoleToUser(certificate, "bob", "user"); } @Test public void testAuthAsBob() throws Exception { - PrivilegeContainer.getInstance().getModelHandler().authenticate("bob", "12345678901"); + privilegeHandler.authenticate("bob", "12345678901"); } /** @@ -154,50 +150,43 @@ public class PrivilegeTest { @Test(expected = AccessDeniedException.class) public void testFailAddUserTedAsBob() throws Exception { - Certificate certificate = PrivilegeContainer.getInstance().getModelHandler().authenticate("bob", - "12345678901"); + Certificate certificate = privilegeHandler.authenticate("bob", "12345678901"); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); // let's add a new user bob UserRep userRep = new UserRep("bob", "Bob", "Newman", UserState.NEW, new HashSet(), null); - PrivilegeContainer.getInstance().getModelHandler().addOrReplaceUser(certificate, userRep, null); + privilegeHandler.addOrReplaceUser(certificate, userRep, null); logger.info("Added user bob"); } @Test public void testAddAdminRoleToBob() throws Exception { - Certificate certificate = PrivilegeContainer.getInstance().getModelHandler().authenticate("eitch", - "1234567890"); - - PrivilegeHandler modelHandler = PrivilegeContainer.getInstance().getModelHandler(); - modelHandler.addRoleToUser(certificate, "bob", PrivilegeContainer.PRIVILEGE_ADMIN_ROLE); + Certificate certificate = privilegeHandler.authenticate("eitch", "1234567890"); + privilegeHandler.addRoleToUser(certificate, "bob", PrivilegeHandler.PRIVILEGE_ADMIN_ROLE); } @Test public void testAddUserTedAsBob() throws Exception { - Certificate certificate = PrivilegeContainer.getInstance().getModelHandler().authenticate("bob", - "12345678901"); + Certificate certificate = privilegeHandler.authenticate("bob", "12345678901"); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); // let's add a new user ted UserRep userRep = new UserRep("ted", "Ted", "Newman", UserState.NEW, new HashSet(), null); - PrivilegeContainer.getInstance().getModelHandler().addOrReplaceUser(certificate, userRep, null); + privilegeHandler.addOrReplaceUser(certificate, userRep, null); logger.info("Added user bob"); } @Test public void testPerformRestrictable() throws Exception { - Certificate certificate = PrivilegeContainer.getInstance().getModelHandler().authenticate("eitch", - "1234567890"); + Certificate certificate = privilegeHandler.authenticate("eitch", "1234567890"); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); // see if eitch can perform restrictable Restrictable restrictable = new TestRestrictable(); - boolean actionAllowed = PrivilegeContainer.getInstance().getModelHandler().actionAllowed(certificate, - restrictable); + boolean actionAllowed = privilegeHandler.actionAllowed(certificate, restrictable); org.junit.Assert.assertTrue("eitch may not perform restrictable!", actionAllowed); } } From a3e2515f3874fb25bdb72f8f17ff7ebeabbfacd6 Mon Sep 17 00:00:00 2001 From: eitch Date: Sun, 19 Sep 2010 18:57:23 +0000 Subject: [PATCH 035/173] [Bugfix] cleaned up a lot of warnings --- .settings/org.eclipse.jdt.core.prefs | 77 ++++++- .../handler/DefaultEncryptionHandler.java | 14 +- .../handler/DefaultPrivilegeHandler.java | 190 +++++++++++------- .../privilege/handler/EncryptionHandler.java | 16 +- .../privilege/handler/PersistenceHandler.java | 131 ++++++++---- .../privilege/handler/PrivilegeHandler.java | 2 + .../handler/XmlPersistenceHandler.java | 165 ++++++++------- .../helper/BootstrapConfigurationHelper.java | 9 +- .../helper/InitializationHelper.java | 1 + .../eitchnet/privilege/model/Certificate.java | 51 +++-- .../privilege/model/PrivilegeRep.java | 10 +- .../privilege/model/Restrictable.java | 6 + src/ch/eitchnet/privilege/model/RoleRep.java | 4 +- src/ch/eitchnet/privilege/model/UserRep.java | 12 +- .../eitchnet/privilege/model/UserState.java | 2 +- .../privilege/model/internal/Privilege.java | 54 ++--- .../privilege/model/internal/Role.java | 24 +-- .../privilege/model/internal/Session.java | 50 +++-- .../privilege/model/internal/User.java | 38 ++-- .../privilege/test/TestRestrictable.java | 8 +- 20 files changed, 525 insertions(+), 339 deletions(-) diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs index 650ae7f9a..e83bae06a 100644 --- a/.settings/org.eclipse.jdt.core.prefs +++ b/.settings/org.eclipse.jdt.core.prefs @@ -1,4 +1,4 @@ -#Wed May 19 19:28:29 CEST 2010 +#Sun Sep 19 16:32:10 CEST 2010 eclipse.preferences.version=1 org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 @@ -7,6 +7,81 @@ org.eclipse.jdt.core.compiler.compliance=1.6 org.eclipse.jdt.core.compiler.debug.lineNumber=generate org.eclipse.jdt.core.compiler.debug.localVariable=generate org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.doc.comment.support=enabled +org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.autoboxing=warning +org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning +org.eclipse.jdt.core.compiler.problem.deadCode=warning +org.eclipse.jdt.core.compiler.problem.deprecation=warning +org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled +org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=enabled +org.eclipse.jdt.core.compiler.problem.discouragedReference=warning +org.eclipse.jdt.core.compiler.problem.emptyStatement=warning org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning +org.eclipse.jdt.core.compiler.problem.fatalOptionalError=enabled +org.eclipse.jdt.core.compiler.problem.fieldHiding=warning +org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning +org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning +org.eclipse.jdt.core.compiler.problem.forbiddenReference=error +org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning +org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning +org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=warning +org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=warning +org.eclipse.jdt.core.compiler.problem.invalidJavadoc=warning +org.eclipse.jdt.core.compiler.problem.invalidJavadocTags=enabled +org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsDeprecatedRef=enabled +org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsNotVisibleRef=enabled +org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsVisibility=private +org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore +org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning +org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=warning +org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=warning +org.eclipse.jdt.core.compiler.problem.missingJavadocComments=warning +org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsOverriding=enabled +org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsVisibility=private +org.eclipse.jdt.core.compiler.problem.missingJavadocTagDescription=all_standard_tags +org.eclipse.jdt.core.compiler.problem.missingJavadocTags=warning +org.eclipse.jdt.core.compiler.problem.missingJavadocTagsOverriding=enabled +org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=private +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=warning +org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning +org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=warning +org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning +org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning +org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore +org.eclipse.jdt.core.compiler.problem.nullReference=warning +org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning +org.eclipse.jdt.core.compiler.problem.parameterAssignment=warning +org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=warning +org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning +org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning +org.eclipse.jdt.core.compiler.problem.redundantNullCheck=warning +org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=warning +org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=enabled +org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning +org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled +org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=warning +org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning +org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning +org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=warning +org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning +org.eclipse.jdt.core.compiler.problem.unnecessaryElse=warning +org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=warning +org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=warning +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=warning +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled +org.eclipse.jdt.core.compiler.problem.unusedImport=warning +org.eclipse.jdt.core.compiler.problem.unusedLabel=warning +org.eclipse.jdt.core.compiler.problem.unusedLocal=warning +org.eclipse.jdt.core.compiler.problem.unusedParameter=warning +org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled +org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning +org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning +org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning org.eclipse.jdt.core.compiler.source=1.6 diff --git a/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java b/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java index 5fd3c4664..1ee77872d 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java @@ -38,10 +38,10 @@ public class DefaultEncryptionHandler implements EncryptionHandler { public String convertToHash(String string) { try { - return EncryptionHelper.encryptString(hashAlgorithm, string); + return EncryptionHelper.encryptString(this.hashAlgorithm, string); } catch (NoSuchAlgorithmException e) { - throw new PrivilegeException("Algorithm " + hashAlgorithm + " was not found!", e); + throw new PrivilegeException("Algorithm " + this.hashAlgorithm + " was not found!", e); } catch (UnsupportedEncodingException e) { throw new PrivilegeException("Charset ASCII is not supported!", e); } @@ -53,7 +53,7 @@ public class DefaultEncryptionHandler implements EncryptionHandler { @Override public String nextToken() { byte[] bytes = new byte[16]; - secureRandom.nextBytes(bytes); + this.secureRandom.nextBytes(bytes); String randomString = new String(bytes); //String randomString = new BigInteger(80, secureRandom).toString(32); // 80 big integer bits = 16 chars return randomString; @@ -65,11 +65,11 @@ public class DefaultEncryptionHandler implements EncryptionHandler { @Override public void initialize(Map parameterMap) { - secureRandom = new SecureRandom(); + this.secureRandom = new SecureRandom(); // get hash algorithm parameters - hashAlgorithm = parameterMap.get(XmlConstants.XML_PARAM_HASH_ALGORITHM); - if (hashAlgorithm == null || hashAlgorithm.isEmpty()) { + this.hashAlgorithm = parameterMap.get(XmlConstants.XML_PARAM_HASH_ALGORITHM); + if (this.hashAlgorithm == null || this.hashAlgorithm.isEmpty()) { throw new PrivilegeException("[" + EncryptionHandler.class.getName() + "] Defined parameter " + XmlConstants.XML_PARAM_HASH_ALGORITHM + " is invalid"); } @@ -77,7 +77,7 @@ public class DefaultEncryptionHandler implements EncryptionHandler { // test hash algorithm try { convertToHash("test"); - logger.info("Using hashing algorithm " + hashAlgorithm); + logger.info("Using hashing algorithm " + this.hashAlgorithm); } catch (Exception e) { throw new PrivilegeException("[" + EncryptionHandler.class.getName() + "] Defined parameter " + XmlConstants.XML_PARAM_HASH_ALGORITHM + " is invalid because of underlying exception: " diff --git a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 1119e8aff..28ee4c489 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -38,13 +38,29 @@ import ch.eitchnet.privilege.policy.PrivilegePolicy; */ public class DefaultPrivilegeHandler implements PrivilegeHandler { + /** + * log4j logger + */ private static final Logger logger = Logger.getLogger(DefaultPrivilegeHandler.class); + /** + * last assigned id for the {@link Session}s + */ private static long lastSessionId; + /** + * Map keeping a reference to all active sessions with their certificates + */ private Map sessionMap; + /** + * 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; /** @@ -52,7 +68,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { */ @Override public PrivilegeRep getPrivilege(String privilegeName) { - return persistenceHandler.getPrivilege(privilegeName).asPrivilegeRep(); + return this.persistenceHandler.getPrivilege(privilegeName).asPrivilegeRep(); } /** @@ -60,7 +76,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { */ @Override public RoleRep getRole(String roleName) { - return persistenceHandler.getRole(roleName).asRoleRep(); + return this.persistenceHandler.getRole(roleName).asRoleRep(); } /** @@ -68,7 +84,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { */ @Override public UserRep getUser(String username) { - return persistenceHandler.getUser(username).asUserRep(); + return this.persistenceHandler.getUser(username).asUserRep(); } /** @@ -76,7 +92,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { */ @Override public PrivilegePolicy getPolicy(String policyName) { - return persistenceHandler.getPolicy(policyName); + return this.persistenceHandler.getPolicy(policyName); } /** @@ -94,7 +110,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { .isAllAllowed(), privilegeRep.getDenyList(), privilegeRep.getAllowList()); // delegate to persistence handler - persistenceHandler.addOrReplacePrivilege(privilege); + this.persistenceHandler.addOrReplacePrivilege(privilege); } /** @@ -111,7 +127,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { Role role = new Role(roleRep.getName(), roleRep.getPrivileges()); // delegate to persistence handler - persistenceHandler.addOrReplaceRole(role); + this.persistenceHandler.addOrReplaceRole(role); } /** @@ -124,22 +140,22 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // validate who is doing this validateIsPrivilegeAdmin(certificate); - // validate password meets basic requirements - validatePassword(password); + String passwordHash = null; + if (password != null) { - // hash password - String passwordHash; - if (password == null) - passwordHash = null; - else - passwordHash = encryptionHandler.convertToHash(password); + // validate password meets basic requirements + validatePassword(password); + + // hash password + passwordHash = this.encryptionHandler.convertToHash(password); + } // create new user User user = new User(userRep.getUsername(), passwordHash, userRep.getFirstname(), userRep.getSurname(), userRep .getUserState(), userRep.getRoles(), userRep.getLocale()); // delegate to persistence handler - persistenceHandler.addOrReplaceUser(user); + this.persistenceHandler.addOrReplaceUser(user); } /** @@ -153,7 +169,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { validateIsPrivilegeAdmin(certificate); // get role - Role role = persistenceHandler.getRole(roleName); + Role role = this.persistenceHandler.getRole(roleName); if (role == null) { throw new PrivilegeException("Role " + roleName + " does not exist!"); } @@ -178,7 +194,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { Role newRole = new Role(role.getName(), newPrivileges); // delegate role replacement to persistence handler - persistenceHandler.addOrReplaceRole(newRole); + this.persistenceHandler.addOrReplaceRole(newRole); } /** @@ -192,7 +208,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { validateIsPrivilegeAdmin(certificate); // get user - User user = persistenceHandler.getUser(username); + User user = this.persistenceHandler.getUser(username); if (user == null) { throw new PrivilegeException("User " + username + " does not exist!"); } @@ -217,7 +233,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { .getState(), newRoles, user.getLocale()); // delegate user replacement to persistence handler - persistenceHandler.addOrReplaceUser(newUser); + this.persistenceHandler.addOrReplaceUser(newUser); } /** @@ -231,13 +247,13 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { validateIsPrivilegeAdmin(certificate); // delegate privilege removal to persistence handler - Privilege removedPrivilege = persistenceHandler.removePrivilege(privilegeName); + Privilege removedPrivilege = this.persistenceHandler.removePrivilege(privilegeName); + + if (removedPrivilege == null) + return null; // return privilege rep if it was removed - if (removedPrivilege != null) - return removedPrivilege.asPrivilegeRep(); - else - return null; + return removedPrivilege.asPrivilegeRep(); } /** @@ -251,7 +267,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { validateIsPrivilegeAdmin(certificate); // get role - Role role = persistenceHandler.getRole(roleName); + Role role = this.persistenceHandler.getRole(roleName); if (role == null) { throw new PrivilegeException("Role " + roleName + " does not exist!"); } @@ -269,7 +285,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { Role newRole = new Role(role.getName(), newPrivileges); // delegate user replacement to persistence handler - persistenceHandler.addOrReplaceRole(newRole); + this.persistenceHandler.addOrReplaceRole(newRole); } /** @@ -283,13 +299,13 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { validateIsPrivilegeAdmin(certificate); // delegate role removal to persistence handler - Role removedRole = persistenceHandler.removeRole(roleName); + Role removedRole = this.persistenceHandler.removeRole(roleName); - // return role rep if it was removed - if (removedRole != null) - return removedRole.asRoleRep(); - else + if (removedRole == null) return null; + + // return role rep if it was removed + return removedRole.asRoleRep(); } /** @@ -303,7 +319,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { validateIsPrivilegeAdmin(certificate); // get User - User user = persistenceHandler.getUser(username); + User user = this.persistenceHandler.getUser(username); if (user == null) { throw new PrivilegeException("User " + username + " does not exist!"); } @@ -322,7 +338,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { .getState(), newRoles, user.getLocale()); // delegate user replacement to persistence handler - persistenceHandler.addOrReplaceUser(newUser); + this.persistenceHandler.addOrReplaceUser(newUser); } /** @@ -336,13 +352,15 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { validateIsPrivilegeAdmin(certificate); // delegate user removal to persistence handler - User removedUser = persistenceHandler.removeUser(username); + User removedUser = this.persistenceHandler.removeUser(username); // return user rep if it was removed - if (removedUser != null) - return removedUser.asUserRep(); - else + if (removedUser == null) return null; + + // return user rep if it was removed + return removedUser.asUserRep(); + } /** @@ -356,7 +374,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { validateIsPrivilegeAdmin(certificate); // get Privilege - Privilege privilege = persistenceHandler.getPrivilege(privilegeName); + Privilege privilege = this.persistenceHandler.getPrivilege(privilegeName); if (privilege == null) { throw new PrivilegeException("Privilege " + privilegeName + " does not exist!"); } @@ -373,7 +391,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { .getDenyList(), privilege.getAllowList()); // delegate privilege replacement to persistence handler - persistenceHandler.addOrReplacePrivilege(newPrivilege); + this.persistenceHandler.addOrReplacePrivilege(newPrivilege); } /** @@ -387,7 +405,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { validateIsPrivilegeAdmin(certificate); // get Privilege - Privilege privilege = persistenceHandler.getPrivilege(privilegeName); + Privilege privilege = this.persistenceHandler.getPrivilege(privilegeName); if (privilege == null) { throw new PrivilegeException("Privilege " + privilegeName + " does not exist!"); } @@ -397,7 +415,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { privilege.getDenyList(), allowList); // delegate privilege replacement to persistence handler - persistenceHandler.addOrReplacePrivilege(newPrivilege); + this.persistenceHandler.addOrReplacePrivilege(newPrivilege); } /** @@ -411,7 +429,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { validateIsPrivilegeAdmin(certificate); // get Privilege - Privilege privilege = persistenceHandler.getPrivilege(privilegeName); + Privilege privilege = this.persistenceHandler.getPrivilege(privilegeName); if (privilege == null) { throw new PrivilegeException("Privilege " + privilegeName + " does not exist!"); } @@ -421,7 +439,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { denyList, privilege.getAllowList()); // delegate privilege replacement to persistence handler - persistenceHandler.addOrReplacePrivilege(newPrivilege); + this.persistenceHandler.addOrReplacePrivilege(newPrivilege); } /** @@ -435,7 +453,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { validateIsPrivilegeAdmin(certificate); // get Privilege - Privilege privilege = persistenceHandler.getPrivilege(privilegeName); + Privilege privilege = this.persistenceHandler.getPrivilege(privilegeName); if (privilege == null) { throw new PrivilegeException("Privilege " + privilegeName + " does not exist!"); } @@ -445,7 +463,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { .getDenyList(), privilege.getAllowList()); // delegate privilege replacement to persistence handler - persistenceHandler.addOrReplacePrivilege(newPrivilege); + this.persistenceHandler.addOrReplacePrivilege(newPrivilege); } /** @@ -459,7 +477,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { validateIsPrivilegeAdmin(certificate); // get User - User user = persistenceHandler.getUser(username); + User user = this.persistenceHandler.getUser(username); if (user == null) { throw new PrivilegeException("User " + username + " does not exist!"); } @@ -469,7 +487,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { .getState(), user.getRoles(), locale); // delegate user replacement to persistence handler - persistenceHandler.addOrReplaceUser(newUser); + this.persistenceHandler.addOrReplaceUser(newUser); } /** @@ -483,7 +501,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { validateIsPrivilegeAdmin(certificate); // get User - User user = persistenceHandler.getUser(username); + User user = this.persistenceHandler.getUser(username); if (user == null) { throw new PrivilegeException("User " + username + " does not exist!"); } @@ -493,7 +511,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { .getRoles(), user.getLocale()); // delegate user replacement to persistence handler - persistenceHandler.addOrReplaceUser(newUser); + this.persistenceHandler.addOrReplaceUser(newUser); } /** @@ -507,20 +525,27 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { validateIsPrivilegeAdmin(certificate); // get User - User user = persistenceHandler.getUser(username); + User user = this.persistenceHandler.getUser(username); if (user == null) { throw new PrivilegeException("User " + username + " does not exist!"); } - // hash password - String passwordHash = encryptionHandler.convertToHash(password); + 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(user.getUsername(), passwordHash, user.getFirstname(), user.getSurname(), user .getState(), user.getRoles(), user.getLocale()); // delegate user replacement to persistence handler - persistenceHandler.addOrReplaceUser(newUser); + this.persistenceHandler.addOrReplaceUser(newUser); } /** @@ -534,7 +559,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { validateIsPrivilegeAdmin(certificate); // get User - User user = persistenceHandler.getUser(username); + User user = this.persistenceHandler.getUser(username); if (user == null) { throw new PrivilegeException("User " + username + " does not exist!"); } @@ -544,11 +569,11 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { user.getRoles(), user.getLocale()); // delegate user replacement to persistence handler - persistenceHandler.addOrReplaceUser(newUser); + this.persistenceHandler.addOrReplaceUser(newUser); } /** - * @see ch.eitchnet.privilege.handler.SessionHandler#authenticate(java.lang.String, java.lang.String) + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#authenticate(java.lang.String, java.lang.String) * * @throws AccessDeniedException * if the user credentials are not valid @@ -563,16 +588,19 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { throw new PrivilegeException("The given password is shorter than 3 characters"); // we only work with hashed passwords - String passwordHash = encryptionHandler.convertToHash(password); + String passwordHash = this.encryptionHandler.convertToHash(password); // get user object - User user = persistenceHandler.getUser(username); + User user = this.persistenceHandler.getUser(username); // no user means no authentication if (user == null) throw new AccessDeniedException("There is no user defined with the credentials: " + username + " / ***..."); // validate password - if (!user.isPassword(passwordHash)) + String pwHash = user.getPassword(); + if (pwHash == null) + throw new AccessDeniedException("User has no password and may not login!"); + if (!pwHash.equals(passwordHash)) throw new AccessDeniedException("Password is incorrect for " + username + " / ***..."); // validate if user is allowed to login @@ -585,8 +613,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } // get 2 auth tokens - String authToken = encryptionHandler.nextToken(); - String authPassword = encryptionHandler.nextToken(); + String authToken = this.encryptionHandler.nextToken(); + String authPassword = this.encryptionHandler.nextToken(); // get next session id String sessionId = nextSessionId(); @@ -597,7 +625,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // create and save a new session Session session = new Session(sessionId, authToken, authPassword, user.getUsername(), System .currentTimeMillis()); - sessionMap.put(sessionId, new CertificateSessionPair(session, certificate)); + this.sessionMap.put(sessionId, new CertificateSessionPair(session, certificate)); // log logger.info("Authenticated: " + session); @@ -609,12 +637,13 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { /** * TODO What is better, validate from {@link Restrictable} to {@link User} or the opposite direction? * - * @see ch.eitchnet.privilege.handler.SessionHandler#actionAllowed(ch.eitchnet.privilege.model.Certificate, - * ch.eitchnet.privilege.model.Restrictable) * * @throws AccessDeniedException * if the {@link Certificate} is not for a currently logged in {@link User} or if the user may not * perform the action defined by the {@link Restrictable} implementation + * + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#actionAllowed(ch.eitchnet.privilege.model.Certificate, + * ch.eitchnet.privilege.model.Restrictable) */ @Override public boolean actionAllowed(Certificate certificate, Restrictable restrictable) { @@ -631,7 +660,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { throw new PrivilegeException("Restrictable may not be null!"); // get user object - User user = persistenceHandler.getUser(certificate.getUsername()); + User user = this.persistenceHandler.getUser(certificate.getUsername()); if (user == null) { throw new PrivilegeException( "Oh boy, how did this happen: No User in user map although the certificate is valid!"); @@ -644,7 +673,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // now iterate roles and validate on policies for (String roleName : user.getRoles()) { - Role role = persistenceHandler.getRole(roleName); + Role role = this.persistenceHandler.getRole(roleName); if (role == null) { logger.error("No role is defined with name " + roleName + " which is configured for user " + user); continue; @@ -661,7 +690,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } /** - * @see ch.eitchnet.privilege.handler.PolicyHandler#actionAllowed(ch.eitchnet.privilege.model.internal.Role, + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#actionAllowed(ch.eitchnet.privilege.model.internal.Role, * ch.eitchnet.privilege.model.Restrictable) */ @Override @@ -687,7 +716,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } // get the privilege for this restrictable - Privilege privilege = persistenceHandler.getPrivilege(privilegeName); + Privilege privilege = this.persistenceHandler.getPrivilege(privilegeName); if (privilege == null) { throw new PrivilegeException("No Privilege exists with the name " + privilegeName + " for Restrictable " + restrictable.getClass().getName()); @@ -705,7 +734,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } /** - * @see ch.eitchnet.privilege.handler.SessionHandler#isCertificateValid(ch.eitchnet.privilege.model.Certificate) + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#isCertificateValid(ch.eitchnet.privilege.model.Certificate) */ @Override public boolean isCertificateValid(Certificate certificate) { @@ -715,7 +744,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { throw new PrivilegeException("Certificate may not be null!"); // first see if a session exists for this certificate - CertificateSessionPair certificateSessionPair = sessionMap.get(certificate.getSessionId()); + CertificateSessionPair certificateSessionPair = this.sessionMap.get(certificate.getSessionId()); if (certificateSessionPair == null) throw new AccessDeniedException("There is no session information for " + certificate.toString()); @@ -732,15 +761,16 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { + certificate.getSessionId()); // get user object - User user = persistenceHandler.getUser(certificateSessionPair.session.getUsername()); + User user = this.persistenceHandler.getUser(certificateSessionPair.session.getUsername()); // if user exists, then certificate is valid if (user == null) { throw new PrivilegeException( "Oh boy, how did this happen: No User in user map although the certificate is valid!"); - } else { - return true; } + + // everything is ok, so return true as the certificate must be valid + return true; } /** @@ -755,7 +785,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } // get user object - User user = persistenceHandler.getUser(certificate.getUsername()); + User user = this.persistenceHandler.getUser(certificate.getUsername()); if (user == null) { throw new PrivilegeException( "Oh boy, how did this happen: No User in user map although the certificate is valid! Certificate: " @@ -789,7 +819,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // validate who is doing this validateIsPrivilegeAdmin(certificate); - return persistenceHandler.persist(certificate); + return this.persistenceHandler.persist(certificate); } /** @@ -804,7 +834,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { this.persistenceHandler = persistenceHandler; lastSessionId = 0l; - sessionMap = new HashMap(); + this.sessionMap = new HashMap(); } /** @@ -820,9 +850,13 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { * @author rvonburg */ private class CertificateSessionPair { - private Session session; - private Certificate certificate; + public final Session session; + public final Certificate certificate; + /** + * @param session + * @param certificate + */ public CertificateSessionPair(Session session, Certificate certificate) { this.session = session; this.certificate = certificate; diff --git a/src/ch/eitchnet/privilege/handler/EncryptionHandler.java b/src/ch/eitchnet/privilege/handler/EncryptionHandler.java index 9efa5f337..6a1d88271 100644 --- a/src/ch/eitchnet/privilege/handler/EncryptionHandler.java +++ b/src/ch/eitchnet/privilege/handler/EncryptionHandler.java @@ -13,24 +13,36 @@ 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 rvonburg * */ public interface EncryptionHandler { /** - * @return + * 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 - * @return + * the string to convert + * @return the hash of the string after converting */ public String convertToHash(String string); /** + * Initialize the concrete {@link EncryptionHandler}. The passed parameter map contains any configuration this map + * might need + * * @param parameterMap + * a map containing configuration properties */ public void initialize(Map parameterMap); } diff --git a/src/ch/eitchnet/privilege/handler/PersistenceHandler.java b/src/ch/eitchnet/privilege/handler/PersistenceHandler.java index 5fcd9c7a6..ee4459a20 100644 --- a/src/ch/eitchnet/privilege/handler/PersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/PersistenceHandler.java @@ -13,80 +13,135 @@ package ch.eitchnet.privilege.handler; import java.util.Map; import ch.eitchnet.privilege.model.Certificate; +import ch.eitchnet.privilege.model.Restrictable; import ch.eitchnet.privilege.model.internal.Privilege; 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 Privilege} + *

    + * * @author rvonburg * */ public interface PersistenceHandler { /** + * Returns a {@link User} object from the underlying database + * * @param username - * @return + * 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); /** - * @param user - */ - public void addOrReplaceUser(User user); - - /** - * @param username - * @return - */ - public User removeUser(String username); - - /** + * Returns a {@link Role} object from the underlying database + * * @param roleName - * @return + * 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); /** - * @param role - */ - public void addOrReplaceRole(Role role); - - /** - * @param roleName - * @return - */ - public Role removeRole(String roleName); - - /** + * Returns a {@link Privilege} object from the underlying database + * * @param privilegeName - * @return + * the name/id of the {@link Privilege} object to return + * + * @return the {@link Privilege} object, or null if it was not found */ public Privilege getPrivilege(String privilegeName); /** - * @param privilege - */ - public void addOrReplacePrivilege(Privilege privilege); - - /** - * @param privilegeName - * @return - */ - public Privilege removePrivilege(String privilegeName); - - /** + *

    + * Thus this method instantiates a {@link PrivilegePolicy} object from the given policyName. The + * {@link PrivilegePolicy} is not stored in a database, but rather behind a privilege name a class name is stored + * which then is used to instantiate a new object + *

    + * * @param policyName - * @return + * the name/id of the {@link PrivilegePolicy} object to return + * + * @return the {@link PrivilegePolicy} object, or null if no class is defined for the given policy name */ public PrivilegePolicy getPolicy(String policyName); + /** + * 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); + + /** + * Removes a {@link Privilege} with the given name and returns the removed object if it existed + * + * @param privilegeName + * the name of the {@link Privilege} to remove + * + * @return the {@link Privilege} removed, or null if it did not exist + */ + public Privilege removePrivilege(String privilegeName); + + /** + * Adds a {@link User} object to the underlying database. If the {@link User} already exists, it is replaced + * + * @param user + * the {@link User} object to add + */ + public void addOrReplaceUser(User user); + + /** + * Adds a {@link Role} object to the underlying database. If the {@link Role} already exists, it is replaced + * + * @param role + * the {@link User} object to add + */ + public void addOrReplaceRole(Role role); + + /** + * Adds a {@link Privilege} object to the underlying database. If the {@link Privilege} already exists, it is + * replaced + * + * @param privilege + * the {@link Privilege} object to add + */ + public void addOrReplacePrivilege(Privilege privilege); + /** * @param certificate + * * @return */ public boolean persist(Certificate certificate); - + /** * @param parameterMap */ diff --git a/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java b/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java index 74f27bd70..0c8264076 100644 --- a/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java +++ b/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java @@ -14,6 +14,7 @@ import java.util.Locale; import java.util.Map; import java.util.Set; +import ch.eitchnet.privilege.i18n.AccessDeniedException; import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.Certificate; import ch.eitchnet.privilege.model.PrivilegeRep; @@ -22,6 +23,7 @@ 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; /** diff --git a/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java b/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java index 6d6ecb46f..d926ae2b2 100644 --- a/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java @@ -60,13 +60,12 @@ public class XmlPersistenceHandler implements PersistenceHandler { private String basePath; /** - * @see ch.eitchnet.privilege.handler.PersistenceHandler#addOrReplacePrivilege(ch.eitchnet.privilege.model.Certificate, - * ch.eitchnet.privilege.model.internal.Privilege) + * @see ch.eitchnet.privilege.handler.PersistenceHandler#addOrReplacePrivilege(ch.eitchnet.privilege.model.internal.Privilege) */ @Override public void addOrReplacePrivilege(Privilege privilege) { - privilegeMap.put(privilege.getName(), privilege); - privilegeMapDirty = true; + this.privilegeMap.put(privilege.getName(), privilege); + this.privilegeMapDirty = true; } /** @@ -74,19 +73,18 @@ public class XmlPersistenceHandler implements PersistenceHandler { */ @Override public Privilege removePrivilege(String privilegeName) { - Privilege privilege = privilegeMap.remove(privilegeName); - privilegeMapDirty = privilege != null; + Privilege privilege = this.privilegeMap.remove(privilegeName); + this.privilegeMapDirty = privilege != null; return privilege; } /** - * @see ch.eitchnet.privilege.handler.PersistenceHandler#addOrReplaceRole(ch.eitchnet.privilege.model.Certificate, - * ch.eitchnet.privilege.model.internal.Role) + * @see ch.eitchnet.privilege.handler.PersistenceHandler#addOrReplaceRole(ch.eitchnet.privilege.model.internal.Role) */ @Override public void addOrReplaceRole(Role role) { - roleMap.put(role.getName(), role); - roleMapDirty = true; + this.roleMap.put(role.getName(), role); + this.roleMapDirty = true; } /** @@ -94,19 +92,18 @@ public class XmlPersistenceHandler implements PersistenceHandler { */ @Override public Role removeRole(String roleName) { - Role role = roleMap.remove(roleName); - roleMapDirty = role != null; + Role role = this.roleMap.remove(roleName); + this.roleMapDirty = role != null; return role; } /** - * @see ch.eitchnet.privilege.handler.PersistenceHandler#addOrReplaceUser(ch.eitchnet.privilege.model.Certificate, - * ch.eitchnet.privilege.model.internal.User) + * @see ch.eitchnet.privilege.handler.PersistenceHandler#addOrReplaceUser(ch.eitchnet.privilege.model.internal.User) */ @Override public void addOrReplaceUser(User user) { - userMap.put(user.getUsername(), user); - userMapDirty = true; + this.userMap.put(user.getUsername(), user); + this.userMapDirty = true; } /** @@ -114,8 +111,8 @@ public class XmlPersistenceHandler implements PersistenceHandler { */ @Override public User removeUser(String username) { - User user = userMap.remove(username); - userMapDirty = user != null; + User user = this.userMap.remove(username); + this.userMapDirty = user != null; return user; } @@ -124,7 +121,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { */ @Override public Privilege getPrivilege(String privilegeName) { - return privilegeMap.get(privilegeName); + return this.privilegeMap.get(privilegeName); } /** @@ -132,7 +129,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { */ @Override public Role getRole(String roleName) { - return roleMap.get(roleName); + return this.roleMap.get(roleName); } /** @@ -140,7 +137,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { */ @Override public User getUser(String username) { - return userMap.get(username); + return this.userMap.get(username); } /** @@ -150,7 +147,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { public PrivilegePolicy getPolicy(String policyName) { // get the policies class - Class policyClazz = policyMap.get(policyName); + Class policyClazz = this.policyMap.get(policyName); if (policyClazz == null) { return null; } @@ -169,21 +166,21 @@ public class XmlPersistenceHandler implements PersistenceHandler { // USERS // get users file name - String usersFileName = parameterMap.get(XmlConstants.XML_PARAM_USERS_FILE); + String usersFileName = this.parameterMap.get(XmlConstants.XML_PARAM_USERS_FILE); if (usersFileName == null || usersFileName.isEmpty()) { throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " + XmlConstants.XML_PARAM_USERS_FILE + " is invalid"); } // get users file - File usersFile = new File(basePath + "/" + usersFileName); - boolean usersFileUnchanged = usersFile.exists() && usersFile.lastModified() == usersFileDate; - if (!userMapDirty && usersFileUnchanged) { + File usersFile = new File(this.basePath + "/" + usersFileName); + boolean usersFileUnchanged = usersFile.exists() && usersFile.lastModified() == this.usersFileDate; + if (!this.userMapDirty && usersFileUnchanged) { logger.warn("No users unpersisted and user file unchanged on file system"); } else { logger.info("Persisting users..."); // build XML DOM of users - List users = toDomUsers(certificate); + List users = toDomUsers(); Element rootElement = DocumentFactory.getInstance().createElement(XmlConstants.XML_USERS); for (Element userElement : users) { rootElement.add(userElement); @@ -191,20 +188,20 @@ public class XmlPersistenceHandler implements PersistenceHandler { // write DOM to file XmlHelper.writeDocument(rootElement, usersFile); - userMapDirty = true; + this.userMapDirty = true; } // ROLES // get roles file name - String rolesFileName = parameterMap.get(XmlConstants.XML_PARAM_ROLES_FILE); + String rolesFileName = this.parameterMap.get(XmlConstants.XML_PARAM_ROLES_FILE); if (rolesFileName == null || rolesFileName.isEmpty()) { throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " + XmlConstants.XML_PARAM_ROLES_FILE + " is invalid"); } // get roles file - File rolesFile = new File(basePath + "/" + rolesFileName); - boolean rolesFileUnchanged = rolesFile.exists() && rolesFile.lastModified() == rolesFileDate; - if (!roleMapDirty && rolesFileUnchanged) { + File rolesFile = new File(this.basePath + "/" + rolesFileName); + boolean rolesFileUnchanged = rolesFile.exists() && rolesFile.lastModified() == this.rolesFileDate; + if (!this.roleMapDirty && rolesFileUnchanged) { logger.warn("No roles unpersisted and roles file unchanged on file system"); } else { logger.info("Persisting roles..."); @@ -218,21 +215,21 @@ public class XmlPersistenceHandler implements PersistenceHandler { // write DOM to file XmlHelper.writeDocument(rootElement, rolesFile); - roleMapDirty = true; + this.roleMapDirty = true; } // PRIVILEGES // get privileges file name - String privilegesFileName = parameterMap.get(XmlConstants.XML_PARAM_PRIVILEGES_FILE); + String privilegesFileName = this.parameterMap.get(XmlConstants.XML_PARAM_PRIVILEGES_FILE); if (privilegesFileName == null || privilegesFileName.isEmpty()) { throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " + XmlConstants.XML_PARAM_PRIVILEGES_FILE + " is invalid"); } // get privileges file - File privilegesFile = new File(basePath + "/" + privilegesFileName); + File privilegesFile = new File(this.basePath + "/" + privilegesFileName); boolean privilegesFileUnchanged = privilegesFile.exists() - && privilegesFile.lastModified() == privilegesFileDate; - if (!privilegeMapDirty && privilegesFileUnchanged) { + && privilegesFile.lastModified() == this.privilegesFileDate; + if (!this.privilegeMapDirty && privilegesFileUnchanged) { logger.warn("No privileges unpersisted and privileges file unchanged on file system"); } else { logger.info("Persisting privileges..."); @@ -246,24 +243,24 @@ public class XmlPersistenceHandler implements PersistenceHandler { // write DOM to file XmlHelper.writeDocument(rootElement, privilegesFile); - privilegeMapDirty = true; + this.privilegeMapDirty = true; } // reset dirty states and return if something was dirty, false otherwise - if (userMapDirty || roleMapDirty || privilegeMapDirty) { - userMapDirty = false; - roleMapDirty = false; - privilegeMapDirty = false; + if (this.userMapDirty || this.roleMapDirty || this.privilegeMapDirty) { + this.userMapDirty = false; + this.roleMapDirty = false; + this.privilegeMapDirty = false; return true; - } else { - userMapDirty = false; - roleMapDirty = false; - privilegeMapDirty = false; - - return false; } + + this.userMapDirty = false; + this.roleMapDirty = false; + this.privilegeMapDirty = false; + + return false; } /** @@ -272,14 +269,14 @@ public class XmlPersistenceHandler implements PersistenceHandler { @Override public void initialize(Map parameterMap) { - roleMap = new HashMap(); - userMap = new HashMap(); - privilegeMap = new HashMap(); - policyMap = new HashMap>(); + this.roleMap = new HashMap(); + this.userMap = new HashMap(); + this.privilegeMap = new HashMap(); + this.policyMap = new HashMap>(); // get and validate base bath - basePath = parameterMap.get(XmlConstants.XML_PARAM_BASE_PATH); - File basePathF = new File(basePath); + this.basePath = parameterMap.get(XmlConstants.XML_PARAM_BASE_PATH); + File basePathF = new File(this.basePath); if (!basePathF.exists() && !basePathF.isDirectory()) { throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " + XmlConstants.XML_PARAM_BASE_PATH + " is invalid"); @@ -294,7 +291,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { } // get roles file - File rolesFile = new File(basePath + "/" + rolesFileName); + File rolesFile = new File(this.basePath + "/" + rolesFileName); if (!rolesFile.exists()) { throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " + XmlConstants.XML_PARAM_ROLES_FILE + " is invalid as roles file does not exist at path " @@ -306,7 +303,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { // read roles readRoles(rolesRootElement); - rolesFileDate = rolesFile.lastModified(); + this.rolesFileDate = rolesFile.lastModified(); // USERS // get users file name @@ -317,7 +314,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { } // get users file - File usersFile = new File(basePath + "/" + usersFileName); + File usersFile = new File(this.basePath + "/" + usersFileName); if (!usersFile.exists()) { throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " + XmlConstants.XML_PARAM_USERS_FILE + " is invalid as users file does not exist at path " @@ -329,7 +326,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { // read users readUsers(usersRootElement); - usersFileDate = usersFile.lastModified(); + this.usersFileDate = usersFile.lastModified(); // PRIVILEGES // get privileges file name @@ -340,7 +337,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { } // get privileges file - File privilegesFile = new File(basePath + "/" + privilegesFileName); + File privilegesFile = new File(this.basePath + "/" + privilegesFileName); if (!privilegesFile.exists()) { throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " + XmlConstants.XML_PARAM_PRIVILEGES_FILE + " is invalid as privileges file does not exist at path " @@ -352,7 +349,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { // read privileges readPrivileges(privilegesRootElement); - privilegesFileDate = privilegesFile.lastModified(); + this.privilegesFileDate = privilegesFile.lastModified(); // POLICIES // get policy file name @@ -363,7 +360,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { } // get policy file - File policyFile = new File(basePath + "/" + policyFileName); + File policyFile = new File(this.basePath + "/" + policyFileName); if (!policyFile.exists()) { throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " + XmlConstants.XML_PARAM_POLICY_FILE + " is invalid as policy file does not exist at path " @@ -376,18 +373,18 @@ public class XmlPersistenceHandler implements PersistenceHandler { // read policies readPolicies(policiesRootElement); - userMapDirty = false; - roleMapDirty = false; - privilegeMapDirty = false; + this.userMapDirty = false; + this.roleMapDirty = false; + this.privilegeMapDirty = false; - logger.info("Read " + userMap.size() + " Users"); - logger.info("Read " + roleMap.size() + " Roles"); - logger.info("Read " + privilegeMap.size() + " Privileges"); + logger.info("Read " + this.userMap.size() + " Users"); + logger.info("Read " + this.roleMap.size() + " Roles"); + logger.info("Read " + this.privilegeMap.size() + " Privileges"); // validate we have a user with PrivilegeAdmin access boolean privilegeAdminExists = false; - for (String username : userMap.keySet()) { - User user = userMap.get(username); + for (String username : this.userMap.keySet()) { + User user = this.userMap.get(username); if (user.hasRole(PrivilegeHandler.PRIVILEGE_ADMIN_ROLE)) { privilegeAdminExists = true; break; @@ -437,7 +434,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { locale); // put user in map - userMap.put(username, user); + this.userMap.put(username, user); } } @@ -462,7 +459,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { } Role role = new Role(roleName, privileges); - roleMap.put(roleName, role); + this.roleMap.put(roleName, role); } } @@ -479,7 +476,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { String privilegePolicy = privilegeElement.attributeValue(XmlConstants.XML_ATTR_POLICY); String allAllowedS = privilegeElement.element(XmlConstants.XML_ALL_ALLOWED).getTextTrim(); - boolean allAllowed = Boolean.valueOf(allAllowedS); + boolean allAllowed = Boolean.valueOf(allAllowedS).booleanValue(); @SuppressWarnings("unchecked") List denyElements = privilegeElement.elements(XmlConstants.XML_DENY); @@ -506,7 +503,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { } Privilege privilege = new Privilege(privilegeName, privilegePolicy, allAllowed, denyList, allowList); - privilegeMap.put(privilegeName, privilege); + this.privilegeMap.put(privilegeName, privilege); } } @@ -523,19 +520,19 @@ public class XmlPersistenceHandler implements PersistenceHandler { Class clazz = ClassHelper.loadClass(policyClass); - policyMap.put(policyName, clazz); + this.policyMap.put(policyName, clazz); } } private List toDomPrivileges() { - List privilegesAsElements = new ArrayList(privilegeMap.size()); + List privilegesAsElements = new ArrayList(this.privilegeMap.size()); DocumentFactory documentFactory = DocumentFactory.getInstance(); - for (String privilegeName : privilegeMap.keySet()) { + for (String privilegeName : this.privilegeMap.keySet()) { // get the privilege object - Privilege privilege = privilegeMap.get(privilegeName); + Privilege privilege = this.privilegeMap.get(privilegeName); // create the privilege element Element privilegeElement = documentFactory.createElement(XmlConstants.XML_PRIVILEGE); @@ -570,13 +567,13 @@ public class XmlPersistenceHandler implements PersistenceHandler { private List toDomRoles() { - List rolesAsElements = new ArrayList(roleMap.size()); + List rolesAsElements = new ArrayList(this.roleMap.size()); DocumentFactory documentFactory = DocumentFactory.getInstance(); - for (String roleName : roleMap.keySet()) { + for (String roleName : this.roleMap.keySet()) { // get the role object - Role role = roleMap.get(roleName); + Role role = this.roleMap.get(roleName); // create the role element Element roleElement = documentFactory.createElement(XmlConstants.XML_ROLE); @@ -596,15 +593,15 @@ public class XmlPersistenceHandler implements PersistenceHandler { return rolesAsElements; } - private List toDomUsers(Certificate certificate) { + private List toDomUsers() { - List usersAsElements = new ArrayList(userMap.size()); + List usersAsElements = new ArrayList(this.userMap.size()); DocumentFactory documentFactory = DocumentFactory.getInstance(); - for (String userName : userMap.keySet()) { + for (String userName : this.userMap.keySet()) { // get the user object - User user = userMap.get(userName); + User user = this.userMap.get(userName); // create the user element Element userElement = documentFactory.createElement(XmlConstants.XML_USER); diff --git a/src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java b/src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java index 4c8a7f7dc..66ddad1d7 100644 --- a/src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java +++ b/src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java @@ -21,7 +21,6 @@ import org.dom4j.Document; import org.dom4j.DocumentFactory; import org.dom4j.Element; - /** *

    * This class is a simple application which can be used to bootstrap a new configuration for the @@ -72,10 +71,10 @@ public class BootstrapConfigurationHelper { File pathF = new File(path); if (pathF.exists()) { throw new RuntimeException("Path already exists: " + pathF.getAbsolutePath()); - } else { - if (!pathF.mkdirs()) { - throw new RuntimeException("Could not create path " + pathF.getAbsolutePath()); - } + } + + if (!pathF.mkdirs()) { + throw new RuntimeException("Could not create path " + pathF.getAbsolutePath()); } // TODO ask other questions... diff --git a/src/ch/eitchnet/privilege/helper/InitializationHelper.java b/src/ch/eitchnet/privilege/helper/InitializationHelper.java index d61e36d04..549dfcd00 100644 --- a/src/ch/eitchnet/privilege/helper/InitializationHelper.java +++ b/src/ch/eitchnet/privilege/helper/InitializationHelper.java @@ -33,6 +33,7 @@ public class InitializationHelper { /** * @param privilegeContainerXmlFile + * @return */ public static PrivilegeHandler initializeFromXml(File privilegeContainerXmlFile) { diff --git a/src/ch/eitchnet/privilege/model/Certificate.java b/src/ch/eitchnet/privilege/model/Certificate.java index c7f3bbf85..e55af8a44 100644 --- a/src/ch/eitchnet/privilege/model/Certificate.java +++ b/src/ch/eitchnet/privilege/model/Certificate.java @@ -60,7 +60,7 @@ public final class Certificate implements Serializable { * @return the locale */ public Locale getLocale() { - return locale; + return this.locale; } /** @@ -75,14 +75,14 @@ public final class Certificate implements Serializable { * @return the sessionId */ public String getSessionId() { - return sessionId; + return this.sessionId; } /** * @return the username */ public String getUsername() { - return username; + return this.username; } /** @@ -94,11 +94,10 @@ public final class Certificate implements Serializable { * @return the authToken if the given authPassword is corret, null otherwise */ public String getAuthToken(String authPassword) { - if (this.authPassword.equals(authPassword)) { - return authToken; - } else { - return null; - } + if (this.authPassword.equals(authPassword)) + return this.authToken; + + return null; } /** @@ -108,11 +107,11 @@ public final class Certificate implements Serializable { public int hashCode() { final int prime = 31; int result = 1; - result = prime * result + ((authPassword == null) ? 0 : authPassword.hashCode()); - result = prime * result + ((authToken == null) ? 0 : authToken.hashCode()); - result = prime * result + ((locale == null) ? 0 : locale.hashCode()); - result = prime * result + ((sessionId == null) ? 0 : sessionId.hashCode()); - result = prime * result + ((username == null) ? 0 : username.hashCode()); + result = prime * result + ((this.authPassword == null) ? 0 : this.authPassword.hashCode()); + 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; } @@ -128,30 +127,30 @@ public final class Certificate implements Serializable { if (!(obj instanceof Certificate)) return false; Certificate other = (Certificate) obj; - if (authPassword == null) { + if (this.authPassword == null) { if (other.authPassword != null) return false; - } else if (!authPassword.equals(other.authPassword)) + } else if (!this.authPassword.equals(other.authPassword)) return false; - if (authToken == null) { + if (this.authToken == null) { if (other.authToken != null) return false; - } else if (!authToken.equals(other.authToken)) + } else if (!this.authToken.equals(other.authToken)) return false; - if (locale == null) { + if (this.locale == null) { if (other.locale != null) return false; - } else if (!locale.equals(other.locale)) + } else if (!this.locale.equals(other.locale)) return false; - if (sessionId == null) { + if (this.sessionId == null) { if (other.sessionId != null) return false; - } else if (!sessionId.equals(other.sessionId)) + } else if (!this.sessionId.equals(other.sessionId)) return false; - if (username == null) { + if (this.username == null) { if (other.username != null) return false; - } else if (!username.equals(other.username)) + } else if (!this.username.equals(other.username)) return false; return true; } @@ -163,11 +162,11 @@ public final class Certificate implements Serializable { public String toString() { StringBuilder builder = new StringBuilder(); builder.append("Certificate [sessionId="); - builder.append(sessionId); + builder.append(this.sessionId); builder.append(", username="); - builder.append(username); + builder.append(this.username); builder.append(", locale="); - builder.append(locale); + builder.append(this.locale); builder.append("]"); return builder.toString(); } diff --git a/src/ch/eitchnet/privilege/model/PrivilegeRep.java b/src/ch/eitchnet/privilege/model/PrivilegeRep.java index bce742671..82ad3e070 100644 --- a/src/ch/eitchnet/privilege/model/PrivilegeRep.java +++ b/src/ch/eitchnet/privilege/model/PrivilegeRep.java @@ -46,7 +46,7 @@ public class PrivilegeRep implements Serializable { * @return the name */ public String getName() { - return name; + return this.name; } /** @@ -61,7 +61,7 @@ public class PrivilegeRep implements Serializable { * @return the policy */ public String getPolicy() { - return policy; + return this.policy; } /** @@ -76,7 +76,7 @@ public class PrivilegeRep implements Serializable { * @return the allAllowed */ public boolean isAllAllowed() { - return allAllowed; + return this.allAllowed; } /** @@ -91,7 +91,7 @@ public class PrivilegeRep implements Serializable { * @return the denyList */ public Set getDenyList() { - return denyList; + return this.denyList; } /** @@ -106,7 +106,7 @@ public class PrivilegeRep implements Serializable { * @return the allowList */ public Set getAllowList() { - return allowList; + return this.allowList; } /** diff --git a/src/ch/eitchnet/privilege/model/Restrictable.java b/src/ch/eitchnet/privilege/model/Restrictable.java index 155876e23..d144b1781 100644 --- a/src/ch/eitchnet/privilege/model/Restrictable.java +++ b/src/ch/eitchnet/privilege/model/Restrictable.java @@ -16,7 +16,13 @@ package ch.eitchnet.privilege.model; */ public interface Restrictable { + /** + * @return + */ public String getPrivilegeName(); + /** + * @return + */ public Object getPrivilegeValue(); } diff --git a/src/ch/eitchnet/privilege/model/RoleRep.java b/src/ch/eitchnet/privilege/model/RoleRep.java index 12c3d755d..8a7bb538f 100644 --- a/src/ch/eitchnet/privilege/model/RoleRep.java +++ b/src/ch/eitchnet/privilege/model/RoleRep.java @@ -37,7 +37,7 @@ public class RoleRep implements Serializable { * @return the name */ public String getName() { - return name; + return this.name; } /** @@ -52,7 +52,7 @@ public class RoleRep implements Serializable { * @return the privileges */ public Set getPrivileges() { - return privileges; + return this.privileges; } /** diff --git a/src/ch/eitchnet/privilege/model/UserRep.java b/src/ch/eitchnet/privilege/model/UserRep.java index c7310d750..be5133b10 100644 --- a/src/ch/eitchnet/privilege/model/UserRep.java +++ b/src/ch/eitchnet/privilege/model/UserRep.java @@ -51,7 +51,7 @@ public class UserRep implements Serializable { * @return the username */ public String getUsername() { - return username; + return this.username; } /** @@ -66,7 +66,7 @@ public class UserRep implements Serializable { * @return the firstname */ public String getFirstname() { - return firstname; + return this.firstname; } /** @@ -81,7 +81,7 @@ public class UserRep implements Serializable { * @return the surname */ public String getSurname() { - return surname; + return this.surname; } /** @@ -96,7 +96,7 @@ public class UserRep implements Serializable { * @return the userState */ public UserState getUserState() { - return userState; + return this.userState; } /** @@ -111,7 +111,7 @@ public class UserRep implements Serializable { * @return the roles */ public Set getRoles() { - return roles; + return this.roles; } /** @@ -126,7 +126,7 @@ public class UserRep implements Serializable { * @return the locale */ public Locale getLocale() { - return locale; + return this.locale; } /** diff --git a/src/ch/eitchnet/privilege/model/UserState.java b/src/ch/eitchnet/privilege/model/UserState.java index 955bdefaf..391975b1f 100644 --- a/src/ch/eitchnet/privilege/model/UserState.java +++ b/src/ch/eitchnet/privilege/model/UserState.java @@ -12,7 +12,7 @@ package ch.eitchnet.privilege.model; /** * @author rvonburg - * + * */ public enum UserState { NEW, diff --git a/src/ch/eitchnet/privilege/model/internal/Privilege.java b/src/ch/eitchnet/privilege/model/internal/Privilege.java index da188f9d8..037a4f492 100644 --- a/src/ch/eitchnet/privilege/model/internal/Privilege.java +++ b/src/ch/eitchnet/privilege/model/internal/Privilege.java @@ -29,6 +29,9 @@ public final class Privilege { private final Set allowList; /** + * + * @param name + * @param policy * @param allAllowed * @param denyList * @param allowList @@ -45,42 +48,43 @@ public final class Privilege { * @return the name */ public String getName() { - return name; + return this.name; } /** * @return the policy */ public String getPolicy() { - return policy; + return this.policy; } /** * @return the allAllowed */ public boolean isAllAllowed() { - return allAllowed; + return this.allAllowed; } /** * @return the allowList */ public Set getAllowList() { - return allowList; + return this.allowList; } /** * @return the denyList */ public Set getDenyList() { - return denyList; + return this.denyList; } /** * @return a {@link PrivilegeRep} which is a representation of this object used to serialize and view on clients */ public PrivilegeRep asPrivilegeRep() { - return new PrivilegeRep(name, policy, allAllowed, new HashSet(denyList), new HashSet(allowList)); + return new PrivilegeRep(this.name, this.policy, this.allAllowed, new HashSet(this.denyList), + new HashSet(this.allowList)); } /** @@ -90,15 +94,15 @@ public final class Privilege { public String toString() { StringBuilder builder = new StringBuilder(); builder.append("Privilege [name="); - builder.append(name); + builder.append(this.name); builder.append(", policy="); - builder.append(policy); + builder.append(this.policy); builder.append(", allAllowed="); - builder.append(allAllowed); + builder.append(this.allAllowed); builder.append(", denyList="); - builder.append(denyList); + builder.append(this.denyList); builder.append(", allowList="); - builder.append(allowList); + builder.append(this.allowList); builder.append("]"); return builder.toString(); } @@ -110,11 +114,11 @@ public final class Privilege { public int hashCode() { final int prime = 31; int result = 1; - result = prime * result + (allAllowed ? 1231 : 1237); - result = prime * result + ((allowList == null) ? 0 : allowList.hashCode()); - result = prime * result + ((denyList == null) ? 0 : denyList.hashCode()); - result = prime * result + ((name == null) ? 0 : name.hashCode()); - result = prime * result + ((policy == null) ? 0 : policy.hashCode()); + result = prime * result + (this.allAllowed ? 1231 : 1237); + result = prime * result + ((this.allowList == null) ? 0 : this.allowList.hashCode()); + result = prime * result + ((this.denyList == null) ? 0 : this.denyList.hashCode()); + result = prime * result + ((this.name == null) ? 0 : this.name.hashCode()); + result = prime * result + ((this.policy == null) ? 0 : this.policy.hashCode()); return result; } @@ -130,27 +134,27 @@ public final class Privilege { if (getClass() != obj.getClass()) return false; Privilege other = (Privilege) obj; - if (allAllowed != other.allAllowed) + if (this.allAllowed != other.allAllowed) return false; - if (allowList == null) { + if (this.allowList == null) { if (other.allowList != null) return false; - } else if (!allowList.equals(other.allowList)) + } else if (!this.allowList.equals(other.allowList)) return false; - if (denyList == null) { + if (this.denyList == null) { if (other.denyList != null) return false; - } else if (!denyList.equals(other.denyList)) + } else if (!this.denyList.equals(other.denyList)) return false; - if (name == null) { + if (this.name == null) { if (other.name != null) return false; - } else if (!name.equals(other.name)) + } else if (!this.name.equals(other.name)) return false; - if (policy == null) { + if (this.policy == null) { if (other.policy != null) return false; - } else if (!policy.equals(other.policy)) + } else if (!this.policy.equals(other.policy)) return false; return true; } diff --git a/src/ch/eitchnet/privilege/model/internal/Role.java b/src/ch/eitchnet/privilege/model/internal/Role.java index a5390bea5..664216cb5 100644 --- a/src/ch/eitchnet/privilege/model/internal/Role.java +++ b/src/ch/eitchnet/privilege/model/internal/Role.java @@ -39,14 +39,14 @@ public final class Role { * @return the name */ public String getName() { - return name; + return this.name; } /** * @return */ public Set getPrivileges() { - return privileges; + return this.privileges; } /** @@ -54,14 +54,14 @@ public final class Role { * @return */ public boolean hasPrivilege(String key) { - return privileges.contains(key); + return this.privileges.contains(key); } /** * @return a {@link RoleRep} which is a representation of this object used to serialize and view on clients */ public RoleRep asRoleRep() { - return new RoleRep(name, new HashSet(privileges)); + return new RoleRep(this.name, new HashSet(this.privileges)); } /** @@ -71,9 +71,9 @@ public final class Role { public String toString() { StringBuilder builder = new StringBuilder(); builder.append("Role [name="); - builder.append(name); + builder.append(this.name); builder.append(", privileges="); - builder.append(privileges); + builder.append(this.privileges); builder.append("]"); return builder.toString(); } @@ -85,8 +85,8 @@ public final class Role { public int hashCode() { final int prime = 31; int result = 1; - result = prime * result + ((name == null) ? 0 : name.hashCode()); - result = prime * result + ((privileges == null) ? 0 : privileges.hashCode()); + result = prime * result + ((this.name == null) ? 0 : this.name.hashCode()); + result = prime * result + ((this.privileges == null) ? 0 : this.privileges.hashCode()); return result; } @@ -102,15 +102,15 @@ public final class Role { if (getClass() != obj.getClass()) return false; Role other = (Role) obj; - if (name == null) { + if (this.name == null) { if (other.name != null) return false; - } else if (!name.equals(other.name)) + } else if (!this.name.equals(other.name)) return false; - if (privileges == null) { + if (this.privileges == null) { if (other.privileges != null) return false; - } else if (!privileges.equals(other.privileges)) + } else if (!this.privileges.equals(other.privileges)) return false; return true; } diff --git a/src/ch/eitchnet/privilege/model/internal/Session.java b/src/ch/eitchnet/privilege/model/internal/Session.java index 4d4ff4355..f5003d0ca 100644 --- a/src/ch/eitchnet/privilege/model/internal/Session.java +++ b/src/ch/eitchnet/privilege/model/internal/Session.java @@ -24,6 +24,12 @@ public final class Session { private final String authPassword; /** + * + * @param sessionId + * @param authToken + * @param authPassword + * @param username + * @param loginTime */ public Session(String sessionId, String authToken, String authPassword, String username, long loginTime) { this.sessionId = sessionId; @@ -37,35 +43,35 @@ public final class Session { * @return the sessionId */ public String getSessionId() { - return sessionId; + return this.sessionId; } /** * @return the authToken */ public String getAuthToken() { - return authToken; + return this.authToken; } /** * @return the authPassword */ public String getAuthPassword() { - return authPassword; + return this.authPassword; } /** * @return the username */ public String getUsername() { - return username; + return this.username; } /** * @return the loginTime */ public long getLoginTime() { - return loginTime; + return this.loginTime; } /** @@ -75,11 +81,11 @@ public final class Session { public int hashCode() { final int prime = 31; int result = 1; - result = prime * result + ((authPassword == null) ? 0 : authPassword.hashCode()); - result = prime * result + ((authToken == null) ? 0 : authToken.hashCode()); - result = prime * result + (int) (loginTime ^ (loginTime >>> 32)); - result = prime * result + ((sessionId == null) ? 0 : sessionId.hashCode()); - result = prime * result + ((username == null) ? 0 : username.hashCode()); + result = prime * result + ((this.authPassword == null) ? 0 : this.authPassword.hashCode()); + result = prime * result + ((this.authToken == null) ? 0 : this.authToken.hashCode()); + result = prime * result + (int) (this.loginTime ^ (this.loginTime >>> 32)); + result = prime * result + ((this.sessionId == null) ? 0 : this.sessionId.hashCode()); + result = prime * result + ((this.username == null) ? 0 : this.username.hashCode()); return result; } @@ -95,27 +101,27 @@ public final class Session { if (!(obj instanceof Session)) return false; Session other = (Session) obj; - if (authPassword == null) { + if (this.authPassword == null) { if (other.authPassword != null) return false; - } else if (!authPassword.equals(other.authPassword)) + } else if (!this.authPassword.equals(other.authPassword)) return false; - if (authToken == null) { + if (this.authToken == null) { if (other.authToken != null) return false; - } else if (!authToken.equals(other.authToken)) + } else if (!this.authToken.equals(other.authToken)) return false; - if (loginTime != other.loginTime) + if (this.loginTime != other.loginTime) return false; - if (sessionId == null) { + if (this.sessionId == null) { if (other.sessionId != null) return false; - } else if (!sessionId.equals(other.sessionId)) + } else if (!this.sessionId.equals(other.sessionId)) return false; - if (username == null) { + if (this.username == null) { if (other.username != null) return false; - } else if (!username.equals(other.username)) + } else if (!this.username.equals(other.username)) return false; return true; } @@ -127,11 +133,11 @@ public final class Session { public String toString() { StringBuilder builder = new StringBuilder(); builder.append("Session [sessionId="); - builder.append(sessionId); + builder.append(this.sessionId); builder.append(", username="); - builder.append(username); + builder.append(this.username); builder.append(", loginTime="); - builder.append(loginTime); + builder.append(this.loginTime); builder.append("]"); return builder.toString(); } diff --git a/src/ch/eitchnet/privilege/model/internal/User.java b/src/ch/eitchnet/privilege/model/internal/User.java index 787a6d8c6..6808b899d 100644 --- a/src/ch/eitchnet/privilege/model/internal/User.java +++ b/src/ch/eitchnet/privilege/model/internal/User.java @@ -65,7 +65,7 @@ public final class User { * @return the username */ public String getUsername() { - return username; + return this.username; } /** @@ -81,42 +81,35 @@ public final class User { // field even though? The User object should be private, but maybe I // forgot something? - return password; - } - - /** - * @return the password - */ - public boolean isPassword(String password) { - return this.password.equals(password); + return this.password; } /** * @return the firstname */ public String getFirstname() { - return firstname; + return this.firstname; } /** * @return the surname */ public String getSurname() { - return surname; + return this.surname; } /** * @return the userState */ public UserState getState() { - return userState; + return this.userState; } /** * @return the roles */ public Set getRoles() { - return roles; + return this.roles; } /** @@ -126,21 +119,22 @@ public final class User { * @return true if the this user has the specified role */ public boolean hasRole(String role) { - return roles.contains(role); + return this.roles.contains(role); } /** * @return the locale */ public Locale getLocale() { - return locale; + return this.locale; } /** * @return a {@link UserRep} which is a representation of this object used to serialize and view on clients */ public UserRep asUserRep() { - return new UserRep(username, firstname, surname, userState, new HashSet(roles), locale); + return new UserRep(this.username, this.firstname, this.surname, this.userState, + new HashSet(this.roles), this.locale); } /** @@ -150,17 +144,17 @@ public final class User { public String toString() { StringBuilder builder = new StringBuilder(); builder.append("User [username="); - builder.append(username); + builder.append(this.username); builder.append(", firstname="); - builder.append(firstname); + builder.append(this.firstname); builder.append(", surname="); - builder.append(surname); + builder.append(this.surname); builder.append(", locale="); - builder.append(locale); + builder.append(this.locale); builder.append(", userState="); - builder.append(userState); + builder.append(this.userState); builder.append(", roles="); - builder.append(roles); + builder.append(this.roles); builder.append("]"); return builder.toString(); } diff --git a/test/ch/eitchnet/privilege/test/TestRestrictable.java b/test/ch/eitchnet/privilege/test/TestRestrictable.java index 1f65beef6..e9badc734 100644 --- a/test/ch/eitchnet/privilege/test/TestRestrictable.java +++ b/test/ch/eitchnet/privilege/test/TestRestrictable.java @@ -14,18 +14,20 @@ import ch.eitchnet.privilege.model.Restrictable; /** * @author rvonburg - * + * */ public class TestRestrictable implements Restrictable { - /**@see ch.eitchnet.privilege.model.Restrictable#getPrivilegeName() + /** + * @see ch.eitchnet.privilege.model.Restrictable#getPrivilegeName() */ @Override public String getPrivilegeName() { return "Service"; } - /**@see ch.eitchnet.privilege.model.Restrictable#getPrivilegeValue() + /** + * @see ch.eitchnet.privilege.model.Restrictable#getPrivilegeValue() */ @Override public Object getPrivilegeValue() { From 6c5b266f677ae6a3b4ca9e295dea40e0584bf23c Mon Sep 17 00:00:00 2001 From: eitch Date: Sun, 19 Sep 2010 20:19:38 +0000 Subject: [PATCH 036/173] [Minor] wrote some java docs --- .../handler/DefaultEncryptionHandler.java | 22 +++ .../handler/DefaultPrivilegeHandler.java | 44 +++--- .../privilege/handler/EncryptionHandler.java | 4 +- .../privilege/handler/PersistenceHandler.java | 11 +- .../privilege/handler/PrivilegeHandler.java | 126 +++++++++++++++--- .../handler/XmlPersistenceHandler.java | 14 +- .../privilege/helper/XmlConstants.java | 1 + src/ch/eitchnet/privilege/model/UserRep.java | 14 +- .../privilege/model/internal/User.java | 23 +++- .../privilege/test/PrivilegeTest.java | 6 +- 10 files changed, 208 insertions(+), 57 deletions(-) diff --git a/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java b/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java index 1ee77872d..4b33f648a 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java @@ -11,6 +11,7 @@ package ch.eitchnet.privilege.handler; import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.Map; @@ -22,13 +23,34 @@ import ch.eitchnet.privilege.helper.XmlConstants; import ch.eitchnet.privilege.i18n.PrivilegeException; /** + *

    + * This default {@link EncryptionHandler} creates nokens by 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 rvonburg * */ public class DefaultEncryptionHandler implements EncryptionHandler { + + /** + * The log4j logger used in this instance + */ private static final Logger logger = Logger.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; /** diff --git a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 28ee4c489..7e8b813a2 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -151,8 +151,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } // create new user - User user = new User(userRep.getUsername(), passwordHash, userRep.getFirstname(), userRep.getSurname(), userRep - .getUserState(), userRep.getRoles(), userRep.getLocale()); + User user = new User(userRep.getUserId(), userRep.getUsername(), passwordHash, userRep.getFirstname(), userRep + .getSurname(), userRep.getUserState(), userRep.getRoles(), userRep.getLocale()); // delegate to persistence handler this.persistenceHandler.addOrReplaceUser(user); @@ -229,8 +229,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { Set newRoles = new HashSet(currentRoles); newRoles.add(roleName); - User newUser = new User(user.getUsername(), user.getPassword(), user.getFirstname(), user.getSurname(), user - .getState(), newRoles, user.getLocale()); + User newUser = new User(user.getUserId(), user.getUsername(), user.getPassword(), user.getFirstname(), user + .getSurname(), user.getUserState(), newRoles, user.getLocale()); // delegate user replacement to persistence handler this.persistenceHandler.addOrReplaceUser(newUser); @@ -334,8 +334,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // create new user Set newRoles = new HashSet(currentRoles); newRoles.remove(roleName); - User newUser = new User(user.getUsername(), user.getPassword(), user.getFirstname(), user.getSurname(), user - .getState(), newRoles, user.getLocale()); + User newUser = new User(user.getUserId(), user.getUsername(), user.getPassword(), user.getFirstname(), user + .getSurname(), user.getUserState(), newRoles, user.getLocale()); // delegate user replacement to persistence handler this.persistenceHandler.addOrReplaceUser(newUser); @@ -483,8 +483,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } // create new user - User newUser = new User(user.getUsername(), user.getPassword(), user.getFirstname(), user.getSurname(), user - .getState(), user.getRoles(), locale); + User newUser = new User(user.getUserId(), user.getUsername(), user.getPassword(), user.getFirstname(), user + .getSurname(), user.getUserState(), user.getRoles(), locale); // delegate user replacement to persistence handler this.persistenceHandler.addOrReplaceUser(newUser); @@ -507,8 +507,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } // create new user - User newUser = new User(user.getUsername(), user.getPassword(), firstname, surname, user.getState(), user - .getRoles(), user.getLocale()); + User newUser = new User(user.getUserId(), user.getUsername(), user.getPassword(), firstname, surname, user + .getUserState(), user.getRoles(), user.getLocale()); // delegate user replacement to persistence handler this.persistenceHandler.addOrReplaceUser(newUser); @@ -541,8 +541,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } // create new user - User newUser = new User(user.getUsername(), passwordHash, user.getFirstname(), user.getSurname(), user - .getState(), user.getRoles(), user.getLocale()); + User newUser = new User(user.getUserId(), user.getUsername(), passwordHash, user.getFirstname(), user + .getSurname(), user.getUserState(), user.getRoles(), user.getLocale()); // delegate user replacement to persistence handler this.persistenceHandler.addOrReplaceUser(newUser); @@ -565,8 +565,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } // create new user - User newUser = new User(user.getUsername(), user.getPassword(), user.getFirstname(), user.getSurname(), state, - user.getRoles(), user.getLocale()); + User newUser = new User(user.getUserId(), user.getUsername(), user.getPassword(), user.getFirstname(), user + .getSurname(), state, user.getRoles(), user.getLocale()); // delegate user replacement to persistence handler this.persistenceHandler.addOrReplaceUser(newUser); @@ -604,8 +604,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { throw new AccessDeniedException("Password is incorrect for " + username + " / ***..."); // validate if user is allowed to login - if (user.getState() != UserState.ENABLED) - throw new AccessDeniedException("User " + username + " is not ENABLED. State is: " + user.getState()); + if (user.getUserState() != UserState.ENABLED) + throw new AccessDeniedException("User " + username + " is not ENABLED. State is: " + user.getUserState()); // validate user has at least one role if (user.getRoles().isEmpty()) { @@ -819,7 +819,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // validate who is doing this validateIsPrivilegeAdmin(certificate); - return this.persistenceHandler.persist(certificate); + return this.persistenceHandler.persist(); } /** @@ -850,12 +850,22 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { * @author rvonburg */ private class CertificateSessionPair { + /** + * The {@link Session} + */ public final Session session; + /** + * The {@link Certificate} + */ public final Certificate certificate; /** + * Creates a new {@link CertificateSessionPair} with the given session and certificate + * * @param session + * the session * @param certificate + * the certificate */ public CertificateSessionPair(Session session, Certificate certificate) { this.session = session; diff --git a/src/ch/eitchnet/privilege/handler/EncryptionHandler.java b/src/ch/eitchnet/privilege/handler/EncryptionHandler.java index 6a1d88271..c4f6b34b9 100644 --- a/src/ch/eitchnet/privilege/handler/EncryptionHandler.java +++ b/src/ch/eitchnet/privilege/handler/EncryptionHandler.java @@ -38,8 +38,8 @@ public interface EncryptionHandler { public String convertToHash(String string); /** - * Initialize the concrete {@link EncryptionHandler}. The passed parameter map contains any configuration this map - * might need + * 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 diff --git a/src/ch/eitchnet/privilege/handler/PersistenceHandler.java b/src/ch/eitchnet/privilege/handler/PersistenceHandler.java index ee4459a20..2e34f1703 100644 --- a/src/ch/eitchnet/privilege/handler/PersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/PersistenceHandler.java @@ -12,7 +12,6 @@ package ch.eitchnet.privilege.handler; import java.util.Map; -import ch.eitchnet.privilege.model.Certificate; import ch.eitchnet.privilege.model.Restrictable; import ch.eitchnet.privilege.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.Role; @@ -136,14 +135,18 @@ public interface PersistenceHandler { public void addOrReplacePrivilege(Privilege privilege); /** - * @param certificate + * Informs this {@link PersistenceHandler} to persist any changes which need to be saved * - * @return + * @return true if changes were persisted successfully, false if something went wrong */ - public boolean persist(Certificate certificate); + public boolean persist(); /** + * 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/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java b/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java index 0c8264076..749d425ff 100644 --- a/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java +++ b/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java @@ -22,6 +22,7 @@ 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.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.Role; import ch.eitchnet.privilege.model.internal.User; import ch.eitchnet.privilege.policy.PrivilegePolicy; @@ -33,118 +34,195 @@ import ch.eitchnet.privilege.policy.PrivilegePolicy; public interface PrivilegeHandler { /** - * This is the role users must have, if they are allowed to modify objects + * value = PrivilegeAdmin: This is the role users must have, if they are allowed to modify objects */ public static final String PRIVILEGE_ADMIN_ROLE = "PrivilegeAdmin"; /** - * @param username + * Returns a {@link UserRep} for the given username * - * @return + * @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(String username); /** - * @param roleName + * Returns a {@link RoleRep} for the given roleName * - * @return + * @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(String roleName); /** - * @param privilegeName + * Returns a {@link PrivilegeRep} for the given privilegeName * - * @return + * @param privilegeName + * the name of the {@link PrivilegeRep} to return + * + * @return the {@link PrivilegeRep} for the given privilegeName, or null if it was not found */ public PrivilegeRep getPrivilege(String privilegeName); /** - * @param policyName + * Returns a {@link PrivilegePolicy} for the given policyName * - * @return + * @param policyName + * the name of the {@link PrivilegePolicy} to return + * + * @return the {@link PrivilegePolicy} for the given policyName, or null if it was not found */ public PrivilegePolicy getPolicy(String policyName); /** - * @param certificate - * @param username + * Removes the user with the given username * - * @return + * @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 */ public UserRep removeUser(Certificate certificate, String username); /** + * 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 */ public void removeRoleFromUser(Certificate certificate, String username, String roleName); /** - * @param certificate - * @param roleName + * Removes the role with the given roleName * - * @return + * @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 */ public RoleRep removeRole(Certificate certificate, String roleName); /** + * 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 */ public void removePrivilegeFromRole(Certificate certificate, String roleName, String privilegeName); /** - * @param certificate - * @param privilegeName + * Removes the privilege with the given privilegeName * - * @return + * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action + * @param privilegeName + * the privilegeName of the privilege to remove + * + * @return the {@link PrivilegeRep} of the privilege removed, or null if the privilege did not exist */ public PrivilegeRep removePrivilege(Certificate certificate, String privilegeName); /** + *

    + * Adds a new user, or replaces the 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(String)} + *

    + * * @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(String)} */ public void addOrReplaceUser(Certificate certificate, UserRep userRep, String password); /** + * Adds a new role, or replaces the role with the information from this {@link RoleRep} if the role already exists + * * @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} */ public void addOrReplaceRole(Certificate certificate, RoleRep roleRep); /** + * Adds a new privilege, or replaces the privilege with the information from this {@link PrivilegeRep} if the + * privilege already exists + * * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action * @param privilegeRep + * the {@link PrivilegeRep} containing the information to create the new {@link Privilege} */ public void addOrReplacePrivilege(Certificate certificate, PrivilegeRep privilegeRep); /** + * 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} */ public void addRoleToUser(Certificate certificate, String username, String roleName); /** + * Adds the privilege with the given privilegeName to the {@link 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 {@link Role} to which the privilege should be added * @param privilegeName + * the privilegeName of the {@link Privilege} which should be added to the {@link Role} */ public void addPrivilegeToRole(Certificate certificate, String roleName, String privilegeName); /** + * 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(String)} + * * @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(String)} */ public void setUserPassword(Certificate certificate, String username, String password); /** * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action * @param username * @param firstname * @param surname @@ -153,6 +231,7 @@ public interface PrivilegeHandler { /** * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action * @param username * @param state */ @@ -160,6 +239,7 @@ public interface PrivilegeHandler { /** * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action * @param username * @param locale */ @@ -167,6 +247,7 @@ public interface PrivilegeHandler { /** * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action * @param privilegeName * @param policyName */ @@ -174,6 +255,7 @@ public interface PrivilegeHandler { /** * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action * @param privilegeName * @param allAllowed */ @@ -181,6 +263,7 @@ public interface PrivilegeHandler { /** * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action * @param privilegeName * @param denyList */ @@ -188,6 +271,7 @@ public interface PrivilegeHandler { /** * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action * @param privilegeName * @param allowList */ @@ -206,6 +290,7 @@ public interface PrivilegeHandler { /** * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action * @param restrictable * * @return @@ -276,14 +361,19 @@ public interface PrivilegeHandler { /** * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action * * @return */ public boolean persist(Certificate certificate); /** + * Initialize the concrete {@link EncryptionHandler}. The passed parameter map contains any configuration this map + * might need * * @param parameterMap + * a map containing configuration properties + * * @param encryptionHandler * @param persistenceHandler */ diff --git a/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java b/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java index d926ae2b2..d1fa6a43c 100644 --- a/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java @@ -28,7 +28,6 @@ import ch.eitchnet.privilege.helper.ClassHelper; import ch.eitchnet.privilege.helper.XmlConstants; import ch.eitchnet.privilege.helper.XmlHelper; import ch.eitchnet.privilege.i18n.PrivilegeException; -import ch.eitchnet.privilege.model.Certificate; import ch.eitchnet.privilege.model.UserState; import ch.eitchnet.privilege.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.Role; @@ -159,10 +158,10 @@ public class XmlPersistenceHandler implements PersistenceHandler { } /** - * @see ch.eitchnet.privilege.handler.PersistenceHandler#persist(ch.eitchnet.privilege.model.Certificate) + * @see ch.eitchnet.privilege.handler.PersistenceHandler#persist() */ @Override - public boolean persist(Certificate certificate) { + public boolean persist() { // USERS // get users file name @@ -404,6 +403,8 @@ public class XmlPersistenceHandler implements PersistenceHandler { List userElements = usersRootElement.elements(XmlConstants.XML_USER); for (Element userElement : userElements) { + String userId = userElement.attributeValue(XmlConstants.XML_ATTR_USER_ID); + String username = userElement.attributeValue(XmlConstants.XML_ATTR_USERNAME); String password = userElement.attributeValue(XmlConstants.XML_ATTR_PASSWORD); @@ -430,8 +431,8 @@ public class XmlPersistenceHandler implements PersistenceHandler { } // create user - User user = new User(username, password, firstname, surname, userState, Collections.unmodifiableSet(roles), - locale); + User user = new User(userId, username, password, firstname, surname, userState, Collections + .unmodifiableSet(roles), locale); // put user in map this.userMap.put(username, user); @@ -605,6 +606,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { // create the user element Element userElement = documentFactory.createElement(XmlConstants.XML_USER); + userElement.addAttribute(XmlConstants.XML_ATTR_USER_ID, user.getUserId()); userElement.addAttribute(XmlConstants.XML_ATTR_USERNAME, user.getUsername()); userElement.addAttribute(XmlConstants.XML_ATTR_PASSWORD, user.getPassword()); @@ -620,7 +622,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { // add state element Element stateElement = documentFactory.createElement(XmlConstants.XML_STATE); - stateElement.setText(user.getState().toString()); + stateElement.setText(user.getUserState().toString()); userElement.add(stateElement); // add locale element diff --git a/src/ch/eitchnet/privilege/helper/XmlConstants.java b/src/ch/eitchnet/privilege/helper/XmlConstants.java index 5a42dbe34..b78ce40fa 100644 --- a/src/ch/eitchnet/privilege/helper/XmlConstants.java +++ b/src/ch/eitchnet/privilege/helper/XmlConstants.java @@ -46,6 +46,7 @@ public class XmlConstants { public static final String XML_ATTR_NAME = "name"; public static final String XML_ATTR_VALUE = "value"; public static final String XML_ATTR_POLICY = "policy"; + public static final String XML_ATTR_USER_ID = "userId"; public static final String XML_ATTR_USERNAME = "username"; public static final String XML_ATTR_PASSWORD = "password"; diff --git a/src/ch/eitchnet/privilege/model/UserRep.java b/src/ch/eitchnet/privilege/model/UserRep.java index be5133b10..a91ab8e2c 100644 --- a/src/ch/eitchnet/privilege/model/UserRep.java +++ b/src/ch/eitchnet/privilege/model/UserRep.java @@ -22,6 +22,7 @@ public class UserRep implements Serializable { private static final long serialVersionUID = 1L; + private final String userId; private String username; private String firstname; private String surname; @@ -30,6 +31,7 @@ public class UserRep implements Serializable { private Locale locale; /** + * @param userId * @param username * @param firstname * @param surname @@ -37,8 +39,9 @@ public class UserRep implements Serializable { * @param roles * @param locale */ - public UserRep(String username, String firstname, String surname, UserState userState, Set roles, - Locale locale) { + public UserRep(String userId, String username, String firstname, String surname, UserState userState, + Set roles, Locale locale) { + this.userId = userId; this.username = username; this.firstname = firstname; this.surname = surname; @@ -47,6 +50,13 @@ public class UserRep implements Serializable { this.locale = locale; } + /** + * @return the userId + */ + public String getUserId() { + return this.userId; + } + /** * @return the username */ diff --git a/src/ch/eitchnet/privilege/model/internal/User.java b/src/ch/eitchnet/privilege/model/internal/User.java index 6808b899d..30f51901c 100644 --- a/src/ch/eitchnet/privilege/model/internal/User.java +++ b/src/ch/eitchnet/privilege/model/internal/User.java @@ -24,6 +24,8 @@ import ch.eitchnet.privilege.model.UserState; */ public final class User { + private final String userId; + private final String username; private final String password; @@ -37,7 +39,7 @@ public final class User { private final Locale locale; /** - * + * @param userId * @param username * @param password * @param firstname @@ -46,9 +48,11 @@ public final class User { * @param roles * @param locale */ - public User(String username, String password, String firstname, String surname, UserState userState, + public User(String userId, String username, String password, String firstname, String surname, UserState userState, Set roles, Locale locale) { + this.userId = userId; + this.username = username; this.password = password; this.userState = userState; @@ -61,6 +65,13 @@ public final class User { this.locale = locale; } + /** + * @return the userId + */ + public String getUserId() { + return userId; + } + /** * @return the username */ @@ -101,7 +112,7 @@ public final class User { /** * @return the userState */ - public UserState getState() { + public UserState getUserState() { return this.userState; } @@ -133,7 +144,7 @@ public final class User { * @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.username, this.firstname, this.surname, this.userState, + return new UserRep(this.userId, this.username, this.firstname, this.surname, this.userState, new HashSet(this.roles), this.locale); } @@ -143,7 +154,9 @@ public final class User { @Override public String toString() { StringBuilder builder = new StringBuilder(); - builder.append("User [username="); + builder.append("User [userId="); + builder.append(this.userId); + builder.append(", username="); builder.append(this.username); builder.append(", firstname="); builder.append(this.firstname); diff --git a/test/ch/eitchnet/privilege/test/PrivilegeTest.java b/test/ch/eitchnet/privilege/test/PrivilegeTest.java index 90960ab95..2416508a9 100644 --- a/test/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/test/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -90,7 +90,7 @@ public class PrivilegeTest { Certificate certificate = privilegeHandler.authenticate("eitch", "1234567890"); // let's add a new user bob - UserRep userRep = new UserRep("bob", "Bob", "Newman", UserState.NEW, new HashSet(), null); + UserRep userRep = new UserRep("1", "bob", "Bob", "Newman", UserState.NEW, new HashSet(), null); privilegeHandler.addOrReplaceUser(certificate, userRep, null); logger.info("Added user bob"); @@ -154,7 +154,7 @@ public class PrivilegeTest { org.junit.Assert.assertTrue("Certificate is null!", certificate != null); // let's add a new user bob - UserRep userRep = new UserRep("bob", "Bob", "Newman", UserState.NEW, new HashSet(), null); + UserRep userRep = new UserRep("1", "bob", "Bob", "Newman", UserState.NEW, new HashSet(), null); privilegeHandler.addOrReplaceUser(certificate, userRep, null); logger.info("Added user bob"); } @@ -173,7 +173,7 @@ public class PrivilegeTest { org.junit.Assert.assertTrue("Certificate is null!", certificate != null); // let's add a new user ted - UserRep userRep = new UserRep("ted", "Ted", "Newman", UserState.NEW, new HashSet(), null); + UserRep userRep = new UserRep("2", "ted", "Ted", "Newman", UserState.NEW, new HashSet(), null); privilegeHandler.addOrReplaceUser(certificate, userRep, null); logger.info("Added user bob"); } From 3238c024bf92726ce35572f4a86810086bdd31fa Mon Sep 17 00:00:00 2001 From: eitch Date: Mon, 1 Nov 2010 22:01:26 +0000 Subject: [PATCH 037/173] --- docs/PrivilegeHandlers.dia | Bin 2034 -> 2890 bytes docs/PrivilegeModelPrivilege.dia | Bin 2230 -> 2108 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/PrivilegeHandlers.dia b/docs/PrivilegeHandlers.dia index 4f8da83b765831d300996f1c1f64ca25a1fdaabb..ca0e91507482e2b4d006b28ed34007a4b0c137dc 100644 GIT binary patch literal 2890 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 b12cJx9BV|Aw85FYGg=ypsl!w$TAKd=Ic%m7 literal 2034 zcmaKqc{~#iAIA&1N^_>)k`0mHOha=H&3zPdOHq_%axcP6IdT_~99whW{2bG$jSRUm z=ah_=4o9vW)11FwJ%2o}=XpJ^*Yo|nUf)07f4u*CgHrep{XQ=4Wj8m>q?R~tweCi* zlB!028MB16ykrfU^egw<$`T=~O!B`~N-3c(1zi6_Dn@9wbD~5&AvYb_rI!Jn#)PD$ zv#9&3-(K)Rj1>0pue@^DK=$bwH5R3kh5LJB@~_veICi(!*B1wCXL8?dp50T42(iys zl%V=+fIuK2eL#}v=qF!PT+=pah5!#KMD1@m){_0(<&%w9->iz3o;gJquid_78Sq{1 z^Ay%@AhNj)Dv^!hk!;*Ta;)}!s9_9zLoTXg@Hq4E+Pt}4aK7IpBaH@5TX@jl^Z^Yv zEK8{fueUwZX}w8xQaC5^-LUN*mi+``@@>4)bqznXMRwE>Gq+0G%z2$ux|;w)6noue z#?FtBmi(E%Y^KeAusLYNOS_0WX#xNMx_y0lJQWeT6_N3hayFpII6lj4wyN)knZ@*rCVg1n4)@UyiDP~* zZnr@O_NXT}7OF`jBKak<22DSR^NyoJk&)xIx zfwC+`wzy(4wX+(w?+rP)rN9E0f_%Dcmu42mUGo!1E--9^Zm#O%YTVv`#E8bT=fK7Z z;DOf4`x=>gS^ju($@5R9&PP1eiLTG%v;23;zVtTg0ZXkex?w(J3Ei{I?gFL?dn{m? zJOYBhGA+jg(t>qP>lp6f*9>HquS{I$q|r6>wjW22tmZ7Hy%TT|>!&95^DdsWfRZu+ zPzhyu&(aH#W`o?NJq*6j6kR!A9bRQdgQUdB1igdtKDl2tXui)S;<0sQUE>35vW@8u zDdJh#;n0RvTb%k)E{o2PhFq(3frCp*M;%Mx3P!xbb&&F6(}@TzqL} z5wm`kgp|3dCy+20=c_fArL>&0z0}hRXV|Kq4*rhCOS82J3;FSdiF5L?tA?=NA_1%x z4PH}%*Qgb1&+u^Uo@5S$dv&L7?DO|^(SrDs4=#Tr(5vcrbr{HW^KXctyBGqpdGV?#(U=OsU{>R=;i(?fED2XsU(H*L;dCRP9w zJg(?gmC2;iK(4K`UOhZ*ssOeq$fiLw=h40 z;pcrV%K<3+HV8Er{Q-Z+T3IcU9>#j3$38DQh-ifO)D#@QI`uW0$+zjZMp0=ofzq|? zH~b11`Bq;(ML{~JLQgtnTMUvb+h&w-#d+nW*wylAbl#H%DUJ46QH452;q_Z)^YrJB=;yaYv8C)Ag| zsGO(X&qj^(l{+BFl=-Z`Ozz4l7maD?I5m5)k5PQpi=2r-524i1I;?_vhuWhkwIZ1N zBaW!hpH{Au@&=C|NFCPZ3g-FB-z$nqY&1tq zUbwlyZ%v{aP|^=tfoYhsF4_=5+we#7 z7J9Z~yaYWdZQ(y+WPw4`L+eUHE$(&>xjY6Bz)0a6G#-xZ*L%*qDiy2i6*i-lVFfPR z(HvwXSifcud0*1xzO>_Y_EHVxr^KX2AgBc}Rc?fuE_vOK`_v}rrB{6^=9dJ_Uw?xu zQe=JC^|!{3MEp-h_A~aL><8{IjB4oN6D(vs*hLr`hLzBzLodrz)LU8%_hV!$gdO-H z(AZxs^1gl=e_VmeASk|j2sTQW=@niyte=r5dDQFL)>J`H?t%irTJ_0NVQXeSLIT@z@^Ehs1?4pf9+_AVGk7DgL#Kji&iU?mV9m|*#) zJ8yjaZ3&H)otOsI+62z4pRBwvr@7(MGP`@AK#&PFT8f_#YB=Sosq)#2=%HaRHoZ&X zW55OEd)*kLe<;5J#5aFD|msue6SJHKsb% zd4wFJEY+6D6OVX)Fnz1@{tCFYi8Bq0s$MudWB2$`*@SoU|9RMe=1@sg4ITofi%)8J zs}0{O=tE{23{upZfp7fCG~6fsK#)I4XrHqN7#@Qd zPSuI9AEM#IDyda=;9uQ2o2*n%v>IpEY3JI9@A_;T{vGz}WpmvDUq8B5uVMbsp??7W CZ2*h_ diff --git a/docs/PrivilegeModelPrivilege.dia b/docs/PrivilegeModelPrivilege.dia index 85fc14ffb44e591e416dc0963e1dfbbad13aad08..8842f4ea446c450e57c1310fee9e10914e695b60 100644 GIT binary patch literal 2108 zcmZvbXEYlM1BMf$R;d`D4vE;67PXbwgiw^Cg0!KPCd4S=O6|~L)TTzGY6Nv#rHX6S z9!1qEQXzIj(HOOF?|09+=X^iD`~G^)^XvWdNF;Cr{vI3qIvO2F(-sNeti*h~mM;{; z&w;dsOtF7 zj%0;Oz3Y-F#_b883RI=tEI(pk@6Fomhm+vpipYA>c0^~d?#Tj=>T9OozUmbYESFZW>B;0HU<>6(%f3E@yB@1xM?lGx(hvS=KDq#C6eDL{G zX-p|Eh_5KJO86wgNlr;NNOHE36l#@OIKN))!fJcpjLx(UDyEu-49xy4IeM(D+fk+S zI%21xCY0hd%B;;ll>N{+cc0E$Xb|oR>RE)Ijunb6_j%RY=5zHZfDndRC5bz z<8u-ULd$+Drlg&71C;lxKL3?Bizf+Gig4?KCLrDOKb1UXJmZbr_0QSy4Bom$-@ zU`*t0M@;Mv4c@i&skL?;f!2>Zy1#|k4sOq2N9&@c2Y>q4<6XYx3ePSO@hyJ4x-00D zwU2mL>(W5qy33_E1|*k=I{xf)eq7v{>NxjEAoS}~muU0;)!+KZB8_vj?3e;NS)QB0Hxp^Z>JF?m(EvncTr+0tbHLGW0W*2iS60q(U z%Q9ldBP|x*uz~f3OH|skD@swTtq9qe3aKOkf<4Kc6{7YfFqs^??E3D9arfbtc)f5n zXXd2>f>y#*awxta=9Ug)Y_FCTz~4;|&=+Af-6f!zJScTZc2-^%g=3M5ZbzwlS)yFD z`0{g97T{k^RFzYPVhRm1rZo4XZ^>=7X-{^ad~^1>Mok>5TpMjYbJxNRj6iB6d%(a# ziq>JgShAhnz^~Eg*Naaz&mcsV^iPHDkb0*+m?@eqb84AvJGC673Ld>DZ$HMDBm&ysq*Jk3=kFn+1E(%ecNc{*vJ?6Ida!i zuI@(31!VJrd#_j<aGyu{D zgbWV(8hx%pnUG;1u2qY8@xjk5eYgxR7C5tnu$wrX9#9Rxxe>JI={vKjw{4EMxuJ4- z6t(JBYm5Cs-j+xOJtdiU+@Ku4@`cD5T#GPpJWEN=&VBU!qF1~^ShaF`JhjBWG@IC? zbcy~4?n35Ipx3 z7WsM0u%BJ=Obz_sAtp?6>tO+%&zr>dY5MAQj*Dy}z<3d3kX+=P+b=!{lY1Tv5vS%8xe{}R~ z1mX66cj|}#HmP9+re&p1$1TaPfYho4Wuz=jQ=ULUMp9%`jyP^1(Eo&-L?@YoF_b1F zIu)%mqxxp;gbo~r7q_LFdDMZqj0mc5u?$W0yNTcJ^RrK8HcX36d%V{6l8Tbg&cwCg z(goaQ51lGT%Q5Q#lTJJ1>4LY*6T1a}Qn=OuG++%oj_b2P0oYJCDj4vOc>MaBdy~iE z9gdGcq_GT-(N&R|??F_#S#szqy9Gc7AOt|3gB}z%&Z!8>U&sKMMM)Wg(l2hI?dMF8 zoZ`SFfVm~3*N36<>w+#CItRpXTw&uGM_QKYl+?v;W5a7WV{E=g1T zFZt?L3i2AH4{mZ24vn@H=(mwsg^tbnu=qkrDmk0i1NNjG`Y%93>KGy)x=98+ED$$D zvzbRpx2B3sbIf;|IkbWp-2R>Lq~q>b2kw7Pmxm5wohJYYfOJ&q$U$mld|5hY+Uf=F z%fKvvRWJs*BMv8319=?pxuF#o(*7>dHcGx#_)f@wOWX|9UYgSO0Qd`_8UtJa literal 2230 zcmZ{Yc{mde1II~BIWjBEeN9oxHQS2hEHTOzLMFr9@Pf8i`)(E_)19w*f6%q>f(rJZt9N zX(B1~1C(sV+zKueFJvRy5?Oa4xSOO92#CZWFjRi8u9Ha;Z6Ze>e9!X?n#4TOUfT3y zbW-jr%)l%ODiQ4Wz4n7|f+^rE1Ja)U)P2iir{o&@(%yL`M_QzkpMm%$5kS3_4pZ?` z3z%f(H&FM_kFQ;l*&Z!9Ev+om^GpmG6YT<;-=VIAHOQ%1Q{9yyRov;QMm8z}3C>uC zHDsq=H%78Z(*uCwgOduG1AKSoVX~={D}7u6HD*+ejv{(va!lY& zLN>qX$IVDPFVTt6p7=m5lDhRQ5BjigHY!?U(&-VxD*1b#L;8~xUdy8oK@t2zyv$~~ zcbM>B3)z3xTnEx61QJe4s#JUsQ$Ee()nzl~`uiLK?gKh+@gi0#Ddc$T}ZvhiF9hG}l5KO2b`gyPO;T<}`k0fo&t$J4FN?CSm zXg)r^?xzCM>S?rta)GbS(>e<4VGzbzE|F6weo zrFS9xp?1E!3~Q`+$xXdyeY7s^coG+l&5hqJ#R}=(9hbMaHGeszq~8i7J=Bn;IVOR| zFxc9^|LvpRy4fG)@OD2pJR#L{_lw@Khsh{=u5*1s7N$|G1OR9d7}ofmZ+-IAPBhG( zBM>}}cFYL>-vL*4%(y&(9w;RPMp^#_ru2GdUB1x#HRoW}9Ygx*1GWQy=0Uye?1|<} z*+{5%;;qE%Rk}y)=g*u!v&lv}IAW>rcoqg#(aen=N?J@Xh9MGMq=TF%++IU9U*Yg8 zg@8=)Y=p4tDLJ>VT+eMT>;HVbV@00PV}-)?Dr-$8AG4@%{9h@kaP$T=?R;BqFrO7) zf@)Qltlu$RR=g|07O>S}hU}#-QFNx0x?3Tw23=5~#oU*RTjd2exQ13ZipPZO$gTsm zo0u4#V@s?+T0xr7%VD0D%^jZGsNL9d0CQx?0)(z+b{8mipjSh2FFfLHt7!**T0_1z z?6#QB>Ih4NIByNrCP7o)<-9zJhJr59W9&ky*DWBIqx*kV^ zm*aC_PRY9UzR2z}{N~fJX#3wRR+sEH)Q)C-my2wnBIOShbc;^C|5w19H@4L! z7q6t@clSdn*H%{zW(G%|zY+2MYo3aDue|mr34-jUCBpsoJR0OA_)|cYXRHZ6!`Yz_ z%ug+S;NEg4Bex4=C7hSZ%Y2{oH6>2B7Hxg;)p(4d{Z>XWdI!gI_HW3IYj1{b+}4~Q zj_|KUZsv#){xZU$6QHk&SDyiCSEO3I_#R8m&AjJ;gJ1L@4GQ>sR{ezpu1BK^V!3L7 z(oi)m>VT2BOGfwpDuAlrYuvt*6w+s?V3V9Cm}!4SNq3>pAq!6Q92fRCb6?2aZfH}Z z7%snC@E8;tO4|E+scb$oW0sb3y;2;cs(7fS`Ul^^WMu-5@ Date: Mon, 1 Nov 2010 22:01:44 +0000 Subject: [PATCH 038/173] --- src/ch/eitchnet/privilege/policy/PrivilegePolicy.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/ch/eitchnet/privilege/policy/PrivilegePolicy.java b/src/ch/eitchnet/privilege/policy/PrivilegePolicy.java index 44c679e5b..3f4b64853 100644 --- a/src/ch/eitchnet/privilege/policy/PrivilegePolicy.java +++ b/src/ch/eitchnet/privilege/policy/PrivilegePolicy.java @@ -20,5 +20,11 @@ import ch.eitchnet.privilege.model.internal.Role; */ public interface PrivilegePolicy { + /** + * @param role + * @param privilege + * @param restrictable + * @return + */ public boolean actionAllowed(Role role, Privilege privilege, Restrictable restrictable); } From 43f3bb33073256c55b49fe2548a658881b36917c Mon Sep 17 00:00:00 2001 From: eitch Date: Sat, 6 Nov 2010 22:26:43 +0000 Subject: [PATCH 039/173] --- .settings/org.eclipse.jdt.core.prefs | 16 ++++++++-------- docs/PrivilegeAuthentication.dia | Bin 0 -> 1544 bytes docs/PrivilegeHandlers.dia | Bin 2890 -> 2880 bytes 3 files changed, 8 insertions(+), 8 deletions(-) create mode 100644 docs/PrivilegeAuthentication.dia diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs index e83bae06a..c436f312a 100644 --- a/.settings/org.eclipse.jdt.core.prefs +++ b/.settings/org.eclipse.jdt.core.prefs @@ -1,4 +1,4 @@ -#Sun Sep 19 16:32:10 CEST 2010 +#Sat Nov 06 15:16:05 CET 2010 eclipse.preferences.version=1 org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 @@ -31,20 +31,20 @@ org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=warning org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=warning org.eclipse.jdt.core.compiler.problem.invalidJavadoc=warning org.eclipse.jdt.core.compiler.problem.invalidJavadocTags=enabled -org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsDeprecatedRef=enabled -org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsNotVisibleRef=enabled -org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsVisibility=private +org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsDeprecatedRef=disabled +org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsNotVisibleRef=disabled +org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsVisibility=public org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=warning org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=warning org.eclipse.jdt.core.compiler.problem.missingJavadocComments=warning -org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsOverriding=enabled -org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsVisibility=private +org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsOverriding=disabled +org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsVisibility=public org.eclipse.jdt.core.compiler.problem.missingJavadocTagDescription=all_standard_tags org.eclipse.jdt.core.compiler.problem.missingJavadocTags=warning -org.eclipse.jdt.core.compiler.problem.missingJavadocTagsOverriding=enabled -org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=private +org.eclipse.jdt.core.compiler.problem.missingJavadocTagsOverriding=disabled +org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=public org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=warning org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=warning diff --git a/docs/PrivilegeAuthentication.dia b/docs/PrivilegeAuthentication.dia new file mode 100644 index 0000000000000000000000000000000000000000..0d4cb6b4ffc8510b0bb062d8966b8bd113c0cffa GIT binary patch literal 1544 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 Pct$m+xaN>7L{0S{J)fU0 delta 2870 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 IO;1nvFC!YIQ~&?~ From 5d871206752d4f87f585b7b8fe95a6c9c60ea8fb Mon Sep 17 00:00:00 2001 From: eitch Date: Thu, 11 Nov 2010 21:30:23 +0000 Subject: [PATCH 040/173] --- docs/PrivilegeAuthentication.dia | Bin 1544 -> 1819 bytes docs/PrivilegeModelUser.dia | Bin 1752 -> 2198 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/PrivilegeAuthentication.dia b/docs/PrivilegeAuthentication.dia index 0d4cb6b4ffc8510b0bb062d8966b8bd113c0cffa..fe9ed43c90f0f54a4a5da0775c29515cee275fd5 100644 GIT binary patch literal 1819 zcmV+$2juu4iwFP!000021MOT(Z`(K!zUNm6>ZJ>$v`FgNG%30T7JF#XJ#5bgZPPZl zvSdhfoLu&|FDWUG^^7f<-Z-Fu1duYC5r>>_X7n-o^!Z^G8uv6wSsdM(5Ll)`qiH;2 z(c;$p`S%|meDm|&)u$OFAH~liAuB`N5vB6%)?9MF{x}+Kw_6ZCri8}{2-yatbo4I? zLoyOVBlGUcF!mM92q*Gdag}hMu*rr~BO)t$Yfi}Y*CL5G(ag-H3b|<<#))xH!dvs* zJUh%$p=MOl)1iA!7IczO@~d67C5|BIqk2t~s@l~$PMHwlkL#L9iyrxVPo|Jcg;2D( zd-siUPLp@}V-**l^))?gt0BH3$$~|7Tf#qK?hFJR9LU3kzJo2~;U~WwZ*Fp4bK!Z- zCFeEA((yV@ctRMjo1DaPNJ*4i<;jNjx06muD3(~)uwZ}fbH;i6!t>8bm>x2K{PHg1 zj*Z?$!e-sUTaswevSl;A96u;>SFZh0k@kC*vPnpro}NYgWa=MJqrN&j8G=9GK5#YU z+FDb@%%=`@j!zMb4V%&Qg=JQjY0fg z*75U_!T@6D+6R8s2XzE#voM)d4J2X+BwB>DFoJ-W8Sy;lMp$&j33PykoPI`EloRuX z1wUg`-cS29j-oM*W|^0c^^W9B@nacWo1{MEa#dzo{Y=bR7#?7vTvHxrBH{76 zzDRbc3}8??a8E0fn73PTPGn`4knMOLPdBx-(NS0&ZD5+jX}5t&=09qQufuU9>Z9eB zrZtZ>rJG}wr}0Le^6beeQTXJiP%dFaz@UKZv=&rPe=(LMn#r6X@SMj2&!K5N|Mk{% z>xrqNG{2lDjqWV+BfHd)L*U>XVqJR*vqt~|=h_v&ZlDV=l6dDbIHwO>aXx(;DO)<~ zrC_PZv^B}uYnYN;<*m(M3A<+@UC=)>JZyV^)6w*_`6-TglVN$?n3EL?AI0;LC^e0g zKNd)w-})yF?6In%<8mdbT8`#K2x`)s`gzh19521Sq-9rrMAv!p2JhY2= zPE;yMZW~yI70Ghjz|YR!0L|U#zIHK9bZeqp6WyBV?k2jCI->hKO;fUH$|UyiOyYSN z#zCct#LI!y0_X-J;LF`Z5ZD)O31O=c6a0JCuCj$`$S_#28%0n4q@u8t#R~y;k{N?=1M_4Bm-T#1B3PJfS^! z!WRxt4UX%;Fs$LJ8J-X@63RD8WPGIwXY&lCE*7E!faEGy2~ic8@sEM13cNw<9GeB* zp%8_TT22q{NIm2N7kQV?>h<^6A+(+i9M7}&T18J&e465W^%UQi^*WTfML?6L160!i z$Og#wea-V}o=@|9n&;CzpXT|L@O<9j)?$~aRPuZX17XA~cs^m%l{Q7+fdlMc^h=ON z|Fw(BA&aqVwy&Lsb1+2Ct^o-g6>J}bWiv>VeVXihU1XoDj_m8mC;qASjX?Vh+eRP; z!QQ?R39j~q=aVM?wM)qnL!C~#UTHc0KF;^vSM<{Ek%}3;dqgLcSC&xl|FL@{IN9zI z2l)6r9IXukAL1L)vjnndfi6J3Ex)#%BvmgqbSh%lSPpxL!{0t@i-EDG$vjS0-zi_l zv-i_y^7e8!n25D+%b6G13@d~pf50XaPu(Vyj=C70>Z4&w>*C^@VMSS7d^8MMAh`6Z zxWRd?LUO#iChn+-F>v>4RSWuxO5)R4(Iv4iiO;l?=sYDc9Q^IDtVHt!9Qpvlz76eR z2?YaN;;sjfFqD_XxnT!h_ z`J<5Nsj7umYe}pUiqHw@Ll^tLj19%@Ig;D%9GuOC>-#s@0=DaAX^#9As|LVAKpo<=$L10=dc%NKCg^WGVaL>F!kvd4qRG=jYj{yzj0`-}=70`VU5K Jg_ILy003O`j|l(( literal 1544 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 From 7d231ebb3aefc9fb107e23a09eda8397b349aa7b Mon Sep 17 00:00:00 2001 From: eitch Date: Sat, 27 Nov 2010 21:00:34 +0000 Subject: [PATCH 041/173] [Minor] added some java docs --- .../handler/DefaultEncryptionHandler.java | 2 +- .../handler/DefaultPrivilegeHandler.java | 108 +++-- .../privilege/handler/PersistenceHandler.java | 16 +- .../privilege/handler/PrivilegeHandler.java | 380 +++++++++++++----- .../handler/XmlPersistenceHandler.java | 12 +- 5 files changed, 377 insertions(+), 141 deletions(-) diff --git a/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java b/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java index 4b33f648a..8fb5dfc28 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java @@ -24,7 +24,7 @@ import ch.eitchnet.privilege.i18n.PrivilegeException; /** *

    - * This default {@link EncryptionHandler} creates nokens by using a {@link SecureRandom} object. Hashing is done by + * 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 *

    * diff --git a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 7e8b813a2..2365c0001 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -33,6 +33,21 @@ import ch.eitchnet.privilege.model.internal.User; import ch.eitchnet.privilege.policy.PrivilegePolicy; /** + *

    + * 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 #validateIsPrivilegeAdmin(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 rvonburg * */ @@ -106,8 +121,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { validateIsPrivilegeAdmin(certificate); // create a new privilege - Privilege privilege = new Privilege(privilegeRep.getName(), privilegeRep.getPolicy(), privilegeRep - .isAllAllowed(), privilegeRep.getDenyList(), privilegeRep.getAllowList()); + Privilege privilege = new Privilege(privilegeRep.getName(), privilegeRep.getPolicy(), + privilegeRep.isAllAllowed(), privilegeRep.getDenyList(), privilegeRep.getAllowList()); // delegate to persistence handler this.persistenceHandler.addOrReplacePrivilege(privilege); @@ -151,8 +166,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } // create new user - User user = new User(userRep.getUserId(), userRep.getUsername(), passwordHash, userRep.getFirstname(), userRep - .getSurname(), userRep.getUserState(), userRep.getRoles(), userRep.getLocale()); + User user = new User(userRep.getUserId(), userRep.getUsername(), passwordHash, userRep.getFirstname(), + userRep.getSurname(), userRep.getUserState(), userRep.getRoles(), userRep.getLocale()); // delegate to persistence handler this.persistenceHandler.addOrReplaceUser(user); @@ -229,8 +244,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { Set newRoles = new HashSet(currentRoles); newRoles.add(roleName); - User newUser = new User(user.getUserId(), user.getUsername(), user.getPassword(), user.getFirstname(), user - .getSurname(), user.getUserState(), newRoles, user.getLocale()); + User newUser = new User(user.getUserId(), user.getUsername(), user.getPassword(), user.getFirstname(), + user.getSurname(), user.getUserState(), newRoles, user.getLocale()); // delegate user replacement to persistence handler this.persistenceHandler.addOrReplaceUser(newUser); @@ -334,8 +349,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // create new user Set newRoles = new HashSet(currentRoles); newRoles.remove(roleName); - User newUser = new User(user.getUserId(), user.getUsername(), user.getPassword(), user.getFirstname(), user - .getSurname(), user.getUserState(), newRoles, user.getLocale()); + User newUser = new User(user.getUserId(), user.getUsername(), user.getPassword(), user.getFirstname(), + user.getSurname(), user.getUserState(), newRoles, user.getLocale()); // delegate user replacement to persistence handler this.persistenceHandler.addOrReplaceUser(newUser); @@ -387,8 +402,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } // create new privilege - Privilege newPrivilege = new Privilege(privilege.getName(), privilege.getPolicy(), allAllowed, privilege - .getDenyList(), privilege.getAllowList()); + Privilege newPrivilege = new Privilege(privilege.getName(), privilege.getPolicy(), allAllowed, + privilege.getDenyList(), privilege.getAllowList()); // delegate privilege replacement to persistence handler this.persistenceHandler.addOrReplacePrivilege(newPrivilege); @@ -458,20 +473,26 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { throw new PrivilegeException("Privilege " + privilegeName + " does not exist!"); } + // validate that the policy exists + PrivilegePolicy policy = this.persistenceHandler.getPolicy(policyName); + if (policy == null) { + throw new PrivilegeException("No privilege policy exists for the name " + policyName); + } + // create new privilege - Privilege newPrivilege = new Privilege(privilege.getName(), policyName, privilege.isAllAllowed(), privilege - .getDenyList(), privilege.getAllowList()); + Privilege newPrivilege = new Privilege(privilege.getName(), policyName, privilege.isAllAllowed(), + privilege.getDenyList(), privilege.getAllowList()); // delegate privilege replacement to persistence handler this.persistenceHandler.addOrReplacePrivilege(newPrivilege); } /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setUserLocaleState(ch.eitchnet.privilege.model.Certificate, + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setUserLocale(ch.eitchnet.privilege.model.Certificate, * java.lang.String, java.util.Locale) */ @Override - public void setUserLocaleState(Certificate certificate, String username, Locale locale) { + public void setUserLocale(Certificate certificate, String username, Locale locale) { // validate who is doing this validateIsPrivilegeAdmin(certificate); @@ -483,8 +504,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } // create new user - User newUser = new User(user.getUserId(), user.getUsername(), user.getPassword(), user.getFirstname(), user - .getSurname(), user.getUserState(), user.getRoles(), locale); + User newUser = new User(user.getUserId(), user.getUsername(), user.getPassword(), user.getFirstname(), + user.getSurname(), user.getUserState(), user.getRoles(), locale); // delegate user replacement to persistence handler this.persistenceHandler.addOrReplaceUser(newUser); @@ -507,8 +528,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } // create new user - User newUser = new User(user.getUserId(), user.getUsername(), user.getPassword(), firstname, surname, user - .getUserState(), user.getRoles(), user.getLocale()); + User newUser = new User(user.getUserId(), user.getUsername(), user.getPassword(), firstname, surname, + user.getUserState(), user.getRoles(), user.getLocale()); // delegate user replacement to persistence handler this.persistenceHandler.addOrReplaceUser(newUser); @@ -541,8 +562,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } // create new user - User newUser = new User(user.getUserId(), user.getUsername(), passwordHash, user.getFirstname(), user - .getSurname(), user.getUserState(), user.getRoles(), user.getLocale()); + User newUser = new User(user.getUserId(), user.getUsername(), passwordHash, user.getFirstname(), + user.getSurname(), user.getUserState(), user.getRoles(), user.getLocale()); // delegate user replacement to persistence handler this.persistenceHandler.addOrReplaceUser(newUser); @@ -565,8 +586,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } // create new user - User newUser = new User(user.getUserId(), user.getUsername(), user.getPassword(), user.getFirstname(), user - .getSurname(), state, user.getRoles(), user.getLocale()); + User newUser = new User(user.getUserId(), user.getUsername(), user.getPassword(), user.getFirstname(), + user.getSurname(), state, user.getRoles(), user.getLocale()); // delegate user replacement to persistence handler this.persistenceHandler.addOrReplaceUser(newUser); @@ -623,8 +644,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { Certificate certificate = new Certificate(sessionId, username, authToken, authPassword, user.getLocale()); // create and save a new session - Session session = new Session(sessionId, authToken, authPassword, user.getUsername(), System - .currentTimeMillis()); + Session session = new Session(sessionId, authToken, authPassword, user.getUsername(), + System.currentTimeMillis()); this.sessionMap.put(sessionId, new CertificateSessionPair(session, certificate)); // log @@ -635,8 +656,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } /** - * TODO What is better, validate from {@link Restrictable} to {@link User} or the opposite direction? - * + * Checks if the action is allowed by iterating the roles of the certificates user and then delegating to + * {@link #actionAllowed(Role, Restrictable)} * * @throws AccessDeniedException * if the {@link Certificate} is not for a currently logged in {@link User} or if the user may not @@ -648,6 +669,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { @Override public boolean actionAllowed(Certificate certificate, Restrictable restrictable) { + // TODO What is better, validate from {@link Restrictable} to {@link User} or the opposite direction? + // first validate certificate if (!isCertificateValid(certificate)) { logger.info("Certificate is not valid, so action is not allowed: " + certificate + " for restrictable: " @@ -690,18 +713,43 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#actionAllowed(ch.eitchnet.privilege.model.internal.Role, + * Checks if the {@link RoleRep} has access to the {@link Restrictable} by delegating to + * {@link PrivilegePolicy#actionAllowed(Role, Privilege, Restrictable)} + * + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#actionAllowed(ch.eitchnet.privilege.model.RoleRep, * ch.eitchnet.privilege.model.Restrictable) */ @Override - public boolean actionAllowed(Role role, Restrictable restrictable) { + public boolean actionAllowed(RoleRep roleRep, Restrictable restrictable) { // user and restrictable must not be null - if (role == null) + if (roleRep == null) throw new PrivilegeException("Role may not be null!"); else if (restrictable == null) throw new PrivilegeException("Restrictable may not be null!"); + // get role for the roleRep + Role role = this.persistenceHandler.getRole(roleRep.getName()); + + // validate that the role exists + if (role == null) { + throw new PrivilegeException("No Role exists with the name " + roleRep.getName()); + } + + return actionAllowed(role, restrictable); + } + + /** + * Checks if the {@link Role} has access to the {@link Restrictable} by delegating to + * {@link PrivilegePolicy#actionAllowed(Role, Privilege, Restrictable)} + * + * @param role + * @param restrictable + * + * @return true if the privilege is granted, false otherwise + */ + protected boolean actionAllowed(Role role, Restrictable restrictable) { + // validate PrivilegeName for this restrictable String privilegeName = restrictable.getPrivilegeName(); if (privilegeName == null || privilegeName.length() < 3) { @@ -800,6 +848,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } /** + * 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(java.lang.String) */ @Override diff --git a/src/ch/eitchnet/privilege/handler/PersistenceHandler.java b/src/ch/eitchnet/privilege/handler/PersistenceHandler.java index 2e34f1703..7a6db6b33 100644 --- a/src/ch/eitchnet/privilege/handler/PersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/PersistenceHandler.java @@ -12,6 +12,7 @@ package ch.eitchnet.privilege.handler; import java.util.Map; +import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.Restrictable; import ch.eitchnet.privilege.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.Role; @@ -67,17 +68,20 @@ public interface PersistenceHandler { /** *

    - * Thus this method instantiates a {@link PrivilegePolicy} object from the given policyName. The - * {@link PrivilegePolicy} is not stored in a database, but rather behind a privilege name a class name is stored - * which then is used to instantiate a new object + * 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 name/id of the {@link PrivilegePolicy} object to return + * the class name of the {@link PrivilegePolicy} object to return * - * @return the {@link PrivilegePolicy} object, or null if no class is defined for the given policy name + * @return the {@link PrivilegePolicy} object + * + * @throws PrivilegeException + * if the {@link PrivilegePolicy} object for the given policy name could not be instantiated */ - public PrivilegePolicy getPolicy(String policyName); + public PrivilegePolicy getPolicy(String policyName) throws PrivilegeException; /** * Removes a {@link User} with the given name and returns the removed object if it existed diff --git a/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java b/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java index 749d425ff..294cde3f0 100644 --- a/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java +++ b/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java @@ -10,6 +10,7 @@ package ch.eitchnet.privilege.handler; +import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; @@ -28,6 +29,10 @@ 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 rvonburg * */ @@ -87,8 +92,14 @@ public interface PrivilegeHandler { * 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); + public UserRep removeUser(Certificate certificate, String username) throws AccessDeniedException, + PrivilegeException; /** * Removes the role with the given roleName from the user with the given username @@ -98,9 +109,15 @@ public interface PrivilegeHandler { * @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 + * 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 void removeRoleFromUser(Certificate certificate, String username, String roleName); + public void removeRoleFromUser(Certificate certificate, String username, String roleName) + throws AccessDeniedException, PrivilegeException; /** * Removes the role with the given roleName @@ -111,8 +128,14 @@ public interface PrivilegeHandler { * 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 */ - public RoleRep removeRole(Certificate certificate, String roleName); + public RoleRep removeRole(Certificate certificate, String roleName) throws AccessDeniedException, + PrivilegeException; /** * Removes the privilege with the given privilegeName from the role with the given roleName @@ -123,8 +146,14 @@ public interface PrivilegeHandler { * 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 void removePrivilegeFromRole(Certificate certificate, String roleName, String privilegeName); + public void removePrivilegeFromRole(Certificate certificate, String roleName, String privilegeName) + throws AccessDeniedException, PrivilegeException; /** * Removes the privilege with the given privilegeName @@ -135,8 +164,14 @@ public interface PrivilegeHandler { * the privilegeName of the privilege to remove * * @return the {@link PrivilegeRep} of the privilege removed, or null if the privilege 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 PrivilegeRep removePrivilege(Certificate certificate, String privilegeName); + public PrivilegeRep removePrivilege(Certificate certificate, String privilegeName) throws AccessDeniedException, + PrivilegeException; /** *

    @@ -156,8 +191,14 @@ public interface PrivilegeHandler { * 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(String)} + * + * @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 addOrReplaceUser(Certificate certificate, UserRep userRep, String password); + public void addOrReplaceUser(Certificate certificate, UserRep userRep, String password) + throws AccessDeniedException, PrivilegeException; /** * Adds a new role, or replaces the role with the information from this {@link RoleRep} if the role already exists @@ -166,8 +207,14 @@ public interface PrivilegeHandler { * 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 */ - public void addOrReplaceRole(Certificate certificate, RoleRep roleRep); + public void addOrReplaceRole(Certificate certificate, RoleRep roleRep) throws AccessDeniedException, + PrivilegeException; /** * Adds a new privilege, or replaces the privilege with the information from this {@link PrivilegeRep} if the @@ -177,8 +224,14 @@ public interface PrivilegeHandler { * the {@link Certificate} of the user which has the privilege to perform this action * @param privilegeRep * the {@link PrivilegeRep} containing the information to create the new {@link Privilege} + * + * @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 addOrReplacePrivilege(Certificate certificate, PrivilegeRep privilegeRep); + public void addOrReplacePrivilege(Certificate certificate, PrivilegeRep privilegeRep) throws AccessDeniedException, + PrivilegeException; /** * Adds the role with the given roleName to the {@link User} with the given username @@ -189,8 +242,14 @@ public interface PrivilegeHandler { * 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 */ - public void addRoleToUser(Certificate certificate, String username, String roleName); + public void addRoleToUser(Certificate certificate, String username, String roleName) throws AccessDeniedException, + PrivilegeException; /** * Adds the privilege with the given privilegeName to the {@link Role} with the given roleName @@ -201,8 +260,14 @@ public interface PrivilegeHandler { * the roleName of the {@link Role} to which the privilege should be added * @param privilegeName * the privilegeName of the {@link Privilege} which should be added to 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 */ - public void addPrivilegeToRole(Certificate certificate, String roleName, String privilegeName); + public void addPrivilegeToRole(Certificate certificate, String roleName, String privilegeName) + throws AccessDeniedException, PrivilegeException; /** * Changes the password for the {@link User} with the given username. If the password is null, then the {@link User} @@ -217,115 +282,213 @@ public interface PrivilegeHandler { * 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(String)} - */ - public void setUserPassword(Certificate certificate, String username, String password); - - /** - * @param certificate - * the {@link Certificate} of the user which has the privilege to perform this action - * @param username - * @param firstname - * @param surname - */ - public void setUserName(Certificate certificate, String username, String firstname, String surname); - - /** - * @param certificate - * the {@link Certificate} of the user which has the privilege to perform this action - * @param username - * @param state - */ - public void setUserState(Certificate certificate, String username, UserState state); - - /** - * @param certificate - * the {@link Certificate} of the user which has the privilege to perform this action - * @param username - * @param locale - */ - public void setUserLocaleState(Certificate certificate, String username, Locale locale); - - /** - * @param certificate - * the {@link Certificate} of the user which has the privilege to perform this action - * @param privilegeName - * @param policyName - */ - public void setPrivilegePolicy(Certificate certificate, String privilegeName, String policyName); - - /** - * @param certificate - * the {@link Certificate} of the user which has the privilege to perform this action - * @param privilegeName - * @param allAllowed - */ - public void setPrivilegeAllAllowed(Certificate certificate, String privilegeName, boolean allAllowed); - - /** - * @param certificate - * the {@link Certificate} of the user which has the privilege to perform this action - * @param privilegeName - * @param denyList - */ - public void setPrivilegeDenyList(Certificate certificate, String privilegeName, Set denyList); - - /** - * @param certificate - * the {@link Certificate} of the user which has the privilege to perform this action - * @param privilegeName - * @param allowList - */ - public void setPrivilegeAllowList(Certificate certificate, String privilegeName, Set allowList); - - /** - * @param username - * @param password * - * @return + * @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, String password) + throws AccessDeniedException, PrivilegeException; + + /** + * Changes the name of the user. This changes the first name and the surname. If either value is null, then that + * value is not changed + * + * @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 name is to be changed + * @param firstname + * the new first name + * @param surname + * the new surname + * + * @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 setUserName(Certificate certificate, String username, String firstname, String surname) + 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 void 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 void setUserLocale(Certificate certificate, String username, Locale locale) throws AccessDeniedException, + PrivilegeException; + + /** + * Sets the class name of the {@link PrivilegePolicy} object for the {@link Privilege} registered for the given + * privilegeName + * + * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action + * @param privilegeName + * the name of the {@link Privilege} for which the policy is to be changed + * @param policyName + * class name of the {@link PrivilegePolicy} to be registered for this {@link Privilege} + * + * @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 class name of the policy is invalid + */ + public void setPrivilegePolicy(Certificate certificate, String privilegeName, String policyName) + throws AccessDeniedException, PrivilegeException; + + /** + * Sets the special flag on the given {@link Privilege} defined by the privilegeName argument so that all is allowed + * and thus the allow and deny list of the privilege are ignored + * + * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action + * @param privilegeName + * the name of the {@link Privilege} for which the allAllowed value is to be changed + * @param allAllowed + * the boolean defining if all privileges are granted for the value true, or not for the value false + * + * @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 setPrivilegeAllAllowed(Certificate certificate, String privilegeName, boolean allAllowed) + throws AccessDeniedException, PrivilegeException; + + /** + * Sets the {@link List} of strings which represent values which denies finer grained privilege rights for the + * {@link Privilege} defined by the privilegeName argument + * + * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action + * @param privilegeName + * the name of the {@link Privilege} for which the deny list is to be changed + * @param denyList + * the {@link List} of strings which represent values which denies finer grained privilege rights + * + * @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 setPrivilegeDenyList(Certificate certificate, String privilegeName, Set denyList) + throws AccessDeniedException, PrivilegeException; + + /** + * Sets the {@link List} of strings which represent values which grants finer grained privilege rights for the + * {@link Privilege} defined by the privilegeName argument + * + * @param certificate + * the {@link Certificate} of the user which has the privilege to perform this action + * @param privilegeName + * the name of the {@link Privilege} for which the allow list is to be changed + * @param allowList + * the {@link List} of strings which represent values which grants finer grained privilege rights + * + * @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 setPrivilegeAllowList(Certificate certificate, String privilegeName, Set allowList) + 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(String)}-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, String password); + public Certificate authenticate(String username, String password) throws AccessDeniedException; /** + * Checks if the {@link User} registered to the given {@link Certificate} is allowed to access the + * {@link Restrictable} + * * @param certificate * the {@link Certificate} of the user which has the privilege to perform this action * @param restrictable + * the {@link Restrictable} to which the user wants access * - * @return + * @return true if the access is allowed, false otherwise * * @throws AccessDeniedException - * if the {@link Certificate} is not for a currently logged in {@link User} or if the user may not - * perform the action defined by the {@link Restrictable} implementation + * if the user for this certificate may not perform the action defined by the {@link Restrictable} + * implementation * @throws PrivilegeException * if there is anything wrong with this certificate */ - public boolean actionAllowed(Certificate certificate, Restrictable restrictable); + public boolean actionAllowed(Certificate certificate, Restrictable restrictable) throws AccessDeniedException, + PrivilegeException; /** - * @param role + * Checks if the {@link RoleRep} is allowed to access the {@link Restrictable} + * + * @param roleRep + * the {@link RoleRep} for which access to the {@link Restrictable} is to be checked * @param restrictable - * @return + * the {@link Restrictable} to which access is to be checked + * + * @return true if the {@link RoleRep} has access, false otherwise * * @throws AccessDeniedException - * if the {@link Certificate} is not for a currently logged in {@link User} or if the user may not - * perform the action defined by the {@link Restrictable} implementation - * @throws PrivilegeException - * if there is anything wrong with this certificate + * if the role may not perform the action defined by the {@link Restrictable} implementation */ - public boolean actionAllowed(Role role, Restrictable restrictable); + public boolean actionAllowed(RoleRep roleRep, Restrictable restrictable) throws AccessDeniedException; /** - * @param certificate - * @return + * 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 + * + * @return true if the {@link Certificate} is valid, false otherwise * - * @throws AccessDeniedException - * if the {@link Certificate} is not for a currently logged in {@link User} * @throws PrivilegeException * if there is anything wrong with this certificate */ - public boolean isCertificateValid(Certificate certificate); + public boolean isCertificateValid(Certificate certificate) throws PrivilegeException; /** *

    @@ -344,39 +507,52 @@ public interface PrivilegeHandler { * @param certificate * the {@link Certificate} for which the role should be validated against * - * @throws ch.eitchnet.privilege.i18n.PrivilegeException + * @throws AccessDeniedException * if the user does not not have admin privileges */ - public void validateIsPrivilegeAdmin(Certificate certificate) throws PrivilegeException; + public void validateIsPrivilegeAdmin(Certificate certificate) throws AccessDeniedException; /** - * Validate that the given password meets any requirements. What these requirements are is a decision made by the - * concrete implementation + * 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(String password) throws PrivilegeException; /** + * 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 + * @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); + public boolean persist(Certificate certificate) throws AccessDeniedException; /** - * Initialize the concrete {@link EncryptionHandler}. The passed parameter map contains any configuration this map - * might need + * 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} + * + * @throws PrivilegeException + * if the this method is called multiple times or an initialization exception occurs */ public void initialize(Map parameterMap, EncryptionHandler encryptionHandler, - PersistenceHandler persistenceHandler); + PersistenceHandler persistenceHandler) throws PrivilegeException; } diff --git a/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java b/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java index d1fa6a43c..720715f57 100644 --- a/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java @@ -152,7 +152,13 @@ public class XmlPersistenceHandler implements PersistenceHandler { } // instantiate the policy - PrivilegePolicy policy = ClassHelper.instantiateClass(policyClazz); + PrivilegePolicy policy; + try { + policy = ClassHelper.instantiateClass(policyClazz); + } catch (Exception e) { + throw new PrivilegeException("The class for the policy with the name " + policyName + " does not exist!" + + policyName, e); + } return policy; } @@ -431,8 +437,8 @@ public class XmlPersistenceHandler implements PersistenceHandler { } // create user - User user = new User(userId, username, password, firstname, surname, userState, Collections - .unmodifiableSet(roles), locale); + User user = new User(userId, username, password, firstname, surname, userState, + Collections.unmodifiableSet(roles), locale); // put user in map this.userMap.put(username, user); From 66128804cd64801101e063a97b81808c4a44d9d9 Mon Sep 17 00:00:00 2001 From: eitch Date: Sat, 27 Nov 2010 21:54:46 +0000 Subject: [PATCH 042/173] [Minor] added some java docs --- .../handler/DefaultEncryptionHandler.java | 4 +- .../handler/XmlPersistenceHandler.java | 6 +- .../helper/BootstrapConfigurationHelper.java | 6 +- .../privilege/helper/ClassHelper.java | 47 +++++++- ...{EncryptionHelper.java => HashHelper.java} | 21 +++- .../helper/InitializationHelper.java | 6 +- .../privilege/helper/PasswordCreator.java | 3 +- .../privilege/helper/XmlConstants.java | 111 ++++++++++++++++++ .../eitchnet/privilege/helper/XmlHelper.java | 29 ++++- .../privilege/model/Restrictable.java | 12 +- .../eitchnet/privilege/model/UserState.java | 24 ++++ .../privilege/model/internal/Role.java | 16 ++- .../privilege/model/internal/User.java | 8 +- .../privilege/policy/PrivilegePolicy.java | 3 +- 14 files changed, 266 insertions(+), 30 deletions(-) rename src/ch/eitchnet/privilege/helper/{EncryptionHelper.java => HashHelper.java} (58%) diff --git a/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java b/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java index 8fb5dfc28..976f62463 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java @@ -18,7 +18,7 @@ import java.util.Map; import org.apache.log4j.Logger; -import ch.eitchnet.privilege.helper.EncryptionHelper; +import ch.eitchnet.privilege.helper.HashHelper; import ch.eitchnet.privilege.helper.XmlConstants; import ch.eitchnet.privilege.i18n.PrivilegeException; @@ -60,7 +60,7 @@ public class DefaultEncryptionHandler implements EncryptionHandler { public String convertToHash(String string) { try { - return EncryptionHelper.encryptString(this.hashAlgorithm, string); + return HashHelper.stringToHash(this.hashAlgorithm, string); } catch (NoSuchAlgorithmException e) { throw new PrivilegeException("Algorithm " + this.hashAlgorithm + " was not found!", e); diff --git a/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java b/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java index 720715f57..dab8bd719 100644 --- a/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java @@ -192,7 +192,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { } // write DOM to file - XmlHelper.writeDocument(rootElement, usersFile); + XmlHelper.writeElement(rootElement, usersFile); this.userMapDirty = true; } @@ -219,7 +219,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { } // write DOM to file - XmlHelper.writeDocument(rootElement, rolesFile); + XmlHelper.writeElement(rootElement, rolesFile); this.roleMapDirty = true; } @@ -247,7 +247,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { } // write DOM to file - XmlHelper.writeDocument(rootElement, privilegesFile); + XmlHelper.writeElement(rootElement, privilegesFile); this.privilegeMapDirty = true; } diff --git a/src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java b/src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java index 66ddad1d7..4966659e9 100644 --- a/src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java +++ b/src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java @@ -21,15 +21,17 @@ import org.dom4j.Document; import org.dom4j.DocumentFactory; import org.dom4j.Element; +import ch.eitchnet.privilege.handler.PrivilegeHandler; + /** *

    * This class is a simple application which can be used to bootstrap a new configuration for the - * {@link PrivilegeContainer} + * {@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 PrivilegeContainer} + * can be used to run the {@link PrivilegeHandler} *

    * * @author rvonburg diff --git a/src/ch/eitchnet/privilege/helper/ClassHelper.java b/src/ch/eitchnet/privilege/helper/ClassHelper.java index 34c9a1139..1ecdae89f 100644 --- a/src/ch/eitchnet/privilege/helper/ClassHelper.java +++ b/src/ch/eitchnet/privilege/helper/ClassHelper.java @@ -13,13 +13,28 @@ package ch.eitchnet.privilege.helper; import ch.eitchnet.privilege.i18n.PrivilegeException; /** + * The {@link ClassHelper} class is a helper to instantiate classes using reflection + * * @author rvonburg * */ public class ClassHelper { + /** + * Returns an instance of the class' name given by instantiating the class through an empty arguments constructor + * + * @param + * the type of the class to return + * @param className + * the name of a class to instantiate through an empty arguments constructor + * + * @return the newly instantiated object from the given class name + * + * @throws PrivilegeException + * if the class could not be instantiated + */ @SuppressWarnings("unchecked") - public static T instantiateClass(String className) { + public static T instantiateClass(String className) throws PrivilegeException { try { Class clazz = (Class) Class.forName(className); @@ -31,7 +46,20 @@ public class ClassHelper { } } - public static T instantiateClass(Class clazz) { + /** + * Instantiates an object for the given {@link Class} using an empty arguments constructor + * + * @param + * the type of the class to return + * @param clazz + * the {@link Class} from which a new object is to be instantiated using an empty arguments constructor + * + * @return the newly instantiated object from the given {@link Class} + * + * @throws PrivilegeException + * if the {@link Class} could not be instantiated + */ + public static T instantiateClass(Class clazz) throws PrivilegeException { try { return clazz.getConstructor().newInstance(); @@ -41,8 +69,21 @@ public class ClassHelper { } } + /** + * Loads the {@link Class} object for the given class name + * + * @param + * the type of {@link Class} to return + * @param className + * the name of the {@link Class} to load and return + * + * @return the {@link Class} object for the given class name + * + * @throws PrivilegeException + * if the class could not be instantiated + */ @SuppressWarnings("unchecked") - public static Class loadClass(String className) { + public static Class loadClass(String className) throws PrivilegeException { try { Class clazz = (Class) Class.forName(className); diff --git a/src/ch/eitchnet/privilege/helper/EncryptionHelper.java b/src/ch/eitchnet/privilege/helper/HashHelper.java similarity index 58% rename from src/ch/eitchnet/privilege/helper/EncryptionHelper.java rename to src/ch/eitchnet/privilege/helper/HashHelper.java index afc52c1f2..fa095ffca 100644 --- a/src/ch/eitchnet/privilege/helper/EncryptionHelper.java +++ b/src/ch/eitchnet/privilege/helper/HashHelper.java @@ -18,16 +18,31 @@ import java.security.NoSuchAlgorithmException; * @author rvonburg * */ -public class EncryptionHelper { +public class HashHelper { /** - * Hex char table for fast calculating of hex value + * Hex char table for fast calculating of hex values */ private static final byte[] HEX_CHAR_TABLE = { (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f' }; - public static String encryptString(String hashAlgorithm, String string) throws NoSuchAlgorithmException, + /** + * Creates the hash of the given string using {@link MessageDigest} and the defined hash algorithm + * + * @param hashAlgorithm + * the algorithm to use for hashing + * @param string + * the string to hash + * + * @return a new string encrypted by the defined algorithm + * + * @throws NoSuchAlgorithmException + * if the algorithm is not found + * @throws UnsupportedEncodingException + * if something is wrong with the given string to hash + */ + public static String stringToHash(String hashAlgorithm, String string) throws NoSuchAlgorithmException, UnsupportedEncodingException { MessageDigest digest = MessageDigest.getInstance(hashAlgorithm); diff --git a/src/ch/eitchnet/privilege/helper/InitializationHelper.java b/src/ch/eitchnet/privilege/helper/InitializationHelper.java index 549dfcd00..46a723212 100644 --- a/src/ch/eitchnet/privilege/helper/InitializationHelper.java +++ b/src/ch/eitchnet/privilege/helper/InitializationHelper.java @@ -33,7 +33,8 @@ public class InitializationHelper { /** * @param privilegeContainerXmlFile - * @return + * + * @return the {@link PrivilegeHandler} instance loaded from the configuration file */ public static PrivilegeHandler initializeFromXml(File privilegeContainerXmlFile) { @@ -110,7 +111,8 @@ public class InitializationHelper { /** * @param element - * @return + * + * @return the {@link Map} of the parameter name/value combinations from the given {@link Element} */ @SuppressWarnings("unchecked") public static Map convertToParameterMap(Element element) { diff --git a/src/ch/eitchnet/privilege/helper/PasswordCreator.java b/src/ch/eitchnet/privilege/helper/PasswordCreator.java index 701e58a94..adf243d26 100644 --- a/src/ch/eitchnet/privilege/helper/PasswordCreator.java +++ b/src/ch/eitchnet/privilege/helper/PasswordCreator.java @@ -22,6 +22,7 @@ public class PasswordCreator { /** * @param args + * @throws Exception */ public static void main(String[] args) throws Exception { @@ -48,7 +49,7 @@ public class PasswordCreator { System.out.print("Password: "); String password = r.readLine().trim(); - System.out.print("Hash is: " + EncryptionHelper.encryptString(hashAlgorithm, password)); + System.out.print("Hash is: " + HashHelper.stringToHash(hashAlgorithm, password)); } } diff --git a/src/ch/eitchnet/privilege/helper/XmlConstants.java b/src/ch/eitchnet/privilege/helper/XmlConstants.java index b78ce40fa..06f838c9f 100644 --- a/src/ch/eitchnet/privilege/helper/XmlConstants.java +++ b/src/ch/eitchnet/privilege/helper/XmlConstants.java @@ -15,45 +15,156 @@ package ch.eitchnet.privilege.helper; * */ public class XmlConstants { + /** + * XML_ROOT_PRIVILEGE_CONTAINER = "PrivilegeContainer" : + */ public static final String XML_ROOT_PRIVILEGE_CONTAINER = "PrivilegeContainer"; + /** + * XML_ROOT_PRIVILEGE_ROLES = "PrivilegeRoles" : + */ public static final String XML_ROOT_PRIVILEGE_ROLES = "PrivilegeRoles"; + /** + * XML_ROOT_PRIVILEGES = "Privileges" : + */ public static final String XML_ROOT_PRIVILEGES = "Privileges"; + /** + * XML_ROOT_PRIVILEGE_USERS = "PrivilegesUsers" : + */ public static final String XML_ROOT_PRIVILEGE_USERS = "PrivilegesUsers"; + /** + * XML_ROOT_PRIVILEGE_POLICIES = "PrivilegePolicies" : + */ public static final String XML_ROOT_PRIVILEGE_POLICIES = "PrivilegePolicies"; + /** + * 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_USER = "User" + */ public static final String XML_USER = "User"; + /** + * XML_PRIVILEGES = "Privileges" : + */ public static final String XML_PRIVILEGES = "Privileges"; + /** + * 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_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_SURNAME = "Surname" : + */ public static final String XML_SURNAME = "Surname"; + /** + * 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_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_USERNAME = "username" : + */ public static final String XML_ATTR_USERNAME = "username"; + /** + * 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_POLICY_FILE = "policyXmlFile" : + */ public static final String XML_PARAM_POLICY_FILE = "policyXmlFile"; + /** + * XML_PARAM_ROLES_FILE = "rolesXmlFile" : + */ public static final String XML_PARAM_ROLES_FILE = "rolesXmlFile"; + /** + * XML_PARAM_USERS_FILE = "usersXmlFile" : + */ public static final String XML_PARAM_USERS_FILE = "usersXmlFile"; + /** + * XML_PARAM_PRIVILEGES_FILE = "privilegesXmlFile" : + */ public static final String XML_PARAM_PRIVILEGES_FILE = "privilegesXmlFile"; + /** + * XML_PARAM_BASE_PATH = "basePath" : + */ public static final String XML_PARAM_BASE_PATH = "basePath"; } diff --git a/src/ch/eitchnet/privilege/helper/XmlHelper.java b/src/ch/eitchnet/privilege/helper/XmlHelper.java index f4f57a87a..53febb9ab 100644 --- a/src/ch/eitchnet/privilege/helper/XmlHelper.java +++ b/src/ch/eitchnet/privilege/helper/XmlHelper.java @@ -35,10 +35,21 @@ import ch.eitchnet.privilege.i18n.PrivilegeException; */ public class XmlHelper { + /** + * DEFAULT_ENCODING = "UTF-8" : defines the default UTF-8 encoding expected of XML files + */ public static final String DEFAULT_ENCODING = "UTF-8"; private static final Logger logger = Logger.getLogger(XmlHelper.class); + /** + * Parses an XML file on the file system using dom4j and returns the resulting {@link Document} object + * + * @param xmlFile + * the {@link File} which has the path to the XML file to read + * + * @return a {@link Document} object containing the dom4j {@link Element}s of the XML file + */ public static Document parseDocument(File xmlFile) { try { @@ -58,6 +69,14 @@ public class XmlHelper { } } + /** + * Writes a dom4j {@link Document} to an XML file on the file system + * + * @param document + * the {@link Document} to write to the file system + * @param file + * the {@link File} describing the path on the file system where the XML file should be written to + */ public static void writeDocument(Document document, File file) { logger.info("Exporting document element " + document.getName() + " to " + file.getAbsolutePath()); @@ -94,7 +113,15 @@ public class XmlHelper { } } - public static void writeDocument(Element rootElement, File file) { + /** + * Writes a dom4j {@link Element} to an XML file on the file system + * + * @param rootElement + * the {@link Element} to write to the file system + * @param file + * the {@link File} describing the path on the file system where the XML file should be written to + */ + public static void writeElement(Element rootElement, File file) { Document document = DocumentFactory.getInstance().createDocument(DEFAULT_ENCODING); document.setRootElement(rootElement); diff --git a/src/ch/eitchnet/privilege/model/Restrictable.java b/src/ch/eitchnet/privilege/model/Restrictable.java index d144b1781..e027fa846 100644 --- a/src/ch/eitchnet/privilege/model/Restrictable.java +++ b/src/ch/eitchnet/privilege/model/Restrictable.java @@ -10,19 +10,27 @@ package ch.eitchnet.privilege.model; +import ch.eitchnet.privilege.model.internal.Privilege; + /** + * TODO javadoc + * * @author rvonburg * */ public interface Restrictable { /** - * @return + * Returns the name of the {@link Privilege} which is to be used to validate privileges against + * + * @return the name of the {@link Privilege} which is to be used to validate privileges against */ public String getPrivilegeName(); /** - * @return + * 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/src/ch/eitchnet/privilege/model/UserState.java b/src/ch/eitchnet/privilege/model/UserState.java index 391975b1f..ab1fbde5e 100644 --- a/src/ch/eitchnet/privilege/model/UserState.java +++ b/src/ch/eitchnet/privilege/model/UserState.java @@ -10,13 +10,37 @@ 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 rvonburg * */ 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; } diff --git a/src/ch/eitchnet/privilege/model/internal/Role.java b/src/ch/eitchnet/privilege/model/internal/Role.java index 664216cb5..d41e73c17 100644 --- a/src/ch/eitchnet/privilege/model/internal/Role.java +++ b/src/ch/eitchnet/privilege/model/internal/Role.java @@ -43,18 +43,24 @@ public final class Role { } /** - * @return + * Returns the {@link Set} of {@link Privilege} names which is granted to this {@link Role} + * + * @return the {@link Set} of {@link Privilege} names which is granted to this */ public Set getPrivileges() { return this.privileges; } /** - * @param key - * @return + * Determines if this {@link Role} has the {@link Privilege} with the given name + * + * @param name + * the name of the {@link Privilege} + * + * @return true if this {@link Role} has the {@link Privilege} with the given name */ - public boolean hasPrivilege(String key) { - return this.privileges.contains(key); + public boolean hasPrivilege(String name) { + return this.privileges.contains(name); } /** diff --git a/src/ch/eitchnet/privilege/model/internal/User.java b/src/ch/eitchnet/privilege/model/internal/User.java index 30f51901c..176481429 100644 --- a/src/ch/eitchnet/privilege/model/internal/User.java +++ b/src/ch/eitchnet/privilege/model/internal/User.java @@ -69,7 +69,7 @@ public final class User { * @return the userId */ public String getUserId() { - return userId; + return this.userId; } /** @@ -80,11 +80,9 @@ public final class User { } /** + * Returns the hashed password for this {@link User} * - * @param privilegeHandler - * @param certificate - * - * @return + * @return the hashed password for this {@link User} */ public String getPassword() { diff --git a/src/ch/eitchnet/privilege/policy/PrivilegePolicy.java b/src/ch/eitchnet/privilege/policy/PrivilegePolicy.java index 3f4b64853..461ea6951 100644 --- a/src/ch/eitchnet/privilege/policy/PrivilegePolicy.java +++ b/src/ch/eitchnet/privilege/policy/PrivilegePolicy.java @@ -24,7 +24,8 @@ public interface PrivilegePolicy { * @param role * @param privilege * @param restrictable - * @return + * + * @return return true if the action is allowed, false if not */ public boolean actionAllowed(Role role, Privilege privilege, Restrictable restrictable); } From d6ff3c86cc603827350f58062ba52c015670a9c7 Mon Sep 17 00:00:00 2001 From: eitch Date: Sat, 27 Nov 2010 21:55:05 +0000 Subject: [PATCH 043/173] [Minor] added some java docs --- .../privilege/test/PrivilegeTest.java | 46 ++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/test/ch/eitchnet/privilege/test/PrivilegeTest.java b/test/ch/eitchnet/privilege/test/PrivilegeTest.java index 2416508a9..ff800f009 100644 --- a/test/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/test/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -41,7 +41,8 @@ public class PrivilegeTest { private static PrivilegeHandler privilegeHandler; /** - * @throws java.lang.Exception + * @throws Exception + * if something goes wrong */ @BeforeClass public static void init() throws Exception { @@ -63,6 +64,10 @@ public class PrivilegeTest { } } + /** + * @throws Exception + * if something goes wrong + */ @Test public void testAuthenticationOk() throws Exception { @@ -70,6 +75,10 @@ public class PrivilegeTest { org.junit.Assert.assertTrue("Certificate is null!", certificate != null); } + /** + * @throws Exception + * if something goes wrong + */ @Test(expected = AccessDeniedException.class) public void testFailAuthenticationNOk() throws Exception { @@ -77,6 +86,10 @@ public class PrivilegeTest { org.junit.Assert.assertTrue("Certificate is null!", certificate != null); } + /** + * @throws Exception + * if something goes wrong + */ @Test(expected = PrivilegeException.class) public void testFailAuthenticationPWNull() throws Exception { @@ -84,6 +97,10 @@ public class PrivilegeTest { org.junit.Assert.assertTrue("Certificate is null!", certificate != null); } + /** + * @throws Exception + * if something goes wrong + */ @Test public void testAddUserBobWithPW() throws Exception { @@ -103,6 +120,7 @@ public class PrivilegeTest { * Will fail because user bob is not yet enabled * * @throws Exception + * if something goes wrong */ @Test(expected = AccessDeniedException.class) public void testFailAuthAsBob() throws Exception { @@ -110,6 +128,10 @@ public class PrivilegeTest { privilegeHandler.authenticate("bob", "12345678901"); } + /** + * @throws Exception + * if something goes wrong + */ @Test public void testEnableUserBob() throws Exception { @@ -121,6 +143,7 @@ public class PrivilegeTest { * Will fail as user bob has no role * * @throws Exception + * if something goes wrong */ @Test(expected = PrivilegeException.class) public void testFailAuthUserBob() throws Exception { @@ -129,6 +152,10 @@ public class PrivilegeTest { org.junit.Assert.assertTrue("Certificate is null!", certificate != null); } + /** + * @throws Exception + * if something goes wrong + */ @Test public void testAddUserRoleToBob() throws Exception { @@ -136,6 +163,10 @@ public class PrivilegeTest { privilegeHandler.addRoleToUser(certificate, "bob", "user"); } + /** + * @throws Exception + * if something goes wrong + */ @Test public void testAuthAsBob() throws Exception { @@ -146,6 +177,7 @@ public class PrivilegeTest { * Will fail because user bob does not have admin rights * * @throws Exception + * if something goes wrong */ @Test(expected = AccessDeniedException.class) public void testFailAddUserTedAsBob() throws Exception { @@ -159,6 +191,10 @@ public class PrivilegeTest { logger.info("Added user bob"); } + /** + * @throws Exception + * if something goes wrong + */ @Test public void testAddAdminRoleToBob() throws Exception { @@ -166,6 +202,10 @@ public class PrivilegeTest { privilegeHandler.addRoleToUser(certificate, "bob", PrivilegeHandler.PRIVILEGE_ADMIN_ROLE); } + /** + * @throws Exception + * if something goes wrong + */ @Test public void testAddUserTedAsBob() throws Exception { @@ -178,6 +218,10 @@ public class PrivilegeTest { logger.info("Added user bob"); } + /** + * @throws Exception + * if something goes wrong + */ @Test public void testPerformRestrictable() throws Exception { From 47927ac99b6152cd505282607a406e0e996d7ee2 Mon Sep 17 00:00:00 2001 From: eitch Date: Mon, 25 Jul 2011 22:00:41 +0000 Subject: [PATCH 044/173] [Major] updated a lot of JavaDoc and made sure that every class has at least a header JavaDoc with a description. Added a TODO under the doc folder with some TODOs =) --- MANIFEST.MF | 2 + docs/TODO | 14 +++++++ privilege.jardesc | 18 +++++++++ .../helper/BootstrapConfigurationHelper.java | 9 ++++- .../privilege/helper/ClassHelper.java | 1 - .../eitchnet/privilege/helper/HashHelper.java | 3 +- .../helper/InitializationHelper.java | 12 +++++- .../privilege/helper/PasswordCreator.java | 13 ++++++- .../privilege/helper/XmlConstants.java | 3 +- .../eitchnet/privilege/helper/XmlHelper.java | 3 +- .../privilege/i18n/AccessDeniedException.java | 10 +++-- .../privilege/i18n/PrivilegeException.java | 9 ++++- .../eitchnet/privilege/model/Certificate.java | 25 ++++++++++--- .../privilege/model/PrivilegeRep.java | 18 ++++++++- .../privilege/model/Restrictable.java | 8 +++- src/ch/eitchnet/privilege/model/RoleRep.java | 11 +++++- src/ch/eitchnet/privilege/model/UserRep.java | 17 ++++++++- .../privilege/model/internal/Privilege.java | 23 +++++++++++- .../privilege/model/internal/Role.java | 14 ++++++- .../privilege/model/internal/Session.java | 37 ++++++++++++++++--- .../privilege/model/internal/User.java | 20 ++++++++++ .../privilege/policy/DefaultPrivilege.java | 3 +- .../privilege/policy/PrivilegePolicy.java | 17 ++++++++- 23 files changed, 258 insertions(+), 32 deletions(-) create mode 100644 MANIFEST.MF create mode 100644 docs/TODO create mode 100644 privilege.jardesc diff --git a/MANIFEST.MF b/MANIFEST.MF new file mode 100644 index 000000000..58630c02e --- /dev/null +++ b/MANIFEST.MF @@ -0,0 +1,2 @@ +Manifest-Version: 1.0 + diff --git a/docs/TODO b/docs/TODO new file mode 100644 index 000000000..296271aac --- /dev/null +++ b/docs/TODO @@ -0,0 +1,14 @@ +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 + +- Re-think the PrivilegePolicy and its + actionAllowed(Role, Privilege, Restrictable)-method. Maybe the Privilege + argument is not needed, as this should be on the Role anyhow... + +- Finish the JavaDoc + +- Set up a website =) + diff --git a/privilege.jardesc b/privilege.jardesc new file mode 100644 index 000000000..d58ef8d02 --- /dev/null +++ b/privilege.jardesc @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java b/src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java index 4966659e9..114d9840b 100644 --- a/src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java +++ b/src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java @@ -34,11 +34,15 @@ import ch.eitchnet.privilege.handler.PrivilegeHandler; * can be used to run the {@link PrivilegeHandler} *

    * - * @author rvonburg + *

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

    * + * @author rvonburg */ public class BootstrapConfigurationHelper { -// private static final Logger logger = Logger.getLogger(BootstrapConfigurationHelper.class); + + // private static final Logger logger = Logger.getLogger(BootstrapConfigurationHelper.class); private static String path; @@ -58,6 +62,7 @@ public class BootstrapConfigurationHelper { /** * @param args + * the args from the command line */ public static void main(String[] args) { BasicConfigurator.resetConfiguration(); diff --git a/src/ch/eitchnet/privilege/helper/ClassHelper.java b/src/ch/eitchnet/privilege/helper/ClassHelper.java index 1ecdae89f..eda6f8eea 100644 --- a/src/ch/eitchnet/privilege/helper/ClassHelper.java +++ b/src/ch/eitchnet/privilege/helper/ClassHelper.java @@ -16,7 +16,6 @@ import ch.eitchnet.privilege.i18n.PrivilegeException; * The {@link ClassHelper} class is a helper to instantiate classes using reflection * * @author rvonburg - * */ public class ClassHelper { diff --git a/src/ch/eitchnet/privilege/helper/HashHelper.java b/src/ch/eitchnet/privilege/helper/HashHelper.java index fa095ffca..cbf6ae8f3 100644 --- a/src/ch/eitchnet/privilege/helper/HashHelper.java +++ b/src/ch/eitchnet/privilege/helper/HashHelper.java @@ -15,8 +15,9 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; /** - * @author rvonburg + * Helper class to hash a String for a certain hash algorithm, using the Java {@link MessageDigest} classes * + * @author rvonburg */ public class HashHelper { diff --git a/src/ch/eitchnet/privilege/helper/InitializationHelper.java b/src/ch/eitchnet/privilege/helper/InitializationHelper.java index 46a723212..e0c2aadb6 100644 --- a/src/ch/eitchnet/privilege/helper/InitializationHelper.java +++ b/src/ch/eitchnet/privilege/helper/InitializationHelper.java @@ -24,15 +24,20 @@ import ch.eitchnet.privilege.handler.PrivilegeHandler; import ch.eitchnet.privilege.i18n.PrivilegeException; /** - * @author rvonburg + * This class implements the initializing of the {@link PrivilegeHandler} by loading an XML file containing the + * configuration * + * @author rvonburg */ public class InitializationHelper { private static final Logger logger = Logger.getLogger(InitializationHelper.class); /** + * Initializes the {@link PrivilegeHandler} from the configuration file + * * @param privilegeContainerXmlFile + * a {@link File} reference to the XML file containing the configuration for Privilege * * @return the {@link PrivilegeHandler} instance loaded from the configuration file */ @@ -110,7 +115,12 @@ public class InitializationHelper { } /** + * Converts an {@link XmlConstants#XML_PARAMETERS} element containing {@link XmlConstants#XML_PARAMETER} elements to + * a {@link Map} of String key/value pairs + * * @param element + * the XML {@link Element} with name {@link XmlConstants#XML_PARAMETERS} containing + * {@link XmlConstants#XML_PARAMETER} elements * * @return the {@link Map} of the parameter name/value combinations from the given {@link Element} */ diff --git a/src/ch/eitchnet/privilege/helper/PasswordCreator.java b/src/ch/eitchnet/privilege/helper/PasswordCreator.java index adf243d26..a70dda885 100644 --- a/src/ch/eitchnet/privilege/helper/PasswordCreator.java +++ b/src/ch/eitchnet/privilege/helper/PasswordCreator.java @@ -15,14 +15,23 @@ import java.io.InputStreamReader; import java.security.MessageDigest; /** - * @author rvonburg + *

    + * 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. This is a TODO + *

    + * + * @author rvonburg */ public class PasswordCreator { /** * @param args - * @throws Exception + * the args from the command line, NOT USED + * @throws Exception + * thrown if anything goes wrong */ public static void main(String[] args) throws Exception { diff --git a/src/ch/eitchnet/privilege/helper/XmlConstants.java b/src/ch/eitchnet/privilege/helper/XmlConstants.java index 06f838c9f..465e1cf53 100644 --- a/src/ch/eitchnet/privilege/helper/XmlConstants.java +++ b/src/ch/eitchnet/privilege/helper/XmlConstants.java @@ -11,8 +11,9 @@ package ch.eitchnet.privilege.helper; /** - * @author rvonburg + * The constants used in parsing XML documents which contain the configuration for Privilege * + * @author rvonburg */ public class XmlConstants { /** diff --git a/src/ch/eitchnet/privilege/helper/XmlHelper.java b/src/ch/eitchnet/privilege/helper/XmlHelper.java index 53febb9ab..c77dcec08 100644 --- a/src/ch/eitchnet/privilege/helper/XmlHelper.java +++ b/src/ch/eitchnet/privilege/helper/XmlHelper.java @@ -30,8 +30,9 @@ import org.dom4j.io.XMLWriter; import ch.eitchnet.privilege.i18n.PrivilegeException; /** - * @author rvonburg + * Helper class for performing XML based tasks using Dom4J * + * @author rvonburg */ public class XmlHelper { diff --git a/src/ch/eitchnet/privilege/i18n/AccessDeniedException.java b/src/ch/eitchnet/privilege/i18n/AccessDeniedException.java index c84978e3f..339bd6fed 100644 --- a/src/ch/eitchnet/privilege/i18n/AccessDeniedException.java +++ b/src/ch/eitchnet/privilege/i18n/AccessDeniedException.java @@ -11,17 +11,19 @@ package ch.eitchnet.privilege.i18n; /** - * @author rvonburg + * Exception thrown if access is denied during login, or if a certain privilege is not granted * + * @author rvonburg */ public class AccessDeniedException extends PrivilegeException { private static final long serialVersionUID = 1L; /** - * @param string + * @param msg + * detail on why and where access was denied */ - public AccessDeniedException(String string) { - super(string); + public AccessDeniedException(String msg) { + super(msg); } } diff --git a/src/ch/eitchnet/privilege/i18n/PrivilegeException.java b/src/ch/eitchnet/privilege/i18n/PrivilegeException.java index 4960a42f9..decd696a9 100644 --- a/src/ch/eitchnet/privilege/i18n/PrivilegeException.java +++ b/src/ch/eitchnet/privilege/i18n/PrivilegeException.java @@ -11,24 +11,31 @@ package ch.eitchnet.privilege.i18n; /** - * @author rvonburg + * Main {@link RuntimeException} thrown if something goes wrong in Privilege * + * @author rvonburg */ 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/src/ch/eitchnet/privilege/model/Certificate.java b/src/ch/eitchnet/privilege/model/Certificate.java index e55af8a44..7dfe3b6a9 100644 --- a/src/ch/eitchnet/privilege/model/Certificate.java +++ b/src/ch/eitchnet/privilege/model/Certificate.java @@ -13,11 +13,16 @@ package ch.eitchnet.privilege.model; import java.io.Serializable; import java.util.Locale; +import ch.eitchnet.privilege.handler.PrivilegeHandler; import ch.eitchnet.privilege.i18n.PrivilegeException; +import ch.eitchnet.privilege.model.internal.Session; /** - * @author rvonburg + * 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, String)} * + * @author rvonburg */ public final class Certificate implements Serializable { @@ -31,11 +36,22 @@ public final class Certificate implements Serializable { private Locale locale; /** + * 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 authToken + * the authentication token defining the users unique session and is a private field of this certificate. + * It corresponds with the authentication token on the {@link Session} * @param authPassword + * the password to access the authentication token, this is not known to the client but set by the + * {@link PrivilegeHandler} on authentication. It corresponds with the authentication password on the {@link Session} * @param locale + * the users {@link Locale} */ public Certificate(String sessionId, String username, String authToken, String authPassword, Locale locale) { @@ -86,12 +102,12 @@ public final class Certificate implements Serializable { } /** - * Returns the authToken if the given authPassword is corret, null otherwise + * Returns the authToken if the given authPassword is correct, null otherwise * * @param authPassword - * the auth password with which this certificate was created + * the authentication password with which this certificate was created * - * @return the authToken if the given authPassword is corret, null otherwise + * @return the authToken if the given authPassword is correct, null otherwise */ public String getAuthToken(String authPassword) { if (this.authPassword.equals(authPassword)) @@ -170,5 +186,4 @@ public final class Certificate implements Serializable { builder.append("]"); return builder.toString(); } - } diff --git a/src/ch/eitchnet/privilege/model/PrivilegeRep.java b/src/ch/eitchnet/privilege/model/PrivilegeRep.java index 82ad3e070..5b92e4613 100644 --- a/src/ch/eitchnet/privilege/model/PrivilegeRep.java +++ b/src/ch/eitchnet/privilege/model/PrivilegeRep.java @@ -13,9 +13,17 @@ package ch.eitchnet.privilege.model; import java.io.Serializable; import java.util.Set; +import ch.eitchnet.privilege.handler.PrivilegeHandler; +import ch.eitchnet.privilege.model.internal.Privilege; +import ch.eitchnet.privilege.model.internal.Role; +import ch.eitchnet.privilege.policy.PrivilegePolicy; + /** - * @author rvonburg + * To keep certain details of the {@link Privilege} 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 Privilege} * + * @author rvonburg */ public class PrivilegeRep implements Serializable { @@ -28,11 +36,19 @@ public class PrivilegeRep implements Serializable { 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 Privilege} has unrestricted access to a + * {@link Restrictable} * @param denyList + * a list of deny rules for this {@link Privilege} * @param allowList + * a list of allow rules for this {@link Privilege} */ public PrivilegeRep(String name, String policy, boolean allAllowed, Set denyList, Set allowList) { this.name = name; diff --git a/src/ch/eitchnet/privilege/model/Restrictable.java b/src/ch/eitchnet/privilege/model/Restrictable.java index e027fa846..9122d52c5 100644 --- a/src/ch/eitchnet/privilege/model/Restrictable.java +++ b/src/ch/eitchnet/privilege/model/Restrictable.java @@ -11,9 +11,15 @@ package ch.eitchnet.privilege.model; import ch.eitchnet.privilege.model.internal.Privilege; +import ch.eitchnet.privilege.policy.PrivilegePolicy; /** - * TODO javadoc + *

    + * 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 Privilege} which has the associated {@link PrivilegePolicy} + * for evaluating access + *

    * * @author rvonburg * diff --git a/src/ch/eitchnet/privilege/model/RoleRep.java b/src/ch/eitchnet/privilege/model/RoleRep.java index 8a7bb538f..019f1a408 100644 --- a/src/ch/eitchnet/privilege/model/RoleRep.java +++ b/src/ch/eitchnet/privilege/model/RoleRep.java @@ -13,9 +13,14 @@ package ch.eitchnet.privilege.model; import java.io.Serializable; import java.util.Set; +import ch.eitchnet.privilege.model.internal.Role; + /** - * @author rvonburg + * 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 rvonburg */ public class RoleRep implements Serializable { @@ -25,8 +30,12 @@ public class RoleRep implements Serializable { private Set privileges; /** + * Default constructor + * * @param name + * the name of this role * @param privileges + * the set of privileges granted to this role */ public RoleRep(String name, Set privileges) { this.name = name; diff --git a/src/ch/eitchnet/privilege/model/UserRep.java b/src/ch/eitchnet/privilege/model/UserRep.java index a91ab8e2c..7efbb8ab9 100644 --- a/src/ch/eitchnet/privilege/model/UserRep.java +++ b/src/ch/eitchnet/privilege/model/UserRep.java @@ -14,9 +14,15 @@ import java.io.Serializable; import java.util.Locale; import java.util.Set; +import ch.eitchnet.privilege.model.internal.Role; +import ch.eitchnet.privilege.model.internal.User; + /** - * @author rvonburg + * 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 rvonburg */ public class UserRep implements Serializable { @@ -31,13 +37,22 @@ public class UserRep implements Serializable { private Locale locale; /** + * Default constructor + * * @param userId + * the user's id * @param username + * the user's login name * @param firstname + * the user's first name * @param surname + * the user's surname * @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} */ public UserRep(String userId, String username, String firstname, String surname, UserState userState, Set roles, Locale locale) { diff --git a/src/ch/eitchnet/privilege/model/internal/Privilege.java b/src/ch/eitchnet/privilege/model/internal/Privilege.java index 037a4f492..7d6ca9b1c 100644 --- a/src/ch/eitchnet/privilege/model/internal/Privilege.java +++ b/src/ch/eitchnet/privilege/model/internal/Privilege.java @@ -14,11 +14,25 @@ import java.util.Collections; import java.util.HashSet; import java.util.Set; +import ch.eitchnet.privilege.handler.PrivilegeHandler; import ch.eitchnet.privilege.model.PrivilegeRep; +import ch.eitchnet.privilege.model.Restrictable; +import ch.eitchnet.privilege.policy.PrivilegePolicy; /** - * @author rvonburg + *

    + * {@link Privilege} 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. For every privilege a {@link PrivilegePolicy} is + * configured which is used to evaluate if privilege is granted to a {@link Restrictable} + *

    * + *

    + * {@link Privilege}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 rvonburg */ public final class Privilege { @@ -29,12 +43,19 @@ public final class Privilege { 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 * @param allAllowed + * a boolean defining if a {@link Role} with this {@link Privilege} has unrestricted access to a + * {@link Restrictable} * @param denyList + * a list of deny rules for this {@link Privilege} * @param allowList + * a list of allow rules for this {@link Privilege} */ public Privilege(String name, String policy, boolean allAllowed, Set denyList, Set allowList) { this.name = name; diff --git a/src/ch/eitchnet/privilege/model/internal/Role.java b/src/ch/eitchnet/privilege/model/internal/Role.java index d41e73c17..a4788ea43 100644 --- a/src/ch/eitchnet/privilege/model/internal/Role.java +++ b/src/ch/eitchnet/privilege/model/internal/Role.java @@ -17,8 +17,17 @@ import java.util.Set; import ch.eitchnet.privilege.model.RoleRep; /** - * @author rvonburg + *

    + * 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 rvonburg */ public final class Role { @@ -26,9 +35,12 @@ public final class Role { private final Set privileges; /** + * Default constructor * * @param name + * the name of the role * @param privileges + * a set of names of privileges granted to this role */ public Role(String name, Set privileges) { this.name = name; diff --git a/src/ch/eitchnet/privilege/model/internal/Session.java b/src/ch/eitchnet/privilege/model/internal/Session.java index f5003d0ca..14e1129be 100644 --- a/src/ch/eitchnet/privilege/model/internal/Session.java +++ b/src/ch/eitchnet/privilege/model/internal/Session.java @@ -10,9 +10,22 @@ package ch.eitchnet.privilege.model.internal; +import ch.eitchnet.privilege.handler.PrivilegeHandler; +import ch.eitchnet.privilege.model.Certificate; + /** - * @author rvonburg + *

    + * A {@link Session} is linked to currently logged in user. The {@link PrivilegeHandler} creates an instance of this + * class once a {@link User} has successfully logged in and keeps this object private but hands out a + * {@link Certificate} which the user must use every time a privilege needs to be granted + *

    * + *

    + * Note: This is an internal object which is not to be serialized or passed to clients, the client must keep his + * {@link Certificate} which is used for accessing privileges + *

    + * + * @author rvonburg */ public final class Session { @@ -24,18 +37,32 @@ public final class Session { private final String authPassword; /** + * Default constructor + * + *

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

    * * @param sessionId - * @param authToken - * @param authPassword + * the users session id * @param username + * the users login name + * @param authToken + * the authentication token defining the users unique session and is a private field of this session. It + * corresponds with the authentication token on the {@link Certificate} + * @param authPassword + * the password to access the authentication token, this is not known to the client but set by the + * {@link PrivilegeHandler} on authentication. It corresponds with the authentication password on the + * {@link Certificate} * @param loginTime + * the time the user logged in */ - public Session(String sessionId, String authToken, String authPassword, String username, long loginTime) { + public Session(String sessionId, String username, String authToken, String authPassword, long loginTime) { this.sessionId = sessionId; + this.username = username; this.authToken = authToken; this.authPassword = authPassword; - this.username = username; this.loginTime = loginTime; } diff --git a/src/ch/eitchnet/privilege/model/internal/User.java b/src/ch/eitchnet/privilege/model/internal/User.java index 176481429..2118763e3 100644 --- a/src/ch/eitchnet/privilege/model/internal/User.java +++ b/src/ch/eitchnet/privilege/model/internal/User.java @@ -19,6 +19,14 @@ import ch.eitchnet.privilege.model.UserRep; import ch.eitchnet.privilege.model.UserState; /** + * 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 surname + * + *

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

    + * * @author rvonburg * */ @@ -39,14 +47,24 @@ public final class User { 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 surname + * the user's surname * @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} */ public User(String userId, String username, String password, String firstname, String surname, UserState userState, Set roles, Locale locale) { @@ -125,6 +143,8 @@ public final class User { * 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) { diff --git a/src/ch/eitchnet/privilege/policy/DefaultPrivilege.java b/src/ch/eitchnet/privilege/policy/DefaultPrivilege.java index 097214169..1eecb1e26 100644 --- a/src/ch/eitchnet/privilege/policy/DefaultPrivilege.java +++ b/src/ch/eitchnet/privilege/policy/DefaultPrivilege.java @@ -16,8 +16,9 @@ import ch.eitchnet.privilege.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.Role; /** - * @author rvonburg + * XXX re-think this implementation... * + * @author rvonburg */ public class DefaultPrivilege implements PrivilegePolicy { diff --git a/src/ch/eitchnet/privilege/policy/PrivilegePolicy.java b/src/ch/eitchnet/privilege/policy/PrivilegePolicy.java index 461ea6951..865d373cd 100644 --- a/src/ch/eitchnet/privilege/policy/PrivilegePolicy.java +++ b/src/ch/eitchnet/privilege/policy/PrivilegePolicy.java @@ -13,17 +13,32 @@ package ch.eitchnet.privilege.policy; import ch.eitchnet.privilege.model.Restrictable; import ch.eitchnet.privilege.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.Role; +import ch.eitchnet.privilege.model.internal.User; /** - * @author rvonburg + *

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

    * + *

    + * Re-think this interface and especially the {@link #actionAllowed(Role, Privilege, Restrictable)}-method... maybe we + * need one with out the {@link Privilege} in its signature? + *

    + * + * @author rvonburg */ public interface PrivilegePolicy { /** + * Checks if the given {@link Role} and the given {@link Privilege} has access to the given {@link Restrictable} + * * @param role + * the {@link Role} trying to access the {@link Restrictable} * @param privilege + * the {@link Privilege} to check with * @param restrictable + * the {@link Restrictable} to which the user wants access * * @return return true if the action is allowed, false if not */ From 4a66c0d2a4ddd1d2415a8ddb44271caef1c319f9 Mon Sep 17 00:00:00 2001 From: eitch Date: Mon, 25 Jul 2011 22:35:47 +0000 Subject: [PATCH 045/173] [New] added an ant build script --- MANIFEST.MF | 7 ++++++- build_package.xml | 34 ++++++++++++++++++++++++++++++++++ docs/TODO | 2 ++ 3 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 build_package.xml diff --git a/MANIFEST.MF b/MANIFEST.MF index 58630c02e..c30795c80 100644 --- a/MANIFEST.MF +++ b/MANIFEST.MF @@ -1,2 +1,7 @@ Manifest-Version: 1.0 - +Implementation-Vendor: eitchnet.ch +Implementation-Title: eitchnet Java Privilege implementation +Implementation-Version: 0.0.1 +Specification-Vendor: eitchnet.ch +Specification-Title: eitchnet Java Privilege implementation +Specification-Version: 1.6 diff --git a/build_package.xml b/build_package.xml new file mode 100644 index 000000000..8a576f51d --- /dev/null +++ b/build_package.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/TODO b/docs/TODO index 296271aac..319d89b1f 100644 --- a/docs/TODO +++ b/docs/TODO @@ -8,6 +8,8 @@ A list of TODOs for Privilege actionAllowed(Role, Privilege, Restrictable)-method. Maybe the Privilege argument is not needed, as this should be on the Role anyhow... +- i18n for any messages and exceptions! + - Finish the JavaDoc - Set up a website =) From a5d52733e9b7fa4ab5d06979ccd16512d4646d41 Mon Sep 17 00:00:00 2001 From: eitch Date: Mon, 25 Jul 2011 22:39:19 +0000 Subject: [PATCH 046/173] --- build_package.xml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/build_package.xml b/build_package.xml index 8a576f51d..d57e5b6d3 100644 --- a/build_package.xml +++ b/build_package.xml @@ -7,7 +7,8 @@ - + + @@ -16,6 +17,14 @@ + + + + + + From bf3ff008b63c866fbc5c60ab233ba0f9088ab1ce Mon Sep 17 00:00:00 2001 From: eitch Date: Mon, 25 Jul 2011 22:42:09 +0000 Subject: [PATCH 047/173] [New] added an ant build script --- build_package.xml | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/build_package.xml b/build_package.xml index d57e5b6d3..aaa219219 100644 --- a/build_package.xml +++ b/build_package.xml @@ -9,7 +9,7 @@ - + @@ -17,14 +17,9 @@ - - - - + - + @@ -40,4 +35,14 @@ + + + + + + + + + + \ No newline at end of file From 42383f940066225aeefcee16ebc71481af9127a0 Mon Sep 17 00:00:00 2001 From: eitch Date: Wed, 27 Jul 2011 20:15:47 +0000 Subject: [PATCH 049/173] [New] added a new PrivilegeHandler.invalidate(Certificate)-method with which users can log out of Privilege --- .../handler/DefaultPrivilegeHandler.java | 22 ++- .../privilege/handler/PrivilegeHandler.java | 11 ++ .../handler/XmlPersistenceHandler.java | 166 +++++++++--------- 3 files changed, 119 insertions(+), 80 deletions(-) diff --git a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 2365c0001..1b88b8221 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -10,6 +10,7 @@ package ch.eitchnet.privilege.handler; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Locale; @@ -655,6 +656,25 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { return certificate; } + /** + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#invalidateSession(ch.eitchnet.privilege.model.Certificate) + */ + @Override + public boolean invalidateSession(Certificate certificate) { + + // first validate certificate + if (!isCertificateValid(certificate)) { + logger.info("Certificate is not valid, so no session to invalidate: " + certificate.toString()); + return false; + } + + // remove registration + CertificateSessionPair certificateSessionPair = this.sessionMap.remove(certificate.getSessionId()); + + // return true if object was really removed + return certificateSessionPair != null; + } + /** * Checks if the action is allowed by iterating the roles of the certificates user and then delegating to * {@link #actionAllowed(Role, Restrictable)} @@ -884,7 +904,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { this.persistenceHandler = persistenceHandler; lastSessionId = 0l; - this.sessionMap = new HashMap(); + this.sessionMap = Collections.synchronizedMap(new HashMap()); } /** diff --git a/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java b/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java index 294cde3f0..84de5495e 100644 --- a/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java +++ b/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java @@ -25,6 +25,7 @@ import ch.eitchnet.privilege.model.UserRep; import ch.eitchnet.privilege.model.UserState; import ch.eitchnet.privilege.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.Role; +import ch.eitchnet.privilege.model.internal.Session; import ch.eitchnet.privilege.model.internal.User; import ch.eitchnet.privilege.policy.PrivilegePolicy; @@ -441,6 +442,16 @@ public interface PrivilegeHandler { */ public Certificate authenticate(String username, String password) throws AccessDeniedException; + /** + * Invalidates the {@link 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 {@link Session} is to be invalidated + * @return true if the {@link Session} was still valid and is now invalidated, false otherwise + */ + public boolean invalidateSession(Certificate certificate); + /** * Checks if the {@link User} registered to the given {@link Certificate} is allowed to access the * {@link Restrictable} diff --git a/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java b/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java index dab8bd719..da7e6c8ec 100644 --- a/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java @@ -35,8 +35,10 @@ import ch.eitchnet.privilege.model.internal.User; import ch.eitchnet.privilege.policy.PrivilegePolicy; /** - * @author rvonburg + * {@link PersistenceHandler} implementation which reads the configuration from XML files. These configuration is passed + * in {@link #initialize(Map)} * + * @author rvonburg */ public class XmlPersistenceHandler implements PersistenceHandler { @@ -274,10 +276,10 @@ public class XmlPersistenceHandler implements PersistenceHandler { @Override public void initialize(Map parameterMap) { - this.roleMap = new HashMap(); - this.userMap = new HashMap(); - this.privilegeMap = new HashMap(); - this.policyMap = new HashMap>(); + this.roleMap = Collections.synchronizedMap(new HashMap()); + this.userMap = Collections.synchronizedMap(new HashMap()); + this.privilegeMap = Collections.synchronizedMap(new HashMap()); + this.policyMap = Collections.synchronizedMap(new HashMap>()); // get and validate base bath this.basePath = parameterMap.get(XmlConstants.XML_PARAM_BASE_PATH); @@ -536,37 +538,39 @@ public class XmlPersistenceHandler implements PersistenceHandler { List privilegesAsElements = new ArrayList(this.privilegeMap.size()); DocumentFactory documentFactory = DocumentFactory.getInstance(); - for (String privilegeName : this.privilegeMap.keySet()) { + synchronized (this.privilegeMap) { + for (String privilegeName : this.privilegeMap.keySet()) { - // get the privilege object - Privilege privilege = this.privilegeMap.get(privilegeName); + // get the privilege object + Privilege privilege = this.privilegeMap.get(privilegeName); - // create the privilege element - Element privilegeElement = documentFactory.createElement(XmlConstants.XML_PRIVILEGE); - privilegeElement.addAttribute(XmlConstants.XML_ATTR_NAME, privilege.getName()); - privilegeElement.addAttribute(XmlConstants.XML_ATTR_POLICY, privilege.getPolicy()); + // create the privilege element + Element privilegeElement = documentFactory.createElement(XmlConstants.XML_PRIVILEGE); + privilegeElement.addAttribute(XmlConstants.XML_ATTR_NAME, privilege.getName()); + privilegeElement.addAttribute(XmlConstants.XML_ATTR_POLICY, privilege.getPolicy()); - // add the all allowed element - Element allAllowedElement = documentFactory.createElement(XmlConstants.XML_ALL_ALLOWED); - allAllowedElement.setText(Boolean.toString(privilege.isAllAllowed())); - privilegeElement.add(allAllowedElement); + // add the all allowed element + Element allAllowedElement = documentFactory.createElement(XmlConstants.XML_ALL_ALLOWED); + allAllowedElement.setText(Boolean.toString(privilege.isAllAllowed())); + privilegeElement.add(allAllowedElement); - // add all the deny values - for (String denyValue : privilege.getDenyList()) { - Element denyValueElement = documentFactory.createElement(XmlConstants.XML_DENY); - denyValueElement.setText(denyValue); - privilegeElement.add(denyValueElement); + // add all the deny values + for (String denyValue : privilege.getDenyList()) { + Element denyValueElement = documentFactory.createElement(XmlConstants.XML_DENY); + denyValueElement.setText(denyValue); + privilegeElement.add(denyValueElement); + } + + // add all the allow values + for (String allowValue : privilege.getAllowList()) { + Element allowValueElement = documentFactory.createElement(XmlConstants.XML_ALLOW); + allowValueElement.setText(allowValue); + privilegeElement.add(allowValueElement); + } + + // add element to return list + privilegesAsElements.add(privilegeElement); } - - // add all the allow values - for (String allowValue : privilege.getAllowList()) { - Element allowValueElement = documentFactory.createElement(XmlConstants.XML_ALLOW); - allowValueElement.setText(allowValue); - privilegeElement.add(allowValueElement); - } - - // add element to return list - privilegesAsElements.add(privilegeElement); } return privilegesAsElements; @@ -577,24 +581,26 @@ public class XmlPersistenceHandler implements PersistenceHandler { List rolesAsElements = new ArrayList(this.roleMap.size()); DocumentFactory documentFactory = DocumentFactory.getInstance(); - for (String roleName : this.roleMap.keySet()) { + synchronized (this.roleMap) { + for (String roleName : this.roleMap.keySet()) { - // get the role object - Role role = this.roleMap.get(roleName); + // get the role object + Role role = this.roleMap.get(roleName); - // create the role element - Element roleElement = documentFactory.createElement(XmlConstants.XML_ROLE); - roleElement.addAttribute(XmlConstants.XML_ATTR_NAME, role.getName()); + // create the role element + Element roleElement = documentFactory.createElement(XmlConstants.XML_ROLE); + roleElement.addAttribute(XmlConstants.XML_ATTR_NAME, role.getName()); - // add all the privileges - for (String privilegeName : role.getPrivileges()) { - Element privilegeElement = documentFactory.createElement(XmlConstants.XML_PRIVILEGE); - privilegeElement.addAttribute(XmlConstants.XML_ATTR_NAME, privilegeName); - roleElement.add(privilegeElement); + // add all the privileges + for (String privilegeName : role.getPrivileges()) { + Element privilegeElement = documentFactory.createElement(XmlConstants.XML_PRIVILEGE); + privilegeElement.addAttribute(XmlConstants.XML_ATTR_NAME, privilegeName); + roleElement.add(privilegeElement); + } + + // add element to return list + rolesAsElements.add(roleElement); } - - // add element to return list - rolesAsElements.add(roleElement); } return rolesAsElements; @@ -605,48 +611,50 @@ public class XmlPersistenceHandler implements PersistenceHandler { List usersAsElements = new ArrayList(this.userMap.size()); DocumentFactory documentFactory = DocumentFactory.getInstance(); - for (String userName : this.userMap.keySet()) { + synchronized (this.userMap) { + for (String userName : this.userMap.keySet()) { - // get the user object - User user = this.userMap.get(userName); + // get the user object + User user = this.userMap.get(userName); - // create the user element - Element userElement = documentFactory.createElement(XmlConstants.XML_USER); - userElement.addAttribute(XmlConstants.XML_ATTR_USER_ID, user.getUserId()); - userElement.addAttribute(XmlConstants.XML_ATTR_USERNAME, user.getUsername()); - userElement.addAttribute(XmlConstants.XML_ATTR_PASSWORD, user.getPassword()); + // create the user element + Element userElement = documentFactory.createElement(XmlConstants.XML_USER); + userElement.addAttribute(XmlConstants.XML_ATTR_USER_ID, user.getUserId()); + userElement.addAttribute(XmlConstants.XML_ATTR_USERNAME, user.getUsername()); + userElement.addAttribute(XmlConstants.XML_ATTR_PASSWORD, user.getPassword()); - // add first name element - Element firstnameElement = documentFactory.createElement(XmlConstants.XML_FIRSTNAME); - firstnameElement.setText(user.getFirstname()); - userElement.add(firstnameElement); + // add first name element + Element firstnameElement = documentFactory.createElement(XmlConstants.XML_FIRSTNAME); + firstnameElement.setText(user.getFirstname()); + userElement.add(firstnameElement); - // add surname element - Element surnameElement = documentFactory.createElement(XmlConstants.XML_SURNAME); - surnameElement.setText(user.getSurname()); - userElement.add(surnameElement); + // add surname element + Element surnameElement = documentFactory.createElement(XmlConstants.XML_SURNAME); + surnameElement.setText(user.getSurname()); + userElement.add(surnameElement); - // add state element - Element stateElement = documentFactory.createElement(XmlConstants.XML_STATE); - stateElement.setText(user.getUserState().toString()); - userElement.add(stateElement); + // add state element + Element stateElement = documentFactory.createElement(XmlConstants.XML_STATE); + stateElement.setText(user.getUserState().toString()); + userElement.add(stateElement); - // add locale element - Element localeElement = documentFactory.createElement(XmlConstants.XML_LOCALE); - localeElement.setText(user.getLocale().toString()); - userElement.add(localeElement); + // add locale element + Element localeElement = documentFactory.createElement(XmlConstants.XML_LOCALE); + localeElement.setText(user.getLocale().toString()); + userElement.add(localeElement); - // add all the role elements - Element rolesElement = documentFactory.createElement(XmlConstants.XML_ROLES); - userElement.add(rolesElement); - for (String roleName : user.getRoles()) { - Element roleElement = documentFactory.createElement(XmlConstants.XML_ROLE); - roleElement.setText(roleName); - rolesElement.add(roleElement); + // add all the role elements + Element rolesElement = documentFactory.createElement(XmlConstants.XML_ROLES); + userElement.add(rolesElement); + for (String roleName : user.getRoles()) { + Element roleElement = documentFactory.createElement(XmlConstants.XML_ROLE); + roleElement.setText(roleName); + rolesElement.add(roleElement); + } + + // add element to return list + usersAsElements.add(userElement); } - - // add element to return list - usersAsElements.add(userElement); } return usersAsElements; From 2758aa61d1bf5b27bbe66ad0f4822fcc91972fbd Mon Sep 17 00:00:00 2001 From: eitch Date: Wed, 27 Jul 2011 20:16:05 +0000 Subject: [PATCH 050/173] --- build_package.xml => build.xml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename build_package.xml => build.xml (100%) diff --git a/build_package.xml b/build.xml similarity index 100% rename from build_package.xml rename to build.xml From e7b8de2765bff7dcd9c2ce7a83ddc60fdc7b344f Mon Sep 17 00:00:00 2001 From: eitch Date: Wed, 27 Jul 2011 20:21:17 +0000 Subject: [PATCH 051/173] --- build.xml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/build.xml b/build.xml index aaa219219..8063f0c42 100644 --- a/build.xml +++ b/build.xml @@ -31,7 +31,10 @@ - + + + + From f26013583de12f0bcf289f90d8df2b4054a35e11 Mon Sep 17 00:00:00 2001 From: eitch Date: Thu, 28 Jul 2011 20:50:35 +0000 Subject: [PATCH 052/173] [Interface] modified privilege to have only two configuration files: Privilege.xml for the configuration for the handlers and Policies. and PrivilegeModel.xml for the configuration of the run time modifiable data (roles, users, privileges) --- config/Privilege.xml | 29 ++ config/PrivilegeContainer.xml | 22 -- config/PrivilegeModel.xml | 49 ++++ config/PrivilegePolicies.xml | 6 - config/PrivilegeRoles.xml | 13 - config/PrivilegeUsers.xml | 16 -- config/Privileges.xml | 16 -- .../handler/DefaultPrivilegeHandler.java | 3 +- .../privilege/handler/PersistenceHandler.java | 4 +- .../handler/XmlPersistenceHandler.java | 250 +++++------------- .../helper/BootstrapConfigurationHelper.java | 47 ++-- .../helper/InitializationHelper.java | 66 ++++- .../privilege/helper/XmlConstants.java | 66 +++-- .../privilege/model/internal/Privilege.java | 20 +- .../privilege/model/internal/Role.java | 9 + .../privilege/model/internal/Session.java | 15 ++ .../privilege/model/internal/User.java | 24 ++ .../privilege/test/PrivilegeTest.java | 2 +- 18 files changed, 328 insertions(+), 329 deletions(-) create mode 100644 config/Privilege.xml delete mode 100644 config/PrivilegeContainer.xml create mode 100644 config/PrivilegeModel.xml delete mode 100644 config/PrivilegePolicies.xml delete mode 100644 config/PrivilegeRoles.xml delete mode 100644 config/PrivilegeUsers.xml delete mode 100644 config/Privileges.xml diff --git a/config/Privilege.xml b/config/Privilege.xml new file mode 100644 index 000000000..0913623cb --- /dev/null +++ b/config/Privilege.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/config/PrivilegeContainer.xml b/config/PrivilegeContainer.xml deleted file mode 100644 index 20b2be359..000000000 --- a/config/PrivilegeContainer.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/config/PrivilegeModel.xml b/config/PrivilegeModel.xml new file mode 100644 index 000000000..429e3a2f8 --- /dev/null +++ b/config/PrivilegeModel.xml @@ -0,0 +1,49 @@ + + + + + + true + + + + + + false + + ch.eitchnet.privilege.test.TestRestrictable + + + + + + + Robert + von Burg + ENABLED + en_GB + + PrivilegeAdmin + admin + serviceExecutor + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/config/PrivilegePolicies.xml b/config/PrivilegePolicies.xml deleted file mode 100644 index 2b2c6f486..000000000 --- a/config/PrivilegePolicies.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/config/PrivilegeRoles.xml b/config/PrivilegeRoles.xml deleted file mode 100644 index 89724934b..000000000 --- a/config/PrivilegeRoles.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/config/PrivilegeUsers.xml b/config/PrivilegeUsers.xml deleted file mode 100644 index f03340020..000000000 --- a/config/PrivilegeUsers.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - Robert - von Burg - ENABLED - en_GB - - PrivilegeAdmin - admin - serviceExecutor - - - - \ No newline at end of file diff --git a/config/Privileges.xml b/config/Privileges.xml deleted file mode 100644 index baec28f6c..000000000 --- a/config/Privileges.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - true - - - - - - false - - ch.eitchnet.privilege.test.TestRestrictable - - - \ No newline at end of file diff --git a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 1b88b8221..a6e7de1b0 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -645,8 +645,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { Certificate certificate = new Certificate(sessionId, username, authToken, authPassword, user.getLocale()); // create and save a new session - Session session = new Session(sessionId, authToken, authPassword, user.getUsername(), - System.currentTimeMillis()); + Session session = new Session(sessionId, username, authToken, authPassword, System.currentTimeMillis()); this.sessionMap.put(sessionId, new CertificateSessionPair(session, certificate)); // log diff --git a/src/ch/eitchnet/privilege/handler/PersistenceHandler.java b/src/ch/eitchnet/privilege/handler/PersistenceHandler.java index 7a6db6b33..13531f834 100644 --- a/src/ch/eitchnet/privilege/handler/PersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/PersistenceHandler.java @@ -151,6 +151,8 @@ public interface PersistenceHandler { * * @param parameterMap * a map containing configuration properties + * @param policyMap + * map of policy key/policy class pairs */ - public void initialize(Map parameterMap); + public void initialize(Map parameterMap, Map> policyMap); } diff --git a/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java b/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java index da7e6c8ec..86c9dd6ae 100644 --- a/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java @@ -36,7 +36,7 @@ import ch.eitchnet.privilege.policy.PrivilegePolicy; /** * {@link PersistenceHandler} implementation which reads the configuration from XML files. These configuration is passed - * in {@link #initialize(Map)} + * in {@link #initialize(Map, Map)} * * @author rvonburg */ @@ -49,11 +49,9 @@ public class XmlPersistenceHandler implements PersistenceHandler { private Map privilegeMap; private Map> policyMap; - private long usersFileDate; + private long modelsFileDate; private boolean userMapDirty; - private long rolesFileDate; private boolean roleMapDirty; - private long privilegesFileDate; private boolean privilegeMapDirty; private Map parameterMap; @@ -171,87 +169,51 @@ public class XmlPersistenceHandler implements PersistenceHandler { @Override public boolean persist() { - // USERS - // get users file name - String usersFileName = this.parameterMap.get(XmlConstants.XML_PARAM_USERS_FILE); - if (usersFileName == null || usersFileName.isEmpty()) { + // get models file name + String modelFileName = this.parameterMap.get(XmlConstants.XML_PARAM_MODEL_FILE); + if (modelFileName == null || modelFileName.isEmpty()) { throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " - + XmlConstants.XML_PARAM_USERS_FILE + " is invalid"); + + XmlConstants.XML_PARAM_MODEL_FILE + " is invalid"); } - // get users file - File usersFile = new File(this.basePath + "/" + usersFileName); - boolean usersFileUnchanged = usersFile.exists() && usersFile.lastModified() == this.usersFileDate; - if (!this.userMapDirty && usersFileUnchanged) { - logger.warn("No users unpersisted and user file unchanged on file system"); - } else { - logger.info("Persisting users..."); - - // build XML DOM of users - List users = toDomUsers(); - Element rootElement = DocumentFactory.getInstance().createElement(XmlConstants.XML_USERS); - for (Element userElement : users) { - rootElement.add(userElement); - } - - // write DOM to file - XmlHelper.writeElement(rootElement, usersFile); - this.userMapDirty = true; + // get model file + File modelFile = new File(this.basePath + "/" + modelFileName); + boolean modelFileUnchanged = modelFile.exists() && modelFile.lastModified() == this.modelsFileDate; + if (!(modelFileUnchanged && this.privilegeMapDirty && this.roleMapDirty && this.userMapDirty)) { + logger.warn("Not persisting as current file is unchanged and model data is not dirty"); + return false; } + DocumentFactory docFactory = DocumentFactory.getInstance(); + + // create root element + Element rootElement = docFactory.createElement(XmlConstants.XML_ROOT_PRIVILEGE_USERS_AND_ROLES); + + // USERS + // build XML DOM of users + List users = toDomUsers(); + Element usersElement = docFactory.createElement(XmlConstants.XML_USERS); + for (Element userElement : users) { + usersElement.add(userElement); + } + rootElement.add(usersElement); + // ROLES - // get roles file name - String rolesFileName = this.parameterMap.get(XmlConstants.XML_PARAM_ROLES_FILE); - if (rolesFileName == null || rolesFileName.isEmpty()) { - throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " - + XmlConstants.XML_PARAM_ROLES_FILE + " is invalid"); - } - // get roles file - File rolesFile = new File(this.basePath + "/" + rolesFileName); - boolean rolesFileUnchanged = rolesFile.exists() && rolesFile.lastModified() == this.rolesFileDate; - if (!this.roleMapDirty && rolesFileUnchanged) { - logger.warn("No roles unpersisted and roles file unchanged on file system"); - } else { - logger.info("Persisting roles..."); - - // build XML DOM of roles - List roles = toDomRoles(); - Element rootElement = DocumentFactory.getInstance().createElement(XmlConstants.XML_ROLES); - for (Element roleElement : roles) { - rootElement.add(roleElement); - } - - // write DOM to file - XmlHelper.writeElement(rootElement, rolesFile); - this.roleMapDirty = true; + // build XML DOM of roles + List roles = toDomRoles(); + Element rolesElement = docFactory.createElement(XmlConstants.XML_ROLES); + for (Element roleElement : roles) { + rolesElement.add(roleElement); } + rootElement.add(rolesElement); // PRIVILEGES - // get privileges file name - String privilegesFileName = this.parameterMap.get(XmlConstants.XML_PARAM_PRIVILEGES_FILE); - if (privilegesFileName == null || privilegesFileName.isEmpty()) { - throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " - + XmlConstants.XML_PARAM_PRIVILEGES_FILE + " is invalid"); - } - // get privileges file - File privilegesFile = new File(this.basePath + "/" + privilegesFileName); - boolean privilegesFileUnchanged = privilegesFile.exists() - && privilegesFile.lastModified() == this.privilegesFileDate; - if (!this.privilegeMapDirty && privilegesFileUnchanged) { - logger.warn("No privileges unpersisted and privileges file unchanged on file system"); - } else { - logger.info("Persisting privileges..."); - - // build XML DOM of privileges - List privileges = toDomPrivileges(); - Element rootElement = DocumentFactory.getInstance().createElement(XmlConstants.XML_PRIVILEGES); - for (Element privilegeElement : privileges) { - rootElement.add(privilegeElement); - } - - // write DOM to file - XmlHelper.writeElement(rootElement, privilegesFile); - this.privilegeMapDirty = true; + // build XML DOM of privileges + List privileges = toDomPrivileges(); + Element privilegesElement = docFactory.createElement(XmlConstants.XML_PRIVILEGES); + for (Element privilegeElement : privileges) { + privilegesElement.add(privilegeElement); } + rootElement.add(privilegesElement); // reset dirty states and return if something was dirty, false otherwise if (this.userMapDirty || this.roleMapDirty || this.privilegeMapDirty) { @@ -274,12 +236,12 @@ public class XmlPersistenceHandler implements PersistenceHandler { * @see ch.eitchnet.privilege.handler.EncryptionHandler#initialize(java.util.Map) */ @Override - public void initialize(Map parameterMap) { + public void initialize(Map parameterMap, Map> policyMap) { this.roleMap = Collections.synchronizedMap(new HashMap()); this.userMap = Collections.synchronizedMap(new HashMap()); this.privilegeMap = Collections.synchronizedMap(new HashMap()); - this.policyMap = Collections.synchronizedMap(new HashMap>()); + this.policyMap = policyMap; // get and validate base bath this.basePath = parameterMap.get(XmlConstants.XML_PARAM_BASE_PATH); @@ -289,96 +251,42 @@ public class XmlPersistenceHandler implements PersistenceHandler { + XmlConstants.XML_PARAM_BASE_PATH + " is invalid"); } + // get model file name + String modelFileName = parameterMap.get(XmlConstants.XML_PARAM_MODEL_FILE); + if (modelFileName == null || modelFileName.isEmpty()) { + throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " + + XmlConstants.XML_PARAM_MODEL_FILE + " is invalid"); + } + + // validate file exists + File modelsFile = new File(this.basePath + "/" + modelFileName); + if (!modelsFile.exists()) { + throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " + + XmlConstants.XML_PARAM_MODEL_FILE + " is invalid as models file does not exist at path " + + modelsFile.getAbsolutePath()); + } + + // parse models xml file to XML document + Element modelsRootElement = XmlHelper.parseDocument(modelsFile).getRootElement(); + this.modelsFileDate = modelsFile.lastModified(); + // ROLES - // get roles file name - String rolesFileName = parameterMap.get(XmlConstants.XML_PARAM_ROLES_FILE); - if (rolesFileName == null || rolesFileName.isEmpty()) { - throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " - + XmlConstants.XML_PARAM_ROLES_FILE + " is invalid"); - } - - // get roles file - File rolesFile = new File(this.basePath + "/" + rolesFileName); - if (!rolesFile.exists()) { - throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " - + XmlConstants.XML_PARAM_ROLES_FILE + " is invalid as roles file does not exist at path " - + rolesFile.getAbsolutePath()); - } - - // parse roles xml file to XML document - Element rolesRootElement = XmlHelper.parseDocument(rolesFile).getRootElement(); - + // get roles element + Element rolesElement = modelsRootElement.element(XmlConstants.XML_ROLES); // read roles - readRoles(rolesRootElement); - this.rolesFileDate = rolesFile.lastModified(); + readRoles(rolesElement); // USERS - // get users file name - String usersFileName = parameterMap.get(XmlConstants.XML_PARAM_USERS_FILE); - if (usersFileName == null || usersFileName.isEmpty()) { - throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " - + XmlConstants.XML_PARAM_USERS_FILE + " is invalid"); - } - - // get users file - File usersFile = new File(this.basePath + "/" + usersFileName); - if (!usersFile.exists()) { - throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " - + XmlConstants.XML_PARAM_USERS_FILE + " is invalid as users file does not exist at path " - + usersFile.getAbsolutePath()); - } - - // parse users xml file to XML document - Element usersRootElement = XmlHelper.parseDocument(usersFile).getRootElement(); - + // get users element + Element usersElement = modelsRootElement.element(XmlConstants.XML_USERS); // read users - readUsers(usersRootElement); - this.usersFileDate = usersFile.lastModified(); + readUsers(usersElement); // PRIVILEGES - // get privileges file name - String privilegesFileName = parameterMap.get(XmlConstants.XML_PARAM_PRIVILEGES_FILE); - if (privilegesFileName == null || privilegesFileName.isEmpty()) { - throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " - + XmlConstants.XML_PARAM_PRIVILEGES_FILE + " is invalid"); - } - - // get privileges file - File privilegesFile = new File(this.basePath + "/" + privilegesFileName); - if (!privilegesFile.exists()) { - throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " - + XmlConstants.XML_PARAM_PRIVILEGES_FILE + " is invalid as privileges file does not exist at path " - + privilegesFile.getAbsolutePath()); - } - - // parse privileges xml file to XML document - Element privilegesRootElement = XmlHelper.parseDocument(privilegesFile).getRootElement(); - + // get privileges element + Element privilegesElement = modelsRootElement.element(XmlConstants.XML_PRIVILEGES); // read privileges - readPrivileges(privilegesRootElement); - this.privilegesFileDate = privilegesFile.lastModified(); - - // POLICIES - // get policy file name - String policyFileName = parameterMap.get(XmlConstants.XML_PARAM_POLICY_FILE); - if (policyFileName == null || policyFileName.isEmpty()) { - throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " - + XmlConstants.XML_PARAM_POLICY_FILE + " is invalid"); - } - - // get policy file - File policyFile = new File(this.basePath + "/" + policyFileName); - if (!policyFile.exists()) { - throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " - + XmlConstants.XML_PARAM_POLICY_FILE + " is invalid as policy file does not exist at path " - + policyFile.getAbsolutePath()); - } - - // parse policy xml file to XML document - Element policiesRootElement = XmlHelper.parseDocument(policyFile).getRootElement(); - - // read policies - readPolicies(policiesRootElement); + readPrivileges(privilegesElement); this.userMapDirty = false; this.roleMapDirty = false; @@ -397,8 +305,10 @@ public class XmlPersistenceHandler implements PersistenceHandler { break; } } + if (!privilegeAdminExists) { - logger.warn("No User with PrivilegeAdmin role exists. Privilege modifications will not be possible!"); + logger.warn("No User with role '" + PrivilegeHandler.PRIVILEGE_ADMIN_ROLE + + "' exists. Privilege modifications will not be possible!"); } } @@ -441,6 +351,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { // create user User user = new User(userId, username, password, firstname, surname, userState, Collections.unmodifiableSet(roles), locale); + logger.info("Added user " + user); // put user in map this.userMap.put(username, user); @@ -516,23 +427,6 @@ public class XmlPersistenceHandler implements PersistenceHandler { } } - /** - * @param policiesRootElement - */ - private void readPolicies(Element policiesRootElement) { - - @SuppressWarnings("unchecked") - List policyElements = policiesRootElement.elements(XmlConstants.XML_POLICY); - for (Element policyElement : policyElements) { - String policyName = policyElement.attributeValue(XmlConstants.XML_ATTR_NAME); - String policyClass = policyElement.attributeValue(XmlConstants.XML_ATTR_CLASS); - - Class clazz = ClassHelper.loadClass(policyClass); - - this.policyMap.put(policyName, clazz); - } - } - private List toDomPrivileges() { List privilegesAsElements = new ArrayList(this.privilegeMap.size()); diff --git a/src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java b/src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java index 114d9840b..b25485d79 100644 --- a/src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java +++ b/src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java @@ -46,17 +46,13 @@ public class BootstrapConfigurationHelper { private static String path; - private static String defaultPrivilegeContainerXmlFile = "PrivilegeContainer.xml"; + private static String defaultPrivilegeContainerXmlFile = "Privilege.xml"; - private static String usersFileName = "PrivilegeUsers.xml"; - private static String rolesFileName = "PrivilegeRoles.xml"; - private static String privilegesFileName = "Privileges.xml"; + private static String basePath = ""; + private static String modelFileName = "PrivilegeUsers.xml"; private static String hashAlgorithm = "SHA-256"; - private static String policyXmlFile = "PrivilegePolicies.xml"; - - private static String defaultPrivilegeHandler = "ch.eitchnet.privilege.handler.DefaultPrivilegeHandler"; private static String defaultPersistenceHandler = "ch.eitchnet.privilege.handler.DefaultPersistenceHandler"; private static String defaultEncryptionHandler = "ch.eitchnet.privilege.handler.DefaultEncryptionHandler"; @@ -116,48 +112,35 @@ public class BootstrapConfigurationHelper { // create document root DocumentFactory factory = DocumentFactory.getInstance(); Document doc = factory.createDocument(XmlHelper.DEFAULT_ENCODING); - doc.setName(XmlConstants.XML_ROOT_PRIVILEGE_CONTAINER); - Element rootElement = factory.createElement(XmlConstants.XML_ROOT_PRIVILEGE_CONTAINER); + doc.setName(XmlConstants.XML_ROOT_PRIVILEGE); + Element rootElement = factory.createElement(XmlConstants.XML_ROOT_PRIVILEGE); doc.setRootElement(rootElement); + Element containerElement = factory.createElement(XmlConstants.XML_CONTAINER); + Element parameterElement; Element parametersElement; // create PersistenceHandler Element persistenceHandlerElem = factory.createElement(XmlConstants.XML_HANDLER_PERSISTENCE); - rootElement.add(persistenceHandlerElem); + containerElement.add(persistenceHandlerElem); persistenceHandlerElem.addAttribute(XmlConstants.XML_ATTR_CLASS, defaultPersistenceHandler); parametersElement = factory.createElement(XmlConstants.XML_PARAMETERS); persistenceHandlerElem.add(parametersElement); - // Parameter usersXmlFile + // Parameter basePath parameterElement = factory.createElement(XmlConstants.XML_PARAMETER); - parameterElement.addAttribute(XmlConstants.XML_ATTR_NAME, XmlConstants.XML_PARAM_USERS_FILE); - parameterElement.addAttribute(XmlConstants.XML_ATTR_VALUE, usersFileName); + parameterElement.addAttribute(XmlConstants.XML_ATTR_NAME, XmlConstants.XML_PARAM_BASE_PATH); + parameterElement.addAttribute(XmlConstants.XML_ATTR_VALUE, basePath); parametersElement.add(parameterElement); - // Parameter rolesXmlFile + // Parameter modelXmlFile parameterElement = factory.createElement(XmlConstants.XML_PARAMETER); - parameterElement.addAttribute(XmlConstants.XML_ATTR_NAME, XmlConstants.XML_PARAM_ROLES_FILE); - parameterElement.addAttribute(XmlConstants.XML_ATTR_VALUE, rolesFileName); + parameterElement.addAttribute(XmlConstants.XML_ATTR_NAME, XmlConstants.XML_PARAM_MODEL_FILE); + parameterElement.addAttribute(XmlConstants.XML_ATTR_VALUE, modelFileName); parametersElement.add(parameterElement); - // Parameter privilegesXmlFile - parameterElement = factory.createElement(XmlConstants.XML_PARAMETER); - parameterElement.addAttribute(XmlConstants.XML_ATTR_NAME, XmlConstants.XML_PARAM_PRIVILEGES_FILE); - parameterElement.addAttribute(XmlConstants.XML_ATTR_VALUE, privilegesFileName); - parametersElement.add(parameterElement); - // Parameter policyXmlFile - parameterElement = factory.createElement(XmlConstants.XML_PARAMETER); - parameterElement.addAttribute(XmlConstants.XML_ATTR_NAME, XmlConstants.XML_PARAM_POLICY_FILE); - parameterElement.addAttribute(XmlConstants.XML_ATTR_VALUE, policyXmlFile); - parametersElement.add(parameterElement); - - // create PrivilegeHandler - Element privilegeHandlerElem = factory.createElement(XmlConstants.XML_HANDLER_PRIVILEGE); - rootElement.add(privilegeHandlerElem); - privilegeHandlerElem.addAttribute(XmlConstants.XML_ATTR_CLASS, defaultPrivilegeHandler); // create EncryptionHandler Element encryptionHandlerElem = factory.createElement(XmlConstants.XML_HANDLER_ENCRYPTION); - rootElement.add(encryptionHandlerElem); + containerElement.add(encryptionHandlerElem); encryptionHandlerElem.addAttribute(XmlConstants.XML_ATTR_CLASS, defaultEncryptionHandler); parametersElement = factory.createElement(XmlConstants.XML_PARAMETERS); encryptionHandlerElem.add(parametersElement); diff --git a/src/ch/eitchnet/privilege/helper/InitializationHelper.java b/src/ch/eitchnet/privilege/helper/InitializationHelper.java index e0c2aadb6..5f7accc50 100644 --- a/src/ch/eitchnet/privilege/helper/InitializationHelper.java +++ b/src/ch/eitchnet/privilege/helper/InitializationHelper.java @@ -18,10 +18,12 @@ import java.util.Map; import org.apache.log4j.Logger; import org.dom4j.Element; +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.i18n.PrivilegeException; +import ch.eitchnet.privilege.policy.PrivilegePolicy; /** * This class implements the initializing of the {@link PrivilegeHandler} by loading an XML file containing the @@ -36,36 +38,38 @@ public class InitializationHelper { /** * Initializes the {@link PrivilegeHandler} from the configuration file * - * @param privilegeContainerXmlFile + * @param privilegeXmlFile * a {@link File} reference to the XML file containing the configuration for Privilege * * @return the {@link PrivilegeHandler} instance loaded from the configuration file */ - public static PrivilegeHandler initializeFromXml(File privilegeContainerXmlFile) { + public static PrivilegeHandler initializeFromXml(File privilegeXmlFile) { // make sure file exists - if (!privilegeContainerXmlFile.exists()) { - throw new PrivilegeException("Privilige file does not exist at path " - + privilegeContainerXmlFile.getAbsolutePath()); + if (!privilegeXmlFile.exists()) { + throw new PrivilegeException("Privilege file does not exist at path " + privilegeXmlFile.getAbsolutePath()); } // parse container xml file to XML document - Element containerRootElement = XmlHelper.parseDocument(privilegeContainerXmlFile).getRootElement(); + Element rootElement = XmlHelper.parseDocument(privilegeXmlFile).getRootElement(); + Element containerElement = rootElement.element(XmlConstants.XML_CONTAINER); // instantiate encryption handler - Element encryptionHandlerElement = containerRootElement.element(XmlConstants.XML_HANDLER_ENCRYPTION); + Element encryptionHandlerElement = containerElement.element(XmlConstants.XML_HANDLER_ENCRYPTION); String encryptionHandlerClassName = encryptionHandlerElement.attributeValue(XmlConstants.XML_ATTR_CLASS); EncryptionHandler encryptionHandler = ClassHelper.instantiateClass(encryptionHandlerClassName); // instantiate persistence handler - Element persistenceHandlerElement = containerRootElement.element(XmlConstants.XML_HANDLER_PERSISTENCE); + Element persistenceHandlerElement = containerElement.element(XmlConstants.XML_HANDLER_PERSISTENCE); String persistenceHandlerClassName = persistenceHandlerElement.attributeValue(XmlConstants.XML_ATTR_CLASS); PersistenceHandler persistenceHandler = ClassHelper.instantiateClass(persistenceHandlerClassName); // instantiate privilege handler - Element privilegeHandlerElement = containerRootElement.element(XmlConstants.XML_HANDLER_PRIVILEGE); - String privilegeHandlerClassName = privilegeHandlerElement.attributeValue(XmlConstants.XML_ATTR_CLASS); - PrivilegeHandler privilegeHandler = ClassHelper.instantiateClass(privilegeHandlerClassName); + PrivilegeHandler privilegeHandler = new DefaultPrivilegeHandler(); + + // get policies + Element policiesElement = rootElement.element(XmlConstants.XML_POLICIES); + Map> policyMap = convertToPolicyMap(policiesElement); try { @@ -89,7 +93,7 @@ public class InitializationHelper { Map parameterMap = convertToParameterMap(parameterElement); // initialize persistence handler - persistenceHandler.initialize(parameterMap); + persistenceHandler.initialize(parameterMap, policyMap); } catch (Exception e) { logger.error(e, e); @@ -100,7 +104,7 @@ public class InitializationHelper { try { // get parameters - Element parameterElement = privilegeHandlerElement.element(XmlConstants.XML_PARAMETERS); + Element parameterElement = containerElement.element(XmlConstants.XML_PARAMETERS); Map parameterMap = convertToParameterMap(parameterElement); // initialize privilege handler @@ -108,7 +112,8 @@ public class InitializationHelper { } catch (Exception e) { logger.error(e, e); - throw new PrivilegeException("PrivilegeHandler " + privilegeHandlerClassName + " could not be initialized"); + throw new PrivilegeException("PrivilegeHandler " + privilegeHandler.getClass().getName() + + " could not be initialized"); } return privilegeHandler; @@ -147,4 +152,37 @@ public class InitializationHelper { return parameterMap; } + + /** + * Converts an {@link XmlConstants#XML_POLICIES} element containing {@link XmlConstants#XML_POLICY} elements to a + * {@link Map} of String/Class pairs + * + * @param element + * the XML {@link Element} with name {@link XmlConstants#XML_POLICIES} containing + * {@link XmlConstants#XML_POLICY} elements + * + * @return the {@link Map} of the policy name/class combinations from the given {@link Element} + */ + @SuppressWarnings("unchecked") + public static Map> convertToPolicyMap(Element element) { + + Map> policyMap = new HashMap>(); + + List policyElements = element.elements(XmlConstants.XML_POLICY); + for (Element policyElement : policyElements) { + String policyName = policyElement.attributeValue(XmlConstants.XML_ATTR_NAME); + String policyClass = policyElement.attributeValue(XmlConstants.XML_ATTR_CLASS); + + Class clazz; + try { + clazz = ClassHelper.loadClass(policyClass); + } catch (PrivilegeException e) { + throw new PrivilegeException("The Policy with name " + policyName + " does not exist", e); + } + + policyMap.put(policyName, clazz); + } + + return policyMap; + } } diff --git a/src/ch/eitchnet/privilege/helper/XmlConstants.java b/src/ch/eitchnet/privilege/helper/XmlConstants.java index 465e1cf53..6bfe82148 100644 --- a/src/ch/eitchnet/privilege/helper/XmlConstants.java +++ b/src/ch/eitchnet/privilege/helper/XmlConstants.java @@ -19,32 +19,38 @@ public class XmlConstants { /** * XML_ROOT_PRIVILEGE_CONTAINER = "PrivilegeContainer" : */ - public static final String XML_ROOT_PRIVILEGE_CONTAINER = "PrivilegeContainer"; + public static final String XML_ROOT_PRIVILEGE = "Privilege"; + /** - * XML_ROOT_PRIVILEGE_ROLES = "PrivilegeRoles" : + * XML_CONTAINER = "Container" : */ - public static final String XML_ROOT_PRIVILEGE_ROLES = "PrivilegeRoles"; + public static final String XML_CONTAINER = "Container"; + /** - * XML_ROOT_PRIVILEGES = "Privileges" : + * XML_POLICIES = "Policies" : */ - public static final String XML_ROOT_PRIVILEGES = "Privileges"; + public static final String XML_POLICIES = "Policies"; + /** - * XML_ROOT_PRIVILEGE_USERS = "PrivilegesUsers" : + * XML_PRIVILEGES = "Privileges" : */ - public static final String XML_ROOT_PRIVILEGE_USERS = "PrivilegesUsers"; + public static final String XML_PRIVILEGES = "Privileges"; + /** - * XML_ROOT_PRIVILEGE_POLICIES = "PrivilegePolicies" : + * XML_ROOT_PRIVILEGE_USERS_AND_ROLES = "UsersAndRoles" : */ - public static final String XML_ROOT_PRIVILEGE_POLICIES = "PrivilegePolicies"; + public static final String XML_ROOT_PRIVILEGE_USERS_AND_ROLES = "UsersAndRoles"; /** * 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" : */ @@ -54,62 +60,72 @@ public class XmlConstants { * 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_USER = "User" */ public static final String XML_USER = "User"; - /** - * XML_PRIVILEGES = "Privileges" : - */ - public static final String XML_PRIVILEGES = "Privileges"; + /** * 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_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_SURNAME = "Surname" : */ public static final String XML_SURNAME = "Surname"; + /** * XML_STATE = "State" : */ public static final String XML_STATE = "State"; + /** * XML_LOCALE = "Locale" : */ @@ -119,26 +135,32 @@ public class XmlConstants { * XML_ATTR_CLASS = "class" : */ public static final String XML_ATTR_CLASS = "class"; + /** * 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_USERNAME = "username" : */ public static final String XML_ATTR_USERNAME = "username"; + /** * XML_ATTR_PASSWORD = "password" : */ @@ -148,22 +170,12 @@ public class XmlConstants { * XML_PARAM_HASH_ALGORITHM = "hashAlgorithm" : */ public static final String XML_PARAM_HASH_ALGORITHM = "hashAlgorithm"; + /** - * XML_PARAM_POLICY_FILE = "policyXmlFile" : + * XML_PARAM_MODEL_FILE = "modelXmlFile" : */ - public static final String XML_PARAM_POLICY_FILE = "policyXmlFile"; - /** - * XML_PARAM_ROLES_FILE = "rolesXmlFile" : - */ - public static final String XML_PARAM_ROLES_FILE = "rolesXmlFile"; - /** - * XML_PARAM_USERS_FILE = "usersXmlFile" : - */ - public static final String XML_PARAM_USERS_FILE = "usersXmlFile"; - /** - * XML_PARAM_PRIVILEGES_FILE = "privilegesXmlFile" : - */ - public static final String XML_PARAM_PRIVILEGES_FILE = "privilegesXmlFile"; + public static final String XML_PARAM_MODEL_FILE = "modelXmlFile"; + /** * XML_PARAM_BASE_PATH = "basePath" : */ diff --git a/src/ch/eitchnet/privilege/model/internal/Privilege.java b/src/ch/eitchnet/privilege/model/internal/Privilege.java index 7d6ca9b1c..40056ebab 100644 --- a/src/ch/eitchnet/privilege/model/internal/Privilege.java +++ b/src/ch/eitchnet/privilege/model/internal/Privilege.java @@ -15,6 +15,7 @@ import java.util.HashSet; import java.util.Set; import ch.eitchnet.privilege.handler.PrivilegeHandler; +import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.PrivilegeRep; import ch.eitchnet.privilege.model.Restrictable; import ch.eitchnet.privilege.policy.PrivilegePolicy; @@ -30,7 +31,10 @@ import ch.eitchnet.privilege.policy.PrivilegePolicy; * {@link Privilege}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

    + *

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

    * * @author rvonburg */ @@ -58,6 +62,20 @@ public final class Privilege { * a list of allow rules for this {@link Privilege} */ public Privilege(String name, String policy, boolean allAllowed, Set denyList, Set allowList) { + + if (name == null || name.isEmpty()) { + throw new PrivilegeException("No name defined!"); + } + if (policy == null || policy.isEmpty()) { + throw new PrivilegeException("No policy defined!"); + } + if (denyList == null) { + throw new PrivilegeException("No denyList defined!"); + } + if (allowList == null) { + throw new PrivilegeException("No allowList defined!"); + } + this.name = name; this.policy = policy; this.allAllowed = allAllowed; diff --git a/src/ch/eitchnet/privilege/model/internal/Role.java b/src/ch/eitchnet/privilege/model/internal/Role.java index a4788ea43..6d9205f34 100644 --- a/src/ch/eitchnet/privilege/model/internal/Role.java +++ b/src/ch/eitchnet/privilege/model/internal/Role.java @@ -14,6 +14,7 @@ import java.util.Collections; import java.util.HashSet; import java.util.Set; +import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.RoleRep; /** @@ -43,6 +44,14 @@ public final class Role { * a set of names of privileges granted to this role */ public Role(String name, Set privileges) { + + if (name == null || name.isEmpty()) { + throw new PrivilegeException("No name defined!"); + } + if (privileges == null) { + throw new PrivilegeException("No privileges defined!"); + } + this.name = name; this.privileges = Collections.unmodifiableSet(privileges); } diff --git a/src/ch/eitchnet/privilege/model/internal/Session.java b/src/ch/eitchnet/privilege/model/internal/Session.java index 14e1129be..7e0df5272 100644 --- a/src/ch/eitchnet/privilege/model/internal/Session.java +++ b/src/ch/eitchnet/privilege/model/internal/Session.java @@ -11,6 +11,7 @@ package ch.eitchnet.privilege.model.internal; import ch.eitchnet.privilege.handler.PrivilegeHandler; +import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.Certificate; /** @@ -59,6 +60,20 @@ public final class Session { * the time the user logged in */ public Session(String sessionId, String username, String authToken, String authPassword, long loginTime) { + + if (sessionId == null || sessionId.isEmpty()) { + throw new PrivilegeException("No sessionId defined!"); + } + if (username == null || username.isEmpty()) { + throw new PrivilegeException("No username defined!"); + } + if (authToken == null || authToken.isEmpty()) { + throw new PrivilegeException("No authToken defined!"); + } + if (authPassword == null || authPassword.isEmpty()) { + throw new PrivilegeException("No authPassword defined!"); + } + this.sessionId = sessionId; this.username = username; this.authToken = authToken; diff --git a/src/ch/eitchnet/privilege/model/internal/User.java b/src/ch/eitchnet/privilege/model/internal/User.java index 2118763e3..975a7e7ee 100644 --- a/src/ch/eitchnet/privilege/model/internal/User.java +++ b/src/ch/eitchnet/privilege/model/internal/User.java @@ -15,6 +15,7 @@ import java.util.HashSet; import java.util.Locale; import java.util.Set; +import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.UserRep; import ch.eitchnet.privilege.model.UserState; @@ -69,6 +70,29 @@ public final class User { public User(String userId, String username, String password, String firstname, String surname, UserState userState, Set roles, Locale locale) { + if (userId == null || userId.isEmpty()) { + throw new PrivilegeException("No UserId defined!"); + } + if (username == null || username.isEmpty()) { + throw new PrivilegeException("No username defined!"); + } + + // password may be null, meaning not able to login + + if (firstname == null || firstname.isEmpty()) { + throw new PrivilegeException("No firstname defined!"); + } + if (surname == null || surname.isEmpty()) { + throw new PrivilegeException("No surname defined!"); + } + if (userState == null) { + throw new PrivilegeException("No userState defined!"); + } + + // roles may be null, meaning not able to login and must be added later + + // local may be null, meaning use system default + this.userId = userId; this.username = username; diff --git a/test/ch/eitchnet/privilege/test/PrivilegeTest.java b/test/ch/eitchnet/privilege/test/PrivilegeTest.java index ff800f009..f48579942 100644 --- a/test/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/test/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -55,7 +55,7 @@ public class PrivilegeTest { // initialize container String pwd = System.getProperty("user.dir"); - File privilegeContainerXmlFile = new File(pwd + "/config/PrivilegeContainer.xml"); + File privilegeContainerXmlFile = new File(pwd + "/config/Privilege.xml"); privilegeHandler = InitializationHelper.initializeFromXml(privilegeContainerXmlFile); } catch (Exception e) { logger.error(e, e); From d2dff36b4b4c7c8eddf373c99d0117c8dc450fc6 Mon Sep 17 00:00:00 2001 From: eitch Date: Thu, 28 Jul 2011 22:31:18 +0000 Subject: [PATCH 053/173] [Minor] exposed some fields of the DefaultPrivilegeHandler by setting them to protected --- .../handler/DefaultPrivilegeHandler.java | 23 +++++++++++++------ .../privilege/helper/XmlConstants.java | 1 + 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index a6e7de1b0..f84c10d15 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -57,27 +57,32 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { /** * log4j logger */ - private static final Logger logger = Logger.getLogger(DefaultPrivilegeHandler.class); + protected static final Logger logger = Logger.getLogger(DefaultPrivilegeHandler.class); /** * last assigned id for the {@link Session}s */ - private static long lastSessionId; + protected long lastSessionId; /** * Map keeping a reference to all active sessions with their certificates */ - private Map sessionMap; + protected Map sessionMap; /** * The persistence handler is used for getting objects and saving changes */ - private PersistenceHandler persistenceHandler; + protected PersistenceHandler persistenceHandler; /** * The encryption handler is used for generating hashes and tokens */ - private EncryptionHandler encryptionHandler; + protected EncryptionHandler encryptionHandler; + + /** + * flag to define if already initialized + */ + protected boolean initialized; /** * @see ch.eitchnet.privilege.handler.PrivilegeHandler#getPrivilege(java.lang.String) @@ -899,18 +904,22 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { public void initialize(Map parameterMap, EncryptionHandler encryptionHandler, PersistenceHandler persistenceHandler) { + if (this.initialized) + throw new PrivilegeException("Already initialized!"); + this.encryptionHandler = encryptionHandler; this.persistenceHandler = persistenceHandler; - lastSessionId = 0l; + this.lastSessionId = 0l; this.sessionMap = Collections.synchronizedMap(new HashMap()); + this.initialized = true; } /** * @return a new session id */ private synchronized String nextSessionId() { - return Long.toString(++lastSessionId % Long.MAX_VALUE); + return Long.toString(++this.lastSessionId % Long.MAX_VALUE); } /** diff --git a/src/ch/eitchnet/privilege/helper/XmlConstants.java b/src/ch/eitchnet/privilege/helper/XmlConstants.java index 6bfe82148..26bfb757b 100644 --- a/src/ch/eitchnet/privilege/helper/XmlConstants.java +++ b/src/ch/eitchnet/privilege/helper/XmlConstants.java @@ -16,6 +16,7 @@ package ch.eitchnet.privilege.helper; * @author rvonburg */ public class XmlConstants { + /** * XML_ROOT_PRIVILEGE_CONTAINER = "PrivilegeContainer" : */ From 7e8228530642136b5080c5e5d9ff77d55901a662 Mon Sep 17 00:00:00 2001 From: eitch Date: Fri, 29 Jul 2011 00:30:08 +0000 Subject: [PATCH 054/173] [Minor] logging of authentication attempts --- .../handler/DefaultPrivilegeHandler.java | 97 ++++++++++--------- 1 file changed, 53 insertions(+), 44 deletions(-) diff --git a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index f84c10d15..2aaa5ecab 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -608,53 +608,62 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { @Override public Certificate authenticate(String username, String password) { - // both username and password must at least have 3 characters in length - if (username == null || username.length() < 3) - throw new PrivilegeException("The given username is shorter than 3 characters"); - else if (password == null || password.length() < 3) - throw new PrivilegeException("The given password is shorter than 3 characters"); - - // 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) - throw new AccessDeniedException("There is no user defined with the credentials: " + username + " / ***..."); - - // validate password - String pwHash = user.getPassword(); - if (pwHash == null) - throw new AccessDeniedException("User has no password and may not login!"); - if (!pwHash.equals(passwordHash)) - throw new AccessDeniedException("Password is incorrect for " + username + " / ***..."); - - // validate if user is allowed to login - if (user.getUserState() != UserState.ENABLED) - throw new AccessDeniedException("User " + username + " is not ENABLED. State is: " + user.getUserState()); - - // validate user has at least one role - if (user.getRoles().isEmpty()) { - throw new PrivilegeException("User " + username + " does not have any roles defined!"); - } - - // get 2 auth tokens - String authToken = this.encryptionHandler.nextToken(); - String authPassword = this.encryptionHandler.nextToken(); - - // get next session id - String sessionId = nextSessionId(); - // create certificate - Certificate certificate = new Certificate(sessionId, username, authToken, authPassword, user.getLocale()); + Certificate certificate; + try { + // both username and password must at least have 3 characters in length + if (username == null || username.length() < 3) + throw new PrivilegeException("The given username is shorter than 3 characters"); + else if (password == null || password.length() < 3) + throw new PrivilegeException("The given password is shorter than 3 characters"); - // create and save a new session - Session session = new Session(sessionId, username, authToken, authPassword, System.currentTimeMillis()); - this.sessionMap.put(sessionId, new CertificateSessionPair(session, certificate)); + // we only work with hashed passwords + String passwordHash = this.encryptionHandler.convertToHash(password); - // log - logger.info("Authenticated: " + session); + // get user object + User user = this.persistenceHandler.getUser(username); + // no user means no authentication + if (user == null) + throw new AccessDeniedException("There is no user defined with the credentials: " + username + + " / ***..."); + + // validate password + String pwHash = user.getPassword(); + if (pwHash == null) + throw new AccessDeniedException("User has no password and may not login!"); + if (!pwHash.equals(passwordHash)) + throw new AccessDeniedException("Password is incorrect for " + username + " / ***..."); + + // validate if user is allowed to login + if (user.getUserState() != UserState.ENABLED) + throw new AccessDeniedException("User " + username + " is not ENABLED. State is: " + + user.getUserState()); + + // validate user has at least one role + if (user.getRoles().isEmpty()) { + throw new PrivilegeException("User " + username + " does not have any roles defined!"); + } + + // get 2 auth tokens + String authToken = this.encryptionHandler.nextToken(); + String authPassword = this.encryptionHandler.nextToken(); + + // get next session id + String sessionId = nextSessionId(); + + certificate = new Certificate(sessionId, username, authToken, authPassword, user.getLocale()); + + // create and save a new session + Session session = new Session(sessionId, username, authToken, authPassword, System.currentTimeMillis()); + this.sessionMap.put(sessionId, new CertificateSessionPair(session, certificate)); + + // log + logger.info("User " + username + " authenticated: " + session); + + } catch (RuntimeException e) { + logger.error("User " + username + " Failed to authenticate: " + e.getLocalizedMessage()); + throw e; + } // return the certificate return certificate; From 20a696918bab35e86468b0b63940baac2b4b5643 Mon Sep 17 00:00:00 2001 From: eitch Date: Fri, 29 Jul 2011 19:51:00 +0000 Subject: [PATCH 055/173] --- .../eitchnet/privilege/helper/HashHelper.java | 22 +++- .../privilege/helper/PasswordCreaterUI.java | 119 ++++++++++++++++++ 2 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 src/ch/eitchnet/privilege/helper/PasswordCreaterUI.java diff --git a/src/ch/eitchnet/privilege/helper/HashHelper.java b/src/ch/eitchnet/privilege/helper/HashHelper.java index cbf6ae8f3..47ed49dc1 100644 --- a/src/ch/eitchnet/privilege/helper/HashHelper.java +++ b/src/ch/eitchnet/privilege/helper/HashHelper.java @@ -45,9 +45,29 @@ public class HashHelper { */ public static String stringToHash(String hashAlgorithm, String string) throws NoSuchAlgorithmException, UnsupportedEncodingException { + return stringToHash(hashAlgorithm, string.getBytes()); + } + + /** + * Creates the hash of the given string using {@link MessageDigest} and the defined hash algorithm + * + * @param hashAlgorithm + * the algorithm to use for hashing + * @param bytes + * the bytes to hash + * + * @return a new string encrypted by the defined algorithm + * + * @throws NoSuchAlgorithmException + * if the algorithm is not found + * @throws UnsupportedEncodingException + * if something is wrong with the given string to hash + */ + public static String stringToHash(String hashAlgorithm, byte[] bytes) throws NoSuchAlgorithmException, + UnsupportedEncodingException { MessageDigest digest = MessageDigest.getInstance(hashAlgorithm); - byte[] hashArray = digest.digest(string.getBytes()); + byte[] hashArray = digest.digest(bytes); byte[] hex = new byte[2 * hashArray.length]; int index = 0; diff --git a/src/ch/eitchnet/privilege/helper/PasswordCreaterUI.java b/src/ch/eitchnet/privilege/helper/PasswordCreaterUI.java new file mode 100644 index 000000000..7ea816207 --- /dev/null +++ b/src/ch/eitchnet/privilege/helper/PasswordCreaterUI.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2010 + * + * Apixxo AG + * Hauptgasse 25 + * 4600 Olten + * + * All rights reserved. + * + */ + +/** + * + */ +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; + +/** + * Simple Swing UI to create passwords + * + * @author rvonburg + */ +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 = HashHelper.stringToHash(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); + } +} From 65c17ab4a31123680f198ca7218b97bc208bced7 Mon Sep 17 00:00:00 2001 From: eitch Date: Fri, 29 Jul 2011 19:52:44 +0000 Subject: [PATCH 056/173] [New] added a PasswordCreaterUI with which hashes of passwords can be created --- src/ch/eitchnet/privilege/helper/PasswordCreaterUI.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ch/eitchnet/privilege/helper/PasswordCreaterUI.java b/src/ch/eitchnet/privilege/helper/PasswordCreaterUI.java index 7ea816207..81690984a 100644 --- a/src/ch/eitchnet/privilege/helper/PasswordCreaterUI.java +++ b/src/ch/eitchnet/privilege/helper/PasswordCreaterUI.java @@ -116,4 +116,4 @@ public class PasswordCreaterUI { frame.setVisible(true); } -} +} \ No newline at end of file From 8b9c8ea1e55721eabb706478b110918eb66797c3 Mon Sep 17 00:00:00 2001 From: eitch Date: Sat, 30 Jul 2011 13:20:08 +0000 Subject: [PATCH 057/173] [Major] refactored the use of the Privilege class, now Roles have Privileges in a composition relationship --- config/PrivilegeModel.xml | 41 +- .../handler/DefaultPrivilegeHandler.java | 312 ++++++--------- .../privilege/handler/PersistenceHandler.java | 73 ++-- .../privilege/handler/PrivilegeHandler.java | 161 +------- .../handler/XmlPersistenceHandler.java | 363 ++++++++---------- .../helper/InitializationHelper.java | 8 +- .../privilege/model/PrivilegeRep.java | 43 +++ src/ch/eitchnet/privilege/model/RoleRep.java | 26 +- .../privilege/model/internal/Privilege.java | 98 ++--- .../privilege/model/internal/Role.java | 69 ++-- .../privilege/model/internal/User.java | 31 ++ .../privilege/test/PrivilegeTest.java | 88 +++-- .../privilege/test/TestRestrictable.java | 2 +- 13 files changed, 556 insertions(+), 759 deletions(-) diff --git a/config/PrivilegeModel.xml b/config/PrivilegeModel.xml index 429e3a2f8..493476d57 100644 --- a/config/PrivilegeModel.xml +++ b/config/PrivilegeModel.xml @@ -1,31 +1,16 @@ - - - true - - - - - - false - - ch.eitchnet.privilege.test.TestRestrictable - - - - - Robert - von Burg + + Featherlite + Administrator ENABLED en_GB PrivilegeAdmin - admin - serviceExecutor + FeatherliteUser @@ -35,14 +20,16 @@ - - - - - - - - + + + true + + + true + + + true + diff --git a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 2aaa5ecab..f92df13c7 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -19,6 +19,7 @@ import java.util.Set; import org.apache.log4j.Logger; +import ch.eitchnet.privilege.helper.ClassHelper; import ch.eitchnet.privilege.i18n.AccessDeniedException; import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.Certificate; @@ -64,6 +65,11 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { */ protected long lastSessionId; + /** + * Map of {@link PrivilegePolicy} classes + */ + private Map> policyMap; + /** * Map keeping a reference to all active sessions with their certificates */ @@ -84,20 +90,15 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { */ protected boolean initialized; - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#getPrivilege(java.lang.String) - */ - @Override - public PrivilegeRep getPrivilege(String privilegeName) { - return this.persistenceHandler.getPrivilege(privilegeName).asPrivilegeRep(); - } - /** * @see ch.eitchnet.privilege.handler.PrivilegeHandler#getRole(java.lang.String) */ @Override public RoleRep getRole(String roleName) { - return this.persistenceHandler.getRole(roleName).asRoleRep(); + Role role = this.persistenceHandler.getRole(roleName); + if (role == null) + throw new PrivilegeException("Role " + roleName + " does not exist!"); + return role.asRoleRep(); } /** @@ -105,33 +106,45 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { */ @Override public UserRep getUser(String username) { - return this.persistenceHandler.getUser(username).asUserRep(); + User user = this.persistenceHandler.getUser(username); + if (user == null) + throw new PrivilegeException("User " + username + " does not exist!"); + return user.asUserRep(); } /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#getPolicy(java.lang.String) + *

    + * 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 */ - @Override - public PrivilegePolicy getPolicy(String policyName) { - return this.persistenceHandler.getPolicy(policyName); - } + protected PrivilegePolicy getPolicy(String policyName) { - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#addOrReplacePrivilege(ch.eitchnet.privilege.model.Certificate, - * ch.eitchnet.privilege.model.PrivilegeRep) - */ - @Override - public void addOrReplacePrivilege(Certificate certificate, PrivilegeRep privilegeRep) { + // get the policies class + Class policyClazz = this.policyMap.get(policyName); + if (policyClazz == null) { + return null; + } - // validate who is doing this - validateIsPrivilegeAdmin(certificate); + // instantiate the policy + PrivilegePolicy policy; + try { + policy = ClassHelper.instantiateClass(policyClazz); + } catch (Exception e) { + throw new PrivilegeException("The class for the policy with the name " + policyName + " does not exist!" + + policyName, e); + } - // create a new privilege - Privilege privilege = new Privilege(privilegeRep.getName(), privilegeRep.getPolicy(), - privilegeRep.isAllAllowed(), privilegeRep.getDenyList(), privilegeRep.getAllowList()); - - // delegate to persistence handler - this.persistenceHandler.addOrReplacePrivilege(privilege); + return policy; } /** @@ -144,8 +157,11 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // validate who is doing this validateIsPrivilegeAdmin(certificate); - // create new role - Role role = new Role(roleRep.getName(), roleRep.getPrivileges()); + // create new role from RoleRep + Role role = new Role(roleRep); + + // validate policy if not null + validatePolicies(role); // delegate to persistence handler this.persistenceHandler.addOrReplaceRole(role); @@ -180,39 +196,37 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#addPrivilegeToRole(ch.eitchnet.privilege.model.Certificate, - * java.lang.String, java.lang.String) + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#addOrReplacePrivilegeOnRole(ch.eitchnet.privilege.model.Certificate, + * java.lang.String, ch.eitchnet.privilege.model.PrivilegeRep) */ @Override - public void addPrivilegeToRole(Certificate certificate, String roleName, String privilegeName) { + public void addOrReplacePrivilegeOnRole(Certificate certificate, String roleName, PrivilegeRep privilegeRep) { // validate who is doing this validateIsPrivilegeAdmin(certificate); + // validate PrivilegeRep + privilegeRep.validate(); + // get role Role role = this.persistenceHandler.getRole(roleName); if (role == null) { throw new PrivilegeException("Role " + roleName + " does not exist!"); } - // ignore if role already has this privilege - Set currentPrivileges = role.getPrivileges(); - if (currentPrivileges.contains(roleName)) { - logger.error("Role " + roleName + " already has privilege " + privilegeName); - return; - } - - // validate that privilege exists - if (getPrivilege(privilegeName) == null) { - throw new PrivilegeException("Privilege " + privilegeName + " does not exist and can not be added to role " - + roleName); + // validate that policy exists if needed + String policy = privilegeRep.getPolicy(); + if (policy != null && !this.policyMap.containsKey(policy)) { + throw new PrivilegeException("Policy " + policy + " for Privilege " + privilegeRep.getName() + + " does not exist"); } // create new role with the additional privilege - Set newPrivileges = new HashSet(currentPrivileges); - newPrivileges.add(roleName); + Privilege newPrivilege = new Privilege(privilegeRep); + Map privilegeMap = new HashMap(role.getPrivilegeMap()); + privilegeMap.put(newPrivilege.getName(), newPrivilege); - Role newRole = new Role(role.getName(), newPrivileges); + Role newRole = new Role(role.getName(), privilegeMap); // delegate role replacement to persistence handler this.persistenceHandler.addOrReplaceRole(newRole); @@ -257,26 +271,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { this.persistenceHandler.addOrReplaceUser(newUser); } - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#removePrivilege(ch.eitchnet.privilege.model.Certificate, - * java.lang.String) - */ - @Override - public PrivilegeRep removePrivilege(Certificate certificate, String privilegeName) { - - // validate who is doing this - validateIsPrivilegeAdmin(certificate); - - // delegate privilege removal to persistence handler - Privilege removedPrivilege = this.persistenceHandler.removePrivilege(privilegeName); - - if (removedPrivilege == null) - return null; - - // return privilege rep if it was removed - return removedPrivilege.asPrivilegeRep(); - } - /** * @see ch.eitchnet.privilege.handler.PrivilegeHandler#removePrivilegeFromRole(ch.eitchnet.privilege.model.Certificate, * java.lang.String, java.lang.String) @@ -294,15 +288,17 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } // ignore if role does not have privilege - Set currentPrivileges = role.getPrivileges(); - if (!currentPrivileges.contains(privilegeName)) { - logger.error("Role " + roleName + " doest not have privilege " + privilegeName); - return; + if (!role.hasPrivilege(privilegeName)) + throw new PrivilegeException("Role " + roleName + " does not have Privilege " + privilegeName); + + // create new set of privileges with out the to remove privilege + Map newPrivileges = new HashMap(role.getPrivilegeMap().size() - 1); + for (Privilege privilege : role.getPrivilegeMap().values()) { + if (!privilege.getName().equals(privilegeName)) + newPrivileges.put(privilege.getName(), privilege); } // create new role - Set newPrivileges = new HashSet(currentPrivileges); - newPrivileges.remove(privilegeName); Role newRole = new Role(role.getName(), newPrivileges); // delegate user replacement to persistence handler @@ -384,115 +380,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setPrivilegeAllAllowed(ch.eitchnet.privilege.model.Certificate, - * java.lang.String, boolean) - */ - @Override - public void setPrivilegeAllAllowed(Certificate certificate, String privilegeName, boolean allAllowed) { - - // validate who is doing this - validateIsPrivilegeAdmin(certificate); - - // get Privilege - Privilege privilege = this.persistenceHandler.getPrivilege(privilegeName); - if (privilege == null) { - throw new PrivilegeException("Privilege " + privilegeName + " does not exist!"); - } - - // ignore if privilege is already set to argument - if (privilege.isAllAllowed() == allAllowed) { - logger.error("Privilege " + privilegeName + " is already set to " - + (allAllowed ? "all allowed" : "not all allowed")); - return; - } - - // create new privilege - Privilege newPrivilege = new Privilege(privilege.getName(), privilege.getPolicy(), allAllowed, - privilege.getDenyList(), privilege.getAllowList()); - - // delegate privilege replacement to persistence handler - this.persistenceHandler.addOrReplacePrivilege(newPrivilege); - } - - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setPrivilegeAllowList(ch.eitchnet.privilege.model.Certificate, - * java.lang.String, java.util.Set) - */ - @Override - public void setPrivilegeAllowList(Certificate certificate, String privilegeName, Set allowList) { - - // validate who is doing this - validateIsPrivilegeAdmin(certificate); - - // get Privilege - Privilege privilege = this.persistenceHandler.getPrivilege(privilegeName); - if (privilege == null) { - throw new PrivilegeException("Privilege " + privilegeName + " does not exist!"); - } - - // create new privilege - Privilege newPrivilege = new Privilege(privilege.getName(), privilege.getPolicy(), privilege.isAllAllowed(), - privilege.getDenyList(), allowList); - - // delegate privilege replacement to persistence handler - this.persistenceHandler.addOrReplacePrivilege(newPrivilege); - } - - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setPrivilegeDenyList(ch.eitchnet.privilege.model.Certificate, - * java.lang.String, java.util.Set) - */ - @Override - public void setPrivilegeDenyList(Certificate certificate, String privilegeName, Set denyList) { - - // validate who is doing this - validateIsPrivilegeAdmin(certificate); - - // get Privilege - Privilege privilege = this.persistenceHandler.getPrivilege(privilegeName); - if (privilege == null) { - throw new PrivilegeException("Privilege " + privilegeName + " does not exist!"); - } - - // create new privilege - Privilege newPrivilege = new Privilege(privilege.getName(), privilege.getPolicy(), privilege.isAllAllowed(), - denyList, privilege.getAllowList()); - - // delegate privilege replacement to persistence handler - this.persistenceHandler.addOrReplacePrivilege(newPrivilege); - } - - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setPrivilegePolicy(ch.eitchnet.privilege.model.Certificate, - * java.lang.String, java.lang.String) - */ - @Override - public void setPrivilegePolicy(Certificate certificate, String privilegeName, String policyName) { - - // validate who is doing this - validateIsPrivilegeAdmin(certificate); - - // get Privilege - Privilege privilege = this.persistenceHandler.getPrivilege(privilegeName); - if (privilege == null) { - throw new PrivilegeException("Privilege " + privilegeName + " does not exist!"); - } - - // validate that the policy exists - PrivilegePolicy policy = this.persistenceHandler.getPolicy(policyName); - if (policy == null) { - throw new PrivilegeException("No privilege policy exists for the name " + policyName); - } - - // create new privilege - Privilege newPrivilege = new Privilege(privilege.getName(), policyName, privilege.isAllAllowed(), - privilege.getDenyList(), privilege.getAllowList()); - - // delegate privilege replacement to persistence handler - this.persistenceHandler.addOrReplacePrivilege(newPrivilege); - } - /** * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setUserLocale(ch.eitchnet.privilege.model.Certificate, * java.lang.String, java.util.Locale) @@ -706,9 +593,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // first validate certificate if (!isCertificateValid(certificate)) { - logger.info("Certificate is not valid, so action is not allowed: " + certificate + " for restrictable: " - + restrictable); - return false; + throw new PrivilegeException("Certificate is not valid, so action is not allowed: " + certificate + + " for restrictable: " + restrictable); } // restrictable must not be null @@ -797,13 +683,13 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } // get the privilege for this restrictable - Privilege privilege = this.persistenceHandler.getPrivilege(privilegeName); - if (privilege == null) { - throw new PrivilegeException("No Privilege exists with the name " + privilegeName + " for Restrictable " - + restrictable.getClass().getName()); - } + Privilege privilege = role.getPrivilegeMap().get(privilegeName); - // get the policy configured for this privilege + // check if all is allowed + if (privilege.isAllAllowed()) + return true; + + // otherwise delegate checking to the policy configured for this privilege PrivilegePolicy policy = this.getPolicy(privilege.getPolicy()); if (policy == null) { throw new PrivilegeException("PrivilegePolicy " + privilege.getPolicy() + " does not exist for Privilege " @@ -906,24 +792,58 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#initialize(java.util.Map, - * ch.eitchnet.privilege.handler.EncryptionHandler, ch.eitchnet.privilege.handler.PersistenceHandler) + * 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 */ - @Override public void initialize(Map parameterMap, EncryptionHandler encryptionHandler, - PersistenceHandler persistenceHandler) { + PersistenceHandler persistenceHandler, Map> policyMap) { if (this.initialized) throw new PrivilegeException("Already initialized!"); + this.policyMap = policyMap; this.encryptionHandler = encryptionHandler; this.persistenceHandler = persistenceHandler; + // validate policies on privileges of Roles + for (Role role : persistenceHandler.getAllRoles()) { + validatePolicies(role); + } + this.lastSessionId = 0l; this.sessionMap = Collections.synchronizedMap(new HashMap()); this.initialized = true; } + /** + * 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 (Privilege privilege : role.getPrivilegeMap().values()) { + String policy = privilege.getPolicy(); + if (policy != null && !this.policyMap.containsKey(policy)) { + throw new PrivilegeException("Policy " + policy + " for Privilege " + privilege.getName() + + " does not exist on role " + role); + } + } + } + /** * @return a new session id */ diff --git a/src/ch/eitchnet/privilege/handler/PersistenceHandler.java b/src/ch/eitchnet/privilege/handler/PersistenceHandler.java index 13531f834..65026f2f0 100644 --- a/src/ch/eitchnet/privilege/handler/PersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/PersistenceHandler.java @@ -10,9 +10,9 @@ package ch.eitchnet.privilege.handler; +import java.util.List; import java.util.Map; -import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.Restrictable; import ch.eitchnet.privilege.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.Role; @@ -36,6 +36,20 @@ import ch.eitchnet.privilege.policy.PrivilegePolicy; */ 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 * @@ -56,33 +70,6 @@ public interface PersistenceHandler { */ public Role getRole(String roleName); - /** - * Returns a {@link Privilege} object from the underlying database - * - * @param privilegeName - * the name/id of the {@link Privilege} object to return - * - * @return the {@link Privilege} object, or null if it was not found - */ - public Privilege getPrivilege(String privilegeName); - - /** - *

    - * 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 - */ - public PrivilegePolicy getPolicy(String policyName) throws PrivilegeException; - /** * Removes a {@link User} with the given name and returns the removed object if it existed * @@ -103,16 +90,6 @@ public interface PersistenceHandler { */ public Role removeRole(String roleName); - /** - * Removes a {@link Privilege} with the given name and returns the removed object if it existed - * - * @param privilegeName - * the name of the {@link Privilege} to remove - * - * @return the {@link Privilege} removed, or null if it did not exist - */ - public Privilege removePrivilege(String privilegeName); - /** * Adds a {@link User} object to the underlying database. If the {@link User} already exists, it is replaced * @@ -129,15 +106,6 @@ public interface PersistenceHandler { */ public void addOrReplaceRole(Role role); - /** - * Adds a {@link Privilege} object to the underlying database. If the {@link Privilege} already exists, it is - * replaced - * - * @param privilege - * the {@link Privilege} object to add - */ - public void addOrReplacePrivilege(Privilege privilege); - /** * Informs this {@link PersistenceHandler} to persist any changes which need to be saved * @@ -145,14 +113,19 @@ public interface PersistenceHandler { */ 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 - * @param policyMap - * map of policy key/policy class pairs */ - public void initialize(Map parameterMap, Map> policyMap); + public void initialize(Map parameterMap); } diff --git a/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java b/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java index 84de5495e..49608c42d 100644 --- a/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java +++ b/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java @@ -10,10 +10,7 @@ package ch.eitchnet.privilege.handler; -import java.util.List; import java.util.Locale; -import java.util.Map; -import java.util.Set; import ch.eitchnet.privilege.i18n.AccessDeniedException; import ch.eitchnet.privilege.i18n.PrivilegeException; @@ -27,7 +24,6 @@ import ch.eitchnet.privilege.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.Role; import ch.eitchnet.privilege.model.internal.Session; 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 @@ -64,26 +60,6 @@ public interface PrivilegeHandler { */ public RoleRep getRole(String roleName); - /** - * Returns a {@link PrivilegeRep} for the given privilegeName - * - * @param privilegeName - * the name of the {@link PrivilegeRep} to return - * - * @return the {@link PrivilegeRep} for the given privilegeName, or null if it was not found - */ - public PrivilegeRep getPrivilege(String privilegeName); - - /** - * Returns a {@link PrivilegePolicy} for the given policyName - * - * @param policyName - * the name of the {@link PrivilegePolicy} to return - * - * @return the {@link PrivilegePolicy} for the given policyName, or null if it was not found - */ - public PrivilegePolicy getPolicy(String policyName); - /** * Removes the user with the given username * @@ -156,24 +132,6 @@ public interface PrivilegeHandler { public void removePrivilegeFromRole(Certificate certificate, String roleName, String privilegeName) throws AccessDeniedException, PrivilegeException; - /** - * Removes the privilege with the given privilegeName - * - * @param certificate - * the {@link Certificate} of the user which has the privilege to perform this action - * @param privilegeName - * the privilegeName of the privilege to remove - * - * @return the {@link PrivilegeRep} of the privilege removed, or null if the privilege 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 PrivilegeRep removePrivilege(Certificate certificate, String privilegeName) throws AccessDeniedException, - PrivilegeException; - /** *

    * Adds a new user, or replaces the user with the information from this {@link UserRep} if the user already exists @@ -217,23 +175,6 @@ public interface PrivilegeHandler { public void addOrReplaceRole(Certificate certificate, RoleRep roleRep) throws AccessDeniedException, PrivilegeException; - /** - * Adds a new privilege, or replaces the privilege with the information from this {@link PrivilegeRep} if the - * privilege already exists - * - * @param certificate - * the {@link Certificate} of the user which has the privilege to perform this action - * @param privilegeRep - * the {@link PrivilegeRep} containing the information to create the new {@link Privilege} - * - * @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 addOrReplacePrivilege(Certificate certificate, PrivilegeRep privilegeRep) throws AccessDeniedException, - PrivilegeException; - /** * Adds the role with the given roleName to the {@link User} with the given username * @@ -253,21 +194,21 @@ public interface PrivilegeHandler { PrivilegeException; /** - * Adds the privilege with the given privilegeName to the {@link Role} with the given roleName + * Adds the {@link PrivilegeRep} to the {@link 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 {@link Role} to which the privilege should be added - * @param privilegeName - * the privilegeName of the {@link Privilege} which should be added to the {@link Role} + * @param privilegeRep + * the representation of the {@link Privilege} which should be added to 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 */ - public void addPrivilegeToRole(Certificate certificate, String roleName, String privilegeName) + public void addOrReplacePrivilegeOnRole(Certificate certificate, String roleName, PrivilegeRep privilegeRep) throws AccessDeniedException, PrivilegeException; /** @@ -349,82 +290,6 @@ public interface PrivilegeHandler { public void setUserLocale(Certificate certificate, String username, Locale locale) throws AccessDeniedException, PrivilegeException; - /** - * Sets the class name of the {@link PrivilegePolicy} object for the {@link Privilege} registered for the given - * privilegeName - * - * @param certificate - * the {@link Certificate} of the user which has the privilege to perform this action - * @param privilegeName - * the name of the {@link Privilege} for which the policy is to be changed - * @param policyName - * class name of the {@link PrivilegePolicy} to be registered for this {@link Privilege} - * - * @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 class name of the policy is invalid - */ - public void setPrivilegePolicy(Certificate certificate, String privilegeName, String policyName) - throws AccessDeniedException, PrivilegeException; - - /** - * Sets the special flag on the given {@link Privilege} defined by the privilegeName argument so that all is allowed - * and thus the allow and deny list of the privilege are ignored - * - * @param certificate - * the {@link Certificate} of the user which has the privilege to perform this action - * @param privilegeName - * the name of the {@link Privilege} for which the allAllowed value is to be changed - * @param allAllowed - * the boolean defining if all privileges are granted for the value true, or not for the value false - * - * @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 setPrivilegeAllAllowed(Certificate certificate, String privilegeName, boolean allAllowed) - throws AccessDeniedException, PrivilegeException; - - /** - * Sets the {@link List} of strings which represent values which denies finer grained privilege rights for the - * {@link Privilege} defined by the privilegeName argument - * - * @param certificate - * the {@link Certificate} of the user which has the privilege to perform this action - * @param privilegeName - * the name of the {@link Privilege} for which the deny list is to be changed - * @param denyList - * the {@link List} of strings which represent values which denies finer grained privilege rights - * - * @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 setPrivilegeDenyList(Certificate certificate, String privilegeName, Set denyList) - throws AccessDeniedException, PrivilegeException; - - /** - * Sets the {@link List} of strings which represent values which grants finer grained privilege rights for the - * {@link Privilege} defined by the privilegeName argument - * - * @param certificate - * the {@link Certificate} of the user which has the privilege to perform this action - * @param privilegeName - * the name of the {@link Privilege} for which the allow list is to be changed - * @param allowList - * the {@link List} of strings which represent values which grants finer grained privilege rights - * - * @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 setPrivilegeAllowList(Certificate certificate, String privilegeName, Set allowList) - 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 @@ -548,22 +413,4 @@ public interface PrivilegeHandler { * if the users of the given certificate does not have the privilege to perform this action */ public boolean persist(Certificate certificate) throws AccessDeniedException; - - /** - * 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} - * - * @throws PrivilegeException - * if the this method is called multiple times or an initialization exception occurs - */ - public void initialize(Map parameterMap, EncryptionHandler encryptionHandler, - PersistenceHandler persistenceHandler) throws PrivilegeException; } diff --git a/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java b/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java index 86c9dd6ae..8d681976f 100644 --- a/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java @@ -15,6 +15,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; @@ -24,7 +25,6 @@ import org.apache.log4j.Logger; import org.dom4j.DocumentFactory; import org.dom4j.Element; -import ch.eitchnet.privilege.helper.ClassHelper; import ch.eitchnet.privilege.helper.XmlConstants; import ch.eitchnet.privilege.helper.XmlHelper; import ch.eitchnet.privilege.i18n.PrivilegeException; @@ -32,11 +32,10 @@ import ch.eitchnet.privilege.model.UserState; import ch.eitchnet.privilege.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.Role; import ch.eitchnet.privilege.model.internal.User; -import ch.eitchnet.privilege.policy.PrivilegePolicy; /** * {@link PersistenceHandler} implementation which reads the configuration from XML files. These configuration is passed - * in {@link #initialize(Map, Map)} + * in {@link #initialize(Map)} * * @author rvonburg */ @@ -46,44 +45,59 @@ public class XmlPersistenceHandler implements PersistenceHandler { private Map userMap; private Map roleMap; - private Map privilegeMap; - private Map> policyMap; private long modelsFileDate; private boolean userMapDirty; private boolean roleMapDirty; - private boolean privilegeMapDirty; private Map parameterMap; - private String basePath; + private String modelPath; /** - * @see ch.eitchnet.privilege.handler.PersistenceHandler#addOrReplacePrivilege(ch.eitchnet.privilege.model.internal.Privilege) + * @see ch.eitchnet.privilege.handler.PersistenceHandler#getAllUsers() */ @Override - public void addOrReplacePrivilege(Privilege privilege) { - this.privilegeMap.put(privilege.getName(), privilege); - this.privilegeMapDirty = true; + public List getAllUsers() { + synchronized (this.userMap) { + return new LinkedList(this.userMap.values()); + } } /** - * @see ch.eitchnet.privilege.handler.PersistenceHandler#removePrivilege(java.lang.String) + * @see ch.eitchnet.privilege.handler.PersistenceHandler#getAllRoles() */ @Override - public Privilege removePrivilege(String privilegeName) { - Privilege privilege = this.privilegeMap.remove(privilegeName); - this.privilegeMapDirty = privilege != null; - return privilege; + public List getAllRoles() { + synchronized (this.roleMap) { + return new LinkedList(this.roleMap.values()); + } } /** - * @see ch.eitchnet.privilege.handler.PersistenceHandler#addOrReplaceRole(ch.eitchnet.privilege.model.internal.Role) + * @see ch.eitchnet.privilege.handler.PersistenceHandler#getUser(java.lang.String) */ @Override - public void addOrReplaceRole(Role role) { - this.roleMap.put(role.getName(), role); - this.roleMapDirty = true; + public User getUser(String username) { + return this.userMap.get(username); + } + + /** + * @see ch.eitchnet.privilege.handler.PersistenceHandler#getRole(java.lang.String) + */ + @Override + public Role getRole(String roleName) { + return this.roleMap.get(roleName); + } + + /** + * @see ch.eitchnet.privilege.handler.PersistenceHandler#removeUser(java.lang.String) + */ + @Override + public User removeUser(String username) { + User user = this.userMap.remove(username); + this.userMapDirty = user != null; + return user; } /** @@ -106,61 +120,12 @@ public class XmlPersistenceHandler implements PersistenceHandler { } /** - * @see ch.eitchnet.privilege.handler.PersistenceHandler#removeUser(java.lang.String) + * @see ch.eitchnet.privilege.handler.PersistenceHandler#addOrReplaceRole(ch.eitchnet.privilege.model.internal.Role) */ @Override - public User removeUser(String username) { - User user = this.userMap.remove(username); - this.userMapDirty = user != null; - return user; - } - - /** - * @see ch.eitchnet.privilege.handler.PersistenceHandler#getPrivilege(java.lang.String) - */ - @Override - public Privilege getPrivilege(String privilegeName) { - return this.privilegeMap.get(privilegeName); - } - - /** - * @see ch.eitchnet.privilege.handler.PersistenceHandler#getRole(java.lang.String) - */ - @Override - public Role getRole(String roleName) { - return this.roleMap.get(roleName); - } - - /** - * @see ch.eitchnet.privilege.handler.PersistenceHandler#getUser(java.lang.String) - */ - @Override - public User getUser(String username) { - return this.userMap.get(username); - } - - /** - * @see ch.eitchnet.privilege.handler.PersistenceHandler#getPolicy(java.lang.String) - */ - @Override - public 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 = ClassHelper.instantiateClass(policyClazz); - } catch (Exception e) { - throw new PrivilegeException("The class for the policy with the name " + policyName + " does not exist!" - + policyName, e); - } - - return policy; + public void addOrReplaceRole(Role role) { + this.roleMap.put(role.getName(), role); + this.roleMapDirty = true; } /** @@ -176,9 +141,9 @@ public class XmlPersistenceHandler implements PersistenceHandler { + XmlConstants.XML_PARAM_MODEL_FILE + " is invalid"); } // get model file - File modelFile = new File(this.basePath + "/" + modelFileName); + File modelFile = new File(this.modelPath); boolean modelFileUnchanged = modelFile.exists() && modelFile.lastModified() == this.modelsFileDate; - if (!(modelFileUnchanged && this.privilegeMapDirty && this.roleMapDirty && this.userMapDirty)) { + if (!(modelFileUnchanged && this.roleMapDirty && this.userMapDirty)) { logger.warn("Not persisting as current file is unchanged and model data is not dirty"); return false; } @@ -206,20 +171,10 @@ public class XmlPersistenceHandler implements PersistenceHandler { } rootElement.add(rolesElement); - // PRIVILEGES - // build XML DOM of privileges - List privileges = toDomPrivileges(); - Element privilegesElement = docFactory.createElement(XmlConstants.XML_PRIVILEGES); - for (Element privilegeElement : privileges) { - privilegesElement.add(privilegeElement); - } - rootElement.add(privilegesElement); - // reset dirty states and return if something was dirty, false otherwise - if (this.userMapDirty || this.roleMapDirty || this.privilegeMapDirty) { + if (this.userMapDirty || this.roleMapDirty) { this.userMapDirty = false; this.roleMapDirty = false; - this.privilegeMapDirty = false; return true; @@ -227,45 +182,27 @@ public class XmlPersistenceHandler implements PersistenceHandler { this.userMapDirty = false; this.roleMapDirty = false; - this.privilegeMapDirty = false; return false; } /** - * @see ch.eitchnet.privilege.handler.EncryptionHandler#initialize(java.util.Map) + * @see ch.eitchnet.privilege.handler.PersistenceHandler#reload() */ @Override - public void initialize(Map parameterMap, Map> policyMap) { - - this.roleMap = Collections.synchronizedMap(new HashMap()); - this.userMap = Collections.synchronizedMap(new HashMap()); - this.privilegeMap = Collections.synchronizedMap(new HashMap()); - this.policyMap = policyMap; - - // get and validate base bath - this.basePath = parameterMap.get(XmlConstants.XML_PARAM_BASE_PATH); - File basePathF = new File(this.basePath); - if (!basePathF.exists() && !basePathF.isDirectory()) { - throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " - + XmlConstants.XML_PARAM_BASE_PATH + " is invalid"); - } - - // get model file name - String modelFileName = parameterMap.get(XmlConstants.XML_PARAM_MODEL_FILE); - if (modelFileName == null || modelFileName.isEmpty()) { - throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " - + XmlConstants.XML_PARAM_MODEL_FILE + " is invalid"); - } + public boolean reload() { // validate file exists - File modelsFile = new File(this.basePath + "/" + modelFileName); + File modelsFile = new File(this.modelPath); if (!modelsFile.exists()) { throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " + XmlConstants.XML_PARAM_MODEL_FILE + " is invalid as models file does not exist at path " + modelsFile.getAbsolutePath()); } + this.roleMap = Collections.synchronizedMap(new HashMap()); + this.userMap = Collections.synchronizedMap(new HashMap()); + // parse models xml file to XML document Element modelsRootElement = XmlHelper.parseDocument(modelsFile).getRootElement(); this.modelsFileDate = modelsFile.lastModified(); @@ -282,19 +219,11 @@ public class XmlPersistenceHandler implements PersistenceHandler { // read users readUsers(usersElement); - // PRIVILEGES - // get privileges element - Element privilegesElement = modelsRootElement.element(XmlConstants.XML_PRIVILEGES); - // read privileges - readPrivileges(privilegesElement); - this.userMapDirty = false; this.roleMapDirty = false; - this.privilegeMapDirty = false; logger.info("Read " + this.userMap.size() + " Users"); logger.info("Read " + this.roleMap.size() + " Roles"); - logger.info("Read " + this.privilegeMap.size() + " Privileges"); // validate we have a user with PrivilegeAdmin access boolean privilegeAdminExists = false; @@ -310,6 +239,36 @@ public class XmlPersistenceHandler implements PersistenceHandler { logger.warn("No User with role '" + PrivilegeHandler.PRIVILEGE_ADMIN_ROLE + "' exists. Privilege modifications will not be possible!"); } + + return true; + } + + /** + * @see ch.eitchnet.privilege.handler.PersistenceHandler#initialize(java.util.Map) + */ + @Override + public void initialize(Map parameterMap) { + + // get and validate base bath + String basePath = parameterMap.get(XmlConstants.XML_PARAM_BASE_PATH); + File basePathF = new File(basePath); + if (!basePathF.exists() && !basePathF.isDirectory()) { + throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " + + XmlConstants.XML_PARAM_BASE_PATH + " is invalid"); + } + + // get model file name + String modelFileName = parameterMap.get(XmlConstants.XML_PARAM_MODEL_FILE); + if (modelFileName == null || modelFileName.isEmpty()) { + throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " + + XmlConstants.XML_PARAM_MODEL_FILE + " is invalid"); + } + + // save path to model + this.modelPath = basePath + "/" + modelFileName; + + if (reload()) + logger.info("Privilege Data loaded."); } /** @@ -369,27 +328,23 @@ public class XmlPersistenceHandler implements PersistenceHandler { String roleName = roleElement.attributeValue(XmlConstants.XML_ATTR_NAME); - @SuppressWarnings("unchecked") - List privilegeElements = roleElement.elements(XmlConstants.XML_PRIVILEGE); - Set privileges = new HashSet(); - for (Element privilegeElement : privilegeElements) { + Map privilegeMap = readPrivileges(roleElement); - String privilegeName = privilegeElement.attributeValue(XmlConstants.XML_ATTR_NAME); - privileges.add(privilegeName); - } - - Role role = new Role(roleName, privileges); + Role role = new Role(roleName, privilegeMap); this.roleMap.put(roleName, role); } } /** - * @param rolesRootElement + * @param roleParentElement + * @return */ - private void readPrivileges(Element privilegesRootElement) { + private Map readPrivileges(Element roleParentElement) { + + Map privilegeMap = new HashMap(); @SuppressWarnings("unchecked") - List privilegeElements = privilegesRootElement.elements(XmlConstants.XML_PRIVILEGE); + List privilegeElements = roleParentElement.elements(XmlConstants.XML_PRIVILEGE); for (Element privilegeElement : privilegeElements) { String privilegeName = privilegeElement.attributeValue(XmlConstants.XML_ATTR_NAME); @@ -403,11 +358,8 @@ public class XmlPersistenceHandler implements PersistenceHandler { Set denyList = new HashSet(denyElements.size()); for (Element denyElement : denyElements) { String denyValue = denyElement.getTextTrim(); - if (denyValue.isEmpty()) { - logger.error("Privilege " + privilegeName + " has an empty deny value!"); - } else { + if (!denyValue.isEmpty()) denyList.add(denyValue); - } } @SuppressWarnings("unchecked") @@ -415,89 +367,15 @@ public class XmlPersistenceHandler implements PersistenceHandler { Set allowList = new HashSet(allowElements.size()); for (Element allowElement : allowElements) { String allowValue = allowElement.getTextTrim(); - if (allowValue.isEmpty()) { - logger.error("Privilege " + privilegeName + " has an empty allow value!"); - } else { + if (!allowValue.isEmpty()) allowList.add(allowValue); - } } Privilege privilege = new Privilege(privilegeName, privilegePolicy, allAllowed, denyList, allowList); - this.privilegeMap.put(privilegeName, privilege); - } - } - - private List toDomPrivileges() { - - List privilegesAsElements = new ArrayList(this.privilegeMap.size()); - - DocumentFactory documentFactory = DocumentFactory.getInstance(); - synchronized (this.privilegeMap) { - for (String privilegeName : this.privilegeMap.keySet()) { - - // get the privilege object - Privilege privilege = this.privilegeMap.get(privilegeName); - - // create the privilege element - Element privilegeElement = documentFactory.createElement(XmlConstants.XML_PRIVILEGE); - privilegeElement.addAttribute(XmlConstants.XML_ATTR_NAME, privilege.getName()); - privilegeElement.addAttribute(XmlConstants.XML_ATTR_POLICY, privilege.getPolicy()); - - // add the all allowed element - Element allAllowedElement = documentFactory.createElement(XmlConstants.XML_ALL_ALLOWED); - allAllowedElement.setText(Boolean.toString(privilege.isAllAllowed())); - privilegeElement.add(allAllowedElement); - - // add all the deny values - for (String denyValue : privilege.getDenyList()) { - Element denyValueElement = documentFactory.createElement(XmlConstants.XML_DENY); - denyValueElement.setText(denyValue); - privilegeElement.add(denyValueElement); - } - - // add all the allow values - for (String allowValue : privilege.getAllowList()) { - Element allowValueElement = documentFactory.createElement(XmlConstants.XML_ALLOW); - allowValueElement.setText(allowValue); - privilegeElement.add(allowValueElement); - } - - // add element to return list - privilegesAsElements.add(privilegeElement); - } + privilegeMap.put(privilegeName, privilege); } - return privilegesAsElements; - } - - private List toDomRoles() { - - List rolesAsElements = new ArrayList(this.roleMap.size()); - - DocumentFactory documentFactory = DocumentFactory.getInstance(); - synchronized (this.roleMap) { - for (String roleName : this.roleMap.keySet()) { - - // get the role object - Role role = this.roleMap.get(roleName); - - // create the role element - Element roleElement = documentFactory.createElement(XmlConstants.XML_ROLE); - roleElement.addAttribute(XmlConstants.XML_ATTR_NAME, role.getName()); - - // add all the privileges - for (String privilegeName : role.getPrivileges()) { - Element privilegeElement = documentFactory.createElement(XmlConstants.XML_PRIVILEGE); - privilegeElement.addAttribute(XmlConstants.XML_ATTR_NAME, privilegeName); - roleElement.add(privilegeElement); - } - - // add element to return list - rolesAsElements.add(roleElement); - } - } - - return rolesAsElements; + return privilegeMap; } private List toDomUsers() { @@ -505,6 +383,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { List usersAsElements = new ArrayList(this.userMap.size()); DocumentFactory documentFactory = DocumentFactory.getInstance(); + synchronized (this.userMap) { for (String userName : this.userMap.keySet()) { @@ -553,4 +432,66 @@ public class XmlPersistenceHandler implements PersistenceHandler { return usersAsElements; } + + private List toDomRoles() { + + List rolesAsElements = new ArrayList(this.roleMap.size()); + + DocumentFactory documentFactory = DocumentFactory.getInstance(); + + synchronized (this.roleMap) { + for (String roleName : this.roleMap.keySet()) { + + // get the role object + Role role = this.roleMap.get(roleName); + + // create the role element + Element roleElement = documentFactory.createElement(XmlConstants.XML_ROLE); + roleElement.addAttribute(XmlConstants.XML_ATTR_NAME, role.getName()); + + // add all the privileges + toDomPrivileges(roleElement, role.getPrivilegeMap()); + + // add element to return list + rolesAsElements.add(roleElement); + } + } + + return rolesAsElements; + } + + private void toDomPrivileges(Element roleParentElement, Map privilegeMap) { + + DocumentFactory documentFactory = DocumentFactory.getInstance(); + + for (Privilege privilege : privilegeMap.values()) { + + // create the privilege element + Element privilegeElement = documentFactory.createElement(XmlConstants.XML_PRIVILEGE); + privilegeElement.addAttribute(XmlConstants.XML_ATTR_NAME, privilege.getName()); + privilegeElement.addAttribute(XmlConstants.XML_ATTR_POLICY, privilege.getPolicy()); + + // add the all allowed element + Element allAllowedElement = documentFactory.createElement(XmlConstants.XML_ALL_ALLOWED); + allAllowedElement.setText(Boolean.toString(privilege.isAllAllowed())); + privilegeElement.add(allAllowedElement); + + // add all the deny values + for (String denyValue : privilege.getDenyList()) { + Element denyValueElement = documentFactory.createElement(XmlConstants.XML_DENY); + denyValueElement.setText(denyValue); + privilegeElement.add(denyValueElement); + } + + // add all the allow values + for (String allowValue : privilege.getAllowList()) { + Element allowValueElement = documentFactory.createElement(XmlConstants.XML_ALLOW); + allowValueElement.setText(allowValue); + privilegeElement.add(allowValueElement); + } + + // add element to parent + roleParentElement.add(privilegeElement); + } + } } diff --git a/src/ch/eitchnet/privilege/helper/InitializationHelper.java b/src/ch/eitchnet/privilege/helper/InitializationHelper.java index 5f7accc50..03c4a60dd 100644 --- a/src/ch/eitchnet/privilege/helper/InitializationHelper.java +++ b/src/ch/eitchnet/privilege/helper/InitializationHelper.java @@ -36,7 +36,7 @@ public class InitializationHelper { private static final Logger logger = Logger.getLogger(InitializationHelper.class); /** - * Initializes the {@link PrivilegeHandler} from the configuration file + * Initializes the {@link DefaultPrivilegeHandler} from the configuration file * * @param privilegeXmlFile * a {@link File} reference to the XML file containing the configuration for Privilege @@ -65,7 +65,7 @@ public class InitializationHelper { PersistenceHandler persistenceHandler = ClassHelper.instantiateClass(persistenceHandlerClassName); // instantiate privilege handler - PrivilegeHandler privilegeHandler = new DefaultPrivilegeHandler(); + DefaultPrivilegeHandler privilegeHandler = new DefaultPrivilegeHandler(); // get policies Element policiesElement = rootElement.element(XmlConstants.XML_POLICIES); @@ -93,7 +93,7 @@ public class InitializationHelper { Map parameterMap = convertToParameterMap(parameterElement); // initialize persistence handler - persistenceHandler.initialize(parameterMap, policyMap); + persistenceHandler.initialize(parameterMap); } catch (Exception e) { logger.error(e, e); @@ -108,7 +108,7 @@ public class InitializationHelper { Map parameterMap = convertToParameterMap(parameterElement); // initialize privilege handler - privilegeHandler.initialize(parameterMap, encryptionHandler, persistenceHandler); + privilegeHandler.initialize(parameterMap, encryptionHandler, persistenceHandler, policyMap); } catch (Exception e) { logger.error(e, e); diff --git a/src/ch/eitchnet/privilege/model/PrivilegeRep.java b/src/ch/eitchnet/privilege/model/PrivilegeRep.java index 5b92e4613..73c8727e4 100644 --- a/src/ch/eitchnet/privilege/model/PrivilegeRep.java +++ b/src/ch/eitchnet/privilege/model/PrivilegeRep.java @@ -14,6 +14,7 @@ import java.io.Serializable; import java.util.Set; import ch.eitchnet.privilege.handler.PrivilegeHandler; +import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.Role; import ch.eitchnet.privilege.policy.PrivilegePolicy; @@ -132,4 +133,46 @@ public class PrivilegeRep implements Serializable { public void setAllowList(Set allowList) { this.allowList = allowList; } + + /** + *

    + * Validates this {@link PrivilegeRep} so that a {@link Privilege} object can later be created from it + *

    + * + * TODO write comment on how validation is done + */ + public void validate() { + + if (this.name == null || this.name.isEmpty()) { + throw new PrivilegeException("No name defined!"); + } + + // if not all allowed, then validate that deny and allow lists are defined + if (this.allAllowed) { + + // all allowed means no policy will be used + if (this.policy != null && !this.policy.isEmpty()) { + throw new PrivilegeException("All is allowed, so Policy may not be set!"); + } + + if (this.denyList != null && !this.denyList.isEmpty()) + throw new PrivilegeException("All is allowed, so deny list must be null"); + if (this.allowList != null && !this.allowList.isEmpty()) + throw new PrivilegeException("All is allowed, so allow list must be null"); + + } else { + + // not all allowed, so policy must be set + if (this.policy == null || this.policy.isEmpty()) { + throw new PrivilegeException("All is not allowed, so Policy must be defined!"); + } + + if (this.denyList == null) { + throw new PrivilegeException("All is not allowed, so deny list must be set, even if empty"); + } + if (this.allowList == null) { + throw new PrivilegeException("All is not allowed, so allow list must be set, even if empty"); + } + } + } } diff --git a/src/ch/eitchnet/privilege/model/RoleRep.java b/src/ch/eitchnet/privilege/model/RoleRep.java index 019f1a408..fde568af3 100644 --- a/src/ch/eitchnet/privilege/model/RoleRep.java +++ b/src/ch/eitchnet/privilege/model/RoleRep.java @@ -11,7 +11,7 @@ package ch.eitchnet.privilege.model; import java.io.Serializable; -import java.util.Set; +import java.util.Map; import ch.eitchnet.privilege.model.internal.Role; @@ -27,19 +27,19 @@ public class RoleRep implements Serializable { private static final long serialVersionUID = 1L; private String name; - private Set privileges; + private Map privilegeMap; /** * Default constructor * * @param name * the name of this role - * @param privileges - * the set of privileges granted to this role + * @param privilegeMap + * the map of privileges granted to this role */ - public RoleRep(String name, Set privileges) { + public RoleRep(String name, Map privilegeMap) { this.name = name; - this.privileges = privileges; + this.privilegeMap = privilegeMap; } /** @@ -58,17 +58,17 @@ public class RoleRep implements Serializable { } /** - * @return the privileges + * @return the privilegeMap */ - public Set getPrivileges() { - return this.privileges; + public Map getPrivilegeMap() { + return this.privilegeMap; } /** - * @param privileges - * the privileges to set + * @param privilegeMap + * the privilegeMap to set */ - public void setPrivileges(Set privileges) { - this.privileges = privileges; + public void setPrivileges(Map privilegeMap) { + this.privilegeMap = privilegeMap; } } diff --git a/src/ch/eitchnet/privilege/model/internal/Privilege.java b/src/ch/eitchnet/privilege/model/internal/Privilege.java index 40056ebab..254df64dd 100644 --- a/src/ch/eitchnet/privilege/model/internal/Privilege.java +++ b/src/ch/eitchnet/privilege/model/internal/Privilege.java @@ -23,8 +23,9 @@ import ch.eitchnet.privilege.policy.PrivilegePolicy; /** *

    * {@link Privilege} 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. For every privilege a {@link PrivilegePolicy} is - * configured which is used to evaluate if privilege is granted to a {@link Restrictable} + * defines the privileges a logged in user with that role has. If the {@link Privilege} 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} *

    * *

    @@ -52,35 +53,64 @@ public final class Privilege { * @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 + * 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 Privilege} has unrestricted access to a - * {@link Restrictable} + * {@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 Privilege} + * a list of deny rules for this {@link Privilege}, can be null if all allowed * @param allowList - * a list of allow rules for this {@link Privilege} + * a list of allow rules for this {@link Privilege}, can be null if all allowed */ public Privilege(String name, String policy, boolean allAllowed, Set denyList, Set allowList) { if (name == null || name.isEmpty()) { throw new PrivilegeException("No name defined!"); } - if (policy == null || policy.isEmpty()) { - throw new PrivilegeException("No policy defined!"); - } - if (denyList == null) { - throw new PrivilegeException("No denyList defined!"); - } - if (allowList == null) { - throw new PrivilegeException("No allowList defined!"); - } - this.name = name; - this.policy = policy; - this.allAllowed = allAllowed; - this.denyList = Collections.unmodifiableSet(denyList); - this.allowList = Collections.unmodifiableSet(allowList); + + // if not all allowed, then validate that deny and allow lists are defined + if (allAllowed) { + + this.allAllowed = true; + + // all allowed means no policy will be used + this.policy = null; + + this.denyList = Collections.emptySet(); + this.allowList = Collections.emptySet(); + } else { + + this.allAllowed = false; + + // not all allowed, so policy must be set + if (policy == null || policy.isEmpty()) { + throw new PrivilegeException("No Policy defined!"); + } + this.policy = policy; + + if (denyList == null) { + throw new PrivilegeException("No denyList defined!"); + } + this.denyList = Collections.unmodifiableSet(denyList); + + if (allowList == null) { + throw new PrivilegeException("No allowList defined!"); + } + this.allowList = Collections.unmodifiableSet(allowList); + } + } + + /** + * Constructs a {@link Privilege} from the {@link PrivilegeRep} + * + * @param privilegeRep + * the {@link PrivilegeRep} from which to create the {@link Privilege} + */ + public Privilege(PrivilegeRep privilegeRep) { + this(privilegeRep.getName(), privilegeRep.getPolicy(), privilegeRep.isAllAllowed(), privilegeRep.getDenyList(), + privilegeRep.getDenyList()); } /** @@ -136,12 +166,6 @@ public final class Privilege { 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); - builder.append(", allowList="); - builder.append(this.allowList); builder.append("]"); return builder.toString(); } @@ -153,11 +177,7 @@ public final class Privilege { public int hashCode() { final int prime = 31; int result = 1; - result = prime * result + (this.allAllowed ? 1231 : 1237); - result = prime * result + ((this.allowList == null) ? 0 : this.allowList.hashCode()); - result = prime * result + ((this.denyList == null) ? 0 : this.denyList.hashCode()); result = prime * result + ((this.name == null) ? 0 : this.name.hashCode()); - result = prime * result + ((this.policy == null) ? 0 : this.policy.hashCode()); return result; } @@ -173,28 +193,12 @@ public final class Privilege { if (getClass() != obj.getClass()) return false; Privilege other = (Privilege) obj; - if (this.allAllowed != other.allAllowed) - return false; - if (this.allowList == null) { - if (other.allowList != null) - return false; - } else if (!this.allowList.equals(other.allowList)) - return false; - if (this.denyList == null) { - if (other.denyList != null) - return false; - } else if (!this.denyList.equals(other.denyList)) - return false; if (this.name == null) { if (other.name != null) return false; } else if (!this.name.equals(other.name)) return false; - if (this.policy == null) { - if (other.policy != null) - return false; - } else if (!this.policy.equals(other.policy)) - return false; return true; } + } diff --git a/src/ch/eitchnet/privilege/model/internal/Role.java b/src/ch/eitchnet/privilege/model/internal/Role.java index 6d9205f34..2d6a9e632 100644 --- a/src/ch/eitchnet/privilege/model/internal/Role.java +++ b/src/ch/eitchnet/privilege/model/internal/Role.java @@ -11,10 +11,11 @@ package ch.eitchnet.privilege.model.internal; import java.util.Collections; -import java.util.HashSet; -import java.util.Set; +import java.util.HashMap; +import java.util.Map; import ch.eitchnet.privilege.i18n.PrivilegeException; +import ch.eitchnet.privilege.model.PrivilegeRep; import ch.eitchnet.privilege.model.RoleRep; /** @@ -33,27 +34,54 @@ import ch.eitchnet.privilege.model.RoleRep; public final class Role { private final String name; - private final Set privileges; + private final Map privilegeMap; /** * Default constructor * * @param name * the name of the role - * @param privileges - * a set of names of privileges granted to this role + * @param privilegeMap + * a map of {@link Privilege}s granted to this role */ - public Role(String name, Set privileges) { + public Role(String name, Map privilegeMap) { if (name == null || name.isEmpty()) { throw new PrivilegeException("No name defined!"); } - if (privileges == null) { + if (privilegeMap == null) { throw new PrivilegeException("No privileges defined!"); } this.name = name; - this.privileges = Collections.unmodifiableSet(privileges); + 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 (name == null || name.isEmpty()) { + throw new PrivilegeException("No name defined!"); + } + + if (roleRep.getPrivilegeMap() == null) { + throw new PrivilegeException("No privileges defined!"); + } + + // build privileges from reps + Map privilegeMap = new HashMap(roleRep.getPrivilegeMap().size()); + for (String privilegeName : roleRep.getPrivilegeMap().keySet()) { + privilegeMap.put(privilegeName, new Privilege(roleRep.getPrivilegeMap().get(privilegeName))); + } + + this.name = name; + this.privilegeMap = Collections.unmodifiableMap(privilegeMap); } /** @@ -64,12 +92,12 @@ public final class Role { } /** - * Returns the {@link Set} of {@link Privilege} names which is granted to this {@link Role} + * Returns the {@link Map} of {@link Privilege}s which are granted to this {@link Role} * - * @return the {@link Set} of {@link Privilege} names which is granted to this + * @return the {@link Map} of {@link Privilege}s which are granted to this {@link Role} */ - public Set getPrivileges() { - return this.privileges; + public Map getPrivilegeMap() { + return this.privilegeMap; } /** @@ -81,14 +109,18 @@ public final class Role { * @return true if this {@link Role} has the {@link Privilege} with the given name */ public boolean hasPrivilege(String name) { - return this.privileges.contains(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() { - return new RoleRep(this.name, new HashSet(this.privileges)); + Map privilegeMap = new HashMap(); + for (String privilegeName : this.privilegeMap.keySet()) { + privilegeMap.put(privilegeName, this.privilegeMap.get(privilegeName).asPrivilegeRep()); + } + return new RoleRep(this.name, privilegeMap); } /** @@ -100,7 +132,7 @@ public final class Role { builder.append("Role [name="); builder.append(this.name); builder.append(", privileges="); - builder.append(this.privileges); + builder.append(this.privilegeMap.keySet()); builder.append("]"); return builder.toString(); } @@ -113,7 +145,6 @@ public final class Role { final int prime = 31; int result = 1; result = prime * result + ((this.name == null) ? 0 : this.name.hashCode()); - result = prime * result + ((this.privileges == null) ? 0 : this.privileges.hashCode()); return result; } @@ -134,12 +165,6 @@ public final class Role { return false; } else if (!this.name.equals(other.name)) return false; - if (this.privileges == null) { - if (other.privileges != null) - return false; - } else if (!this.privileges.equals(other.privileges)) - return false; return true; } - } diff --git a/src/ch/eitchnet/privilege/model/internal/User.java b/src/ch/eitchnet/privilege/model/internal/User.java index 975a7e7ee..84ee1e2eb 100644 --- a/src/ch/eitchnet/privilege/model/internal/User.java +++ b/src/ch/eitchnet/privilege/model/internal/User.java @@ -213,4 +213,35 @@ public final class User { builder.append("]"); return builder.toString(); } + + /** + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((this.userId == null) ? 0 : this.userId.hashCode()); + return result; + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + @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/test/ch/eitchnet/privilege/test/PrivilegeTest.java b/test/ch/eitchnet/privilege/test/PrivilegeTest.java index f48579942..908cf2658 100644 --- a/test/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/test/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -11,7 +11,9 @@ package ch.eitchnet.privilege.test; import java.io.File; +import java.util.HashMap; import java.util.HashSet; +import java.util.Map; import org.apache.log4j.BasicConfigurator; import org.apache.log4j.ConsoleAppender; @@ -26,16 +28,28 @@ import ch.eitchnet.privilege.helper.InitializationHelper; import ch.eitchnet.privilege.i18n.AccessDeniedException; import ch.eitchnet.privilege.i18n.PrivilegeException; 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; /** - * @author rvonburg + * JUnit for performing Privilege tests. This JUnit is by no means complete, but checks the bare minimum. TODO add more + * tests, especially with deny and allow lists * + * @author rvonburg */ public class PrivilegeTest { + private static final String ADMIN = "admin"; + private static final String PASS_ADMIN = "admin"; + private static final String BOB = "bob"; + private static final String TED = "ted"; + private static final String PASS_BOB = "admin1"; + private static final String ROLE_USER = "user"; + private static final String PASS_BAD = "123"; + private static final Logger logger = Logger.getLogger(PrivilegeTest.class); private static PrivilegeHandler privilegeHandler; @@ -71,7 +85,7 @@ public class PrivilegeTest { @Test public void testAuthenticationOk() throws Exception { - Certificate certificate = privilegeHandler.authenticate("eitch", "1234567890"); + Certificate certificate = privilegeHandler.authenticate(ADMIN, PASS_ADMIN); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); } @@ -82,7 +96,7 @@ public class PrivilegeTest { @Test(expected = AccessDeniedException.class) public void testFailAuthenticationNOk() throws Exception { - Certificate certificate = privilegeHandler.authenticate("eitch", "123"); + Certificate certificate = privilegeHandler.authenticate(ADMIN, PASS_BAD); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); } @@ -93,7 +107,7 @@ public class PrivilegeTest { @Test(expected = PrivilegeException.class) public void testFailAuthenticationPWNull() throws Exception { - Certificate certificate = privilegeHandler.authenticate("eitch", null); + Certificate certificate = privilegeHandler.authenticate(ADMIN, null); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); } @@ -102,17 +116,17 @@ public class PrivilegeTest { * if something goes wrong */ @Test - public void testAddUserBobWithPW() throws Exception { + public void testAddUserBobAsAdmin() throws Exception { - Certificate certificate = privilegeHandler.authenticate("eitch", "1234567890"); + Certificate certificate = privilegeHandler.authenticate(ADMIN, PASS_ADMIN); // let's add a new user bob - UserRep userRep = new UserRep("1", "bob", "Bob", "Newman", UserState.NEW, new HashSet(), null); + UserRep userRep = new UserRep("1", BOB, "Bob", "Newman", UserState.NEW, new HashSet(), null); privilegeHandler.addOrReplaceUser(certificate, userRep, null); - logger.info("Added user bob"); + logger.info("Added user " + BOB); // set bob's password - privilegeHandler.setUserPassword(certificate, "bob", "12345678901"); + privilegeHandler.setUserPassword(certificate, BOB, PASS_BOB); logger.info("Set Bob's password"); } @@ -124,8 +138,7 @@ public class PrivilegeTest { */ @Test(expected = AccessDeniedException.class) public void testFailAuthAsBob() throws Exception { - - privilegeHandler.authenticate("bob", "12345678901"); + privilegeHandler.authenticate(BOB, PASS_BOB); } /** @@ -134,9 +147,8 @@ public class PrivilegeTest { */ @Test public void testEnableUserBob() throws Exception { - - Certificate certificate = privilegeHandler.authenticate("eitch", "1234567890"); - privilegeHandler.setUserState(certificate, "bob", UserState.ENABLED); + Certificate certificate = privilegeHandler.authenticate(ADMIN, PASS_ADMIN); + privilegeHandler.setUserState(certificate, BOB, UserState.ENABLED); } /** @@ -148,7 +160,7 @@ public class PrivilegeTest { @Test(expected = PrivilegeException.class) public void testFailAuthUserBob() throws Exception { - Certificate certificate = privilegeHandler.authenticate("bob", "12345678901"); + Certificate certificate = privilegeHandler.authenticate(BOB, PASS_BOB); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); } @@ -157,10 +169,23 @@ public class PrivilegeTest { * if something goes wrong */ @Test - public void testAddUserRoleToBob() throws Exception { + public void testAddRole() throws Exception { + Certificate certificate = privilegeHandler.authenticate(ADMIN, PASS_ADMIN); - Certificate certificate = privilegeHandler.authenticate("eitch", "1234567890"); - privilegeHandler.addRoleToUser(certificate, "bob", "user"); + Map privilegeMap = new HashMap(); + RoleRep roleRep = new RoleRep(ROLE_USER, privilegeMap); + + privilegeHandler.addOrReplaceRole(certificate, roleRep); + } + + /** + * @throws Exception + * if something goes wrong + */ + @Test + public void testAddRoleToBob() throws Exception { + Certificate certificate = privilegeHandler.authenticate(ADMIN, PASS_ADMIN); + privilegeHandler.addRoleToUser(certificate, BOB, ROLE_USER); } /** @@ -169,8 +194,7 @@ public class PrivilegeTest { */ @Test public void testAuthAsBob() throws Exception { - - privilegeHandler.authenticate("bob", "12345678901"); + privilegeHandler.authenticate(BOB, PASS_BOB); } /** @@ -182,13 +206,14 @@ public class PrivilegeTest { @Test(expected = AccessDeniedException.class) public void testFailAddUserTedAsBob() throws Exception { - Certificate certificate = privilegeHandler.authenticate("bob", "12345678901"); + // auth as Bog + Certificate certificate = privilegeHandler.authenticate(BOB, PASS_BOB); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); - // let's add a new user bob - UserRep userRep = new UserRep("1", "bob", "Bob", "Newman", UserState.NEW, new HashSet(), null); + // let's add a new user Ted + UserRep userRep = new UserRep("1", TED, "Ted", "And then Some", UserState.NEW, new HashSet(), null); privilegeHandler.addOrReplaceUser(certificate, userRep, null); - logger.info("Added user bob"); + logger.info("Added user " + TED); } /** @@ -198,8 +223,9 @@ public class PrivilegeTest { @Test public void testAddAdminRoleToBob() throws Exception { - Certificate certificate = privilegeHandler.authenticate("eitch", "1234567890"); - privilegeHandler.addRoleToUser(certificate, "bob", PrivilegeHandler.PRIVILEGE_ADMIN_ROLE); + Certificate certificate = privilegeHandler.authenticate(ADMIN, PASS_ADMIN); + privilegeHandler.addRoleToUser(certificate, BOB, PrivilegeHandler.PRIVILEGE_ADMIN_ROLE); + logger.info("Added " + PrivilegeHandler.PRIVILEGE_ADMIN_ROLE + " to " + ADMIN); } /** @@ -209,13 +235,13 @@ public class PrivilegeTest { @Test public void testAddUserTedAsBob() throws Exception { - Certificate certificate = privilegeHandler.authenticate("bob", "12345678901"); + Certificate certificate = privilegeHandler.authenticate(BOB, PASS_BOB); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); // let's add a new user ted - UserRep userRep = new UserRep("2", "ted", "Ted", "Newman", UserState.NEW, new HashSet(), null); + UserRep userRep = new UserRep("2", TED, "Ted", "Newman", UserState.NEW, new HashSet(), null); privilegeHandler.addOrReplaceUser(certificate, userRep, null); - logger.info("Added user bob"); + logger.info("Added user " + TED); } /** @@ -225,12 +251,12 @@ public class PrivilegeTest { @Test public void testPerformRestrictable() throws Exception { - Certificate certificate = privilegeHandler.authenticate("eitch", "1234567890"); + Certificate certificate = privilegeHandler.authenticate(ADMIN, PASS_ADMIN); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); // see if eitch can perform restrictable Restrictable restrictable = new TestRestrictable(); boolean actionAllowed = privilegeHandler.actionAllowed(certificate, restrictable); - org.junit.Assert.assertTrue("eitch may not perform restrictable!", actionAllowed); + org.junit.Assert.assertTrue(ADMIN + " may not perform restrictable!", actionAllowed); } } diff --git a/test/ch/eitchnet/privilege/test/TestRestrictable.java b/test/ch/eitchnet/privilege/test/TestRestrictable.java index e9badc734..ee8921722 100644 --- a/test/ch/eitchnet/privilege/test/TestRestrictable.java +++ b/test/ch/eitchnet/privilege/test/TestRestrictable.java @@ -23,7 +23,7 @@ public class TestRestrictable implements Restrictable { */ @Override public String getPrivilegeName() { - return "Service"; + return "com.rsp.core.base.service.Service"; } /** From 6191949c3e36d29a8fd56ddef780963dc22b7c03 Mon Sep 17 00:00:00 2001 From: eitch Date: Sat, 30 Jul 2011 14:10:47 +0000 Subject: [PATCH 058/173] [Minor] logging of authentication attempts --- .../privilege/handler/DefaultPrivilegeHandler.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index f92df13c7..bde9ab4fc 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -572,7 +572,12 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { CertificateSessionPair certificateSessionPair = this.sessionMap.remove(certificate.getSessionId()); // return true if object was really removed - return certificateSessionPair != null; + boolean loggedOut = certificateSessionPair != null; + if (loggedOut) + logger.info("User " + certificate.getUsername() + " logged out."); + else + logger.warn("User already logged out!"); + return loggedOut; } /** From e8a7fb5cf91964e085a58897282853690d738769 Mon Sep 17 00:00:00 2001 From: eitch Date: Sat, 30 Jul 2011 17:34:53 +0000 Subject: [PATCH 059/173] [Interface] changed all methods which return boolean on action allowed to be void as they must throw exceptions if the privilege is not granted --- .../handler/DefaultPrivilegeHandler.java | 52 ++++++------- .../privilege/handler/PrivilegeHandler.java | 15 ++-- .../privilege/policy/DefaultPrivilege.java | 22 +++--- .../privilege/policy/PrivilegePolicy.java | 6 +- .../privilege/test/PrivilegeTest.java | 77 +++++++++++++++++-- 5 files changed, 119 insertions(+), 53 deletions(-) diff --git a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index bde9ab4fc..2dd4be781 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -563,10 +563,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { public boolean invalidateSession(Certificate certificate) { // first validate certificate - if (!isCertificateValid(certificate)) { - logger.info("Certificate is not valid, so no session to invalidate: " + certificate.toString()); - return false; - } + isCertificateValid(certificate); // remove registration CertificateSessionPair certificateSessionPair = this.sessionMap.remove(certificate.getSessionId()); @@ -592,15 +589,12 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { * ch.eitchnet.privilege.model.Restrictable) */ @Override - public boolean actionAllowed(Certificate certificate, Restrictable restrictable) { + public void actionAllowed(Certificate certificate, Restrictable restrictable) { // TODO What is better, validate from {@link Restrictable} to {@link User} or the opposite direction? // first validate certificate - if (!isCertificateValid(certificate)) { - throw new PrivilegeException("Certificate is not valid, so action is not allowed: " + certificate - + " for restrictable: " + restrictable); - } + isCertificateValid(certificate); // restrictable must not be null if (restrictable == null) @@ -613,9 +607,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { "Oh boy, how did this happen: No User in user map although the certificate is valid!"); } - // default is to not allow the action - // TODO should default deny/allow policy be configurable? - boolean actionAllowed = false; + String privilegeName = restrictable.getPrivilegeName(); // now iterate roles and validate on policies for (String roleName : user.getRoles()) { @@ -626,14 +618,17 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { continue; } - actionAllowed = actionAllowed(role, restrictable); + // ignore if this role does not have the privilege + if (!role.hasPrivilege(privilegeName)) + continue; // if action is allowed, then break iteration as a privilege match has been made - if (actionAllowed) - break; + if (actionAllowed(role, restrictable)) + return; } - return actionAllowed; + throw new AccessDeniedException("User " + user.getUsername() + " does not have Privilege " + privilegeName + + " needed for Restrictable " + restrictable.getClass().getName()); } /** @@ -644,7 +639,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { * ch.eitchnet.privilege.model.Restrictable) */ @Override - public boolean actionAllowed(RoleRep roleRep, Restrictable restrictable) { + public void actionAllowed(RoleRep roleRep, Restrictable restrictable) { // user and restrictable must not be null if (roleRep == null) @@ -660,7 +655,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { throw new PrivilegeException("No Role exists with the name " + roleRep.getName()); } - return actionAllowed(role, restrictable); + // delegate to method with Role + actionAllowed(role, restrictable); } /** @@ -669,8 +665,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { * * @param role * @param restrictable - * - * @return true if the privilege is granted, false otherwise */ protected boolean actionAllowed(Role role, Restrictable restrictable) { @@ -691,8 +685,9 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { Privilege privilege = role.getPrivilegeMap().get(privilegeName); // check if all is allowed - if (privilege.isAllAllowed()) + if (privilege.isAllAllowed()) { return true; + } // otherwise delegate checking to the policy configured for this privilege PrivilegePolicy policy = this.getPolicy(privilege.getPolicy()); @@ -702,14 +697,18 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } // delegate checking to privilege policy - return policy.actionAllowed(role, privilege, restrictable); + policy.actionAllowed(role, privilege, restrictable); + + // the policy must throw an exception if action not allowed, + // so return true if we arrive here + return true; } /** * @see ch.eitchnet.privilege.handler.PrivilegeHandler#isCertificateValid(ch.eitchnet.privilege.model.Certificate) */ @Override - public boolean isCertificateValid(Certificate certificate) { + public void isCertificateValid(Certificate certificate) { // certificate must not be null if (certificate == null) @@ -741,8 +740,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { "Oh boy, how did this happen: No User in user map although the certificate is valid!"); } - // everything is ok, so return true as the certificate must be valid - return true; + // everything is ok } /** @@ -752,9 +750,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { public void validateIsPrivilegeAdmin(Certificate certificate) throws PrivilegeException { // validate certificate - if (!this.isCertificateValid(certificate)) { - throw new PrivilegeException("Certificate " + certificate + " is not valid!"); - } + this.isCertificateValid(certificate); // get user object User user = this.persistenceHandler.getUser(certificate.getUsername()); diff --git a/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java b/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java index 49608c42d..997d35ec6 100644 --- a/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java +++ b/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java @@ -326,15 +326,13 @@ public interface PrivilegeHandler { * @param restrictable * the {@link Restrictable} to which the user wants access * - * @return true if the access is allowed, false otherwise - * * @throws AccessDeniedException * if the user for this certificate may not perform the action defined by the {@link Restrictable} * implementation * @throws PrivilegeException * if there is anything wrong with this certificate */ - public boolean actionAllowed(Certificate certificate, Restrictable restrictable) throws AccessDeniedException, + public void actionAllowed(Certificate certificate, Restrictable restrictable) throws AccessDeniedException, PrivilegeException; /** @@ -345,12 +343,13 @@ public interface PrivilegeHandler { * @param restrictable * the {@link Restrictable} to which access is to be checked * - * @return true if the {@link RoleRep} has access, false otherwise - * + * @throws PrivilegeException + * if the {@link Role} does not exist * @throws AccessDeniedException * if the role may not perform the action defined by the {@link Restrictable} implementation */ - public boolean actionAllowed(RoleRep roleRep, Restrictable restrictable) throws AccessDeniedException; + public void actionAllowed(RoleRep roleRep, Restrictable restrictable) throws PrivilegeException, + AccessDeniedException; /** * Checks if the given {@link Certificate} is valid. This means that the certificate is for a valid session and that @@ -359,12 +358,10 @@ public interface PrivilegeHandler { * @param certificate * the {@link Certificate} to check * - * @return true if the {@link Certificate} is valid, false otherwise - * * @throws PrivilegeException * if there is anything wrong with this certificate */ - public boolean isCertificateValid(Certificate certificate) throws PrivilegeException; + public void isCertificateValid(Certificate certificate) throws PrivilegeException; /** *

    diff --git a/src/ch/eitchnet/privilege/policy/DefaultPrivilege.java b/src/ch/eitchnet/privilege/policy/DefaultPrivilege.java index 1eecb1e26..b455ef14b 100644 --- a/src/ch/eitchnet/privilege/policy/DefaultPrivilege.java +++ b/src/ch/eitchnet/privilege/policy/DefaultPrivilege.java @@ -10,6 +10,7 @@ package ch.eitchnet.privilege.policy; +import ch.eitchnet.privilege.i18n.AccessDeniedException; import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.Restrictable; import ch.eitchnet.privilege.model.internal.Privilege; @@ -27,7 +28,7 @@ public class DefaultPrivilege implements PrivilegePolicy { * ch.eitchnet.privilege.model.internal.Privilege, ch.eitchnet.privilege.model.Restrictable) */ @Override - public boolean actionAllowed(Role role, Privilege privilege, Restrictable restrictable) { + public void actionAllowed(Role role, Privilege privilege, Restrictable restrictable) { // validate user is not null if (role == null) @@ -39,10 +40,6 @@ public class DefaultPrivilege implements PrivilegePolicy { throw new PrivilegeException("The PrivilegeName for the Restrictable is null or empty: " + restrictable); } - // does this role have privilege for any values? - if (privilege.isAllAllowed()) - return true; - // get the value on which the action is to be performed Object object = restrictable.getPrivilegeValue(); @@ -56,17 +53,24 @@ public class DefaultPrivilege implements PrivilegePolicy { // first check values not allowed for (String denied : privilege.getDenyList()) { - if (denied.equals(privilegeValue)) - return false; + + // if value in deny list + if (denied.equals(privilegeValue)) { + + // then throw access denied + throw new AccessDeniedException("Role " + role.getName() + " does not have Privilege " + privilegeName + + " needed for Restrictable " + restrictable.getClass().getName()); + } } // now check values allowed for (String allowed : privilege.getAllowList()) { if (allowed.equals(privilegeValue)) - return true; + return; } // default is not allowed - return false; + throw new AccessDeniedException("Role " + role.getName() + " does not have Privilege " + privilegeName + + " needed for Restrictable " + restrictable.getClass().getName()); } } diff --git a/src/ch/eitchnet/privilege/policy/PrivilegePolicy.java b/src/ch/eitchnet/privilege/policy/PrivilegePolicy.java index 865d373cd..63807fe1c 100644 --- a/src/ch/eitchnet/privilege/policy/PrivilegePolicy.java +++ b/src/ch/eitchnet/privilege/policy/PrivilegePolicy.java @@ -10,6 +10,7 @@ package ch.eitchnet.privilege.policy; +import ch.eitchnet.privilege.i18n.AccessDeniedException; import ch.eitchnet.privilege.model.Restrictable; import ch.eitchnet.privilege.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.Role; @@ -40,7 +41,8 @@ public interface PrivilegePolicy { * @param restrictable * the {@link Restrictable} to which the user wants access * - * @return return true if the action is allowed, false if not + * @throws AccessDeniedException + * if action not allowed */ - public boolean actionAllowed(Role role, Privilege privilege, Restrictable restrictable); + public void actionAllowed(Role role, Privilege privilege, Restrictable restrictable) throws AccessDeniedException; } diff --git a/test/ch/eitchnet/privilege/test/PrivilegeTest.java b/test/ch/eitchnet/privilege/test/PrivilegeTest.java index 908cf2658..1a375e233 100644 --- a/test/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/test/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -47,6 +47,7 @@ public class PrivilegeTest { private static final String BOB = "bob"; private static final String TED = "ted"; private static final String PASS_BOB = "admin1"; + private static final String ROLE_FEATHERLITE_USER = "FeatherliteUser"; private static final String ROLE_USER = "user"; private static final String PASS_BAD = "123"; @@ -87,6 +88,7 @@ public class PrivilegeTest { Certificate certificate = privilegeHandler.authenticate(ADMIN, PASS_ADMIN); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); + privilegeHandler.invalidateSession(certificate); } /** @@ -98,6 +100,7 @@ public class PrivilegeTest { Certificate certificate = privilegeHandler.authenticate(ADMIN, PASS_BAD); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); + privilegeHandler.invalidateSession(certificate); } /** @@ -109,6 +112,7 @@ public class PrivilegeTest { Certificate certificate = privilegeHandler.authenticate(ADMIN, null); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); + privilegeHandler.invalidateSession(certificate); } /** @@ -128,6 +132,7 @@ public class PrivilegeTest { // set bob's password privilegeHandler.setUserPassword(certificate, BOB, PASS_BOB); logger.info("Set Bob's password"); + privilegeHandler.invalidateSession(certificate); } /** @@ -138,7 +143,8 @@ public class PrivilegeTest { */ @Test(expected = AccessDeniedException.class) public void testFailAuthAsBob() throws Exception { - privilegeHandler.authenticate(BOB, PASS_BOB); + Certificate certificate = privilegeHandler.authenticate(BOB, PASS_BOB); + privilegeHandler.invalidateSession(certificate); } /** @@ -149,6 +155,7 @@ public class PrivilegeTest { public void testEnableUserBob() throws Exception { Certificate certificate = privilegeHandler.authenticate(ADMIN, PASS_ADMIN); privilegeHandler.setUserState(certificate, BOB, UserState.ENABLED); + privilegeHandler.invalidateSession(certificate); } /** @@ -162,6 +169,7 @@ public class PrivilegeTest { Certificate certificate = privilegeHandler.authenticate(BOB, PASS_BOB); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); + privilegeHandler.invalidateSession(certificate); } /** @@ -176,6 +184,7 @@ public class PrivilegeTest { RoleRep roleRep = new RoleRep(ROLE_USER, privilegeMap); privilegeHandler.addOrReplaceRole(certificate, roleRep); + privilegeHandler.invalidateSession(certificate); } /** @@ -186,6 +195,7 @@ public class PrivilegeTest { public void testAddRoleToBob() throws Exception { Certificate certificate = privilegeHandler.authenticate(ADMIN, PASS_ADMIN); privilegeHandler.addRoleToUser(certificate, BOB, ROLE_USER); + privilegeHandler.invalidateSession(certificate); } /** @@ -194,7 +204,8 @@ public class PrivilegeTest { */ @Test public void testAuthAsBob() throws Exception { - privilegeHandler.authenticate(BOB, PASS_BOB); + Certificate certificate = privilegeHandler.authenticate(BOB, PASS_BOB); + privilegeHandler.invalidateSession(certificate); } /** @@ -214,6 +225,7 @@ public class PrivilegeTest { UserRep userRep = new UserRep("1", TED, "Ted", "And then Some", UserState.NEW, new HashSet(), null); privilegeHandler.addOrReplaceUser(certificate, userRep, null); logger.info("Added user " + TED); + privilegeHandler.invalidateSession(certificate); } /** @@ -226,6 +238,7 @@ public class PrivilegeTest { Certificate certificate = privilegeHandler.authenticate(ADMIN, PASS_ADMIN); privilegeHandler.addRoleToUser(certificate, BOB, PrivilegeHandler.PRIVILEGE_ADMIN_ROLE); logger.info("Added " + PrivilegeHandler.PRIVILEGE_ADMIN_ROLE + " to " + ADMIN); + privilegeHandler.invalidateSession(certificate); } /** @@ -242,6 +255,7 @@ public class PrivilegeTest { UserRep userRep = new UserRep("2", TED, "Ted", "Newman", UserState.NEW, new HashSet(), null); privilegeHandler.addOrReplaceUser(certificate, userRep, null); logger.info("Added user " + TED); + privilegeHandler.invalidateSession(certificate); } /** @@ -249,14 +263,67 @@ public class PrivilegeTest { * if something goes wrong */ @Test - public void testPerformRestrictable() throws Exception { + public void testPerformRestrictableAsAdmin() throws Exception { Certificate certificate = privilegeHandler.authenticate(ADMIN, PASS_ADMIN); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); // see if eitch can perform restrictable Restrictable restrictable = new TestRestrictable(); - boolean actionAllowed = privilegeHandler.actionAllowed(certificate, restrictable); - org.junit.Assert.assertTrue(ADMIN + " may not perform restrictable!", actionAllowed); + privilegeHandler.actionAllowed(certificate, restrictable); + privilegeHandler.invalidateSession(certificate); + } + + /** + * Tests if the user bob, who does not have FeatherliteUser role can perform restrictable + * + * @throws Exception + * if something goes wrong + */ + @Test(expected = AccessDeniedException.class) + public void testFailPerformRestrictableAsBob() throws Exception { + Certificate certificate = privilegeHandler.authenticate(BOB, PASS_BOB); + org.junit.Assert.assertTrue("Certificate is null!", certificate != null); + + // see if bob can perform restrictable + Restrictable restrictable = new TestRestrictable(); + try { + privilegeHandler.actionAllowed(certificate, restrictable); + } finally { + privilegeHandler.invalidateSession(certificate); + } + } + + /** + * @throws Exception + * if something goes wrong + */ + @Test + public void testAddFeatherliteRoleToBob() throws Exception { + + Certificate certificate = privilegeHandler.authenticate(ADMIN, PASS_ADMIN); + privilegeHandler.addRoleToUser(certificate, BOB, ROLE_FEATHERLITE_USER); + logger.info("Added " + ROLE_FEATHERLITE_USER + " to " + BOB); + privilegeHandler.invalidateSession(certificate); + } + + /** + * Tests if the user bob, who now has FeatherliteUser role can perform restrictable + * + * @throws Exception + * if something goes wrong + */ + @Test + public void testPerformRestrictableAsBob() throws Exception { + Certificate certificate = privilegeHandler.authenticate(BOB, PASS_BOB); + org.junit.Assert.assertTrue("Certificate is null!", certificate != null); + + // see if bob can perform restrictable + Restrictable restrictable = new TestRestrictable(); + try { + privilegeHandler.actionAllowed(certificate, restrictable); + } finally { + privilegeHandler.invalidateSession(certificate); + } } } From 4c455c130aa6164893daeadb1fed479f726f7119 Mon Sep 17 00:00:00 2001 From: eitch Date: Sun, 31 Jul 2011 15:18:29 +0000 Subject: [PATCH 060/173] --- config/PrivilegeModel.xml | 3 --- src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/config/PrivilegeModel.xml b/config/PrivilegeModel.xml index 493476d57..c5d531266 100644 --- a/config/PrivilegeModel.xml +++ b/config/PrivilegeModel.xml @@ -27,9 +27,6 @@ true - - true - diff --git a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 2dd4be781..800131392 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -857,7 +857,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { * * @author rvonburg */ - private class CertificateSessionPair { + protected class CertificateSessionPair { /** * The {@link Session} */ From bef87b1a184deae571f0d0ea3be038aa8183bf47 Mon Sep 17 00:00:00 2001 From: eitch Date: Sun, 7 Aug 2011 10:14:40 +0000 Subject: [PATCH 061/173] [Minor] code comments cleanup, added LGPL reference and copyright notice --- COPYING | 674 ++++++++++++++++++ COPYING.LESSER | 165 +++++ build.xml | 25 + config/Privilege.xml | 23 + config/PrivilegeModel.xml | 22 + .../handler/DefaultEncryptionHandler.java | 29 +- .../handler/DefaultPrivilegeHandler.java | 27 +- .../privilege/handler/EncryptionHandler.java | 25 +- .../privilege/handler/PersistenceHandler.java | 25 +- .../privilege/handler/PrivilegeHandler.java | 25 +- .../handler/XmlPersistenceHandler.java | 157 ++-- .../helper/BootstrapConfigurationHelper.java | 25 +- .../privilege/helper/ClassHelper.java | 25 +- .../eitchnet/privilege/helper/HashHelper.java | 25 +- .../helper/InitializationHelper.java | 25 +- .../privilege/helper/PasswordCreaterUI.java | 27 +- .../privilege/helper/PasswordCreator.java | 25 +- .../privilege/helper/XmlConstants.java | 25 +- .../eitchnet/privilege/helper/XmlHelper.java | 25 +- .../privilege/i18n/AccessDeniedException.java | 25 +- .../privilege/i18n/PrivilegeException.java | 25 +- .../eitchnet/privilege/model/Certificate.java | 33 +- .../privilege/model/PrivilegeRep.java | 25 +- .../privilege/model/Restrictable.java | 25 +- src/ch/eitchnet/privilege/model/RoleRep.java | 25 +- src/ch/eitchnet/privilege/model/UserRep.java | 25 +- .../eitchnet/privilege/model/UserState.java | 25 +- .../privilege/model/internal/Privilege.java | 25 +- .../privilege/model/internal/Role.java | 25 +- .../privilege/model/internal/Session.java | 25 +- .../privilege/model/internal/User.java | 25 +- .../privilege/policy/DefaultPrivilege.java | 25 +- .../privilege/policy/PrivilegePolicy.java | 25 +- .../privilege/test/PrivilegeTest.java | 25 +- .../privilege/test/TestRestrictable.java | 25 +- 35 files changed, 1588 insertions(+), 219 deletions(-) create mode 100644 COPYING create mode 100644 COPYING.LESSER diff --git a/COPYING b/COPYING new file mode 100644 index 000000000..94a9ed024 --- /dev/null +++ b/COPYING @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/COPYING.LESSER b/COPYING.LESSER new file mode 100644 index 000000000..65c5ca88a --- /dev/null +++ b/COPYING.LESSER @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/build.xml b/build.xml index 8063f0c42..64bb5f254 100644 --- a/build.xml +++ b/build.xml @@ -1,4 +1,28 @@ + + + @@ -34,6 +58,7 @@ + diff --git a/config/Privilege.xml b/config/Privilege.xml index 0913623cb..2cfaf41e1 100644 --- a/config/Privilege.xml +++ b/config/Privilege.xml @@ -1,4 +1,27 @@ + + diff --git a/config/PrivilegeModel.xml b/config/PrivilegeModel.xml index c5d531266..a9c40c5fa 100644 --- a/config/PrivilegeModel.xml +++ b/config/PrivilegeModel.xml @@ -1,4 +1,26 @@ + diff --git a/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java b/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java index 976f62463..10d02c49e 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java @@ -1,10 +1,25 @@ /* - * Copyright (c) 2010 + * Copyright (c) 2010, 2011 * - * Robert von Burg - * eitch@eitchnet.ch + * Robert von Burg * - * All rights reserved. + */ + +/* + * This file is part of Privilege. + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . * */ @@ -24,8 +39,8 @@ import ch.eitchnet.privilege.i18n.PrivilegeException; /** *

    - * 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 + * 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: @@ -33,7 +48,7 @@ import ch.eitchnet.privilege.i18n.PrivilegeException; *
  • {@link XmlConstants#XML_PARAM_HASH_ALGORITHM}
  • * * - * @author rvonburg + * @author Robert von Burg * */ public class DefaultEncryptionHandler implements EncryptionHandler { diff --git a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 800131392..07b38e558 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -1,10 +1,25 @@ /* - * Copyright (c) 2010 + * Copyright (c) 2010, 2011 * - * Robert von Burg - * eitch@eitchnet.ch + * Robert von Burg * - * All rights reserved. + */ + +/* + * This file is part of Privilege. + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . * */ @@ -50,7 +65,7 @@ import ch.eitchnet.privilege.policy.PrivilegePolicy; *
  • Password requirements are simple: Non null and non empty/length 0
  • * * - * @author rvonburg + * @author Robert von Burg * */ public class DefaultPrivilegeHandler implements PrivilegeHandler { @@ -855,7 +870,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { /** * An internal class used to keep a record of sessions with the certificate * - * @author rvonburg + * @author Robert von Burg */ protected class CertificateSessionPair { /** diff --git a/src/ch/eitchnet/privilege/handler/EncryptionHandler.java b/src/ch/eitchnet/privilege/handler/EncryptionHandler.java index c4f6b34b9..50d758173 100644 --- a/src/ch/eitchnet/privilege/handler/EncryptionHandler.java +++ b/src/ch/eitchnet/privilege/handler/EncryptionHandler.java @@ -1,10 +1,25 @@ /* - * Copyright (c) 2010 + * Copyright (c) 2010, 2011 * - * Robert von Burg - * eitch@eitchnet.ch + * Robert von Burg * - * All rights reserved. + */ + +/* + * This file is part of Privilege. + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . * */ @@ -16,7 +31,7 @@ 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 rvonburg + * @author Robert von Burg * */ public interface EncryptionHandler { diff --git a/src/ch/eitchnet/privilege/handler/PersistenceHandler.java b/src/ch/eitchnet/privilege/handler/PersistenceHandler.java index 65026f2f0..29ad14fac 100644 --- a/src/ch/eitchnet/privilege/handler/PersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/PersistenceHandler.java @@ -1,10 +1,25 @@ /* - * Copyright (c) 2010 + * Copyright (c) 2010, 2011 * - * Robert von Burg - * eitch@eitchnet.ch + * Robert von Burg * - * All rights reserved. + */ + +/* + * This file is part of Privilege. + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . * */ @@ -31,7 +46,7 @@ import ch.eitchnet.privilege.policy.PrivilegePolicy; * and {@link Privilege} *

    * - * @author rvonburg + * @author Robert von Burg * */ public interface PersistenceHandler { diff --git a/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java b/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java index 997d35ec6..2af410f25 100644 --- a/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java +++ b/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java @@ -1,10 +1,25 @@ /* - * Copyright (c) 2010 + * Copyright (c) 2010, 2011 * - * Robert von Burg - * eitch@eitchnet.ch + * Robert von Burg * - * All rights reserved. + */ + +/* + * This file is part of Privilege. + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . * */ @@ -30,7 +45,7 @@ import ch.eitchnet.privilege.model.internal.User; * methods to access Privilege data model objects, modify them and validate if users or roles have privileges to perform * an action * - * @author rvonburg + * @author Robert von Burg * */ public interface PrivilegeHandler { diff --git a/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java b/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java index 8d681976f..7fb74dd70 100644 --- a/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java @@ -1,10 +1,25 @@ /* - * Copyright (c) 2010 + * Copyright (c) 2010, 2011 * - * Robert von Burg - * eitch@eitchnet.ch + * Robert von Burg * - * All rights reserved. + */ + +/* + * This file is part of Privilege. + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . * */ @@ -37,7 +52,7 @@ import ch.eitchnet.privilege.model.internal.User; * {@link PersistenceHandler} implementation which reads the configuration from XML files. These configuration is passed * in {@link #initialize(Map)} * - * @author rvonburg + * @author Robert von Burg */ public class XmlPersistenceHandler implements PersistenceHandler { @@ -155,7 +170,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { // USERS // build XML DOM of users - List users = toDomUsers(); + List users = toDomUsers(this.userMap); Element usersElement = docFactory.createElement(XmlConstants.XML_USERS); for (Element userElement : users) { usersElement.add(userElement); @@ -164,7 +179,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { // ROLES // build XML DOM of roles - List roles = toDomRoles(); + List roles = toDomRoles(this.roleMap); Element rolesElement = docFactory.createElement(XmlConstants.XML_ROLES); for (Element roleElement : roles) { rolesElement.add(roleElement); @@ -211,13 +226,15 @@ public class XmlPersistenceHandler implements PersistenceHandler { // get roles element Element rolesElement = modelsRootElement.element(XmlConstants.XML_ROLES); // read roles - readRoles(rolesElement); + Map roles = readRoles(rolesElement); + this.roleMap = roles; // USERS // get users element Element usersElement = modelsRootElement.element(XmlConstants.XML_USERS); // read users - readUsers(usersElement); + Map users = readUsers(usersElement); + this.userMap = users; this.userMapDirty = false; this.roleMapDirty = false; @@ -274,7 +291,9 @@ public class XmlPersistenceHandler implements PersistenceHandler { /** * @param usersRootElement */ - private void readUsers(Element usersRootElement) { + private static Map readUsers(Element usersRootElement) { + + Map userMap = new HashMap(); @SuppressWarnings("unchecked") List userElements = usersRootElement.elements(XmlConstants.XML_USER); @@ -313,14 +332,18 @@ public class XmlPersistenceHandler implements PersistenceHandler { logger.info("Added user " + user); // put user in map - this.userMap.put(username, user); + userMap.put(username, user); } + + return userMap; } /** * @param rolesRootElement */ - private void readRoles(Element rolesRootElement) { + private static Map readRoles(Element rolesRootElement) { + + Map roleMap = new HashMap(); @SuppressWarnings("unchecked") List roleElements = rolesRootElement.elements(XmlConstants.XML_ROLE); @@ -331,64 +354,23 @@ public class XmlPersistenceHandler implements PersistenceHandler { Map privilegeMap = readPrivileges(roleElement); Role role = new Role(roleName, privilegeMap); - this.roleMap.put(roleName, role); - } - } - - /** - * @param roleParentElement - * @return - */ - private Map readPrivileges(Element roleParentElement) { - - Map privilegeMap = new HashMap(); - - @SuppressWarnings("unchecked") - List privilegeElements = roleParentElement.elements(XmlConstants.XML_PRIVILEGE); - for (Element privilegeElement : privilegeElements) { - - String privilegeName = privilegeElement.attributeValue(XmlConstants.XML_ATTR_NAME); - String privilegePolicy = privilegeElement.attributeValue(XmlConstants.XML_ATTR_POLICY); - - String allAllowedS = privilegeElement.element(XmlConstants.XML_ALL_ALLOWED).getTextTrim(); - boolean allAllowed = Boolean.valueOf(allAllowedS).booleanValue(); - - @SuppressWarnings("unchecked") - List denyElements = privilegeElement.elements(XmlConstants.XML_DENY); - Set denyList = new HashSet(denyElements.size()); - for (Element denyElement : denyElements) { - String denyValue = denyElement.getTextTrim(); - if (!denyValue.isEmpty()) - denyList.add(denyValue); - } - - @SuppressWarnings("unchecked") - List allowElements = privilegeElement.elements(XmlConstants.XML_ALLOW); - Set allowList = new HashSet(allowElements.size()); - for (Element allowElement : allowElements) { - String allowValue = allowElement.getTextTrim(); - if (!allowValue.isEmpty()) - allowList.add(allowValue); - } - - Privilege privilege = new Privilege(privilegeName, privilegePolicy, allAllowed, denyList, allowList); - privilegeMap.put(privilegeName, privilege); + roleMap.put(roleName, role); } - return privilegeMap; + return roleMap; } - private List toDomUsers() { + private static List toDomUsers(Map userMap) { - List usersAsElements = new ArrayList(this.userMap.size()); + List usersAsElements = new ArrayList(userMap.size()); DocumentFactory documentFactory = DocumentFactory.getInstance(); - synchronized (this.userMap) { - for (String userName : this.userMap.keySet()) { + synchronized (userMap) { + for (String userName : userMap.keySet()) { // get the user object - User user = this.userMap.get(userName); + User user = userMap.get(userName); // create the user element Element userElement = documentFactory.createElement(XmlConstants.XML_USER); @@ -433,17 +415,17 @@ public class XmlPersistenceHandler implements PersistenceHandler { return usersAsElements; } - private List toDomRoles() { + private static List toDomRoles(Map roleMap) { - List rolesAsElements = new ArrayList(this.roleMap.size()); + List rolesAsElements = new ArrayList(roleMap.size()); DocumentFactory documentFactory = DocumentFactory.getInstance(); - synchronized (this.roleMap) { - for (String roleName : this.roleMap.keySet()) { + synchronized (roleMap) { + for (String roleName : roleMap.keySet()) { // get the role object - Role role = this.roleMap.get(roleName); + Role role = roleMap.get(roleName); // create the role element Element roleElement = documentFactory.createElement(XmlConstants.XML_ROLE); @@ -460,7 +442,50 @@ public class XmlPersistenceHandler implements PersistenceHandler { return rolesAsElements; } - private void toDomPrivileges(Element roleParentElement, Map privilegeMap) { + /** + * @param roleParentElement + * @return + */ + private static Map readPrivileges(Element roleParentElement) { + + Map privilegeMap = new HashMap(); + + @SuppressWarnings("unchecked") + List privilegeElements = roleParentElement.elements(XmlConstants.XML_PRIVILEGE); + for (Element privilegeElement : privilegeElements) { + + String privilegeName = privilegeElement.attributeValue(XmlConstants.XML_ATTR_NAME); + String privilegePolicy = privilegeElement.attributeValue(XmlConstants.XML_ATTR_POLICY); + + String allAllowedS = privilegeElement.element(XmlConstants.XML_ALL_ALLOWED).getTextTrim(); + boolean allAllowed = Boolean.valueOf(allAllowedS).booleanValue(); + + @SuppressWarnings("unchecked") + List denyElements = privilegeElement.elements(XmlConstants.XML_DENY); + Set denyList = new HashSet(denyElements.size()); + for (Element denyElement : denyElements) { + String denyValue = denyElement.getTextTrim(); + if (!denyValue.isEmpty()) + denyList.add(denyValue); + } + + @SuppressWarnings("unchecked") + List allowElements = privilegeElement.elements(XmlConstants.XML_ALLOW); + Set allowList = new HashSet(allowElements.size()); + for (Element allowElement : allowElements) { + String allowValue = allowElement.getTextTrim(); + if (!allowValue.isEmpty()) + allowList.add(allowValue); + } + + Privilege privilege = new Privilege(privilegeName, privilegePolicy, allAllowed, denyList, allowList); + privilegeMap.put(privilegeName, privilege); + } + + return privilegeMap; + } + + private static void toDomPrivileges(Element roleParentElement, Map privilegeMap) { DocumentFactory documentFactory = DocumentFactory.getInstance(); diff --git a/src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java b/src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java index b25485d79..386ce8476 100644 --- a/src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java +++ b/src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java @@ -1,10 +1,25 @@ /* - * Copyright (c) 2010 + * Copyright (c) 2010, 2011 * - * Robert von Burg - * eitch@eitchnet.ch + * Robert von Burg * - * All rights reserved. + */ + +/* + * This file is part of Privilege. + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . * */ @@ -38,7 +53,7 @@ import ch.eitchnet.privilege.handler.PrivilegeHandler; * Note:This class is not yet implemented! *

    * - * @author rvonburg + * @author Robert von Burg */ public class BootstrapConfigurationHelper { diff --git a/src/ch/eitchnet/privilege/helper/ClassHelper.java b/src/ch/eitchnet/privilege/helper/ClassHelper.java index eda6f8eea..7a741681f 100644 --- a/src/ch/eitchnet/privilege/helper/ClassHelper.java +++ b/src/ch/eitchnet/privilege/helper/ClassHelper.java @@ -1,10 +1,25 @@ /* - * Copyright (c) 2010 + * Copyright (c) 2010, 2011 * - * Robert von Burg - * eitch@eitchnet.ch + * Robert von Burg * - * All rights reserved. + */ + +/* + * This file is part of Privilege. + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . * */ @@ -15,7 +30,7 @@ import ch.eitchnet.privilege.i18n.PrivilegeException; /** * The {@link ClassHelper} class is a helper to instantiate classes using reflection * - * @author rvonburg + * @author Robert von Burg */ public class ClassHelper { diff --git a/src/ch/eitchnet/privilege/helper/HashHelper.java b/src/ch/eitchnet/privilege/helper/HashHelper.java index 47ed49dc1..628ac518a 100644 --- a/src/ch/eitchnet/privilege/helper/HashHelper.java +++ b/src/ch/eitchnet/privilege/helper/HashHelper.java @@ -1,10 +1,25 @@ /* - * Copyright (c) 2010 + * Copyright (c) 2010, 2011 * - * Robert von Burg - * eitch@eitchnet.ch + * Robert von Burg * - * All rights reserved. + */ + +/* + * This file is part of Privilege. + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . * */ @@ -17,7 +32,7 @@ import java.security.NoSuchAlgorithmException; /** * Helper class to hash a String for a certain hash algorithm, using the Java {@link MessageDigest} classes * - * @author rvonburg + * @author Robert von Burg */ public class HashHelper { diff --git a/src/ch/eitchnet/privilege/helper/InitializationHelper.java b/src/ch/eitchnet/privilege/helper/InitializationHelper.java index 03c4a60dd..f1b1c57e6 100644 --- a/src/ch/eitchnet/privilege/helper/InitializationHelper.java +++ b/src/ch/eitchnet/privilege/helper/InitializationHelper.java @@ -1,10 +1,25 @@ /* - * Copyright (c) 2010 + * Copyright (c) 2010, 2011 * - * Robert von Burg - * eitch@eitchnet.ch + * Robert von Burg * - * All rights reserved. + */ + +/* + * This file is part of Privilege. + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . * */ @@ -29,7 +44,7 @@ import ch.eitchnet.privilege.policy.PrivilegePolicy; * This class implements the initializing of the {@link PrivilegeHandler} by loading an XML file containing the * configuration * - * @author rvonburg + * @author Robert von Burg */ public class InitializationHelper { diff --git a/src/ch/eitchnet/privilege/helper/PasswordCreaterUI.java b/src/ch/eitchnet/privilege/helper/PasswordCreaterUI.java index 81690984a..ad6aa9546 100644 --- a/src/ch/eitchnet/privilege/helper/PasswordCreaterUI.java +++ b/src/ch/eitchnet/privilege/helper/PasswordCreaterUI.java @@ -1,17 +1,28 @@ /* - * Copyright (c) 2010 + * Copyright (c) 2010, 2011 * - * Apixxo AG - * Hauptgasse 25 - * 4600 Olten - * - * All rights reserved. + * Robert von Burg * */ -/** +/* + * This file is part of Privilege. + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . * */ + package ch.eitchnet.privilege.helper; import java.awt.Dimension; @@ -33,7 +44,7 @@ import javax.swing.SwingConstants; /** * Simple Swing UI to create passwords * - * @author rvonburg + * @author Robert von Burg */ public class PasswordCreaterUI { diff --git a/src/ch/eitchnet/privilege/helper/PasswordCreator.java b/src/ch/eitchnet/privilege/helper/PasswordCreator.java index a70dda885..f57a5615e 100644 --- a/src/ch/eitchnet/privilege/helper/PasswordCreator.java +++ b/src/ch/eitchnet/privilege/helper/PasswordCreator.java @@ -1,10 +1,25 @@ /* - * Copyright (c) 2010 + * Copyright (c) 2010, 2011 * - * Robert von Burg - * eitch@eitchnet.ch + * Robert von Burg * - * All rights reserved. + */ + +/* + * This file is part of Privilege. + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . * */ @@ -23,7 +38,7 @@ import java.security.MessageDigest; * TODO: Note: currently the password input is echoed which is a security risk. This is a TODO *

    * - * @author rvonburg + * @author Robert von Burg */ public class PasswordCreator { diff --git a/src/ch/eitchnet/privilege/helper/XmlConstants.java b/src/ch/eitchnet/privilege/helper/XmlConstants.java index 26bfb757b..54e3f4042 100644 --- a/src/ch/eitchnet/privilege/helper/XmlConstants.java +++ b/src/ch/eitchnet/privilege/helper/XmlConstants.java @@ -1,10 +1,25 @@ /* - * Copyright (c) 2010 + * Copyright (c) 2010, 2011 * - * Robert von Burg - * eitch@eitchnet.ch + * Robert von Burg * - * All rights reserved. + */ + +/* + * This file is part of Privilege. + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . * */ @@ -13,7 +28,7 @@ package ch.eitchnet.privilege.helper; /** * The constants used in parsing XML documents which contain the configuration for Privilege * - * @author rvonburg + * @author Robert von Burg */ public class XmlConstants { diff --git a/src/ch/eitchnet/privilege/helper/XmlHelper.java b/src/ch/eitchnet/privilege/helper/XmlHelper.java index c77dcec08..50433c212 100644 --- a/src/ch/eitchnet/privilege/helper/XmlHelper.java +++ b/src/ch/eitchnet/privilege/helper/XmlHelper.java @@ -1,10 +1,25 @@ /* - * Copyright (c) 2010 + * Copyright (c) 2010, 2011 * - * Robert von Burg - * eitch@eitchnet.ch + * Robert von Burg * - * All rights reserved. + */ + +/* + * This file is part of Privilege. + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . * */ @@ -32,7 +47,7 @@ import ch.eitchnet.privilege.i18n.PrivilegeException; /** * Helper class for performing XML based tasks using Dom4J * - * @author rvonburg + * @author Robert von Burg */ public class XmlHelper { diff --git a/src/ch/eitchnet/privilege/i18n/AccessDeniedException.java b/src/ch/eitchnet/privilege/i18n/AccessDeniedException.java index 339bd6fed..5e9029717 100644 --- a/src/ch/eitchnet/privilege/i18n/AccessDeniedException.java +++ b/src/ch/eitchnet/privilege/i18n/AccessDeniedException.java @@ -1,10 +1,25 @@ /* - * Copyright (c) 2010 + * Copyright (c) 2010, 2011 * - * Robert von Burg - * eitch@eitchnet.ch + * Robert von Burg * - * All rights reserved. + */ + +/* + * This file is part of Privilege. + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . * */ @@ -13,7 +28,7 @@ package ch.eitchnet.privilege.i18n; /** * Exception thrown if access is denied during login, or if a certain privilege is not granted * - * @author rvonburg + * @author Robert von Burg */ public class AccessDeniedException extends PrivilegeException { diff --git a/src/ch/eitchnet/privilege/i18n/PrivilegeException.java b/src/ch/eitchnet/privilege/i18n/PrivilegeException.java index decd696a9..db281372a 100644 --- a/src/ch/eitchnet/privilege/i18n/PrivilegeException.java +++ b/src/ch/eitchnet/privilege/i18n/PrivilegeException.java @@ -1,10 +1,25 @@ /* - * Copyright (c) 2010 + * Copyright (c) 2010, 2011 * - * Robert von Burg - * eitch@eitchnet.ch + * Robert von Burg * - * All rights reserved. + */ + +/* + * This file is part of Privilege. + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . * */ @@ -13,7 +28,7 @@ package ch.eitchnet.privilege.i18n; /** * Main {@link RuntimeException} thrown if something goes wrong in Privilege * - * @author rvonburg + * @author Robert von Burg */ public class PrivilegeException extends RuntimeException { diff --git a/src/ch/eitchnet/privilege/model/Certificate.java b/src/ch/eitchnet/privilege/model/Certificate.java index 7dfe3b6a9..7ae4f5f56 100644 --- a/src/ch/eitchnet/privilege/model/Certificate.java +++ b/src/ch/eitchnet/privilege/model/Certificate.java @@ -1,10 +1,25 @@ /* - * Copyright (c) 2010 + * Copyright (c) 2010, 2011 * - * Robert von Burg - * eitch@eitchnet.ch + * Robert von Burg * - * All rights reserved. + */ + +/* + * This file is part of Privilege. + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . * */ @@ -22,7 +37,7 @@ import ch.eitchnet.privilege.model.internal.Session; * instance which is always used when performing an access and is returned when a user performs a login through * {@link PrivilegeHandler#authenticate(String, String)} * - * @author rvonburg + * @author Robert von Burg */ public final class Certificate implements Serializable { @@ -38,7 +53,10 @@ public final class Certificate implements Serializable { /** * 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}

    + *

    + * 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 @@ -49,7 +67,8 @@ public final class Certificate implements Serializable { * It corresponds with the authentication token on the {@link Session} * @param authPassword * the password to access the authentication token, this is not known to the client but set by the - * {@link PrivilegeHandler} on authentication. It corresponds with the authentication password on the {@link Session} + * {@link PrivilegeHandler} on authentication. It corresponds with the authentication password on the + * {@link Session} * @param locale * the users {@link Locale} */ diff --git a/src/ch/eitchnet/privilege/model/PrivilegeRep.java b/src/ch/eitchnet/privilege/model/PrivilegeRep.java index 73c8727e4..6d39f685e 100644 --- a/src/ch/eitchnet/privilege/model/PrivilegeRep.java +++ b/src/ch/eitchnet/privilege/model/PrivilegeRep.java @@ -1,10 +1,25 @@ /* - * Copyright (c) 2010 + * Copyright (c) 2010, 2011 * - * Robert von Burg - * eitch@eitchnet.ch + * Robert von Burg * - * All rights reserved. + */ + +/* + * This file is part of Privilege. + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . * */ @@ -24,7 +39,7 @@ import ch.eitchnet.privilege.policy.PrivilegePolicy; * 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 Privilege} * - * @author rvonburg + * @author Robert von Burg */ public class PrivilegeRep implements Serializable { diff --git a/src/ch/eitchnet/privilege/model/Restrictable.java b/src/ch/eitchnet/privilege/model/Restrictable.java index 9122d52c5..d3d401a61 100644 --- a/src/ch/eitchnet/privilege/model/Restrictable.java +++ b/src/ch/eitchnet/privilege/model/Restrictable.java @@ -1,10 +1,25 @@ /* - * Copyright (c) 2010 + * Copyright (c) 2010, 2011 * - * Robert von Burg - * eitch@eitchnet.ch + * Robert von Burg * - * All rights reserved. + */ + +/* + * This file is part of Privilege. + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . * */ @@ -21,7 +36,7 @@ import ch.eitchnet.privilege.policy.PrivilegePolicy; * for evaluating access *

    * - * @author rvonburg + * @author Robert von Burg * */ public interface Restrictable { diff --git a/src/ch/eitchnet/privilege/model/RoleRep.java b/src/ch/eitchnet/privilege/model/RoleRep.java index fde568af3..4af09ca17 100644 --- a/src/ch/eitchnet/privilege/model/RoleRep.java +++ b/src/ch/eitchnet/privilege/model/RoleRep.java @@ -1,10 +1,25 @@ /* - * Copyright (c) 2010 + * Copyright (c) 2010, 2011 * - * Robert von Burg - * eitch@eitchnet.ch + * Robert von Burg * - * All rights reserved. + */ + +/* + * This file is part of Privilege. + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . * */ @@ -20,7 +35,7 @@ import ch.eitchnet.privilege.model.internal.Role; * 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 rvonburg + * @author Robert von Burg */ public class RoleRep implements Serializable { diff --git a/src/ch/eitchnet/privilege/model/UserRep.java b/src/ch/eitchnet/privilege/model/UserRep.java index 7efbb8ab9..491d42fda 100644 --- a/src/ch/eitchnet/privilege/model/UserRep.java +++ b/src/ch/eitchnet/privilege/model/UserRep.java @@ -1,10 +1,25 @@ /* - * Copyright (c) 2010 + * Copyright (c) 2010, 2011 * - * Robert von Burg - * eitch@eitchnet.ch + * Robert von Burg * - * All rights reserved. + */ + +/* + * This file is part of Privilege. + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . * */ @@ -22,7 +37,7 @@ import ch.eitchnet.privilege.model.internal.User; * 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 rvonburg + * @author Robert von Burg */ public class UserRep implements Serializable { diff --git a/src/ch/eitchnet/privilege/model/UserState.java b/src/ch/eitchnet/privilege/model/UserState.java index ab1fbde5e..d98b9df14 100644 --- a/src/ch/eitchnet/privilege/model/UserState.java +++ b/src/ch/eitchnet/privilege/model/UserState.java @@ -1,10 +1,25 @@ /* - * Copyright (c) 2010 + * Copyright (c) 2010, 2011 * - * Robert von Burg - * eitch@eitchnet.ch + * Robert von Burg * - * All rights reserved. + */ + +/* + * This file is part of Privilege. + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . * */ @@ -22,7 +37,7 @@ import ch.eitchnet.privilege.model.internal.User; *
  • EXPIRED - the user has automatically expired through a predefined time
  • * * - * @author rvonburg + * @author Robert von Burg * */ public enum UserState { diff --git a/src/ch/eitchnet/privilege/model/internal/Privilege.java b/src/ch/eitchnet/privilege/model/internal/Privilege.java index 254df64dd..96fa4b418 100644 --- a/src/ch/eitchnet/privilege/model/internal/Privilege.java +++ b/src/ch/eitchnet/privilege/model/internal/Privilege.java @@ -1,10 +1,25 @@ /* - * Copyright (c) 2010 + * Copyright (c) 2010, 2011 * - * Robert von Burg - * eitch@eitchnet.ch + * Robert von Burg * - * All rights reserved. + */ + +/* + * This file is part of Privilege. + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . * */ @@ -37,7 +52,7 @@ import ch.eitchnet.privilege.policy.PrivilegePolicy; * for that *

    * - * @author rvonburg + * @author Robert von Burg */ public final class Privilege { diff --git a/src/ch/eitchnet/privilege/model/internal/Role.java b/src/ch/eitchnet/privilege/model/internal/Role.java index 2d6a9e632..aa76e6bd6 100644 --- a/src/ch/eitchnet/privilege/model/internal/Role.java +++ b/src/ch/eitchnet/privilege/model/internal/Role.java @@ -1,10 +1,25 @@ /* - * Copyright (c) 2010 + * Copyright (c) 2010, 2011 * - * Robert von Burg - * eitch@eitchnet.ch + * Robert von Burg * - * All rights reserved. + */ + +/* + * This file is part of Privilege. + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . * */ @@ -29,7 +44,7 @@ import ch.eitchnet.privilege.model.RoleRep; * that *

    * - * @author rvonburg + * @author Robert von Burg */ public final class Role { diff --git a/src/ch/eitchnet/privilege/model/internal/Session.java b/src/ch/eitchnet/privilege/model/internal/Session.java index 7e0df5272..2210c8fc3 100644 --- a/src/ch/eitchnet/privilege/model/internal/Session.java +++ b/src/ch/eitchnet/privilege/model/internal/Session.java @@ -1,10 +1,25 @@ /* - * Copyright (c) 2010 + * Copyright (c) 2010, 2011 * - * Robert von Burg - * eitch@eitchnet.ch + * Robert von Burg * - * All rights reserved. + */ + +/* + * This file is part of Privilege. + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . * */ @@ -26,7 +41,7 @@ import ch.eitchnet.privilege.model.Certificate; * {@link Certificate} which is used for accessing privileges *

    * - * @author rvonburg + * @author Robert von Burg */ public final class Session { diff --git a/src/ch/eitchnet/privilege/model/internal/User.java b/src/ch/eitchnet/privilege/model/internal/User.java index 84ee1e2eb..e72908652 100644 --- a/src/ch/eitchnet/privilege/model/internal/User.java +++ b/src/ch/eitchnet/privilege/model/internal/User.java @@ -1,10 +1,25 @@ /* - * Copyright (c) 2010 + * Copyright (c) 2010, 2011 * - * Robert von Burg - * eitch@eitchnet.ch + * Robert von Burg * - * All rights reserved. + */ + +/* + * This file is part of Privilege. + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . * */ @@ -28,7 +43,7 @@ import ch.eitchnet.privilege.model.UserState; * that *

    * - * @author rvonburg + * @author Robert von Burg * */ public final class User { diff --git a/src/ch/eitchnet/privilege/policy/DefaultPrivilege.java b/src/ch/eitchnet/privilege/policy/DefaultPrivilege.java index b455ef14b..bd67adf0e 100644 --- a/src/ch/eitchnet/privilege/policy/DefaultPrivilege.java +++ b/src/ch/eitchnet/privilege/policy/DefaultPrivilege.java @@ -1,10 +1,25 @@ /* - * Copyright (c) 2010 + * Copyright (c) 2010, 2011 * - * Robert von Burg - * eitch@eitchnet.ch + * Robert von Burg * - * All rights reserved. + */ + +/* + * This file is part of Privilege. + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . * */ @@ -19,7 +34,7 @@ import ch.eitchnet.privilege.model.internal.Role; /** * XXX re-think this implementation... * - * @author rvonburg + * @author Robert von Burg */ public class DefaultPrivilege implements PrivilegePolicy { diff --git a/src/ch/eitchnet/privilege/policy/PrivilegePolicy.java b/src/ch/eitchnet/privilege/policy/PrivilegePolicy.java index 63807fe1c..9c0d14bb0 100644 --- a/src/ch/eitchnet/privilege/policy/PrivilegePolicy.java +++ b/src/ch/eitchnet/privilege/policy/PrivilegePolicy.java @@ -1,10 +1,25 @@ /* - * Copyright (c) 2010 + * Copyright (c) 2010, 2011 * - * Robert von Burg - * eitch@eitchnet.ch + * Robert von Burg * - * All rights reserved. + */ + +/* + * This file is part of Privilege. + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . * */ @@ -27,7 +42,7 @@ import ch.eitchnet.privilege.model.internal.User; * need one with out the {@link Privilege} in its signature? *

    * - * @author rvonburg + * @author Robert von Burg */ public interface PrivilegePolicy { diff --git a/test/ch/eitchnet/privilege/test/PrivilegeTest.java b/test/ch/eitchnet/privilege/test/PrivilegeTest.java index 1a375e233..72370488f 100644 --- a/test/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/test/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -1,10 +1,25 @@ /* - * Copyright (c) 2010 + * Copyright (c) 2010, 2011 * - * Robert von Burg - * eitch@eitchnet.ch + * Robert von Burg * - * All rights reserved. + */ + +/* + * This file is part of Privilege. + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . * */ @@ -38,7 +53,7 @@ import ch.eitchnet.privilege.model.UserState; * JUnit for performing Privilege tests. This JUnit is by no means complete, but checks the bare minimum. TODO add more * tests, especially with deny and allow lists * - * @author rvonburg + * @author Robert von Burg */ public class PrivilegeTest { diff --git a/test/ch/eitchnet/privilege/test/TestRestrictable.java b/test/ch/eitchnet/privilege/test/TestRestrictable.java index ee8921722..e384e89df 100644 --- a/test/ch/eitchnet/privilege/test/TestRestrictable.java +++ b/test/ch/eitchnet/privilege/test/TestRestrictable.java @@ -1,10 +1,25 @@ /* - * Copyright (c) 2010 + * Copyright (c) 2010, 2011 * - * Robert von Burg - * eitch@eitchnet.ch + * Robert von Burg * - * All rights reserved. + */ + +/* + * This file is part of Privilege. + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . * */ @@ -13,7 +28,7 @@ package ch.eitchnet.privilege.test; import ch.eitchnet.privilege.model.Restrictable; /** - * @author rvonburg + * @author Robert von Burg * */ public class TestRestrictable implements Restrictable { From c594d373d40df1f5ae572041bee761ad4fc6c92a Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 7 Aug 2011 13:48:56 +0200 Subject: [PATCH 062/173] [New] added the README file with a general overview, it is not finished, but has the basics --- README | 103 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 README diff --git a/README b/README new file mode 100644 index 000000000..c4c3ef642 --- /dev/null +++ b/README @@ -0,0 +1,103 @@ + +Privilege README file + +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/eitch/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 +################################ + +Since Privilege is a Java library, it is built using Apache Ant. The build.xml +file is configured to build Privilege directly from the root directory by simply +calling ant at the command line: + +$ ant +Buildfile: /data/src/apixxo_WS/Privilege/build.xml + +dist: + [mkdir] Created dir: /data/src/apixxo_WS/Privilege/dist + [copy] Copying 28 files to /data/src/apixxo_WS/Privilege/bin + [jar] Building jar: /data/src/apixxo_WS/Privilege/dist/Privilege.jar + +BUILD SUCCESSFUL +Total time: 0 seconds + +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 7. August 2011 + Robert von Burg + + + From 8967f78113c086e3c41754effb2b67e48e34c8ba Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 7 Aug 2011 13:52:09 +0200 Subject: [PATCH 063/173] [Minor] Added the build directory dist to the ignore list --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..e493dd6ee --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +bin +dist From bb515756cb6ff767aa1a1644da1c150b42470ebb Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 7 Aug 2011 16:00:38 +0200 Subject: [PATCH 064/173] [New] Changed Privilege Users to have a map of properties with which meta data of a user can be stored. This can be used for validationg privileges on user specific data --- config/PrivilegeModel.xml | 4 + .../handler/DefaultPrivilegeHandler.java | 16 ++-- .../handler/XmlPersistenceHandler.java | 96 +++++++++++++++++-- .../helper/InitializationHelper.java | 9 +- .../privilege/helper/XmlConstants.java | 10 ++ src/ch/eitchnet/privilege/model/UserRep.java | 49 +++++++++- .../privilege/model/internal/User.java | 42 +++++++- .../privilege/test/PrivilegeTest.java | 9 +- 8 files changed, 208 insertions(+), 27 deletions(-) diff --git a/config/PrivilegeModel.xml b/config/PrivilegeModel.xml index a9c40c5fa..e43216141 100644 --- a/config/PrivilegeModel.xml +++ b/config/PrivilegeModel.xml @@ -34,6 +34,10 @@ along with Privilege. If not, see . PrivilegeAdmin FeatherliteUser + + + + diff --git a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 07b38e558..5068b6ed0 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -203,8 +203,10 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } // create new user + // XXX should the collections be recreated and the getRoles() and getProperties() methods be removed? User user = new User(userRep.getUserId(), userRep.getUsername(), passwordHash, userRep.getFirstname(), - userRep.getSurname(), userRep.getUserState(), userRep.getRoles(), userRep.getLocale()); + userRep.getSurname(), userRep.getUserState(), userRep.getRoles(), userRep.getLocale(), + userRep.getProperties()); // delegate to persistence handler this.persistenceHandler.addOrReplaceUser(user); @@ -280,7 +282,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { newRoles.add(roleName); User newUser = new User(user.getUserId(), user.getUsername(), user.getPassword(), user.getFirstname(), - user.getSurname(), user.getUserState(), newRoles, user.getLocale()); + user.getSurname(), user.getUserState(), newRoles, user.getLocale(), user.getProperties()); // delegate user replacement to persistence handler this.persistenceHandler.addOrReplaceUser(newUser); @@ -367,7 +369,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { Set newRoles = new HashSet(currentRoles); newRoles.remove(roleName); User newUser = new User(user.getUserId(), user.getUsername(), user.getPassword(), user.getFirstname(), - user.getSurname(), user.getUserState(), newRoles, user.getLocale()); + user.getSurname(), user.getUserState(), newRoles, user.getLocale(), user.getProperties()); // delegate user replacement to persistence handler this.persistenceHandler.addOrReplaceUser(newUser); @@ -413,7 +415,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // create new user User newUser = new User(user.getUserId(), user.getUsername(), user.getPassword(), user.getFirstname(), - user.getSurname(), user.getUserState(), user.getRoles(), locale); + user.getSurname(), user.getUserState(), user.getRoles(), locale, user.getProperties()); // delegate user replacement to persistence handler this.persistenceHandler.addOrReplaceUser(newUser); @@ -437,7 +439,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // create new user User newUser = new User(user.getUserId(), user.getUsername(), user.getPassword(), firstname, surname, - user.getUserState(), user.getRoles(), user.getLocale()); + user.getUserState(), user.getRoles(), user.getLocale(), user.getProperties()); // delegate user replacement to persistence handler this.persistenceHandler.addOrReplaceUser(newUser); @@ -471,7 +473,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // create new user User newUser = new User(user.getUserId(), user.getUsername(), passwordHash, user.getFirstname(), - user.getSurname(), user.getUserState(), user.getRoles(), user.getLocale()); + user.getSurname(), user.getUserState(), user.getRoles(), user.getLocale(), user.getProperties()); // delegate user replacement to persistence handler this.persistenceHandler.addOrReplaceUser(newUser); @@ -495,7 +497,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // create new user User newUser = new User(user.getUserId(), user.getUsername(), user.getPassword(), user.getFirstname(), - user.getSurname(), state, user.getRoles(), user.getLocale()); + user.getSurname(), state, user.getRoles(), user.getLocale(), user.getProperties()); // delegate user replacement to persistence handler this.persistenceHandler.addOrReplaceUser(newUser); diff --git a/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java b/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java index 7fb74dd70..b5a962f4d 100644 --- a/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java @@ -56,7 +56,7 @@ import ch.eitchnet.privilege.model.internal.User; */ public class XmlPersistenceHandler implements PersistenceHandler { - private static final Logger logger = Logger.getLogger(XmlPersistenceHandler.class); + protected static final Logger logger = Logger.getLogger(XmlPersistenceHandler.class); private Map userMap; private Map roleMap; @@ -289,9 +289,14 @@ public class XmlPersistenceHandler implements PersistenceHandler { } /** + * Parses {@link User} objects from their XML representations + * * @param usersRootElement + * the element containing suer elements + * + * @return the map of converted {@link User} objects */ - private static Map readUsers(Element usersRootElement) { + protected static Map readUsers(Element usersRootElement) { Map userMap = new HashMap(); @@ -313,6 +318,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { String localeName = userElement.element(XmlConstants.XML_LOCALE).getTextTrim(); Locale locale = new Locale(localeName); + // read roles Element rolesElement = userElement.element(XmlConstants.XML_ROLES); @SuppressWarnings("unchecked") List rolesElementList = rolesElement.elements(XmlConstants.XML_ROLE); @@ -326,9 +332,12 @@ public class XmlPersistenceHandler implements PersistenceHandler { } } + // read properties + Element propertiesElement = userElement.element(XmlConstants.XML_PROPERTIES); + Map propertyMap = convertToPropertyMap(propertiesElement); + // create user - User user = new User(userId, username, password, firstname, surname, userState, - Collections.unmodifiableSet(roles), locale); + User user = new User(userId, username, password, firstname, surname, userState, roles, locale, propertyMap); logger.info("Added user " + user); // put user in map @@ -339,9 +348,14 @@ public class XmlPersistenceHandler implements PersistenceHandler { } /** + * Parses {@link Role} objects from their XML representations + * * @param rolesRootElement + * the element containing role elements + * + * @return the map of converted {@link Role} objects */ - private static Map readRoles(Element rolesRootElement) { + protected static Map readRoles(Element rolesRootElement) { Map roleMap = new HashMap(); @@ -360,7 +374,15 @@ public class XmlPersistenceHandler implements PersistenceHandler { return roleMap; } - private static List toDomUsers(Map userMap) { + /** + * Converts {@link User} objects to their XML representations + * + * @param userMap + * the map of users to convert + * + * @return the list of XML User elements + */ + protected static List toDomUsers(Map userMap) { List usersAsElements = new ArrayList(userMap.size()); @@ -415,7 +437,15 @@ public class XmlPersistenceHandler implements PersistenceHandler { return usersAsElements; } - private static List toDomRoles(Map roleMap) { + /** + * Converts {@link Role} objects to their XML representations + * + * @param roleMap + * the roles to convert + * + * @return the list of XML Role elements + */ + protected static List toDomRoles(Map roleMap) { List rolesAsElements = new ArrayList(roleMap.size()); @@ -443,10 +473,14 @@ public class XmlPersistenceHandler implements PersistenceHandler { } /** + * Parses {@link Privilege} objects from their XML representation to their objects + * * @param roleParentElement - * @return + * the parent on which the Privilege XML elements are + * + * @return the map of {@link Privilege} objects */ - private static Map readPrivileges(Element roleParentElement) { + protected static Map readPrivileges(Element roleParentElement) { Map privilegeMap = new HashMap(); @@ -485,7 +519,15 @@ public class XmlPersistenceHandler implements PersistenceHandler { return privilegeMap; } - private static void toDomPrivileges(Element roleParentElement, Map privilegeMap) { + /** + * Converts {@link Privilege} objects to their XML representation + * + * @param roleParentElement + * the XML element of the parent {@link Role} + * @param privilegeMap + * the map of {@link Privilege}s to convert + */ + protected static void toDomPrivileges(Element roleParentElement, Map privilegeMap) { DocumentFactory documentFactory = DocumentFactory.getInstance(); @@ -519,4 +561,38 @@ public class XmlPersistenceHandler implements PersistenceHandler { roleParentElement.add(privilegeElement); } } + + /** + * Converts an {@link XmlConstants#XML_PROPERTIES} element containing {@link XmlConstants#XML_PROPERTY} elements to + * a {@link Map} of String key/value pairs + * + * @param element + * the XML {@link Element} with name {@link XmlConstants#XML_PROPERTIES} containing + * {@link XmlConstants#XML_PROPERTY} elements + * + * @return the {@link Map} of the property name/value combinations from the given {@link Element} + */ + @SuppressWarnings("unchecked") + protected static Map convertToPropertyMap(Element element) { + + // if element is null then there are no properties, so return empty map + if (element == null) + return Collections.emptyMap(); + + List elements = element.elements(XmlConstants.XML_PROPERTY); + + // if elements is null or empty then there are no properties, so return empty map + if (elements == null || elements.isEmpty()) + return Collections.emptyMap(); + + Map propertyMap = new HashMap(); + + for (Element property : elements) { + String name = property.attributeValue(XmlConstants.XML_ATTR_NAME); + String value = property.attributeValue(XmlConstants.XML_ATTR_VALUE); + propertyMap.put(name, value); + } + + return propertyMap; + } } diff --git a/src/ch/eitchnet/privilege/helper/InitializationHelper.java b/src/ch/eitchnet/privilege/helper/InitializationHelper.java index f1b1c57e6..9f2dda7ba 100644 --- a/src/ch/eitchnet/privilege/helper/InitializationHelper.java +++ b/src/ch/eitchnet/privilege/helper/InitializationHelper.java @@ -26,6 +26,7 @@ package ch.eitchnet.privilege.helper; import java.io.File; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -147,17 +148,17 @@ public class InitializationHelper { @SuppressWarnings("unchecked") public static Map convertToParameterMap(Element element) { - Map parameterMap = new HashMap(); - // if element is null then there are no parameters, so return empty map if (element == null) - return parameterMap; + return Collections.emptyMap(); List elements = element.elements(XmlConstants.XML_PARAMETER); // if elements is null or empty then there are no parameters, so return empty map if (elements == null || elements.isEmpty()) - return parameterMap; + return Collections.emptyMap(); + + Map parameterMap = new HashMap(); for (Element parameter : elements) { String name = parameter.attributeValue(XmlConstants.XML_ATTR_NAME); diff --git a/src/ch/eitchnet/privilege/helper/XmlConstants.java b/src/ch/eitchnet/privilege/helper/XmlConstants.java index 54e3f4042..c554473a9 100644 --- a/src/ch/eitchnet/privilege/helper/XmlConstants.java +++ b/src/ch/eitchnet/privilege/helper/XmlConstants.java @@ -112,6 +112,16 @@ public class XmlConstants { */ 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" : */ diff --git a/src/ch/eitchnet/privilege/model/UserRep.java b/src/ch/eitchnet/privilege/model/UserRep.java index 491d42fda..38e3562e3 100644 --- a/src/ch/eitchnet/privilege/model/UserRep.java +++ b/src/ch/eitchnet/privilege/model/UserRep.java @@ -27,6 +27,7 @@ package ch.eitchnet.privilege.model; import java.io.Serializable; import java.util.Locale; +import java.util.Map; import java.util.Set; import ch.eitchnet.privilege.model.internal.Role; @@ -50,6 +51,7 @@ public class UserRep implements Serializable { private UserState userState; private Set roles; private Locale locale; + private Map propertyMap; /** * Default constructor @@ -68,9 +70,11 @@ public class UserRep implements Serializable { * 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 surname, UserState userState, - Set roles, Locale locale) { + Set roles, Locale locale, Map propertyMap) { this.userId = userId; this.username = username; this.firstname = firstname; @@ -78,6 +82,7 @@ public class UserRep implements Serializable { this.userState = userState; this.roles = roles; this.locale = locale; + this.propertyMap = propertyMap; } /** @@ -176,4 +181,46 @@ public class UserRep implements Serializable { 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) { + return this.propertyMap.get(key); + } + + /** + * 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) { + this.propertyMap.put(key, value); + } + + /** + * 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; + } } diff --git a/src/ch/eitchnet/privilege/model/internal/User.java b/src/ch/eitchnet/privilege/model/internal/User.java index e72908652..bdfc7a438 100644 --- a/src/ch/eitchnet/privilege/model/internal/User.java +++ b/src/ch/eitchnet/privilege/model/internal/User.java @@ -26,8 +26,10 @@ 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.i18n.PrivilegeException; @@ -60,6 +62,8 @@ public final class User { private final Set roles; + private final Map propertyMap; + private final Locale locale; /** @@ -81,9 +85,11 @@ public final class User { * 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 surname, UserState userState, - Set roles, Locale locale) { + Set roles, Locale locale, Map propertyMap) { if (userId == null || userId.isEmpty()) { throw new PrivilegeException("No UserId defined!"); @@ -120,6 +126,8 @@ public final class User { this.roles = Collections.unmodifiableSet(roles); this.locale = locale; + + this.propertyMap = Collections.unmodifiableMap(propertyMap); } /** @@ -202,7 +210,7 @@ public final class User { */ public UserRep asUserRep() { return new UserRep(this.userId, this.username, this.firstname, this.surname, this.userState, - new HashSet(this.roles), this.locale); + new HashSet(this.roles), this.locale, new HashMap(this.propertyMap)); } /** @@ -259,4 +267,34 @@ public final class User { return false; return true; } + + /** + * 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; + } } diff --git a/test/ch/eitchnet/privilege/test/PrivilegeTest.java b/test/ch/eitchnet/privilege/test/PrivilegeTest.java index 72370488f..641ad883e 100644 --- a/test/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/test/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -140,7 +140,8 @@ public class PrivilegeTest { Certificate certificate = privilegeHandler.authenticate(ADMIN, PASS_ADMIN); // let's add a new user bob - UserRep userRep = new UserRep("1", BOB, "Bob", "Newman", UserState.NEW, new HashSet(), null); + UserRep userRep = new UserRep("1", BOB, "Bob", "Newman", UserState.NEW, new HashSet(), null, + new HashMap()); privilegeHandler.addOrReplaceUser(certificate, userRep, null); logger.info("Added user " + BOB); @@ -237,7 +238,8 @@ public class PrivilegeTest { org.junit.Assert.assertTrue("Certificate is null!", certificate != null); // let's add a new user Ted - UserRep userRep = new UserRep("1", TED, "Ted", "And then Some", UserState.NEW, new HashSet(), null); + UserRep userRep = new UserRep("1", TED, "Ted", "And then Some", UserState.NEW, new HashSet(), null, + new HashMap()); privilegeHandler.addOrReplaceUser(certificate, userRep, null); logger.info("Added user " + TED); privilegeHandler.invalidateSession(certificate); @@ -267,7 +269,8 @@ public class PrivilegeTest { org.junit.Assert.assertTrue("Certificate is null!", certificate != null); // let's add a new user ted - UserRep userRep = new UserRep("2", TED, "Ted", "Newman", UserState.NEW, new HashSet(), null); + UserRep userRep = new UserRep("2", TED, "Ted", "Newman", UserState.NEW, new HashSet(), null, + new HashMap()); privilegeHandler.addOrReplaceUser(certificate, userRep, null); logger.info("Added user " + TED); privilegeHandler.invalidateSession(certificate); From 058e67f10eeb2be95dd4fcc8f8d34bee9ad63b2a Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 7 Aug 2011 16:13:23 +0200 Subject: [PATCH 065/173] [New] it is now possible for a user to change their own password --- .../handler/DefaultPrivilegeHandler.java | 13 +++++- .../privilege/handler/PrivilegeHandler.java | 6 +++ .../privilege/test/PrivilegeTest.java | 44 ++++++++++++++++++- 3 files changed, 60 insertions(+), 3 deletions(-) diff --git a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 5068b6ed0..55471cb63 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -452,8 +452,17 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { @Override public void setUserPassword(Certificate certificate, String username, String password) { - // validate who is doing this - validateIsPrivilegeAdmin(certificate); + // check if certificate is for same user, in which case user is changing their own password + if (certificate.getUsername().equals(username)) { + + // validate the certificate + isCertificateValid(certificate); + + } else { + + // otherwise validate the the certificate is for a privilege admin + validateIsPrivilegeAdmin(certificate); + } // get User User user = this.persistenceHandler.getUser(username); diff --git a/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java b/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java index 2af410f25..03bda0c84 100644 --- a/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java +++ b/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java @@ -227,9 +227,15 @@ public interface PrivilegeHandler { 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(String)} + *

    + * + *

    + * 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 diff --git a/test/ch/eitchnet/privilege/test/PrivilegeTest.java b/test/ch/eitchnet/privilege/test/PrivilegeTest.java index 641ad883e..859ff4415 100644 --- a/test/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/test/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -64,7 +64,9 @@ public class PrivilegeTest { private static final String PASS_BOB = "admin1"; private static final String ROLE_FEATHERLITE_USER = "FeatherliteUser"; private static final String ROLE_USER = "user"; + private static final String PASS_DEF = "def"; private static final String PASS_BAD = "123"; + private static final String PASS_TED = "12345"; private static final Logger logger = Logger.getLogger(PrivilegeTest.class); @@ -269,10 +271,50 @@ public class PrivilegeTest { org.junit.Assert.assertTrue("Certificate is null!", certificate != null); // let's add a new user ted - UserRep userRep = new UserRep("2", TED, "Ted", "Newman", UserState.NEW, new HashSet(), null, + HashSet roles = new HashSet(); + roles.add(ROLE_USER); + UserRep userRep = new UserRep("2", TED, "Ted", "Newman", UserState.ENABLED, roles, null, new HashMap()); privilegeHandler.addOrReplaceUser(certificate, userRep, null); logger.info("Added user " + TED); + + privilegeHandler.invalidateSession(certificate); + } + + /** + * @throws Exception + * if something goes wrong + */ + @Test + public void testSetTedPwdAsBob() throws Exception { + + Certificate certificate = privilegeHandler.authenticate(BOB, PASS_BOB); + org.junit.Assert.assertTrue("Certificate is null!", certificate != null); + + // set ted's password to default + privilegeHandler.setUserPassword(certificate, TED, PASS_DEF); + + privilegeHandler.invalidateSession(certificate); + } + + /** + * @throws Exception + * if something goes wrong + */ + @Test + public void testTedChangesOwnPwd() throws Exception { + Certificate certificate = privilegeHandler.authenticate(TED, PASS_DEF); + privilegeHandler.setUserPassword(certificate, TED, PASS_TED); + privilegeHandler.invalidateSession(certificate); + } + + /** + * @throws Exception + * if something goes wrong + */ + @Test + public void testAuthAsTed() throws Exception { + Certificate certificate = privilegeHandler.authenticate(TED, PASS_TED); privilegeHandler.invalidateSession(certificate); } From cb6215b2353e848cea054e52e37d1fe6c4756631 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Wed, 9 Nov 2011 15:03:50 +0100 Subject: [PATCH 066/173] [Bugfix] fixed a bug where if the tag was not defined in the PrivilegeModel.xml file then a NullPointer was thrown. It is now possible to leave out this tag, in which case false is assumed --- .../eitchnet/privilege/handler/XmlPersistenceHandler.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java b/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java index b5a962f4d..6910ad735 100644 --- a/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java +++ b/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java @@ -491,8 +491,11 @@ public class XmlPersistenceHandler implements PersistenceHandler { String privilegeName = privilegeElement.attributeValue(XmlConstants.XML_ATTR_NAME); String privilegePolicy = privilegeElement.attributeValue(XmlConstants.XML_ATTR_POLICY); - String allAllowedS = privilegeElement.element(XmlConstants.XML_ALL_ALLOWED).getTextTrim(); - boolean allAllowed = Boolean.valueOf(allAllowedS).booleanValue(); + Element allAllowedE = privilegeElement.element(XmlConstants.XML_ALL_ALLOWED); + boolean allAllowed = false; + if (allAllowedE != null) { + allAllowed = Boolean.valueOf(allAllowedE.getTextTrim()).booleanValue(); + } @SuppressWarnings("unchecked") List denyElements = privilegeElement.elements(XmlConstants.XML_DENY); From db3c086d391f18c789b5a85942b7a444a485e1c9 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Wed, 9 Nov 2011 15:05:05 +0100 Subject: [PATCH 067/173] [Minor] minor message change where if is false, the policy, deny and allow must be defined. This is now better communicated in the exception message --- src/ch/eitchnet/privilege/model/internal/Privilege.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ch/eitchnet/privilege/model/internal/Privilege.java b/src/ch/eitchnet/privilege/model/internal/Privilege.java index 96fa4b418..2ee6121f5 100644 --- a/src/ch/eitchnet/privilege/model/internal/Privilege.java +++ b/src/ch/eitchnet/privilege/model/internal/Privilege.java @@ -101,17 +101,17 @@ public final class Privilege { // not all allowed, so policy must be set if (policy == null || policy.isEmpty()) { - throw new PrivilegeException("No Policy defined!"); + throw new PrivilegeException("All is not allowed and no Policy defined!"); } this.policy = policy; if (denyList == null) { - throw new PrivilegeException("No denyList defined!"); + throw new PrivilegeException("All is not allowed and no denyList defined!"); } this.denyList = Collections.unmodifiableSet(denyList); if (allowList == null) { - throw new PrivilegeException("No allowList defined!"); + throw new PrivilegeException("All is not allowed and no allowList defined!"); } this.allowList = Collections.unmodifiableSet(allowList); } From 3c322c5b8869f0174bfec1761e77fd99f0974b85 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Wed, 9 Nov 2011 15:05:36 +0100 Subject: [PATCH 068/173] [Internal] minor project building changes --- MANIFEST.MF | 4 ++-- build.xml | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/MANIFEST.MF b/MANIFEST.MF index c30795c80..ab9c3e6d7 100644 --- a/MANIFEST.MF +++ b/MANIFEST.MF @@ -1,7 +1,7 @@ Manifest-Version: 1.0 Implementation-Vendor: eitchnet.ch Implementation-Title: eitchnet Java Privilege implementation -Implementation-Version: 0.0.1 +Implementation-Version: 0.0.2 Specification-Vendor: eitchnet.ch Specification-Title: eitchnet Java Privilege implementation -Specification-Version: 1.6 +Specification-Version: 0.1 diff --git a/build.xml b/build.xml index 64bb5f254..34f371db8 100644 --- a/build.xml +++ b/build.xml @@ -59,6 +59,7 @@ along with Privilege. If not, see . + From 81cb2aa8ae947e854dc03c8d7034fccc3a56eea1 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 11 Nov 2011 10:56:56 +0100 Subject: [PATCH 069/173] [Interface] added a property map to the Certificate which is a copy of the users properties on login. This map can be used to configure the user's session --- .../handler/DefaultPrivilegeHandler.java | 4 +++- .../eitchnet/privilege/model/Certificate.java | 18 +++++++++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 55471cb63..28442e470 100644 --- a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -564,7 +564,9 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // get next session id String sessionId = nextSessionId(); - certificate = new Certificate(sessionId, username, authToken, authPassword, user.getLocale()); + // create a new certificate, with details of the user + certificate = new Certificate(sessionId, username, authToken, authPassword, user.getLocale(), + new HashMap(user.getProperties())); // create and save a new session Session session = new Session(sessionId, username, authToken, authPassword, System.currentTimeMillis()); diff --git a/src/ch/eitchnet/privilege/model/Certificate.java b/src/ch/eitchnet/privilege/model/Certificate.java index 7ae4f5f56..762ff0606 100644 --- a/src/ch/eitchnet/privilege/model/Certificate.java +++ b/src/ch/eitchnet/privilege/model/Certificate.java @@ -27,6 +27,7 @@ package ch.eitchnet.privilege.model; import java.io.Serializable; import java.util.Locale; +import java.util.Map; import ch.eitchnet.privilege.handler.PrivilegeHandler; import ch.eitchnet.privilege.i18n.PrivilegeException; @@ -50,6 +51,8 @@ public final class Certificate implements Serializable { private Locale locale; + private Map propertyMap; + /** * Default constructor initializing with all information needed for this certificate * @@ -71,8 +74,12 @@ public final class Certificate implements Serializable { * {@link Session} * @param locale * the users {@link Locale} + * @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 authToken, String authPassword, Locale locale) { + public Certificate(String sessionId, String username, String authToken, String authPassword, Locale locale, + Map propertyMap) { // validate arguments are not null if (sessionId == null || username == null || authToken == null || authPassword == null) { @@ -89,6 +96,15 @@ public final class Certificate implements Serializable { this.locale = Locale.getDefault(); else this.locale = locale; + + this.propertyMap = propertyMap; + } + + /** + * @return the propertyMap + */ + public Map getPropertyMap() { + return this.propertyMap; } /** From c44b30a1e1546893d0c3101a53459c5f0548a64a Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 11 Nov 2011 10:58:19 +0100 Subject: [PATCH 070/173] [Internal] bumped the MANIFEST.MF version to 0.0.3 --- MANIFEST.MF | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MANIFEST.MF b/MANIFEST.MF index ab9c3e6d7..8a69d98b1 100644 --- a/MANIFEST.MF +++ b/MANIFEST.MF @@ -1,7 +1,7 @@ Manifest-Version: 1.0 Implementation-Vendor: eitchnet.ch Implementation-Title: eitchnet Java Privilege implementation -Implementation-Version: 0.0.2 +Implementation-Version: 0.0.3 Specification-Vendor: eitchnet.ch Specification-Title: eitchnet Java Privilege implementation Specification-Version: 0.1 From 0e026bf7130716dd4862f64d6755b6bfdfeb577a Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 28 Jul 2012 15:40:01 +0200 Subject: [PATCH 071/173] [Major] rebuilt project to use Maven. - Now a "mvn compile" will build the project - Note, that since ch.eitchnet.utils is a dependency, it must be installed first using "mvn install" to be able to compile Privilege --- .classpath | 10 - .gitignore | 4 + .project | 17 -- .settings/org.eclipse.jdt.core.prefs | 87 --------- lib/dom4j-1.6.1-src.zip | Bin 440064 -> 0 bytes lib/dom4j-1.6.1.jar | Bin 313898 -> 0 bytes lib/log4j-1.2.15.jar | Bin 391834 -> 0 bytes pom.xml | 176 ++++++++++++++++++ .../handler/DefaultEncryptionHandler.java | 0 .../handler/DefaultPrivilegeHandler.java | 0 .../privilege/handler/EncryptionHandler.java | 0 .../privilege/handler/PersistenceHandler.java | 0 .../privilege/handler/PrivilegeHandler.java | 0 .../handler/XmlPersistenceHandler.java | 0 .../helper/BootstrapConfigurationHelper.java | 0 .../privilege/helper/ClassHelper.java | 0 .../eitchnet/privilege/helper/HashHelper.java | 0 .../helper/InitializationHelper.java | 0 .../privilege/helper/PasswordCreaterUI.java | 0 .../privilege/helper/PasswordCreator.java | 0 .../privilege/helper/XmlConstants.java | 0 .../eitchnet/privilege/helper/XmlHelper.java | 0 .../privilege/i18n/AccessDeniedException.java | 0 .../privilege/i18n/PrivilegeException.java | 0 .../eitchnet/privilege/model/Certificate.java | 0 .../privilege/model/PrivilegeRep.java | 0 .../privilege/model/Restrictable.java | 0 .../ch/eitchnet/privilege/model/RoleRep.java | 0 .../ch/eitchnet/privilege/model/UserRep.java | 0 .../eitchnet/privilege/model/UserState.java | 0 .../privilege/model/internal/Privilege.java | 0 .../privilege/model/internal/Role.java | 0 .../privilege/model/internal/Session.java | 0 .../privilege/model/internal/User.java | 0 .../privilege/policy/DefaultPrivilege.java | 0 .../privilege/policy/PrivilegePolicy.java | 0 .../privilege/test/PrivilegeTest.java | 0 .../privilege/test/TestRestrictable.java | 0 38 files changed, 180 insertions(+), 114 deletions(-) delete mode 100644 .classpath delete mode 100644 .project delete mode 100644 .settings/org.eclipse.jdt.core.prefs delete mode 100644 lib/dom4j-1.6.1-src.zip delete mode 100644 lib/dom4j-1.6.1.jar delete mode 100644 lib/log4j-1.2.15.jar create mode 100644 pom.xml rename src/{ => main/java}/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java (100%) rename src/{ => main/java}/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java (100%) rename src/{ => main/java}/ch/eitchnet/privilege/handler/EncryptionHandler.java (100%) rename src/{ => main/java}/ch/eitchnet/privilege/handler/PersistenceHandler.java (100%) rename src/{ => main/java}/ch/eitchnet/privilege/handler/PrivilegeHandler.java (100%) rename src/{ => main/java}/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java (100%) rename src/{ => main/java}/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java (100%) rename src/{ => main/java}/ch/eitchnet/privilege/helper/ClassHelper.java (100%) rename src/{ => main/java}/ch/eitchnet/privilege/helper/HashHelper.java (100%) rename src/{ => main/java}/ch/eitchnet/privilege/helper/InitializationHelper.java (100%) rename src/{ => main/java}/ch/eitchnet/privilege/helper/PasswordCreaterUI.java (100%) rename src/{ => main/java}/ch/eitchnet/privilege/helper/PasswordCreator.java (100%) rename src/{ => main/java}/ch/eitchnet/privilege/helper/XmlConstants.java (100%) rename src/{ => main/java}/ch/eitchnet/privilege/helper/XmlHelper.java (100%) rename src/{ => main/java}/ch/eitchnet/privilege/i18n/AccessDeniedException.java (100%) rename src/{ => main/java}/ch/eitchnet/privilege/i18n/PrivilegeException.java (100%) rename src/{ => main/java}/ch/eitchnet/privilege/model/Certificate.java (100%) rename src/{ => main/java}/ch/eitchnet/privilege/model/PrivilegeRep.java (100%) rename src/{ => main/java}/ch/eitchnet/privilege/model/Restrictable.java (100%) rename src/{ => main/java}/ch/eitchnet/privilege/model/RoleRep.java (100%) rename src/{ => main/java}/ch/eitchnet/privilege/model/UserRep.java (100%) rename src/{ => main/java}/ch/eitchnet/privilege/model/UserState.java (100%) rename src/{ => main/java}/ch/eitchnet/privilege/model/internal/Privilege.java (100%) rename src/{ => main/java}/ch/eitchnet/privilege/model/internal/Role.java (100%) rename src/{ => main/java}/ch/eitchnet/privilege/model/internal/Session.java (100%) rename src/{ => main/java}/ch/eitchnet/privilege/model/internal/User.java (100%) rename src/{ => main/java}/ch/eitchnet/privilege/policy/DefaultPrivilege.java (100%) rename src/{ => main/java}/ch/eitchnet/privilege/policy/PrivilegePolicy.java (100%) rename {test => src/test/java}/ch/eitchnet/privilege/test/PrivilegeTest.java (100%) rename {test => src/test/java}/ch/eitchnet/privilege/test/TestRestrictable.java (100%) diff --git a/.classpath b/.classpath deleted file mode 100644 index f52b649dc..000000000 --- a/.classpath +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/.gitignore b/.gitignore index e493dd6ee..7cfc64f07 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,6 @@ bin dist + +# Maven target +target/ + diff --git a/.project b/.project deleted file mode 100644 index 2bbc43fc5..000000000 --- a/.project +++ /dev/null @@ -1,17 +0,0 @@ - - - Privilege - - - - - - org.eclipse.jdt.core.javabuilder - - - - - - org.eclipse.jdt.core.javanature - - diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs deleted file mode 100644 index c436f312a..000000000 --- a/.settings/org.eclipse.jdt.core.prefs +++ /dev/null @@ -1,87 +0,0 @@ -#Sat Nov 06 15:16:05 CET 2010 -eclipse.preferences.version=1 -org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 -org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve -org.eclipse.jdt.core.compiler.compliance=1.6 -org.eclipse.jdt.core.compiler.debug.lineNumber=generate -org.eclipse.jdt.core.compiler.debug.localVariable=generate -org.eclipse.jdt.core.compiler.debug.sourceFile=generate -org.eclipse.jdt.core.compiler.doc.comment.support=enabled -org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning -org.eclipse.jdt.core.compiler.problem.assertIdentifier=error -org.eclipse.jdt.core.compiler.problem.autoboxing=warning -org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning -org.eclipse.jdt.core.compiler.problem.deadCode=warning -org.eclipse.jdt.core.compiler.problem.deprecation=warning -org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled -org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=enabled -org.eclipse.jdt.core.compiler.problem.discouragedReference=warning -org.eclipse.jdt.core.compiler.problem.emptyStatement=warning -org.eclipse.jdt.core.compiler.problem.enumIdentifier=error -org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning -org.eclipse.jdt.core.compiler.problem.fatalOptionalError=enabled -org.eclipse.jdt.core.compiler.problem.fieldHiding=warning -org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning -org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning -org.eclipse.jdt.core.compiler.problem.forbiddenReference=error -org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning -org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning -org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=warning -org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=warning -org.eclipse.jdt.core.compiler.problem.invalidJavadoc=warning -org.eclipse.jdt.core.compiler.problem.invalidJavadocTags=enabled -org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsDeprecatedRef=disabled -org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsNotVisibleRef=disabled -org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsVisibility=public -org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore -org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning -org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=warning -org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=warning -org.eclipse.jdt.core.compiler.problem.missingJavadocComments=warning -org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsOverriding=disabled -org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsVisibility=public -org.eclipse.jdt.core.compiler.problem.missingJavadocTagDescription=all_standard_tags -org.eclipse.jdt.core.compiler.problem.missingJavadocTags=warning -org.eclipse.jdt.core.compiler.problem.missingJavadocTagsOverriding=disabled -org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=public -org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=warning -org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning -org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=warning -org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning -org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning -org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore -org.eclipse.jdt.core.compiler.problem.nullReference=warning -org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning -org.eclipse.jdt.core.compiler.problem.parameterAssignment=warning -org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=warning -org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning -org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning -org.eclipse.jdt.core.compiler.problem.redundantNullCheck=warning -org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=warning -org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=enabled -org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning -org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled -org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=warning -org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning -org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning -org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=warning -org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning -org.eclipse.jdt.core.compiler.problem.unnecessaryElse=warning -org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=warning -org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=warning -org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=warning -org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled -org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled -org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled -org.eclipse.jdt.core.compiler.problem.unusedImport=warning -org.eclipse.jdt.core.compiler.problem.unusedLabel=warning -org.eclipse.jdt.core.compiler.problem.unusedLocal=warning -org.eclipse.jdt.core.compiler.problem.unusedParameter=warning -org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled -org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled -org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled -org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning -org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning -org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning -org.eclipse.jdt.core.compiler.source=1.6 diff --git a/lib/dom4j-1.6.1-src.zip b/lib/dom4j-1.6.1-src.zip deleted file mode 100644 index 39b4137d350e6bad0405b9b3aca2ea6143ea79de..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 440064 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%0ArZNyr#a_uiccET<7nN;-(B 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 Z!Q0dci2%c+Q1#&NE-;_kWe5G|{{hteC$azl diff --git a/lib/dom4j-1.6.1.jar b/lib/dom4j-1.6.1.jar deleted file mode 100644 index c8c4dbb92d6c23a7fbb2813eb721eb4cce91750c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 313898 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

    sl*CtvD7j$_;?KD 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

    {0o23%x0zG^lL-+)0hE&z$21eha>paa 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 LL+IIXJH`J1EdB>a diff --git a/pom.xml b/pom.xml new file mode 100644 index 000000000..53f666142 --- /dev/null +++ b/pom.xml @@ -0,0 +1,176 @@ + + 4.0.0 + ch.eitchnet + ch.eitchnet.privilege + jar + 1.0-SNAPSHOT + ch.eitchnet.privilege + https://github.com/eitch/ch.eitchnet.privilege + + + UTF-8 + + + + + 2011 + + + GNU Lesser General Public License + http://www.gnu.org/licenses/lgpl.html + repo + + + + eitchnet.ch + http://blog.eitchnet.ch + + + + eitch + Robert von Vurg + eitch@eitchnet.ch + http://blog.eitchnet.ch + eitchnet.ch + http://blog.eitchnet.ch + + architect + developer + + +1 + + http://localhost + + + + + + Github Issues + https://github.com/eitch/ch.eitchnet.privilege/issues + + + + + + scm:git:https://github.com/eitch/ch.eitchnet.privilege.git + scm:git:git@github.com:eitch/ch.eitchnet.privilege.git + https://github.com/eitch/ch.eitchnet.privilege + + + + + + + junit + junit + 4.10 + test + + + log4j + log4j + 1.2.17 + + + maven + dom4j + 1.7-20060614 + + + ch.eitchnet + ch.eitchnet.utils + 1.0-SNAPSHOT + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 2.3.2 + + 1.6 + 1.6 + + + + + org.apache.maven.plugins + maven-jar-plugin + 2.4 + + + + true + true + + + + + + + + org.apache.maven.plugins + maven-site-plugin + 2.3 + + UTF-8 + + + + + + diff --git a/src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java similarity index 100% rename from src/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java rename to src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java diff --git a/src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java similarity index 100% rename from src/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java rename to src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java diff --git a/src/ch/eitchnet/privilege/handler/EncryptionHandler.java b/src/main/java/ch/eitchnet/privilege/handler/EncryptionHandler.java similarity index 100% rename from src/ch/eitchnet/privilege/handler/EncryptionHandler.java rename to src/main/java/ch/eitchnet/privilege/handler/EncryptionHandler.java diff --git a/src/ch/eitchnet/privilege/handler/PersistenceHandler.java b/src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java similarity index 100% rename from src/ch/eitchnet/privilege/handler/PersistenceHandler.java rename to src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java diff --git a/src/ch/eitchnet/privilege/handler/PrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java similarity index 100% rename from src/ch/eitchnet/privilege/handler/PrivilegeHandler.java rename to src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java diff --git a/src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java similarity index 100% rename from src/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java rename to src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java diff --git a/src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java b/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java similarity index 100% rename from src/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java rename to src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java diff --git a/src/ch/eitchnet/privilege/helper/ClassHelper.java b/src/main/java/ch/eitchnet/privilege/helper/ClassHelper.java similarity index 100% rename from src/ch/eitchnet/privilege/helper/ClassHelper.java rename to src/main/java/ch/eitchnet/privilege/helper/ClassHelper.java diff --git a/src/ch/eitchnet/privilege/helper/HashHelper.java b/src/main/java/ch/eitchnet/privilege/helper/HashHelper.java similarity index 100% rename from src/ch/eitchnet/privilege/helper/HashHelper.java rename to src/main/java/ch/eitchnet/privilege/helper/HashHelper.java diff --git a/src/ch/eitchnet/privilege/helper/InitializationHelper.java b/src/main/java/ch/eitchnet/privilege/helper/InitializationHelper.java similarity index 100% rename from src/ch/eitchnet/privilege/helper/InitializationHelper.java rename to src/main/java/ch/eitchnet/privilege/helper/InitializationHelper.java diff --git a/src/ch/eitchnet/privilege/helper/PasswordCreaterUI.java b/src/main/java/ch/eitchnet/privilege/helper/PasswordCreaterUI.java similarity index 100% rename from src/ch/eitchnet/privilege/helper/PasswordCreaterUI.java rename to src/main/java/ch/eitchnet/privilege/helper/PasswordCreaterUI.java diff --git a/src/ch/eitchnet/privilege/helper/PasswordCreator.java b/src/main/java/ch/eitchnet/privilege/helper/PasswordCreator.java similarity index 100% rename from src/ch/eitchnet/privilege/helper/PasswordCreator.java rename to src/main/java/ch/eitchnet/privilege/helper/PasswordCreator.java diff --git a/src/ch/eitchnet/privilege/helper/XmlConstants.java b/src/main/java/ch/eitchnet/privilege/helper/XmlConstants.java similarity index 100% rename from src/ch/eitchnet/privilege/helper/XmlConstants.java rename to src/main/java/ch/eitchnet/privilege/helper/XmlConstants.java diff --git a/src/ch/eitchnet/privilege/helper/XmlHelper.java b/src/main/java/ch/eitchnet/privilege/helper/XmlHelper.java similarity index 100% rename from src/ch/eitchnet/privilege/helper/XmlHelper.java rename to src/main/java/ch/eitchnet/privilege/helper/XmlHelper.java diff --git a/src/ch/eitchnet/privilege/i18n/AccessDeniedException.java b/src/main/java/ch/eitchnet/privilege/i18n/AccessDeniedException.java similarity index 100% rename from src/ch/eitchnet/privilege/i18n/AccessDeniedException.java rename to src/main/java/ch/eitchnet/privilege/i18n/AccessDeniedException.java diff --git a/src/ch/eitchnet/privilege/i18n/PrivilegeException.java b/src/main/java/ch/eitchnet/privilege/i18n/PrivilegeException.java similarity index 100% rename from src/ch/eitchnet/privilege/i18n/PrivilegeException.java rename to src/main/java/ch/eitchnet/privilege/i18n/PrivilegeException.java diff --git a/src/ch/eitchnet/privilege/model/Certificate.java b/src/main/java/ch/eitchnet/privilege/model/Certificate.java similarity index 100% rename from src/ch/eitchnet/privilege/model/Certificate.java rename to src/main/java/ch/eitchnet/privilege/model/Certificate.java diff --git a/src/ch/eitchnet/privilege/model/PrivilegeRep.java b/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java similarity index 100% rename from src/ch/eitchnet/privilege/model/PrivilegeRep.java rename to src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java diff --git a/src/ch/eitchnet/privilege/model/Restrictable.java b/src/main/java/ch/eitchnet/privilege/model/Restrictable.java similarity index 100% rename from src/ch/eitchnet/privilege/model/Restrictable.java rename to src/main/java/ch/eitchnet/privilege/model/Restrictable.java diff --git a/src/ch/eitchnet/privilege/model/RoleRep.java b/src/main/java/ch/eitchnet/privilege/model/RoleRep.java similarity index 100% rename from src/ch/eitchnet/privilege/model/RoleRep.java rename to src/main/java/ch/eitchnet/privilege/model/RoleRep.java diff --git a/src/ch/eitchnet/privilege/model/UserRep.java b/src/main/java/ch/eitchnet/privilege/model/UserRep.java similarity index 100% rename from src/ch/eitchnet/privilege/model/UserRep.java rename to src/main/java/ch/eitchnet/privilege/model/UserRep.java diff --git a/src/ch/eitchnet/privilege/model/UserState.java b/src/main/java/ch/eitchnet/privilege/model/UserState.java similarity index 100% rename from src/ch/eitchnet/privilege/model/UserState.java rename to src/main/java/ch/eitchnet/privilege/model/UserState.java diff --git a/src/ch/eitchnet/privilege/model/internal/Privilege.java b/src/main/java/ch/eitchnet/privilege/model/internal/Privilege.java similarity index 100% rename from src/ch/eitchnet/privilege/model/internal/Privilege.java rename to src/main/java/ch/eitchnet/privilege/model/internal/Privilege.java diff --git a/src/ch/eitchnet/privilege/model/internal/Role.java b/src/main/java/ch/eitchnet/privilege/model/internal/Role.java similarity index 100% rename from src/ch/eitchnet/privilege/model/internal/Role.java rename to src/main/java/ch/eitchnet/privilege/model/internal/Role.java diff --git a/src/ch/eitchnet/privilege/model/internal/Session.java b/src/main/java/ch/eitchnet/privilege/model/internal/Session.java similarity index 100% rename from src/ch/eitchnet/privilege/model/internal/Session.java rename to src/main/java/ch/eitchnet/privilege/model/internal/Session.java diff --git a/src/ch/eitchnet/privilege/model/internal/User.java b/src/main/java/ch/eitchnet/privilege/model/internal/User.java similarity index 100% rename from src/ch/eitchnet/privilege/model/internal/User.java rename to src/main/java/ch/eitchnet/privilege/model/internal/User.java diff --git a/src/ch/eitchnet/privilege/policy/DefaultPrivilege.java b/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java similarity index 100% rename from src/ch/eitchnet/privilege/policy/DefaultPrivilege.java rename to src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java diff --git a/src/ch/eitchnet/privilege/policy/PrivilegePolicy.java b/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicy.java similarity index 100% rename from src/ch/eitchnet/privilege/policy/PrivilegePolicy.java rename to src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicy.java diff --git a/test/ch/eitchnet/privilege/test/PrivilegeTest.java b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java similarity index 100% rename from test/ch/eitchnet/privilege/test/PrivilegeTest.java rename to src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java diff --git a/test/ch/eitchnet/privilege/test/TestRestrictable.java b/src/test/java/ch/eitchnet/privilege/test/TestRestrictable.java similarity index 100% rename from test/ch/eitchnet/privilege/test/TestRestrictable.java rename to src/test/java/ch/eitchnet/privilege/test/TestRestrictable.java From 7e6987b35823a51134cec91e6bfe20c262c3be29 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 28 Jul 2012 15:47:26 +0200 Subject: [PATCH 072/173] [Minor] Added eclipse settings to .gitignore --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.gitignore b/.gitignore index 7cfc64f07..8d6b8f627 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,8 @@ dist # Maven target target/ +# Eclipse settings +.classpath +.project +.settings/ + From f471be1ea73e0c6cc218e502bc59c684a3ad13e3 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 28 Jul 2012 23:17:45 +0200 Subject: [PATCH 073/173] [Minor] change mvn eclipse configuration to load sources as well --- pom.xml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pom.xml b/pom.xml index 53f666142..61b5d186c 100644 --- a/pom.xml +++ b/pom.xml @@ -136,6 +136,16 @@ + + + org.apache.maven.plugins + maven-eclipse-plugin + 2.9 + + true + true + + org.apache.maven.plugins From b78e9bb064c348dd3887ae997298b244dcbff7a5 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 29 Jul 2012 18:23:31 +0200 Subject: [PATCH 074/173] [New] added a new PrivilegeHandler.queryUser(UserRep) to query users - The querying is done by passing a UserRep with all the fields set which need to be equal to select the user. Null fields are understood as not required - Performed further clean up in that all password variables have been changed to type byte[]. - Cleaned up copyright notices in files - fixed version of Privilege 0.1.0-SNAPSHOT in pom.xml --- MANIFEST.MF | 7 - README | 20 +- build.xml | 77 ----- pom.xml | 17 +- .../handler/DefaultEncryptionHandler.java | 24 +- .../handler/DefaultPrivilegeHandler.java | 280 ++++++++++++++---- .../privilege/handler/EncryptionHandler.java | 17 +- .../privilege/handler/PersistenceHandler.java | 8 +- .../privilege/handler/PrivilegeHandler.java | 28 +- .../handler/XmlPersistenceHandler.java | 8 +- .../helper/BootstrapConfigurationHelper.java | 8 +- .../privilege/helper/ClassHelper.java | 8 +- .../eitchnet/privilege/helper/HashHelper.java | 8 +- .../helper/InitializationHelper.java | 14 +- .../privilege/helper/PasswordCreaterUI.java | 8 +- .../privilege/helper/PasswordCreator.java | 8 +- .../privilege/helper/XmlConstants.java | 8 +- .../eitchnet/privilege/helper/XmlHelper.java | 8 +- .../privilege/i18n/AccessDeniedException.java | 8 +- .../privilege/i18n/PrivilegeException.java | 8 +- .../eitchnet/privilege/model/Certificate.java | 8 +- .../privilege/model/PrivilegeRep.java | 8 +- .../privilege/model/Restrictable.java | 8 +- .../ch/eitchnet/privilege/model/RoleRep.java | 8 +- .../ch/eitchnet/privilege/model/UserRep.java | 8 +- .../eitchnet/privilege/model/UserState.java | 8 +- .../privilege/model/internal/Privilege.java | 8 +- .../privilege/model/internal/Role.java | 8 +- .../privilege/model/internal/Session.java | 8 +- .../privilege/model/internal/User.java | 8 +- .../privilege/policy/DefaultPrivilege.java | 8 +- .../privilege/policy/PrivilegePolicy.java | 8 +- .../privilege/test/PrivilegeTest.java | 68 ++--- .../privilege/test/TestRestrictable.java | 8 +- 34 files changed, 346 insertions(+), 398 deletions(-) delete mode 100644 MANIFEST.MF delete mode 100644 build.xml diff --git a/MANIFEST.MF b/MANIFEST.MF deleted file mode 100644 index 8a69d98b1..000000000 --- a/MANIFEST.MF +++ /dev/null @@ -1,7 +0,0 @@ -Manifest-Version: 1.0 -Implementation-Vendor: eitchnet.ch -Implementation-Title: eitchnet Java Privilege implementation -Implementation-Version: 0.0.3 -Specification-Vendor: eitchnet.ch -Specification-Title: eitchnet Java Privilege implementation -Specification-Version: 0.1 diff --git a/README b/README index c4c3ef642..73ec1b402 100644 --- a/README +++ b/README @@ -17,7 +17,7 @@ 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/eitch/Privilege + https://github.com/eitch/ch.eitchnet.privilege The main developer is Robert von Burg who also maintains the Github repository. He is available for all questions regarding Privilege @@ -72,20 +72,10 @@ directory of the Repository Compiling ################################ -Since Privilege is a Java library, it is built using Apache Ant. The build.xml -file is configured to build Privilege directly from the root directory by simply -calling ant at the command line: +Privilege is a Maven3 project and can be built by simply performing the +following command: -$ ant -Buildfile: /data/src/apixxo_WS/Privilege/build.xml - -dist: - [mkdir] Created dir: /data/src/apixxo_WS/Privilege/dist - [copy] Copying 28 files to /data/src/apixxo_WS/Privilege/bin - [jar] Building jar: /data/src/apixxo_WS/Privilege/dist/Privilege.jar - -BUILD SUCCESSFUL -Total time: 0 seconds +$ mvn compile Using ################################ @@ -96,7 +86,7 @@ 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 7. August 2011 + Switzerland, the 29. July 2012 Robert von Burg diff --git a/build.xml b/build.xml deleted file mode 100644 index 34f371db8..000000000 --- a/build.xml +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/pom.xml b/pom.xml index 61b5d186c..5a7c18a40 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ ch.eitchnet ch.eitchnet.privilege jar - 1.0-SNAPSHOT + 0.1.0-SNAPSHOT ch.eitchnet.privilege https://github.com/eitch/ch.eitchnet.privilege @@ -156,6 +156,21 @@ 1.6 + + + org.apache.maven.plugins + maven-source-plugin + 2.1.2 + + + attach-sources + verify + + jar-no-fork + + + + org.apache.maven.plugins diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java index 10d02c49e..98a5609ba 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java @@ -1,11 +1,6 @@ /* - * Copyright (c) 2010, 2011 + * Copyright (c) 2010 - 2012 * - * Robert von Burg - * - */ - -/* * This file is part of Privilege. * * Privilege is free software: you can redistribute it and/or modify @@ -22,7 +17,6 @@ * along with Privilege. If not, see . * */ - package ch.eitchnet.privilege.handler; import java.io.UnsupportedEncodingException; @@ -84,6 +78,22 @@ public class DefaultEncryptionHandler implements EncryptionHandler { } } + /** + * @see ch.eitchnet.privilege.handler.EncryptionHandler#convertToHash(java.lang.String) + */ + @Override + public String convertToHash(byte[] bytes) { + try { + + return HashHelper.stringToHash(this.hashAlgorithm, bytes); + + } catch (NoSuchAlgorithmException e) { + throw new PrivilegeException("Algorithm " + this.hashAlgorithm + " was not found!", e); + } catch (UnsupportedEncodingException e) { + throw new PrivilegeException("Charset ASCII is not supported!", e); + } + } + /** * @see ch.eitchnet.privilege.handler.EncryptionHandler#nextToken() */ diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 28442e470..748619a16 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -1,11 +1,6 @@ /* - * Copyright (c) 2010, 2011 + * Copyright (c) 2010 - 2012 * - * Robert von Burg - * - */ - -/* * This file is part of Privilege. * * Privilege is free software: you can redistribute it and/or modify @@ -22,12 +17,13 @@ * along with Privilege. If not, see . * */ - package ch.eitchnet.privilege.handler; +import java.util.ArrayList; 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; @@ -112,7 +108,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { public RoleRep getRole(String roleName) { Role role = this.persistenceHandler.getRole(roleName); if (role == null) - throw new PrivilegeException("Role " + roleName + " does not exist!"); + return null; return role.asRoleRep(); } @@ -123,7 +119,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { public UserRep getUser(String username) { User user = this.persistenceHandler.getUser(username); if (user == null) - throw new PrivilegeException("User " + username + " does not exist!"); + return null; return user.asUserRep(); } @@ -162,6 +158,135 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { return policy; } + /** + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#queryUsers(ch.eitchnet.privilege.model.UserRep) + */ + @Override + public List queryUsers(UserRep selectorRep) { + + String selUserId = selectorRep.getUserId(); + String selUsername = selectorRep.getUsername(); + String selFirstname = selectorRep.getFirstname(); + String selSurname = selectorRep.getSurname(); + UserState selUserState = selectorRep.getUserState(); + Locale selLocale = selectorRep.getLocale(); + Set selRoles = selectorRep.getRoles(); + Map selPropertyMap = selectorRep.getProperties(); + + List result = new ArrayList(); + List allUsers = this.persistenceHandler.getAllUsers(); + for (User user : allUsers) { + + // selections + boolean userIdSelected; + boolean usernameSelected; + boolean firstnameSelected; + boolean surnameSelected; + 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; + + // surname + if (selSurname == null) + surnameSelected = true; + else if (selSurname.equals(user.getSurname())) + surnameSelected = true; + else + surnameSelected = 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 && surnameSelected + && userStateSelected && localeSelected && roleSelected && propertySelected; + + if (selected) + result.add(user.asUserRep()); + } + + return result; + } + + /** + * @param selPropertyMap + * @param properties + * @return + */ + private boolean isSelectedByProperty(Map selPropertyMap, Map properties) { + + if (selPropertyMap == null) + return true; + + if (selPropertyMap.isEmpty() && properties.isEmpty()) + return true; + + for (String selKey : selPropertyMap.keySet()) { + + String value = properties.get(selKey); + if (value == null || !value.equals(selPropertyMap.get(selKey))) + return false; + } + + return true; + } + + /** + * @param selRoles + * @param roles + * @return + */ + private boolean isSelectedByRole(Set selRoles, Set roles) { + + if (selRoles == null) + return true; + + return roles.containsAll(selRoles); + } + /** * @see ch.eitchnet.privilege.handler.PrivilegeHandler#addOrReplaceRole(ch.eitchnet.privilege.model.Certificate, * ch.eitchnet.privilege.model.RoleRep) @@ -187,29 +312,34 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { * ch.eitchnet.privilege.model.UserRep, java.lang.String) */ @Override - public void addOrReplaceUser(Certificate certificate, UserRep userRep, String password) { + public void addOrReplaceUser(Certificate certificate, UserRep userRep, byte[] password) { + try { - // validate who is doing this - validateIsPrivilegeAdmin(certificate); + // validate who is doing this + validateIsPrivilegeAdmin(certificate); - String passwordHash = null; - if (password != null) { + String passwordHash = null; + if (password != null) { - // validate password meets basic requirements - validatePassword(password); + // validate password meets basic requirements + validatePassword(password); - // hash password - passwordHash = this.encryptionHandler.convertToHash(password); + // hash password + passwordHash = this.encryptionHandler.convertToHash(password); + } + + // create new user + // XXX should the collections be recreated and the getRoles() and getProperties() methods be removed? + User user = new User(userRep.getUserId(), userRep.getUsername(), passwordHash, userRep.getFirstname(), + userRep.getSurname(), userRep.getUserState(), userRep.getRoles(), userRep.getLocale(), + userRep.getProperties()); + + // delegate to persistence handler + this.persistenceHandler.addOrReplaceUser(user); + + } finally { + clearPassword(password); } - - // create new user - // XXX should the collections be recreated and the getRoles() and getProperties() methods be removed? - User user = new User(userRep.getUserId(), userRep.getUsername(), passwordHash, userRep.getFirstname(), - userRep.getSurname(), userRep.getUserState(), userRep.getRoles(), userRep.getLocale(), - userRep.getProperties()); - - // delegate to persistence handler - this.persistenceHandler.addOrReplaceUser(user); } /** @@ -450,42 +580,47 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { * java.lang.String, java.lang.String) */ @Override - public void setUserPassword(Certificate certificate, String username, String password) { + public void setUserPassword(Certificate certificate, String username, byte[] password) { + try { - // check if certificate is for same user, in which case user is changing their own password - if (certificate.getUsername().equals(username)) { + // check if certificate is for same user, in which case user is changing their own password + if (certificate.getUsername().equals(username)) { - // validate the certificate - isCertificateValid(certificate); + // validate the certificate + isCertificateValid(certificate); - } else { + } else { - // otherwise validate the the certificate is for a privilege admin - validateIsPrivilegeAdmin(certificate); + // otherwise validate the the certificate is for a privilege admin + validateIsPrivilegeAdmin(certificate); + } + + // get User + User user = this.persistenceHandler.getUser(username); + if (user == null) { + throw new PrivilegeException("User " + username + " does not exist!"); + } + + 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(user.getUserId(), user.getUsername(), passwordHash, user.getFirstname(), + user.getSurname(), user.getUserState(), user.getRoles(), user.getLocale(), user.getProperties()); + + // delegate user replacement to persistence handler + this.persistenceHandler.addOrReplaceUser(newUser); + + } finally { + clearPassword(password); } - - // get User - User user = this.persistenceHandler.getUser(username); - if (user == null) { - throw new PrivilegeException("User " + username + " does not exist!"); - } - - 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(user.getUserId(), user.getUsername(), passwordHash, user.getFirstname(), - user.getSurname(), user.getUserState(), user.getRoles(), user.getLocale(), user.getProperties()); - - // delegate user replacement to persistence handler - this.persistenceHandler.addOrReplaceUser(newUser); } /** @@ -519,16 +654,17 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { * if the user credentials are not valid */ @Override - public Certificate authenticate(String username, String password) { + public Certificate authenticate(String username, byte[] password) { // create certificate Certificate certificate; try { - // both username and password must at least have 3 characters in length + // username must be at least 3 characters in length if (username == null || username.length() < 3) throw new PrivilegeException("The given username is shorter than 3 characters"); - else if (password == null || password.length() < 3) - throw new PrivilegeException("The given password is shorter than 3 characters"); + + // and validate the password + validatePassword(password); // we only work with hashed passwords String passwordHash = this.encryptionHandler.convertToHash(password); @@ -578,6 +714,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } catch (RuntimeException e) { logger.error("User " + username + " Failed to authenticate: " + e.getLocalizedMessage()); throw e; + } finally { + clearPassword(password); } // return the certificate @@ -801,11 +939,15 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { * @see ch.eitchnet.privilege.handler.PrivilegeHandler#validatePassword(java.lang.String) */ @Override - public void validatePassword(String password) throws PrivilegeException { + public void validatePassword(byte[] password) throws PrivilegeException { - if (password == null || password.isEmpty()) { + if (password == null || password.length == 0) { throw new PrivilegeException("A password may not be empty!"); } + + if (password.length < 3) { + throw new PrivilegeException("The given password is shorter than 3 characters"); + } } /** @@ -909,4 +1051,14 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } } + /** + * @param password + */ + private void clearPassword(byte[] password) { + if (password != null) { + for (int i = 0; i < password.length; i++) { + password[i] = 0; + } + } + } } diff --git a/src/main/java/ch/eitchnet/privilege/handler/EncryptionHandler.java b/src/main/java/ch/eitchnet/privilege/handler/EncryptionHandler.java index 50d758173..b25dbd82a 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/EncryptionHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/EncryptionHandler.java @@ -1,11 +1,6 @@ /* - * Copyright (c) 2010, 2011 + * Copyright (c) 2010 - 2012 * - * Robert von Burg - * - */ - -/* * This file is part of Privilege. * * Privilege is free software: you can redistribute it and/or modify @@ -22,7 +17,6 @@ * along with Privilege. If not, see . * */ - package ch.eitchnet.privilege.handler; import java.util.Map; @@ -51,6 +45,15 @@ public interface EncryptionHandler { * @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 diff --git a/src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java b/src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java index 29ad14fac..bba8f20c0 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java @@ -1,11 +1,6 @@ /* - * Copyright (c) 2010, 2011 + * Copyright (c) 2010 - 2012 * - * Robert von Burg - * - */ - -/* * This file is part of Privilege. * * Privilege is free software: you can redistribute it and/or modify @@ -22,7 +17,6 @@ * along with Privilege. If not, see . * */ - package ch.eitchnet.privilege.handler; import java.util.List; diff --git a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java index 03bda0c84..c6577ea64 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java @@ -1,11 +1,6 @@ /* - * Copyright (c) 2010, 2011 + * Copyright (c) 2010 - 2012 * - * Robert von Burg - * - */ - -/* * This file is part of Privilege. * * Privilege is free software: you can redistribute it and/or modify @@ -22,9 +17,9 @@ * along with Privilege. If not, see . * */ - package ch.eitchnet.privilege.handler; +import java.util.List; import java.util.Locale; import ch.eitchnet.privilege.i18n.AccessDeniedException; @@ -75,6 +70,17 @@ public interface PrivilegeHandler { */ public RoleRep getRole(String roleName); + /** + * Method to query {@link UserRep} which meet the criteria set in the given {@link UserRep}. Null fields mean the + * fields are not relevant. + * + * @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(UserRep selectorRep); + /** * Removes the user with the given username * @@ -171,7 +177,7 @@ public interface PrivilegeHandler { * @throws PrivilegeException * if there is anything wrong with this certificate */ - public void addOrReplaceUser(Certificate certificate, UserRep userRep, String password) + public void addOrReplaceUser(Certificate certificate, UserRep userRep, byte[] password) throws AccessDeniedException, PrivilegeException; /** @@ -251,7 +257,7 @@ public interface PrivilegeHandler { * @throws PrivilegeException * if there is anything wrong with this certificate */ - public void setUserPassword(Certificate certificate, String username, String password) + public void setUserPassword(Certificate certificate, String username, byte[] password) throws AccessDeniedException, PrivilegeException; /** @@ -326,7 +332,7 @@ public interface PrivilegeHandler { * @throws AccessDeniedException * if the user credentials are not valid */ - public Certificate authenticate(String username, String password) throws AccessDeniedException; + public Certificate authenticate(String username, byte[] password) throws AccessDeniedException; /** * Invalidates the {@link Session} for the given {@link Certificate}, effectively logging out the user who was @@ -416,7 +422,7 @@ public interface PrivilegeHandler { * @throws PrivilegeException * if the password does not implement the requirement of the concrete implementation */ - public void validatePassword(String password) throws PrivilegeException; + public void validatePassword(byte[] password) throws PrivilegeException; /** * Persists any changes to the privilege data model. Changes are thus not persisted immediately, but must be diff --git a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java index 6910ad735..9e2608437 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java @@ -1,11 +1,6 @@ /* - * Copyright (c) 2010, 2011 + * Copyright (c) 2010 - 2012 * - * Robert von Burg - * - */ - -/* * This file is part of Privilege. * * Privilege is free software: you can redistribute it and/or modify @@ -22,7 +17,6 @@ * along with Privilege. If not, see . * */ - package ch.eitchnet.privilege.handler; import java.io.File; diff --git a/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java b/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java index 386ce8476..42677a198 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java +++ b/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java @@ -1,11 +1,6 @@ /* - * Copyright (c) 2010, 2011 + * Copyright (c) 2010 - 2012 * - * Robert von Burg - * - */ - -/* * This file is part of Privilege. * * Privilege is free software: you can redistribute it and/or modify @@ -22,7 +17,6 @@ * along with Privilege. If not, see . * */ - package ch.eitchnet.privilege.helper; import java.io.File; diff --git a/src/main/java/ch/eitchnet/privilege/helper/ClassHelper.java b/src/main/java/ch/eitchnet/privilege/helper/ClassHelper.java index 7a741681f..41b10b775 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/ClassHelper.java +++ b/src/main/java/ch/eitchnet/privilege/helper/ClassHelper.java @@ -1,11 +1,6 @@ /* - * Copyright (c) 2010, 2011 + * Copyright (c) 2010 - 2012 * - * Robert von Burg - * - */ - -/* * This file is part of Privilege. * * Privilege is free software: you can redistribute it and/or modify @@ -22,7 +17,6 @@ * along with Privilege. If not, see . * */ - package ch.eitchnet.privilege.helper; import ch.eitchnet.privilege.i18n.PrivilegeException; diff --git a/src/main/java/ch/eitchnet/privilege/helper/HashHelper.java b/src/main/java/ch/eitchnet/privilege/helper/HashHelper.java index 628ac518a..b0da71fee 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/HashHelper.java +++ b/src/main/java/ch/eitchnet/privilege/helper/HashHelper.java @@ -1,11 +1,6 @@ /* - * Copyright (c) 2010, 2011 + * Copyright (c) 2010 - 2012 * - * Robert von Burg - * - */ - -/* * This file is part of Privilege. * * Privilege is free software: you can redistribute it and/or modify @@ -22,7 +17,6 @@ * along with Privilege. If not, see . * */ - package ch.eitchnet.privilege.helper; import java.io.UnsupportedEncodingException; diff --git a/src/main/java/ch/eitchnet/privilege/helper/InitializationHelper.java b/src/main/java/ch/eitchnet/privilege/helper/InitializationHelper.java index 9f2dda7ba..fad40de25 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/InitializationHelper.java +++ b/src/main/java/ch/eitchnet/privilege/helper/InitializationHelper.java @@ -1,11 +1,6 @@ /* - * Copyright (c) 2010, 2011 + * Copyright (c) 2010 - 2012 * - * Robert von Burg - * - */ - -/* * This file is part of Privilege. * * Privilege is free software: you can redistribute it and/or modify @@ -22,7 +17,6 @@ * along with Privilege. If not, see . * */ - package ch.eitchnet.privilege.helper; import java.io.File; @@ -40,6 +34,8 @@ import ch.eitchnet.privilege.handler.PersistenceHandler; import ch.eitchnet.privilege.handler.PrivilegeHandler; import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.policy.PrivilegePolicy; +import ch.eitchnet.utils.helper.StringHelper; +import ch.eitchnet.utils.helper.SystemHelper; /** * This class implements the initializing of the {@link PrivilegeHandler} by loading an XML file containing the @@ -163,6 +159,10 @@ public class InitializationHelper { for (Element parameter : elements) { String name = parameter.attributeValue(XmlConstants.XML_ATTR_NAME); String value = parameter.attributeValue(XmlConstants.XML_ATTR_VALUE); + + // replace any defined system properties + value = StringHelper.replaceSystemPropertiesIn(value); + parameterMap.put(name, value); } diff --git a/src/main/java/ch/eitchnet/privilege/helper/PasswordCreaterUI.java b/src/main/java/ch/eitchnet/privilege/helper/PasswordCreaterUI.java index ad6aa9546..f2c4231c8 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/PasswordCreaterUI.java +++ b/src/main/java/ch/eitchnet/privilege/helper/PasswordCreaterUI.java @@ -1,11 +1,6 @@ /* - * Copyright (c) 2010, 2011 + * Copyright (c) 2010 - 2012 * - * Robert von Burg - * - */ - -/* * This file is part of Privilege. * * Privilege is free software: you can redistribute it and/or modify @@ -22,7 +17,6 @@ * along with Privilege. If not, see . * */ - package ch.eitchnet.privilege.helper; import java.awt.Dimension; diff --git a/src/main/java/ch/eitchnet/privilege/helper/PasswordCreator.java b/src/main/java/ch/eitchnet/privilege/helper/PasswordCreator.java index f57a5615e..228cd529c 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/PasswordCreator.java +++ b/src/main/java/ch/eitchnet/privilege/helper/PasswordCreator.java @@ -1,11 +1,6 @@ /* - * Copyright (c) 2010, 2011 + * Copyright (c) 2010 - 2012 * - * Robert von Burg - * - */ - -/* * This file is part of Privilege. * * Privilege is free software: you can redistribute it and/or modify @@ -22,7 +17,6 @@ * along with Privilege. If not, see . * */ - package ch.eitchnet.privilege.helper; import java.io.BufferedReader; diff --git a/src/main/java/ch/eitchnet/privilege/helper/XmlConstants.java b/src/main/java/ch/eitchnet/privilege/helper/XmlConstants.java index c554473a9..3a9a16bc3 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/XmlConstants.java +++ b/src/main/java/ch/eitchnet/privilege/helper/XmlConstants.java @@ -1,11 +1,6 @@ /* - * Copyright (c) 2010, 2011 + * Copyright (c) 2010 - 2012 * - * Robert von Burg - * - */ - -/* * This file is part of Privilege. * * Privilege is free software: you can redistribute it and/or modify @@ -22,7 +17,6 @@ * along with Privilege. If not, see . * */ - package ch.eitchnet.privilege.helper; /** diff --git a/src/main/java/ch/eitchnet/privilege/helper/XmlHelper.java b/src/main/java/ch/eitchnet/privilege/helper/XmlHelper.java index 50433c212..9cb3786b7 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/XmlHelper.java +++ b/src/main/java/ch/eitchnet/privilege/helper/XmlHelper.java @@ -1,11 +1,6 @@ /* - * Copyright (c) 2010, 2011 + * Copyright (c) 2010 - 2012 * - * Robert von Burg - * - */ - -/* * This file is part of Privilege. * * Privilege is free software: you can redistribute it and/or modify @@ -22,7 +17,6 @@ * along with Privilege. If not, see . * */ - package ch.eitchnet.privilege.helper; import java.io.File; diff --git a/src/main/java/ch/eitchnet/privilege/i18n/AccessDeniedException.java b/src/main/java/ch/eitchnet/privilege/i18n/AccessDeniedException.java index 5e9029717..818335103 100644 --- a/src/main/java/ch/eitchnet/privilege/i18n/AccessDeniedException.java +++ b/src/main/java/ch/eitchnet/privilege/i18n/AccessDeniedException.java @@ -1,11 +1,6 @@ /* - * Copyright (c) 2010, 2011 + * Copyright (c) 2010 - 2012 * - * Robert von Burg - * - */ - -/* * This file is part of Privilege. * * Privilege is free software: you can redistribute it and/or modify @@ -22,7 +17,6 @@ * along with Privilege. If not, see . * */ - package ch.eitchnet.privilege.i18n; /** diff --git a/src/main/java/ch/eitchnet/privilege/i18n/PrivilegeException.java b/src/main/java/ch/eitchnet/privilege/i18n/PrivilegeException.java index db281372a..0f9fe5111 100644 --- a/src/main/java/ch/eitchnet/privilege/i18n/PrivilegeException.java +++ b/src/main/java/ch/eitchnet/privilege/i18n/PrivilegeException.java @@ -1,11 +1,6 @@ /* - * Copyright (c) 2010, 2011 + * Copyright (c) 2010 - 2012 * - * Robert von Burg - * - */ - -/* * This file is part of Privilege. * * Privilege is free software: you can redistribute it and/or modify @@ -22,7 +17,6 @@ * along with Privilege. If not, see . * */ - package ch.eitchnet.privilege.i18n; /** diff --git a/src/main/java/ch/eitchnet/privilege/model/Certificate.java b/src/main/java/ch/eitchnet/privilege/model/Certificate.java index 762ff0606..87d30a3da 100644 --- a/src/main/java/ch/eitchnet/privilege/model/Certificate.java +++ b/src/main/java/ch/eitchnet/privilege/model/Certificate.java @@ -1,11 +1,6 @@ /* - * Copyright (c) 2010, 2011 + * Copyright (c) 2010 - 2012 * - * Robert von Burg - * - */ - -/* * This file is part of Privilege. * * Privilege is free software: you can redistribute it and/or modify @@ -22,7 +17,6 @@ * along with Privilege. If not, see . * */ - package ch.eitchnet.privilege.model; import java.io.Serializable; diff --git a/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java b/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java index 6d39f685e..e13d6a1f4 100644 --- a/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java +++ b/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java @@ -1,11 +1,6 @@ /* - * Copyright (c) 2010, 2011 + * Copyright (c) 2010 - 2012 * - * Robert von Burg - * - */ - -/* * This file is part of Privilege. * * Privilege is free software: you can redistribute it and/or modify @@ -22,7 +17,6 @@ * along with Privilege. If not, see . * */ - package ch.eitchnet.privilege.model; import java.io.Serializable; diff --git a/src/main/java/ch/eitchnet/privilege/model/Restrictable.java b/src/main/java/ch/eitchnet/privilege/model/Restrictable.java index d3d401a61..95ef2e3c4 100644 --- a/src/main/java/ch/eitchnet/privilege/model/Restrictable.java +++ b/src/main/java/ch/eitchnet/privilege/model/Restrictable.java @@ -1,11 +1,6 @@ /* - * Copyright (c) 2010, 2011 + * Copyright (c) 2010 - 2012 * - * Robert von Burg - * - */ - -/* * This file is part of Privilege. * * Privilege is free software: you can redistribute it and/or modify @@ -22,7 +17,6 @@ * along with Privilege. If not, see . * */ - package ch.eitchnet.privilege.model; import ch.eitchnet.privilege.model.internal.Privilege; diff --git a/src/main/java/ch/eitchnet/privilege/model/RoleRep.java b/src/main/java/ch/eitchnet/privilege/model/RoleRep.java index 4af09ca17..f7a37a0f5 100644 --- a/src/main/java/ch/eitchnet/privilege/model/RoleRep.java +++ b/src/main/java/ch/eitchnet/privilege/model/RoleRep.java @@ -1,11 +1,6 @@ /* - * Copyright (c) 2010, 2011 + * Copyright (c) 2010 - 2012 * - * Robert von Burg - * - */ - -/* * This file is part of Privilege. * * Privilege is free software: you can redistribute it and/or modify @@ -22,7 +17,6 @@ * along with Privilege. If not, see . * */ - package ch.eitchnet.privilege.model; import java.io.Serializable; diff --git a/src/main/java/ch/eitchnet/privilege/model/UserRep.java b/src/main/java/ch/eitchnet/privilege/model/UserRep.java index 38e3562e3..66b27284c 100644 --- a/src/main/java/ch/eitchnet/privilege/model/UserRep.java +++ b/src/main/java/ch/eitchnet/privilege/model/UserRep.java @@ -1,11 +1,6 @@ /* - * Copyright (c) 2010, 2011 + * Copyright (c) 2010 - 2012 * - * Robert von Burg - * - */ - -/* * This file is part of Privilege. * * Privilege is free software: you can redistribute it and/or modify @@ -22,7 +17,6 @@ * along with Privilege. If not, see . * */ - package ch.eitchnet.privilege.model; import java.io.Serializable; diff --git a/src/main/java/ch/eitchnet/privilege/model/UserState.java b/src/main/java/ch/eitchnet/privilege/model/UserState.java index d98b9df14..4b451b66f 100644 --- a/src/main/java/ch/eitchnet/privilege/model/UserState.java +++ b/src/main/java/ch/eitchnet/privilege/model/UserState.java @@ -1,11 +1,6 @@ /* - * Copyright (c) 2010, 2011 + * Copyright (c) 2010 - 2012 * - * Robert von Burg - * - */ - -/* * This file is part of Privilege. * * Privilege is free software: you can redistribute it and/or modify @@ -22,7 +17,6 @@ * along with Privilege. If not, see . * */ - package ch.eitchnet.privilege.model; import ch.eitchnet.privilege.model.internal.User; diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/Privilege.java b/src/main/java/ch/eitchnet/privilege/model/internal/Privilege.java index 2ee6121f5..c62456fbd 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/Privilege.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/Privilege.java @@ -1,11 +1,6 @@ /* - * Copyright (c) 2010, 2011 + * Copyright (c) 2010 - 2012 * - * Robert von Burg - * - */ - -/* * This file is part of Privilege. * * Privilege is free software: you can redistribute it and/or modify @@ -22,7 +17,6 @@ * along with Privilege. If not, see . * */ - package ch.eitchnet.privilege.model.internal; import java.util.Collections; diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/Role.java b/src/main/java/ch/eitchnet/privilege/model/internal/Role.java index aa76e6bd6..ab929ffde 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/Role.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/Role.java @@ -1,11 +1,6 @@ /* - * Copyright (c) 2010, 2011 + * Copyright (c) 2010 - 2012 * - * Robert von Burg - * - */ - -/* * This file is part of Privilege. * * Privilege is free software: you can redistribute it and/or modify @@ -22,7 +17,6 @@ * along with Privilege. If not, see . * */ - package ch.eitchnet.privilege.model.internal; import java.util.Collections; diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/Session.java b/src/main/java/ch/eitchnet/privilege/model/internal/Session.java index 2210c8fc3..7e3bb8614 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/Session.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/Session.java @@ -1,11 +1,6 @@ /* - * Copyright (c) 2010, 2011 + * Copyright (c) 2010 - 2012 * - * Robert von Burg - * - */ - -/* * This file is part of Privilege. * * Privilege is free software: you can redistribute it and/or modify @@ -22,7 +17,6 @@ * along with Privilege. If not, see . * */ - package ch.eitchnet.privilege.model.internal; import ch.eitchnet.privilege.handler.PrivilegeHandler; diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/User.java b/src/main/java/ch/eitchnet/privilege/model/internal/User.java index bdfc7a438..9e12026ac 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/User.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/User.java @@ -1,11 +1,6 @@ /* - * Copyright (c) 2010, 2011 + * Copyright (c) 2010 - 2012 * - * Robert von Burg - * - */ - -/* * This file is part of Privilege. * * Privilege is free software: you can redistribute it and/or modify @@ -22,7 +17,6 @@ * along with Privilege. If not, see . * */ - package ch.eitchnet.privilege.model.internal; import java.util.Collections; diff --git a/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java b/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java index bd67adf0e..ebb061656 100644 --- a/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java +++ b/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java @@ -1,11 +1,6 @@ /* - * Copyright (c) 2010, 2011 + * Copyright (c) 2010 - 2012 * - * Robert von Burg - * - */ - -/* * This file is part of Privilege. * * Privilege is free software: you can redistribute it and/or modify @@ -22,7 +17,6 @@ * along with Privilege. If not, see . * */ - package ch.eitchnet.privilege.policy; import ch.eitchnet.privilege.i18n.AccessDeniedException; diff --git a/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicy.java b/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicy.java index 9c0d14bb0..50fc79837 100644 --- a/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicy.java +++ b/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicy.java @@ -1,11 +1,6 @@ /* - * Copyright (c) 2010, 2011 + * Copyright (c) 2010 - 2012 * - * Robert von Burg - * - */ - -/* * This file is part of Privilege. * * Privilege is free software: you can redistribute it and/or modify @@ -22,7 +17,6 @@ * along with Privilege. If not, see . * */ - package ch.eitchnet.privilege.policy; import ch.eitchnet.privilege.i18n.AccessDeniedException; diff --git a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java index 859ff4415..2bd5c0a77 100644 --- a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -1,11 +1,6 @@ /* - * Copyright (c) 2010, 2011 + * Copyright (c) 2010 - 2012 * - * Robert von Burg - * - */ - -/* * This file is part of Privilege. * * Privilege is free software: you can redistribute it and/or modify @@ -22,7 +17,6 @@ * along with Privilege. If not, see . * */ - package ch.eitchnet.privilege.test; import java.io.File; @@ -58,15 +52,15 @@ import ch.eitchnet.privilege.model.UserState; public class PrivilegeTest { private static final String ADMIN = "admin"; - private static final String PASS_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 PASS_BOB = "admin1"; + private static final byte[] PASS_BOB = "admin1".getBytes(); private static final String ROLE_FEATHERLITE_USER = "FeatherliteUser"; private static final String ROLE_USER = "user"; - private static final String PASS_DEF = "def"; - private static final String PASS_BAD = "123"; - private static final String PASS_TED = "12345"; + 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 = Logger.getLogger(PrivilegeTest.class); @@ -103,11 +97,17 @@ public class PrivilegeTest { @Test public void testAuthenticationOk() throws Exception { - Certificate certificate = privilegeHandler.authenticate(ADMIN, PASS_ADMIN); + Certificate certificate = privilegeHandler.authenticate(ADMIN, copyBytes(PASS_ADMIN)); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); privilegeHandler.invalidateSession(certificate); } + private byte[] copyBytes(byte[] bytes) { + byte[] copy = new byte[bytes.length]; + System.arraycopy(bytes, 0, copy, 0, bytes.length); + return copy; + } + /** * @throws Exception * if something goes wrong @@ -115,7 +115,7 @@ public class PrivilegeTest { @Test(expected = AccessDeniedException.class) public void testFailAuthenticationNOk() throws Exception { - Certificate certificate = privilegeHandler.authenticate(ADMIN, PASS_BAD); + Certificate certificate = privilegeHandler.authenticate(ADMIN, copyBytes(PASS_BAD)); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); privilegeHandler.invalidateSession(certificate); } @@ -139,7 +139,7 @@ public class PrivilegeTest { @Test public void testAddUserBobAsAdmin() throws Exception { - Certificate certificate = privilegeHandler.authenticate(ADMIN, PASS_ADMIN); + Certificate certificate = privilegeHandler.authenticate(ADMIN, copyBytes(PASS_ADMIN)); // let's add a new user bob UserRep userRep = new UserRep("1", BOB, "Bob", "Newman", UserState.NEW, new HashSet(), null, @@ -148,7 +148,7 @@ public class PrivilegeTest { logger.info("Added user " + BOB); // set bob's password - privilegeHandler.setUserPassword(certificate, BOB, PASS_BOB); + privilegeHandler.setUserPassword(certificate, BOB, copyBytes(PASS_BOB)); logger.info("Set Bob's password"); privilegeHandler.invalidateSession(certificate); } @@ -161,7 +161,7 @@ public class PrivilegeTest { */ @Test(expected = AccessDeniedException.class) public void testFailAuthAsBob() throws Exception { - Certificate certificate = privilegeHandler.authenticate(BOB, PASS_BOB); + Certificate certificate = privilegeHandler.authenticate(BOB, copyBytes(PASS_BOB)); privilegeHandler.invalidateSession(certificate); } @@ -171,7 +171,7 @@ public class PrivilegeTest { */ @Test public void testEnableUserBob() throws Exception { - Certificate certificate = privilegeHandler.authenticate(ADMIN, PASS_ADMIN); + Certificate certificate = privilegeHandler.authenticate(ADMIN, copyBytes(PASS_ADMIN)); privilegeHandler.setUserState(certificate, BOB, UserState.ENABLED); privilegeHandler.invalidateSession(certificate); } @@ -185,7 +185,7 @@ public class PrivilegeTest { @Test(expected = PrivilegeException.class) public void testFailAuthUserBob() throws Exception { - Certificate certificate = privilegeHandler.authenticate(BOB, PASS_BOB); + Certificate certificate = privilegeHandler.authenticate(BOB, copyBytes(PASS_BOB)); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); privilegeHandler.invalidateSession(certificate); } @@ -196,7 +196,7 @@ public class PrivilegeTest { */ @Test public void testAddRole() throws Exception { - Certificate certificate = privilegeHandler.authenticate(ADMIN, PASS_ADMIN); + Certificate certificate = privilegeHandler.authenticate(ADMIN, copyBytes(PASS_ADMIN)); Map privilegeMap = new HashMap(); RoleRep roleRep = new RoleRep(ROLE_USER, privilegeMap); @@ -211,7 +211,7 @@ public class PrivilegeTest { */ @Test public void testAddRoleToBob() throws Exception { - Certificate certificate = privilegeHandler.authenticate(ADMIN, PASS_ADMIN); + Certificate certificate = privilegeHandler.authenticate(ADMIN, copyBytes(PASS_ADMIN)); privilegeHandler.addRoleToUser(certificate, BOB, ROLE_USER); privilegeHandler.invalidateSession(certificate); } @@ -222,7 +222,7 @@ public class PrivilegeTest { */ @Test public void testAuthAsBob() throws Exception { - Certificate certificate = privilegeHandler.authenticate(BOB, PASS_BOB); + Certificate certificate = privilegeHandler.authenticate(BOB, copyBytes(PASS_BOB)); privilegeHandler.invalidateSession(certificate); } @@ -236,7 +236,7 @@ public class PrivilegeTest { public void testFailAddUserTedAsBob() throws Exception { // auth as Bog - Certificate certificate = privilegeHandler.authenticate(BOB, PASS_BOB); + Certificate certificate = privilegeHandler.authenticate(BOB, copyBytes(PASS_BOB)); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); // let's add a new user Ted @@ -254,7 +254,7 @@ public class PrivilegeTest { @Test public void testAddAdminRoleToBob() throws Exception { - Certificate certificate = privilegeHandler.authenticate(ADMIN, PASS_ADMIN); + Certificate certificate = privilegeHandler.authenticate(ADMIN, copyBytes(PASS_ADMIN)); privilegeHandler.addRoleToUser(certificate, BOB, PrivilegeHandler.PRIVILEGE_ADMIN_ROLE); logger.info("Added " + PrivilegeHandler.PRIVILEGE_ADMIN_ROLE + " to " + ADMIN); privilegeHandler.invalidateSession(certificate); @@ -267,7 +267,7 @@ public class PrivilegeTest { @Test public void testAddUserTedAsBob() throws Exception { - Certificate certificate = privilegeHandler.authenticate(BOB, PASS_BOB); + Certificate certificate = privilegeHandler.authenticate(BOB, copyBytes(PASS_BOB)); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); // let's add a new user ted @@ -288,11 +288,11 @@ public class PrivilegeTest { @Test public void testSetTedPwdAsBob() throws Exception { - Certificate certificate = privilegeHandler.authenticate(BOB, PASS_BOB); + Certificate certificate = privilegeHandler.authenticate(BOB, copyBytes(PASS_BOB)); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); // set ted's password to default - privilegeHandler.setUserPassword(certificate, TED, PASS_DEF); + privilegeHandler.setUserPassword(certificate, TED, copyBytes(PASS_DEF)); privilegeHandler.invalidateSession(certificate); } @@ -303,8 +303,8 @@ public class PrivilegeTest { */ @Test public void testTedChangesOwnPwd() throws Exception { - Certificate certificate = privilegeHandler.authenticate(TED, PASS_DEF); - privilegeHandler.setUserPassword(certificate, TED, PASS_TED); + Certificate certificate = privilegeHandler.authenticate(TED, copyBytes(PASS_DEF)); + privilegeHandler.setUserPassword(certificate, TED, copyBytes(PASS_TED)); privilegeHandler.invalidateSession(certificate); } @@ -314,7 +314,7 @@ public class PrivilegeTest { */ @Test public void testAuthAsTed() throws Exception { - Certificate certificate = privilegeHandler.authenticate(TED, PASS_TED); + Certificate certificate = privilegeHandler.authenticate(TED, copyBytes(PASS_TED)); privilegeHandler.invalidateSession(certificate); } @@ -325,7 +325,7 @@ public class PrivilegeTest { @Test public void testPerformRestrictableAsAdmin() throws Exception { - Certificate certificate = privilegeHandler.authenticate(ADMIN, PASS_ADMIN); + Certificate certificate = privilegeHandler.authenticate(ADMIN, copyBytes(PASS_ADMIN)); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); // see if eitch can perform restrictable @@ -342,7 +342,7 @@ public class PrivilegeTest { */ @Test(expected = AccessDeniedException.class) public void testFailPerformRestrictableAsBob() throws Exception { - Certificate certificate = privilegeHandler.authenticate(BOB, PASS_BOB); + Certificate certificate = privilegeHandler.authenticate(BOB, copyBytes(PASS_BOB)); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); // see if bob can perform restrictable @@ -361,7 +361,7 @@ public class PrivilegeTest { @Test public void testAddFeatherliteRoleToBob() throws Exception { - Certificate certificate = privilegeHandler.authenticate(ADMIN, PASS_ADMIN); + Certificate certificate = privilegeHandler.authenticate(ADMIN, copyBytes(PASS_ADMIN)); privilegeHandler.addRoleToUser(certificate, BOB, ROLE_FEATHERLITE_USER); logger.info("Added " + ROLE_FEATHERLITE_USER + " to " + BOB); privilegeHandler.invalidateSession(certificate); @@ -375,7 +375,7 @@ public class PrivilegeTest { */ @Test public void testPerformRestrictableAsBob() throws Exception { - Certificate certificate = privilegeHandler.authenticate(BOB, PASS_BOB); + Certificate certificate = privilegeHandler.authenticate(BOB, copyBytes(PASS_BOB)); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); // see if bob can perform restrictable diff --git a/src/test/java/ch/eitchnet/privilege/test/TestRestrictable.java b/src/test/java/ch/eitchnet/privilege/test/TestRestrictable.java index e384e89df..117103ee6 100644 --- a/src/test/java/ch/eitchnet/privilege/test/TestRestrictable.java +++ b/src/test/java/ch/eitchnet/privilege/test/TestRestrictable.java @@ -1,11 +1,6 @@ /* - * Copyright (c) 2010, 2011 + * Copyright (c) 2010 - 2012 * - * Robert von Burg - * - */ - -/* * This file is part of Privilege. * * Privilege is free software: you can redistribute it and/or modify @@ -22,7 +17,6 @@ * along with Privilege. If not, see . * */ - package ch.eitchnet.privilege.test; import ch.eitchnet.privilege.model.Restrictable; From 45e5eac46df365f3d4476040c03018f5a8fb56d8 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 4 Aug 2012 21:31:56 +0200 Subject: [PATCH 075/173] [Minor] change modifier of DefaultPrivilegeHandler.nextSessionId() set method DefaultPrivilegeHandler.nextSessionId() to protected from private so it can be used in sub classes --- .../ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 748619a16..afedc0189 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -1018,7 +1018,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { /** * @return a new session id */ - private synchronized String nextSessionId() { + protected synchronized String nextSessionId() { return Long.toString(++this.lastSessionId % Long.MAX_VALUE); } From 3831d29a12db520315c2d07160669bf746fb344a Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 4 Aug 2012 21:36:49 +0200 Subject: [PATCH 076/173] [Minor] updated dependency to ch.eitchnet.utils to 0.1.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 5a7c18a40..75a37097c 100644 --- a/pom.xml +++ b/pom.xml @@ -130,7 +130,7 @@ ch.eitchnet ch.eitchnet.utils - 1.0-SNAPSHOT + 0.1.0-SNAPSHOT From 8b780368d79440340f44fc0255fa2bf41babba2b Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 5 Aug 2012 01:33:54 +0200 Subject: [PATCH 077/173] [Devel] implementing PrivilegeHandler.runAsSystem() Implementing the runAsSystem() so that special actions can be performed, where no user is logged in Refactored the test classes so that model specific classes are in the model sub package. --- config/PrivilegeModel.xml | 27 ++- .../handler/DefaultPrivilegeHandler.java | 166 ++++++++++++++++-- .../privilege/handler/PrivilegeHandler.java | 14 +- .../privilege/handler/SystemUserAction.java | 31 ++++ .../handler/XmlPersistenceHandler.java | 108 ++++++------ .../helper/CertificateThreadLocal.java | 46 +++++ .../eitchnet/privilege/model/UserState.java | 7 +- .../privilege/test/PrivilegeTest.java | 79 ++++++++- .../test/{ => model}/TestRestrictable.java | 4 +- .../test/model/TestSystemRestrictable.java | 46 +++++ .../test/model/TestSystemUserAction.java | 52 ++++++ .../test/model/TestSystemUserActionDeny.java | 50 ++++++ 12 files changed, 544 insertions(+), 86 deletions(-) create mode 100644 src/main/java/ch/eitchnet/privilege/handler/SystemUserAction.java create mode 100644 src/main/java/ch/eitchnet/privilege/helper/CertificateThreadLocal.java rename src/test/java/ch/eitchnet/privilege/test/{ => model}/TestRestrictable.java (93%) create mode 100644 src/test/java/ch/eitchnet/privilege/test/model/TestSystemRestrictable.java create mode 100644 src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java create mode 100644 src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserActionDeny.java diff --git a/config/PrivilegeModel.xml b/config/PrivilegeModel.xml index e43216141..396878bc4 100644 --- a/config/PrivilegeModel.xml +++ b/config/PrivilegeModel.xml @@ -26,19 +26,30 @@ along with Privilege. If not, see . - Featherlite + Application Administrator ENABLED en_GB PrivilegeAdmin - FeatherliteUser + AppUser + + + System User + Administrator + SYSTEM + en_GB + + system_admin_privileges + + + @@ -46,11 +57,17 @@ along with Privilege. If not, see . - - + + true - + + + + + true + + true diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index afedc0189..d4444d9af 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -74,32 +74,37 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { /** * last assigned id for the {@link Session}s */ - protected long lastSessionId; + private long lastSessionId; + + /** + * This map stores certificates for system users as a cache + */ + private Map systemUserCertificateMap; + + /** + * Map keeping a reference to all active sessions with their certificates + */ + private Map sessionMap; /** * Map of {@link PrivilegePolicy} classes */ private Map> policyMap; - /** - * Map keeping a reference to all active sessions with their certificates - */ - protected Map sessionMap; - /** * The persistence handler is used for getting objects and saving changes */ - protected PersistenceHandler persistenceHandler; + private PersistenceHandler persistenceHandler; /** * The encryption handler is used for generating hashes and tokens */ - protected EncryptionHandler encryptionHandler; + private EncryptionHandler encryptionHandler; /** * flag to define if already initialized */ - protected boolean initialized; + private boolean initialized; /** * @see ch.eitchnet.privilege.handler.PrivilegeHandler#getRole(java.lang.String) @@ -138,7 +143,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { * @throws PrivilegeException * if the {@link PrivilegePolicy} object for the given policy name could not be instantiated */ - protected PrivilegePolicy getPolicy(String policyName) { + private PrivilegePolicy getPolicy(String policyName) { // get the policies class Class policyClazz = this.policyMap.get(policyName); @@ -673,20 +678,20 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { User user = this.persistenceHandler.getUser(username); // no user means no authentication if (user == null) - throw new AccessDeniedException("There is no user defined with the credentials: " + username - + " / ***..."); + throw new AccessDeniedException("There is no user defined with the username " + username); // validate password String pwHash = user.getPassword(); if (pwHash == null) throw new AccessDeniedException("User has no password and may not login!"); if (!pwHash.equals(passwordHash)) - throw new AccessDeniedException("Password is incorrect for " + username + " / ***..."); + throw new AccessDeniedException("Password is incorrect for " + username); // validate if user is allowed to login + // this also capture the trying to login of SYSTEM user if (user.getUserState() != UserState.ENABLED) - throw new AccessDeniedException("User " + username + " is not ENABLED. State is: " - + user.getUserState()); + throw new AccessDeniedException("User " + username + " does not have state " + UserState.ENABLED + + " and can not login!"); // validate user has at least one role if (user.getRoles().isEmpty()) { @@ -832,7 +837,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { * @param role * @param restrictable */ - protected boolean actionAllowed(Role role, Restrictable restrictable) { + private boolean actionAllowed(Role role, Restrictable restrictable) { // validate PrivilegeName for this restrictable String privilegeName = restrictable.getPrivilegeName(); @@ -979,7 +984,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { * @throws PrivilegeException * if the this method is called multiple times or an initialization exception occurs */ - public void initialize(Map parameterMap, EncryptionHandler encryptionHandler, + public synchronized void initialize(Map parameterMap, EncryptionHandler encryptionHandler, PersistenceHandler persistenceHandler, Map> policyMap) { if (this.initialized) @@ -996,6 +1001,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { this.lastSessionId = 0l; this.sessionMap = Collections.synchronizedMap(new HashMap()); + this.systemUserCertificateMap = Collections.synchronizedMap(new HashMap()); this.initialized = true; } @@ -1018,7 +1024,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { /** * @return a new session id */ - protected synchronized String nextSessionId() { + private synchronized String nextSessionId() { return Long.toString(++this.lastSessionId % Long.MAX_VALUE); } @@ -1061,4 +1067,128 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } } } + + /** + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#runAsSystem(java.lang.String, + * ch.eitchnet.privilege.handler.SystemUserAction) + */ + @Override + public void runAsSystem(String systemUsername, SystemUserAction action) throws PrivilegeException { + + if (systemUsername == null) + throw new PrivilegeException("systemUsername may not be null!"); + if (action == null) + throw new PrivilegeException("action may not be null!"); + + // get the system user + User systemUser = this.persistenceHandler.getUser(systemUsername); + if (systemUser == null) + throw new PrivilegeException("System user " + systemUsername + " does not exist!"); + + // validate this is a system user + if (systemUser.getUserState() != UserState.SYSTEM) + throw new PrivilegeException("User " + systemUsername + " is not a System user!"); + + // validate this system user may perform the given action + String actionClassname = action.getClass().getName(); + checkPrivilege(actionClassname, systemUser); + + // get certificate for this system user + Certificate systemUserCertificate = getSystemUserCertificate(systemUsername); + + // perform the action + action.execute(systemUserCertificate); + } + + /** + * Checks if the given user has the given privilege + * + * @param privilegeName + * the name of the privilege to check on the user + * @param user + * the user to check for the given privilege + * + * @throws PrivilegeException + * if the user does not have the privilege + */ + private void checkPrivilege(String privilegeName, User user) throws PrivilegeException { + + // check each role if it has the privilege + for (String roleName : user.getRoles()) { + + Role role = this.persistenceHandler.getRole(roleName); + + // on the first occurrence of our privilege, stop + if (role.hasPrivilege(privilegeName)) + return; + } + + // default throw exception, as the user does not have the privilege + throw new PrivilegeException("User " + user.getUsername() + " does not have Privilege " + privilegeName); + } + + /** + * 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 Certificate getSystemUserCertificate(String systemUsername) { + + // see if a certificate has already been created for this system user + Certificate systemUserCertificate = systemUserCertificateMap.get(systemUsername); + if (systemUserCertificate != null) + return systemUserCertificate; + + // otherwise log this system user in, by performing a slightly different authentication + + // we only work with hashed passwords + String passwordHash = this.encryptionHandler.convertToHash(systemUsername.getBytes()); + + // get user object + User user = this.persistenceHandler.getUser(systemUsername); + // no user means no authentication + if (user == null) + throw new AccessDeniedException("The system user with username " + systemUsername + " does not exist!"); + + // validate password + String pwHash = user.getPassword(); + if (pwHash == null) + throw new AccessDeniedException("System user " + systemUsername + " has no password and may not login!"); + if (!pwHash.equals(passwordHash)) + throw new AccessDeniedException("System user " + systemUsername + " has an incorrect password defined!"); + + // validate user state is system + if (user.getUserState() != UserState.SYSTEM) + throw new PrivilegeException("The system " + systemUsername + " user does not have expected user state " + + UserState.SYSTEM); + + // validate user has at least one role + if (user.getRoles().isEmpty()) { + throw new PrivilegeException("The system user " + systemUsername + " does not have any roles defined!"); + } + + // get 2 auth tokens + String authToken = this.encryptionHandler.nextToken(); + String authPassword = this.encryptionHandler.nextToken(); + + // get next session id + String sessionId = nextSessionId(); + + // create a new certificate, with details of the user + systemUserCertificate = new Certificate(sessionId, systemUsername, authToken, authPassword, user.getLocale(), + new HashMap(user.getProperties())); + + // create and save a new session + Session session = new Session(sessionId, systemUsername, authToken, authPassword, System.currentTimeMillis()); + this.sessionMap.put(sessionId, new CertificateSessionPair(session, systemUserCertificate)); + + // log + logger.info("The system user " + systemUsername + " is logged in with session " + session); + + return systemUserCertificate; + } } diff --git a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java index c6577ea64..4f2c54b2a 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java @@ -46,7 +46,7 @@ import ch.eitchnet.privilege.model.internal.User; public interface PrivilegeHandler { /** - * value = PrivilegeAdmin: This is the role users must have, if they are allowed to modify objects + * PRIVILEGE_ADMIN_ROLE = PrivilegeAdmin: This is the role users must have, if they are allowed to modify objects */ public static final String PRIVILEGE_ADMIN_ROLE = "PrivilegeAdmin"; @@ -437,4 +437,16 @@ public interface PrivilegeHandler { * if the users of the given certificate does not have the privilege to perform this action */ public boolean persist(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 + */ + public void runAsSystem(String systemUsername, SystemUserAction action) throws PrivilegeException; } diff --git a/src/main/java/ch/eitchnet/privilege/handler/SystemUserAction.java b/src/main/java/ch/eitchnet/privilege/handler/SystemUserAction.java new file mode 100644 index 000000000..0aac16d1d --- /dev/null +++ b/src/main/java/ch/eitchnet/privilege/handler/SystemUserAction.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2012 + * + * This file is part of ??????????????? + * + * ?????????????? is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with ????????????????. If not, see . + * + */ +package ch.eitchnet.privilege.handler; + +import ch.eitchnet.privilege.model.Certificate; + +/** + * @author Robert von Burg + * + */ +public interface SystemUserAction { + + public void execute(Certificate certificate); +} diff --git a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java index 9e2608437..e1323f7ff 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java @@ -290,7 +290,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { * * @return the map of converted {@link User} objects */ - protected static Map readUsers(Element usersRootElement) { + protected Map readUsers(Element usersRootElement) { Map userMap = new HashMap(); @@ -321,6 +321,8 @@ public class XmlPersistenceHandler implements PersistenceHandler { String roleName = roleElement.getTextTrim(); if (roleName.isEmpty()) { logger.error("User " + username + " has a role defined with no name, Skipped."); + } else if (!this.roleMap.containsKey(roleName)) { + logger.error("User " + username + " has a inexistant role " + roleName + ", Skipped."); } else { roles.add(roleName); } @@ -332,10 +334,10 @@ public class XmlPersistenceHandler implements PersistenceHandler { // create user User user = new User(userId, username, password, firstname, surname, userState, roles, locale, propertyMap); - logger.info("Added user " + user); // put user in map userMap.put(username, user); + logger.info("Loaded user " + user); } return userMap; @@ -349,7 +351,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { * * @return the map of converted {@link Role} objects */ - protected static Map readRoles(Element rolesRootElement) { + protected Map readRoles(Element rolesRootElement) { Map roleMap = new HashMap(); @@ -368,6 +370,56 @@ public class XmlPersistenceHandler implements PersistenceHandler { return roleMap; } + /** + * Parses {@link Privilege} objects from their XML representation to their objects + * + * @param roleParentElement + * the parent on which the Privilege XML elements are + * + * @return the map of {@link Privilege} objects + */ + protected Map readPrivileges(Element roleParentElement) { + + Map privilegeMap = new HashMap(); + + @SuppressWarnings("unchecked") + List privilegeElements = roleParentElement.elements(XmlConstants.XML_PRIVILEGE); + for (Element privilegeElement : privilegeElements) { + + String privilegeName = privilegeElement.attributeValue(XmlConstants.XML_ATTR_NAME); + String privilegePolicy = privilegeElement.attributeValue(XmlConstants.XML_ATTR_POLICY); + + Element allAllowedE = privilegeElement.element(XmlConstants.XML_ALL_ALLOWED); + boolean allAllowed = false; + if (allAllowedE != null) { + allAllowed = Boolean.valueOf(allAllowedE.getTextTrim()).booleanValue(); + } + + @SuppressWarnings("unchecked") + List denyElements = privilegeElement.elements(XmlConstants.XML_DENY); + Set denyList = new HashSet(denyElements.size()); + for (Element denyElement : denyElements) { + String denyValue = denyElement.getTextTrim(); + if (!denyValue.isEmpty()) + denyList.add(denyValue); + } + + @SuppressWarnings("unchecked") + List allowElements = privilegeElement.elements(XmlConstants.XML_ALLOW); + Set allowList = new HashSet(allowElements.size()); + for (Element allowElement : allowElements) { + String allowValue = allowElement.getTextTrim(); + if (!allowValue.isEmpty()) + allowList.add(allowValue); + } + + Privilege privilege = new Privilege(privilegeName, privilegePolicy, allAllowed, denyList, allowList); + privilegeMap.put(privilegeName, privilege); + } + + return privilegeMap; + } + /** * Converts {@link User} objects to their XML representations * @@ -466,56 +518,6 @@ public class XmlPersistenceHandler implements PersistenceHandler { return rolesAsElements; } - /** - * Parses {@link Privilege} objects from their XML representation to their objects - * - * @param roleParentElement - * the parent on which the Privilege XML elements are - * - * @return the map of {@link Privilege} objects - */ - protected static Map readPrivileges(Element roleParentElement) { - - Map privilegeMap = new HashMap(); - - @SuppressWarnings("unchecked") - List privilegeElements = roleParentElement.elements(XmlConstants.XML_PRIVILEGE); - for (Element privilegeElement : privilegeElements) { - - String privilegeName = privilegeElement.attributeValue(XmlConstants.XML_ATTR_NAME); - String privilegePolicy = privilegeElement.attributeValue(XmlConstants.XML_ATTR_POLICY); - - Element allAllowedE = privilegeElement.element(XmlConstants.XML_ALL_ALLOWED); - boolean allAllowed = false; - if (allAllowedE != null) { - allAllowed = Boolean.valueOf(allAllowedE.getTextTrim()).booleanValue(); - } - - @SuppressWarnings("unchecked") - List denyElements = privilegeElement.elements(XmlConstants.XML_DENY); - Set denyList = new HashSet(denyElements.size()); - for (Element denyElement : denyElements) { - String denyValue = denyElement.getTextTrim(); - if (!denyValue.isEmpty()) - denyList.add(denyValue); - } - - @SuppressWarnings("unchecked") - List allowElements = privilegeElement.elements(XmlConstants.XML_ALLOW); - Set allowList = new HashSet(allowElements.size()); - for (Element allowElement : allowElements) { - String allowValue = allowElement.getTextTrim(); - if (!allowValue.isEmpty()) - allowList.add(allowValue); - } - - Privilege privilege = new Privilege(privilegeName, privilegePolicy, allAllowed, denyList, allowList); - privilegeMap.put(privilegeName, privilege); - } - - return privilegeMap; - } - /** * Converts {@link Privilege} objects to their XML representation * diff --git a/src/main/java/ch/eitchnet/privilege/helper/CertificateThreadLocal.java b/src/main/java/ch/eitchnet/privilege/helper/CertificateThreadLocal.java new file mode 100644 index 000000000..9830541e3 --- /dev/null +++ b/src/main/java/ch/eitchnet/privilege/helper/CertificateThreadLocal.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2012 + * + * This file is part of ??????????????? + * + * ?????????????? is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with ????????????????. If not, see . + * + */ +package ch.eitchnet.privilege.helper; + +import ch.eitchnet.privilege.model.Certificate; + +/** + * @author Robert von Burg + * + */ +public class CertificateThreadLocal extends ThreadLocal { + + private static final CertificateThreadLocal instance; + static { + instance = new CertificateThreadLocal(); + } + + public static CertificateThreadLocal getInstance() { + return instance; + } + + public static Certificate getCert() { + return instance.get(); + } + + public static void setCert(Certificate certificate) { + instance.set(certificate); + } +} diff --git a/src/main/java/ch/eitchnet/privilege/model/UserState.java b/src/main/java/ch/eitchnet/privilege/model/UserState.java index 4b451b66f..c34782e1b 100644 --- a/src/main/java/ch/eitchnet/privilege/model/UserState.java +++ b/src/main/java/ch/eitchnet/privilege/model/UserState.java @@ -51,5 +51,10 @@ public enum UserState { /** * the user has automatically expired through a predefined time */ - EXPIRED; + EXPIRED, + + /** + * This is the System user state which is special and thus exempted from normal uses + */ + SYSTEM; } diff --git a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java index 2bd5c0a77..cd1ada34d 100644 --- a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -33,6 +33,7 @@ import org.junit.BeforeClass; import org.junit.Test; import ch.eitchnet.privilege.handler.PrivilegeHandler; +import ch.eitchnet.privilege.helper.CertificateThreadLocal; import ch.eitchnet.privilege.helper.InitializationHelper; import ch.eitchnet.privilege.i18n.AccessDeniedException; import ch.eitchnet.privilege.i18n.PrivilegeException; @@ -42,6 +43,9 @@ 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; /** * JUnit for performing Privilege tests. This JUnit is by no means complete, but checks the bare minimum. TODO add more @@ -55,8 +59,9 @@ public class PrivilegeTest { 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 byte[] PASS_BOB = "admin1".getBytes(); - private static final String ROLE_FEATHERLITE_USER = "FeatherliteUser"; + private static final String ROLE_APP_USER = "AppUser"; private static final String ROLE_USER = "user"; private static final byte[] PASS_DEF = "def".getBytes(); private static final byte[] PASS_BAD = "123".getBytes(); @@ -335,7 +340,7 @@ public class PrivilegeTest { } /** - * Tests if the user bob, who does not have FeatherliteUser role can perform restrictable + * Tests if the user bob, who does not have AppUser role can perform restrictable * * @throws Exception * if something goes wrong @@ -359,16 +364,16 @@ public class PrivilegeTest { * if something goes wrong */ @Test - public void testAddFeatherliteRoleToBob() throws Exception { + public void testAddAppRoleToBob() throws Exception { Certificate certificate = privilegeHandler.authenticate(ADMIN, copyBytes(PASS_ADMIN)); - privilegeHandler.addRoleToUser(certificate, BOB, ROLE_FEATHERLITE_USER); - logger.info("Added " + ROLE_FEATHERLITE_USER + " to " + BOB); + privilegeHandler.addRoleToUser(certificate, BOB, ROLE_APP_USER); + logger.info("Added " + ROLE_APP_USER + " to " + BOB); privilegeHandler.invalidateSession(certificate); } /** - * Tests if the user bob, who now has FeatherliteUser role can perform restrictable + * Tests if the user bob, who now has AppUser role can perform restrictable * * @throws Exception * if something goes wrong @@ -386,4 +391,66 @@ public class PrivilegeTest { privilegeHandler.invalidateSession(certificate); } } + + /** + * Tests if an action can be performed as a system user + * + * @throws Exception + * if something goes wrong + */ + @Test + public void testPerformSystemRestrictable() throws Exception { + + // create the action to be performed as a system user + TestSystemUserAction action = new TestSystemUserAction(privilegeHandler); + + // and then perform the action + privilegeHandler.runAsSystem(SYSTEM_USER_ADMIN, action); + } + + /** + * Checks that the system user can not perform a valid action, but illegal privilege + * + * @throws Exception + * if something goes wrong + */ + @Test(expected = PrivilegeException.class) + public void testPerformSystemRestrictableFailPrivilege() throws Exception { + + // create the action to be performed as a system user + TestSystemUserActionDeny action = new TestSystemUserActionDeny(privilegeHandler); + + // and then perform the action + privilegeHandler.runAsSystem(SYSTEM_USER_ADMIN, action); + } + + /** + * System user may not login + * + * @throws Exception + * if something goes wrong + */ + @Test(expected = AccessDeniedException.class) + public void testLoginSystemUser() throws Exception { + + privilegeHandler.authenticate(SYSTEM_USER_ADMIN, SYSTEM_USER_ADMIN.getBytes()); + } + + @Test + public void testCertificateThreadLocal() { + + Certificate certificate = privilegeHandler.authenticate(ADMIN, copyBytes(PASS_ADMIN)); + org.junit.Assert.assertTrue("Certificate is null!", certificate != null); + + // set certificate into thread local + CertificateThreadLocal.getInstance().set(certificate); + + // see if bob can perform restrictable by returning certificate from CertificateThreadLocal + Restrictable restrictable = new TestRestrictable(); + try { + privilegeHandler.actionAllowed(CertificateThreadLocal.getInstance().get(), restrictable); + } finally { + privilegeHandler.invalidateSession(certificate); + } + } } diff --git a/src/test/java/ch/eitchnet/privilege/test/TestRestrictable.java b/src/test/java/ch/eitchnet/privilege/test/model/TestRestrictable.java similarity index 93% rename from src/test/java/ch/eitchnet/privilege/test/TestRestrictable.java rename to src/test/java/ch/eitchnet/privilege/test/model/TestRestrictable.java index 117103ee6..7b677373c 100644 --- a/src/test/java/ch/eitchnet/privilege/test/TestRestrictable.java +++ b/src/test/java/ch/eitchnet/privilege/test/model/TestRestrictable.java @@ -17,7 +17,7 @@ * along with Privilege. If not, see . * */ -package ch.eitchnet.privilege.test; +package ch.eitchnet.privilege.test.model; import ch.eitchnet.privilege.model.Restrictable; @@ -32,7 +32,7 @@ public class TestRestrictable implements Restrictable { */ @Override public String getPrivilegeName() { - return "com.rsp.core.base.service.Service"; + return TestRestrictable.class.getName(); } /** diff --git a/src/test/java/ch/eitchnet/privilege/test/model/TestSystemRestrictable.java b/src/test/java/ch/eitchnet/privilege/test/model/TestSystemRestrictable.java new file mode 100644 index 000000000..d0e6145ba --- /dev/null +++ b/src/test/java/ch/eitchnet/privilege/test/model/TestSystemRestrictable.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2010 - 2012 + * + * This file is part of Privilege. + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . + * + */ +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/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java b/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java new file mode 100644 index 000000000..2b7cfc786 --- /dev/null +++ b/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2012 + * + * This file is part of ??????????????? + * + * ?????????????? is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with ????????????????. If not, see . + * + */ +package ch.eitchnet.privilege.test.model; + +import ch.eitchnet.privilege.handler.PrivilegeHandler; +import ch.eitchnet.privilege.handler.SystemUserAction; +import ch.eitchnet.privilege.model.Certificate; + +/** + * @author Robert von Burg + * + */ +public class TestSystemUserAction implements SystemUserAction { + + private PrivilegeHandler handler; + + /** + * + */ + public TestSystemUserAction(PrivilegeHandler handler) { + this.handler = handler; + } + + /** + * @see ch.eitchnet.privilege.handler.SystemUserAction#execute(ch.eitchnet.privilege.model.Certificate) + */ + @Override + public void execute(Certificate certificate) { + + TestSystemRestrictable restrictable = new TestSystemRestrictable(); + + handler.actionAllowed(certificate, restrictable); + } + +} diff --git a/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserActionDeny.java b/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserActionDeny.java new file mode 100644 index 000000000..1220d008b --- /dev/null +++ b/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserActionDeny.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2012 + * + * This file is part of ??????????????? + * + * ?????????????? is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with ????????????????. If not, see . + * + */ +package ch.eitchnet.privilege.test.model; + +import ch.eitchnet.privilege.handler.PrivilegeHandler; +import ch.eitchnet.privilege.handler.SystemUserAction; +import ch.eitchnet.privilege.model.Certificate; + +/** + * @author Robert von Burg + * + */ +public class TestSystemUserActionDeny implements SystemUserAction { + + private PrivilegeHandler handler; + + /** + * + */ + public TestSystemUserActionDeny(PrivilegeHandler handler) { + this.handler = handler; + } + + /** + * @see ch.eitchnet.privilege.handler.SystemUserAction#execute(ch.eitchnet.privilege.model.Certificate) + */ + @Override + public void execute(Certificate certificate) { + + TestRestrictable restrictable = new TestRestrictable(); + handler.actionAllowed(certificate, restrictable); + } +} From 59e25a8e757c6ba6cf446d2d0958b7b373a087ff Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Mon, 19 Nov 2012 22:50:32 +0100 Subject: [PATCH 078/173] [Minor] code cleanup --- pom.xml | 332 ++++++++---------- .../handler/DefaultEncryptionHandler.java | 2 +- .../handler/DefaultPrivilegeHandler.java | 22 +- .../handler/XmlPersistenceHandler.java | 24 +- .../helper/BootstrapConfigurationHelper.java | 22 +- .../helper/CertificateThreadLocal.java | 6 +- .../eitchnet/privilege/helper/HashHelper.java | 6 +- .../helper/InitializationHelper.java | 15 +- .../eitchnet/privilege/helper/XmlHelper.java | 12 +- .../privilege/test/PrivilegeTest.java | 150 ++++---- .../test/model/TestSystemUserAction.java | 2 +- .../test/model/TestSystemUserActionDeny.java | 2 +- 12 files changed, 278 insertions(+), 317 deletions(-) diff --git a/pom.xml b/pom.xml index 75a37097c..0a189f43f 100644 --- a/pom.xml +++ b/pom.xml @@ -1,201 +1,163 @@ - 4.0.0 - ch.eitchnet - ch.eitchnet.privilege - jar - 0.1.0-SNAPSHOT - ch.eitchnet.privilege - https://github.com/eitch/ch.eitchnet.privilege + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + 4.0.0 - - UTF-8 - + ch.eitchnet + ch.eitchnet.privilege + jar + 0.1.0-SNAPSHOT + ch.eitchnet.privilege + https://github.com/eitch/ch.eitchnet.privilege - + + UTF-8 + - 2011 - - - GNU Lesser General Public License - http://www.gnu.org/licenses/lgpl.html - repo - - - - eitchnet.ch - http://blog.eitchnet.ch - - - - eitch - Robert von Vurg - eitch@eitchnet.ch - http://blog.eitchnet.ch - eitchnet.ch - http://blog.eitchnet.ch - - architect - developer - - +1 - - http://localhost - - - + - - Github Issues - https://github.com/eitch/ch.eitchnet.privilege/issues - + 2011 + + + GNU Lesser General Public License + http://www.gnu.org/licenses/lgpl.html + repo + + + + eitchnet.ch + http://blog.eitchnet.ch + + + + eitch + Robert von Vurg + eitch@eitchnet.ch + http://blog.eitchnet.ch + eitchnet.ch + http://blog.eitchnet.ch + + architect + developer + + +1 + + http://localhost + + + - + - - scm:git:https://github.com/eitch/ch.eitchnet.privilege.git - scm:git:git@github.com:eitch/ch.eitchnet.privilege.git - https://github.com/eitch/ch.eitchnet.privilege - + + scm:git:https://github.com/eitch/ch.eitchnet.privilege.git + scm:git:git@github.com:eitch/ch.eitchnet.privilege.git + https://github.com/eitch/ch.eitchnet.privilege + - + - - - junit - junit - 4.10 - test - - - log4j - log4j - 1.2.17 - - - maven - dom4j - 1.7-20060614 - - - ch.eitchnet - ch.eitchnet.utils - 0.1.0-SNAPSHOT - - + + + junit + junit + 4.10 + test + + + log4j + log4j + 1.2.17 + + + maven + dom4j + 1.7-20060614 + + + ch.eitchnet + ch.eitchnet.utils + 0.1.0-SNAPSHOT + + - - - - - org.apache.maven.plugins - maven-eclipse-plugin - 2.9 - - true - true - - + + + + org.apache.maven.plugins + maven-eclipse-plugin + 2.9 + + true + true + + - - org.apache.maven.plugins - maven-compiler-plugin - 2.3.2 - - 1.6 - 1.6 - - - - - org.apache.maven.plugins - maven-source-plugin - 2.1.2 - - - attach-sources - verify - - jar-no-fork - - - - + + org.apache.maven.plugins + maven-compiler-plugin + 3.0 + + 1.6 + 1.6 + + - - org.apache.maven.plugins - maven-jar-plugin - 2.4 - - - - true - true - - - - - + + org.apache.maven.plugins + maven-source-plugin + 2.1.2 + + + attach-sources + verify + + jar-no-fork + + + + - - org.apache.maven.plugins - maven-site-plugin - 2.3 - - UTF-8 - - + + org.apache.maven.plugins + maven-jar-plugin + 2.4 + + + + true + true + + + + + - - + + org.apache.maven.plugins + maven-site-plugin + 2.3 + + UTF-8 + + + + diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java index 98a5609ba..76d63ce5c 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java @@ -124,7 +124,7 @@ public class DefaultEncryptionHandler implements EncryptionHandler { // test hash algorithm try { convertToHash("test"); - logger.info("Using hashing algorithm " + this.hashAlgorithm); + DefaultEncryptionHandler.logger.info("Using hashing algorithm " + this.hashAlgorithm); } catch (Exception e) { throw new PrivilegeException("[" + EncryptionHandler.class.getName() + "] Defined parameter " + XmlConstants.XML_PARAM_HASH_ALGORITHM + " is invalid because of underlying exception: " diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index d4444d9af..cf87c63b8 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -403,7 +403,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // ignore if user already has role Set currentRoles = user.getRoles(); if (currentRoles.contains(roleName)) { - logger.error("User " + username + " already has role " + roleName); + DefaultPrivilegeHandler.logger.error("User " + username + " already has role " + roleName); return; } @@ -496,7 +496,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // ignore if user does not have role Set currentRoles = user.getRoles(); if (!currentRoles.contains(roleName)) { - logger.error("User " + user + " does not have role " + roleName); + DefaultPrivilegeHandler.logger.error("User " + user + " does not have role " + roleName); return; } @@ -714,10 +714,10 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { this.sessionMap.put(sessionId, new CertificateSessionPair(session, certificate)); // log - logger.info("User " + username + " authenticated: " + session); + DefaultPrivilegeHandler.logger.info("User " + username + " authenticated: " + session); } catch (RuntimeException e) { - logger.error("User " + username + " Failed to authenticate: " + e.getLocalizedMessage()); + DefaultPrivilegeHandler.logger.error("User " + username + " Failed to authenticate: " + e.getLocalizedMessage()); throw e; } finally { clearPassword(password); @@ -742,9 +742,9 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // return true if object was really removed boolean loggedOut = certificateSessionPair != null; if (loggedOut) - logger.info("User " + certificate.getUsername() + " logged out."); + DefaultPrivilegeHandler.logger.info("User " + certificate.getUsername() + " logged out."); else - logger.warn("User already logged out!"); + DefaultPrivilegeHandler.logger.warn("User already logged out!"); return loggedOut; } @@ -785,7 +785,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { Role role = this.persistenceHandler.getRole(roleName); if (role == null) { - logger.error("No role is defined with name " + roleName + " which is configured for user " + user); + DefaultPrivilegeHandler.logger.error("No role is defined with name " + roleName + " which is configured for user " + user); continue; } @@ -861,7 +861,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } // otherwise delegate checking to the policy configured for this privilege - PrivilegePolicy policy = this.getPolicy(privilege.getPolicy()); + PrivilegePolicy policy = getPolicy(privilege.getPolicy()); if (policy == null) { throw new PrivilegeException("PrivilegePolicy " + privilege.getPolicy() + " does not exist for Privilege " + privilegeName); @@ -921,7 +921,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { public void validateIsPrivilegeAdmin(Certificate certificate) throws PrivilegeException { // validate certificate - this.isCertificateValid(certificate); + isCertificateValid(certificate); // get user object User user = this.persistenceHandler.getUser(certificate.getUsername()); @@ -1139,7 +1139,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { private Certificate getSystemUserCertificate(String systemUsername) { // see if a certificate has already been created for this system user - Certificate systemUserCertificate = systemUserCertificateMap.get(systemUsername); + Certificate systemUserCertificate = this.systemUserCertificateMap.get(systemUsername); if (systemUserCertificate != null) return systemUserCertificate; @@ -1187,7 +1187,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { this.sessionMap.put(sessionId, new CertificateSessionPair(session, systemUserCertificate)); // log - logger.info("The system user " + systemUsername + " is logged in with session " + session); + DefaultPrivilegeHandler.logger.info("The system user " + systemUsername + " is logged in with session " + session); return systemUserCertificate; } diff --git a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java index e1323f7ff..0dd164e7d 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java @@ -153,7 +153,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { File modelFile = new File(this.modelPath); boolean modelFileUnchanged = modelFile.exists() && modelFile.lastModified() == this.modelsFileDate; if (!(modelFileUnchanged && this.roleMapDirty && this.userMapDirty)) { - logger.warn("Not persisting as current file is unchanged and model data is not dirty"); + XmlPersistenceHandler.logger.warn("Not persisting as current file is unchanged and model data is not dirty"); return false; } @@ -164,7 +164,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { // USERS // build XML DOM of users - List users = toDomUsers(this.userMap); + List users = XmlPersistenceHandler.toDomUsers(this.userMap); Element usersElement = docFactory.createElement(XmlConstants.XML_USERS); for (Element userElement : users) { usersElement.add(userElement); @@ -173,7 +173,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { // ROLES // build XML DOM of roles - List roles = toDomRoles(this.roleMap); + List roles = XmlPersistenceHandler.toDomRoles(this.roleMap); Element rolesElement = docFactory.createElement(XmlConstants.XML_ROLES); for (Element roleElement : roles) { rolesElement.add(roleElement); @@ -233,8 +233,8 @@ public class XmlPersistenceHandler implements PersistenceHandler { this.userMapDirty = false; this.roleMapDirty = false; - logger.info("Read " + this.userMap.size() + " Users"); - logger.info("Read " + this.roleMap.size() + " Roles"); + XmlPersistenceHandler.logger.info("Read " + this.userMap.size() + " Users"); + XmlPersistenceHandler.logger.info("Read " + this.roleMap.size() + " Roles"); // validate we have a user with PrivilegeAdmin access boolean privilegeAdminExists = false; @@ -247,7 +247,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { } if (!privilegeAdminExists) { - logger.warn("No User with role '" + PrivilegeHandler.PRIVILEGE_ADMIN_ROLE + XmlPersistenceHandler.logger.warn("No User with role '" + PrivilegeHandler.PRIVILEGE_ADMIN_ROLE + "' exists. Privilege modifications will not be possible!"); } @@ -279,7 +279,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { this.modelPath = basePath + "/" + modelFileName; if (reload()) - logger.info("Privilege Data loaded."); + XmlPersistenceHandler.logger.info("Privilege Data loaded."); } /** @@ -320,9 +320,9 @@ public class XmlPersistenceHandler implements PersistenceHandler { for (Element roleElement : rolesElementList) { String roleName = roleElement.getTextTrim(); if (roleName.isEmpty()) { - logger.error("User " + username + " has a role defined with no name, Skipped."); + XmlPersistenceHandler.logger.error("User " + username + " has a role defined with no name, Skipped."); } else if (!this.roleMap.containsKey(roleName)) { - logger.error("User " + username + " has a inexistant role " + roleName + ", Skipped."); + XmlPersistenceHandler.logger.error("User " + username + " has a inexistant role " + roleName + ", Skipped."); } else { roles.add(roleName); } @@ -330,14 +330,14 @@ public class XmlPersistenceHandler implements PersistenceHandler { // read properties Element propertiesElement = userElement.element(XmlConstants.XML_PROPERTIES); - Map propertyMap = convertToPropertyMap(propertiesElement); + Map propertyMap = XmlPersistenceHandler.convertToPropertyMap(propertiesElement); // create user User user = new User(userId, username, password, firstname, surname, userState, roles, locale, propertyMap); // put user in map userMap.put(username, user); - logger.info("Loaded user " + user); + XmlPersistenceHandler.logger.info("Loaded user " + user); } return userMap; @@ -508,7 +508,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { roleElement.addAttribute(XmlConstants.XML_ATTR_NAME, role.getName()); // add all the privileges - toDomPrivileges(roleElement, role.getPrivilegeMap()); + XmlPersistenceHandler.toDomPrivileges(roleElement, role.getPrivilegeMap()); // add element to return list rolesAsElements.add(roleElement); diff --git a/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java b/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java index 42677a198..f24577af0 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java +++ b/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java @@ -75,12 +75,12 @@ public class BootstrapConfigurationHelper { Logger.getRootLogger().setLevel(Level.INFO); // get current directory - path = System.getProperty("user.dir") + "/newConfig"; + 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(path); + File pathF = new File(BootstrapConfigurationHelper.path); if (pathF.exists()) { throw new RuntimeException("Path already exists: " + pathF.getAbsolutePath()); } @@ -92,9 +92,9 @@ public class BootstrapConfigurationHelper { // TODO ask other questions... // now perform work: - createXmlPrivilegeContainer(); - createPolicyConfiguration(); - createModel(); + BootstrapConfigurationHelper.createXmlPrivilegeContainer(); + BootstrapConfigurationHelper.createPolicyConfiguration(); + BootstrapConfigurationHelper.createModel(); } /** @@ -133,34 +133,34 @@ public class BootstrapConfigurationHelper { // create PersistenceHandler Element persistenceHandlerElem = factory.createElement(XmlConstants.XML_HANDLER_PERSISTENCE); containerElement.add(persistenceHandlerElem); - persistenceHandlerElem.addAttribute(XmlConstants.XML_ATTR_CLASS, defaultPersistenceHandler); + persistenceHandlerElem.addAttribute(XmlConstants.XML_ATTR_CLASS, BootstrapConfigurationHelper.defaultPersistenceHandler); parametersElement = factory.createElement(XmlConstants.XML_PARAMETERS); persistenceHandlerElem.add(parametersElement); // Parameter basePath parameterElement = factory.createElement(XmlConstants.XML_PARAMETER); parameterElement.addAttribute(XmlConstants.XML_ATTR_NAME, XmlConstants.XML_PARAM_BASE_PATH); - parameterElement.addAttribute(XmlConstants.XML_ATTR_VALUE, basePath); + parameterElement.addAttribute(XmlConstants.XML_ATTR_VALUE, BootstrapConfigurationHelper.basePath); parametersElement.add(parameterElement); // Parameter modelXmlFile parameterElement = factory.createElement(XmlConstants.XML_PARAMETER); parameterElement.addAttribute(XmlConstants.XML_ATTR_NAME, XmlConstants.XML_PARAM_MODEL_FILE); - parameterElement.addAttribute(XmlConstants.XML_ATTR_VALUE, modelFileName); + parameterElement.addAttribute(XmlConstants.XML_ATTR_VALUE, BootstrapConfigurationHelper.modelFileName); parametersElement.add(parameterElement); // create EncryptionHandler Element encryptionHandlerElem = factory.createElement(XmlConstants.XML_HANDLER_ENCRYPTION); containerElement.add(encryptionHandlerElem); - encryptionHandlerElem.addAttribute(XmlConstants.XML_ATTR_CLASS, defaultEncryptionHandler); + encryptionHandlerElem.addAttribute(XmlConstants.XML_ATTR_CLASS, BootstrapConfigurationHelper.defaultEncryptionHandler); parametersElement = factory.createElement(XmlConstants.XML_PARAMETERS); encryptionHandlerElem.add(parametersElement); // Parameter hashAlgorithm parameterElement = factory.createElement(XmlConstants.XML_PARAMETER); parameterElement.addAttribute(XmlConstants.XML_ATTR_NAME, XmlConstants.XML_PARAM_HASH_ALGORITHM); - parameterElement.addAttribute(XmlConstants.XML_ATTR_VALUE, hashAlgorithm); + parameterElement.addAttribute(XmlConstants.XML_ATTR_VALUE, BootstrapConfigurationHelper.hashAlgorithm); parametersElement.add(parameterElement); // write the container file to disk - File privilegeContainerFile = new File(path + "/" + defaultPrivilegeContainerXmlFile); + File privilegeContainerFile = new File(BootstrapConfigurationHelper.path + "/" + BootstrapConfigurationHelper.defaultPrivilegeContainerXmlFile); XmlHelper.writeDocument(doc, privilegeContainerFile); } } diff --git a/src/main/java/ch/eitchnet/privilege/helper/CertificateThreadLocal.java b/src/main/java/ch/eitchnet/privilege/helper/CertificateThreadLocal.java index 9830541e3..161687258 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/CertificateThreadLocal.java +++ b/src/main/java/ch/eitchnet/privilege/helper/CertificateThreadLocal.java @@ -33,14 +33,14 @@ public class CertificateThreadLocal extends ThreadLocal { } public static CertificateThreadLocal getInstance() { - return instance; + return CertificateThreadLocal.instance; } public static Certificate getCert() { - return instance.get(); + return CertificateThreadLocal.instance.get(); } public static void setCert(Certificate certificate) { - instance.set(certificate); + CertificateThreadLocal.instance.set(certificate); } } diff --git a/src/main/java/ch/eitchnet/privilege/helper/HashHelper.java b/src/main/java/ch/eitchnet/privilege/helper/HashHelper.java index b0da71fee..c038740bd 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/HashHelper.java +++ b/src/main/java/ch/eitchnet/privilege/helper/HashHelper.java @@ -54,7 +54,7 @@ public class HashHelper { */ public static String stringToHash(String hashAlgorithm, String string) throws NoSuchAlgorithmException, UnsupportedEncodingException { - return stringToHash(hashAlgorithm, string.getBytes()); + return HashHelper.stringToHash(hashAlgorithm, string.getBytes()); } /** @@ -83,8 +83,8 @@ public class HashHelper { for (byte b : hashArray) { int v = b & 0xFF; - hex[index++] = HEX_CHAR_TABLE[v >>> 4]; - hex[index++] = HEX_CHAR_TABLE[v & 0xF]; + hex[index++] = HashHelper.HEX_CHAR_TABLE[v >>> 4]; + hex[index++] = HashHelper.HEX_CHAR_TABLE[v & 0xF]; } return new String(hex, "ASCII"); diff --git a/src/main/java/ch/eitchnet/privilege/helper/InitializationHelper.java b/src/main/java/ch/eitchnet/privilege/helper/InitializationHelper.java index fad40de25..0a9164091 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/InitializationHelper.java +++ b/src/main/java/ch/eitchnet/privilege/helper/InitializationHelper.java @@ -35,7 +35,6 @@ import ch.eitchnet.privilege.handler.PrivilegeHandler; import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.policy.PrivilegePolicy; import ch.eitchnet.utils.helper.StringHelper; -import ch.eitchnet.utils.helper.SystemHelper; /** * This class implements the initializing of the {@link PrivilegeHandler} by loading an XML file containing the @@ -81,19 +80,19 @@ public class InitializationHelper { // get policies Element policiesElement = rootElement.element(XmlConstants.XML_POLICIES); - Map> policyMap = convertToPolicyMap(policiesElement); + Map> policyMap = InitializationHelper.convertToPolicyMap(policiesElement); try { // get parameters Element parameterElement = encryptionHandlerElement.element(XmlConstants.XML_PARAMETERS); - Map parameterMap = convertToParameterMap(parameterElement); + Map parameterMap = InitializationHelper.convertToParameterMap(parameterElement); // initialize encryption handler encryptionHandler.initialize(parameterMap); } catch (Exception e) { - logger.error(e, e); + InitializationHelper.logger.error(e, e); throw new PrivilegeException("EncryptionHandler " + encryptionHandlerClassName + " could not be initialized"); } @@ -102,13 +101,13 @@ public class InitializationHelper { // get parameters Element parameterElement = persistenceHandlerElement.element(XmlConstants.XML_PARAMETERS); - Map parameterMap = convertToParameterMap(parameterElement); + Map parameterMap = InitializationHelper.convertToParameterMap(parameterElement); // initialize persistence handler persistenceHandler.initialize(parameterMap); } catch (Exception e) { - logger.error(e, e); + InitializationHelper.logger.error(e, e); throw new PrivilegeException("PersistenceHandler " + persistenceHandlerElement + " could not be initialized"); } @@ -117,13 +116,13 @@ public class InitializationHelper { // get parameters Element parameterElement = containerElement.element(XmlConstants.XML_PARAMETERS); - Map parameterMap = convertToParameterMap(parameterElement); + Map parameterMap = InitializationHelper.convertToParameterMap(parameterElement); // initialize privilege handler privilegeHandler.initialize(parameterMap, encryptionHandler, persistenceHandler, policyMap); } catch (Exception e) { - logger.error(e, e); + InitializationHelper.logger.error(e, e); throw new PrivilegeException("PrivilegeHandler " + privilegeHandler.getClass().getName() + " could not be initialized"); } diff --git a/src/main/java/ch/eitchnet/privilege/helper/XmlHelper.java b/src/main/java/ch/eitchnet/privilege/helper/XmlHelper.java index 9cb3786b7..ea86556b7 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/XmlHelper.java +++ b/src/main/java/ch/eitchnet/privilege/helper/XmlHelper.java @@ -69,7 +69,7 @@ public class XmlHelper { SAXReader reader = new SAXReader(); Document document = reader.read(inStream); - logger.info("Read XML document " + document.getRootElement().getName()); + XmlHelper.logger.info("Read XML document " + document.getRootElement().getName()); return document; } catch (FileNotFoundException e) { @@ -89,7 +89,7 @@ public class XmlHelper { */ public static void writeDocument(Document document, File file) { - logger.info("Exporting document element " + document.getName() + " to " + file.getAbsolutePath()); + XmlHelper.logger.info("Exporting document element " + document.getName() + " to " + file.getAbsolutePath()); OutputStream fileOutputStream = null; @@ -99,7 +99,7 @@ public class XmlHelper { String aEncodingScheme = document.getXMLEncoding(); if (aEncodingScheme == null || aEncodingScheme.isEmpty()) { - aEncodingScheme = DEFAULT_ENCODING; + aEncodingScheme = XmlHelper.DEFAULT_ENCODING; } OutputFormat outformat = OutputFormat.createPrettyPrint(); outformat.setEncoding(aEncodingScheme); @@ -117,7 +117,7 @@ public class XmlHelper { try { fileOutputStream.close(); } catch (IOException e) { - logger.error("Could not close file output stream: " + e, e); + XmlHelper.logger.error("Could not close file output stream: " + e, e); } } } @@ -133,10 +133,10 @@ public class XmlHelper { */ public static void writeElement(Element rootElement, File file) { - Document document = DocumentFactory.getInstance().createDocument(DEFAULT_ENCODING); + Document document = DocumentFactory.getInstance().createDocument(XmlHelper.DEFAULT_ENCODING); document.setRootElement(rootElement); document.setName(rootElement.getName()); - writeDocument(document, file); + XmlHelper.writeDocument(document, file); } } diff --git a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java index cd1ada34d..778ac48fd 100644 --- a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -87,9 +87,9 @@ public class PrivilegeTest { // initialize container String pwd = System.getProperty("user.dir"); File privilegeContainerXmlFile = new File(pwd + "/config/Privilege.xml"); - privilegeHandler = InitializationHelper.initializeFromXml(privilegeContainerXmlFile); + PrivilegeTest.privilegeHandler = InitializationHelper.initializeFromXml(privilegeContainerXmlFile); } catch (Exception e) { - logger.error(e, e); + PrivilegeTest.logger.error(e, e); throw new RuntimeException("Initialization failed: " + e.getLocalizedMessage(), e); } @@ -102,9 +102,9 @@ public class PrivilegeTest { @Test public void testAuthenticationOk() throws Exception { - Certificate certificate = privilegeHandler.authenticate(ADMIN, copyBytes(PASS_ADMIN)); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, copyBytes(PrivilegeTest.PASS_ADMIN)); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); - privilegeHandler.invalidateSession(certificate); + PrivilegeTest.privilegeHandler.invalidateSession(certificate); } private byte[] copyBytes(byte[] bytes) { @@ -120,9 +120,9 @@ public class PrivilegeTest { @Test(expected = AccessDeniedException.class) public void testFailAuthenticationNOk() throws Exception { - Certificate certificate = privilegeHandler.authenticate(ADMIN, copyBytes(PASS_BAD)); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, copyBytes(PrivilegeTest.PASS_BAD)); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); - privilegeHandler.invalidateSession(certificate); + PrivilegeTest.privilegeHandler.invalidateSession(certificate); } /** @@ -132,9 +132,9 @@ public class PrivilegeTest { @Test(expected = PrivilegeException.class) public void testFailAuthenticationPWNull() throws Exception { - Certificate certificate = privilegeHandler.authenticate(ADMIN, null); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, null); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); - privilegeHandler.invalidateSession(certificate); + PrivilegeTest.privilegeHandler.invalidateSession(certificate); } /** @@ -144,18 +144,18 @@ public class PrivilegeTest { @Test public void testAddUserBobAsAdmin() throws Exception { - Certificate certificate = privilegeHandler.authenticate(ADMIN, copyBytes(PASS_ADMIN)); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, copyBytes(PrivilegeTest.PASS_ADMIN)); // let's add a new user bob - UserRep userRep = new UserRep("1", BOB, "Bob", "Newman", UserState.NEW, new HashSet(), null, + UserRep userRep = new UserRep("1", PrivilegeTest.BOB, "Bob", "Newman", UserState.NEW, new HashSet(), null, new HashMap()); - privilegeHandler.addOrReplaceUser(certificate, userRep, null); - logger.info("Added user " + BOB); + PrivilegeTest.privilegeHandler.addOrReplaceUser(certificate, userRep, null); + PrivilegeTest.logger.info("Added user " + PrivilegeTest.BOB); // set bob's password - privilegeHandler.setUserPassword(certificate, BOB, copyBytes(PASS_BOB)); - logger.info("Set Bob's password"); - privilegeHandler.invalidateSession(certificate); + PrivilegeTest.privilegeHandler.setUserPassword(certificate, PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); + PrivilegeTest.logger.info("Set Bob's password"); + PrivilegeTest.privilegeHandler.invalidateSession(certificate); } /** @@ -166,8 +166,8 @@ public class PrivilegeTest { */ @Test(expected = AccessDeniedException.class) public void testFailAuthAsBob() throws Exception { - Certificate certificate = privilegeHandler.authenticate(BOB, copyBytes(PASS_BOB)); - privilegeHandler.invalidateSession(certificate); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); + PrivilegeTest.privilegeHandler.invalidateSession(certificate); } /** @@ -176,9 +176,9 @@ public class PrivilegeTest { */ @Test public void testEnableUserBob() throws Exception { - Certificate certificate = privilegeHandler.authenticate(ADMIN, copyBytes(PASS_ADMIN)); - privilegeHandler.setUserState(certificate, BOB, UserState.ENABLED); - privilegeHandler.invalidateSession(certificate); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, copyBytes(PrivilegeTest.PASS_ADMIN)); + PrivilegeTest.privilegeHandler.setUserState(certificate, PrivilegeTest.BOB, UserState.ENABLED); + PrivilegeTest.privilegeHandler.invalidateSession(certificate); } /** @@ -190,9 +190,9 @@ public class PrivilegeTest { @Test(expected = PrivilegeException.class) public void testFailAuthUserBob() throws Exception { - Certificate certificate = privilegeHandler.authenticate(BOB, copyBytes(PASS_BOB)); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); - privilegeHandler.invalidateSession(certificate); + PrivilegeTest.privilegeHandler.invalidateSession(certificate); } /** @@ -201,13 +201,13 @@ public class PrivilegeTest { */ @Test public void testAddRole() throws Exception { - Certificate certificate = privilegeHandler.authenticate(ADMIN, copyBytes(PASS_ADMIN)); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, copyBytes(PrivilegeTest.PASS_ADMIN)); Map privilegeMap = new HashMap(); - RoleRep roleRep = new RoleRep(ROLE_USER, privilegeMap); + RoleRep roleRep = new RoleRep(PrivilegeTest.ROLE_USER, privilegeMap); - privilegeHandler.addOrReplaceRole(certificate, roleRep); - privilegeHandler.invalidateSession(certificate); + PrivilegeTest.privilegeHandler.addOrReplaceRole(certificate, roleRep); + PrivilegeTest.privilegeHandler.invalidateSession(certificate); } /** @@ -216,9 +216,9 @@ public class PrivilegeTest { */ @Test public void testAddRoleToBob() throws Exception { - Certificate certificate = privilegeHandler.authenticate(ADMIN, copyBytes(PASS_ADMIN)); - privilegeHandler.addRoleToUser(certificate, BOB, ROLE_USER); - privilegeHandler.invalidateSession(certificate); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, copyBytes(PrivilegeTest.PASS_ADMIN)); + PrivilegeTest.privilegeHandler.addRoleToUser(certificate, PrivilegeTest.BOB, PrivilegeTest.ROLE_USER); + PrivilegeTest.privilegeHandler.invalidateSession(certificate); } /** @@ -227,8 +227,8 @@ public class PrivilegeTest { */ @Test public void testAuthAsBob() throws Exception { - Certificate certificate = privilegeHandler.authenticate(BOB, copyBytes(PASS_BOB)); - privilegeHandler.invalidateSession(certificate); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); + PrivilegeTest.privilegeHandler.invalidateSession(certificate); } /** @@ -241,15 +241,15 @@ public class PrivilegeTest { public void testFailAddUserTedAsBob() throws Exception { // auth as Bog - Certificate certificate = privilegeHandler.authenticate(BOB, copyBytes(PASS_BOB)); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); // let's add a new user Ted - UserRep userRep = new UserRep("1", TED, "Ted", "And then Some", UserState.NEW, new HashSet(), null, + UserRep userRep = new UserRep("1", PrivilegeTest.TED, "Ted", "And then Some", UserState.NEW, new HashSet(), null, new HashMap()); - privilegeHandler.addOrReplaceUser(certificate, userRep, null); - logger.info("Added user " + TED); - privilegeHandler.invalidateSession(certificate); + PrivilegeTest.privilegeHandler.addOrReplaceUser(certificate, userRep, null); + PrivilegeTest.logger.info("Added user " + PrivilegeTest.TED); + PrivilegeTest.privilegeHandler.invalidateSession(certificate); } /** @@ -259,10 +259,10 @@ public class PrivilegeTest { @Test public void testAddAdminRoleToBob() throws Exception { - Certificate certificate = privilegeHandler.authenticate(ADMIN, copyBytes(PASS_ADMIN)); - privilegeHandler.addRoleToUser(certificate, BOB, PrivilegeHandler.PRIVILEGE_ADMIN_ROLE); - logger.info("Added " + PrivilegeHandler.PRIVILEGE_ADMIN_ROLE + " to " + ADMIN); - privilegeHandler.invalidateSession(certificate); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, copyBytes(PrivilegeTest.PASS_ADMIN)); + PrivilegeTest.privilegeHandler.addRoleToUser(certificate, PrivilegeTest.BOB, PrivilegeHandler.PRIVILEGE_ADMIN_ROLE); + PrivilegeTest.logger.info("Added " + PrivilegeHandler.PRIVILEGE_ADMIN_ROLE + " to " + PrivilegeTest.ADMIN); + PrivilegeTest.privilegeHandler.invalidateSession(certificate); } /** @@ -272,18 +272,18 @@ public class PrivilegeTest { @Test public void testAddUserTedAsBob() throws Exception { - Certificate certificate = privilegeHandler.authenticate(BOB, copyBytes(PASS_BOB)); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); // let's add a new user ted HashSet roles = new HashSet(); - roles.add(ROLE_USER); - UserRep userRep = new UserRep("2", TED, "Ted", "Newman", UserState.ENABLED, roles, null, + roles.add(PrivilegeTest.ROLE_USER); + UserRep userRep = new UserRep("2", PrivilegeTest.TED, "Ted", "Newman", UserState.ENABLED, roles, null, new HashMap()); - privilegeHandler.addOrReplaceUser(certificate, userRep, null); - logger.info("Added user " + TED); + PrivilegeTest.privilegeHandler.addOrReplaceUser(certificate, userRep, null); + PrivilegeTest.logger.info("Added user " + PrivilegeTest.TED); - privilegeHandler.invalidateSession(certificate); + PrivilegeTest.privilegeHandler.invalidateSession(certificate); } /** @@ -293,13 +293,13 @@ public class PrivilegeTest { @Test public void testSetTedPwdAsBob() throws Exception { - Certificate certificate = privilegeHandler.authenticate(BOB, copyBytes(PASS_BOB)); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); // set ted's password to default - privilegeHandler.setUserPassword(certificate, TED, copyBytes(PASS_DEF)); + PrivilegeTest.privilegeHandler.setUserPassword(certificate, PrivilegeTest.TED, copyBytes(PrivilegeTest.PASS_DEF)); - privilegeHandler.invalidateSession(certificate); + PrivilegeTest.privilegeHandler.invalidateSession(certificate); } /** @@ -308,9 +308,9 @@ public class PrivilegeTest { */ @Test public void testTedChangesOwnPwd() throws Exception { - Certificate certificate = privilegeHandler.authenticate(TED, copyBytes(PASS_DEF)); - privilegeHandler.setUserPassword(certificate, TED, copyBytes(PASS_TED)); - privilegeHandler.invalidateSession(certificate); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.TED, copyBytes(PrivilegeTest.PASS_DEF)); + PrivilegeTest.privilegeHandler.setUserPassword(certificate, PrivilegeTest.TED, copyBytes(PrivilegeTest.PASS_TED)); + PrivilegeTest.privilegeHandler.invalidateSession(certificate); } /** @@ -319,8 +319,8 @@ public class PrivilegeTest { */ @Test public void testAuthAsTed() throws Exception { - Certificate certificate = privilegeHandler.authenticate(TED, copyBytes(PASS_TED)); - privilegeHandler.invalidateSession(certificate); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.TED, copyBytes(PrivilegeTest.PASS_TED)); + PrivilegeTest.privilegeHandler.invalidateSession(certificate); } /** @@ -330,13 +330,13 @@ public class PrivilegeTest { @Test public void testPerformRestrictableAsAdmin() throws Exception { - Certificate certificate = privilegeHandler.authenticate(ADMIN, copyBytes(PASS_ADMIN)); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, copyBytes(PrivilegeTest.PASS_ADMIN)); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); // see if eitch can perform restrictable Restrictable restrictable = new TestRestrictable(); - privilegeHandler.actionAllowed(certificate, restrictable); - privilegeHandler.invalidateSession(certificate); + PrivilegeTest.privilegeHandler.actionAllowed(certificate, restrictable); + PrivilegeTest.privilegeHandler.invalidateSession(certificate); } /** @@ -347,15 +347,15 @@ public class PrivilegeTest { */ @Test(expected = AccessDeniedException.class) public void testFailPerformRestrictableAsBob() throws Exception { - Certificate certificate = privilegeHandler.authenticate(BOB, copyBytes(PASS_BOB)); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); // see if bob can perform restrictable Restrictable restrictable = new TestRestrictable(); try { - privilegeHandler.actionAllowed(certificate, restrictable); + PrivilegeTest.privilegeHandler.actionAllowed(certificate, restrictable); } finally { - privilegeHandler.invalidateSession(certificate); + PrivilegeTest.privilegeHandler.invalidateSession(certificate); } } @@ -366,10 +366,10 @@ public class PrivilegeTest { @Test public void testAddAppRoleToBob() throws Exception { - Certificate certificate = privilegeHandler.authenticate(ADMIN, copyBytes(PASS_ADMIN)); - privilegeHandler.addRoleToUser(certificate, BOB, ROLE_APP_USER); - logger.info("Added " + ROLE_APP_USER + " to " + BOB); - privilegeHandler.invalidateSession(certificate); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, copyBytes(PrivilegeTest.PASS_ADMIN)); + PrivilegeTest.privilegeHandler.addRoleToUser(certificate, PrivilegeTest.BOB, PrivilegeTest.ROLE_APP_USER); + PrivilegeTest.logger.info("Added " + PrivilegeTest.ROLE_APP_USER + " to " + PrivilegeTest.BOB); + PrivilegeTest.privilegeHandler.invalidateSession(certificate); } /** @@ -380,15 +380,15 @@ public class PrivilegeTest { */ @Test public void testPerformRestrictableAsBob() throws Exception { - Certificate certificate = privilegeHandler.authenticate(BOB, copyBytes(PASS_BOB)); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); // see if bob can perform restrictable Restrictable restrictable = new TestRestrictable(); try { - privilegeHandler.actionAllowed(certificate, restrictable); + PrivilegeTest.privilegeHandler.actionAllowed(certificate, restrictable); } finally { - privilegeHandler.invalidateSession(certificate); + PrivilegeTest.privilegeHandler.invalidateSession(certificate); } } @@ -402,10 +402,10 @@ public class PrivilegeTest { public void testPerformSystemRestrictable() throws Exception { // create the action to be performed as a system user - TestSystemUserAction action = new TestSystemUserAction(privilegeHandler); + TestSystemUserAction action = new TestSystemUserAction(PrivilegeTest.privilegeHandler); // and then perform the action - privilegeHandler.runAsSystem(SYSTEM_USER_ADMIN, action); + PrivilegeTest.privilegeHandler.runAsSystem(PrivilegeTest.SYSTEM_USER_ADMIN, action); } /** @@ -418,10 +418,10 @@ public class PrivilegeTest { public void testPerformSystemRestrictableFailPrivilege() throws Exception { // create the action to be performed as a system user - TestSystemUserActionDeny action = new TestSystemUserActionDeny(privilegeHandler); + TestSystemUserActionDeny action = new TestSystemUserActionDeny(PrivilegeTest.privilegeHandler); // and then perform the action - privilegeHandler.runAsSystem(SYSTEM_USER_ADMIN, action); + PrivilegeTest.privilegeHandler.runAsSystem(PrivilegeTest.SYSTEM_USER_ADMIN, action); } /** @@ -433,13 +433,13 @@ public class PrivilegeTest { @Test(expected = AccessDeniedException.class) public void testLoginSystemUser() throws Exception { - privilegeHandler.authenticate(SYSTEM_USER_ADMIN, SYSTEM_USER_ADMIN.getBytes()); + PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.SYSTEM_USER_ADMIN, PrivilegeTest.SYSTEM_USER_ADMIN.getBytes()); } @Test public void testCertificateThreadLocal() { - Certificate certificate = privilegeHandler.authenticate(ADMIN, copyBytes(PASS_ADMIN)); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, copyBytes(PrivilegeTest.PASS_ADMIN)); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); // set certificate into thread local @@ -448,9 +448,9 @@ public class PrivilegeTest { // see if bob can perform restrictable by returning certificate from CertificateThreadLocal Restrictable restrictable = new TestRestrictable(); try { - privilegeHandler.actionAllowed(CertificateThreadLocal.getInstance().get(), restrictable); + PrivilegeTest.privilegeHandler.actionAllowed(CertificateThreadLocal.getInstance().get(), restrictable); } finally { - privilegeHandler.invalidateSession(certificate); + PrivilegeTest.privilegeHandler.invalidateSession(certificate); } } } diff --git a/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java b/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java index 2b7cfc786..f247d680b 100644 --- a/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java +++ b/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java @@ -46,7 +46,7 @@ public class TestSystemUserAction implements SystemUserAction { TestSystemRestrictable restrictable = new TestSystemRestrictable(); - handler.actionAllowed(certificate, restrictable); + this.handler.actionAllowed(certificate, restrictable); } } diff --git a/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserActionDeny.java b/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserActionDeny.java index 1220d008b..14f6e566a 100644 --- a/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserActionDeny.java +++ b/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserActionDeny.java @@ -45,6 +45,6 @@ public class TestSystemUserActionDeny implements SystemUserAction { public void execute(Certificate certificate) { TestRestrictable restrictable = new TestRestrictable(); - handler.actionAllowed(certificate, restrictable); + this.handler.actionAllowed(certificate, restrictable); } } From 2b0e23eb285520acf084a90a6cee413bf0ac2e1f Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 24 Nov 2012 13:23:49 +0100 Subject: [PATCH 079/173] [Major] refactored use of log4j to slf4j --- pom.xml | 20 +++- .../handler/DefaultEncryptionHandler.java | 5 +- .../handler/DefaultPrivilegeHandler.java | 5 +- .../handler/XmlPersistenceHandler.java | 5 +- .../helper/BootstrapConfigurationHelper.java | 21 ++-- .../helper/InitializationHelper.java | 11 ++- .../eitchnet/privilege/helper/XmlHelper.java | 5 +- .../privilege/test/PrivilegeTest.java | 99 +++++++++++-------- 8 files changed, 102 insertions(+), 69 deletions(-) diff --git a/pom.xml b/pom.xml index 0a189f43f..7605305ef 100644 --- a/pom.xml +++ b/pom.xml @@ -81,11 +81,6 @@ 4.10 test - - log4j - log4j - 1.2.17 - maven dom4j @@ -96,6 +91,21 @@ ch.eitchnet.utils 0.1.0-SNAPSHOT + + ch.eitchnet + ch.eitchnet.log4j + 0.1.0-SNAPSHOT + + + org.slf4j + slf4j-api + 1.7.2 + + + org.slf4j + slf4j-log4j12 + 1.7.2 + diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java index 76d63ce5c..1dbd99a0f 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java @@ -25,7 +25,8 @@ import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.Map; -import org.apache.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import ch.eitchnet.privilege.helper.HashHelper; import ch.eitchnet.privilege.helper.XmlConstants; @@ -50,7 +51,7 @@ public class DefaultEncryptionHandler implements EncryptionHandler { /** * The log4j logger used in this instance */ - private static final Logger logger = Logger.getLogger(DefaultEncryptionHandler.class); + private static final Logger logger = LoggerFactory.getLogger(DefaultEncryptionHandler.class); /** * The {@link SecureRandom} which is used to create new tokens diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index cf87c63b8..9cc7e9127 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -28,7 +28,8 @@ import java.util.Locale; import java.util.Map; import java.util.Set; -import org.apache.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import ch.eitchnet.privilege.helper.ClassHelper; import ch.eitchnet.privilege.i18n.AccessDeniedException; @@ -69,7 +70,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { /** * log4j logger */ - protected static final Logger logger = Logger.getLogger(DefaultPrivilegeHandler.class); + protected static final Logger logger = LoggerFactory.getLogger(DefaultPrivilegeHandler.class); /** * last assigned id for the {@link Session}s diff --git a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java index 0dd164e7d..2d5fc12b8 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java @@ -30,9 +30,10 @@ import java.util.Locale; import java.util.Map; import java.util.Set; -import org.apache.log4j.Logger; import org.dom4j.DocumentFactory; import org.dom4j.Element; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import ch.eitchnet.privilege.helper.XmlConstants; import ch.eitchnet.privilege.helper.XmlHelper; @@ -50,7 +51,7 @@ import ch.eitchnet.privilege.model.internal.User; */ public class XmlPersistenceHandler implements PersistenceHandler { - protected static final Logger logger = Logger.getLogger(XmlPersistenceHandler.class); + protected static final Logger logger = LoggerFactory.getLogger(XmlPersistenceHandler.class); private Map userMap; private Map roleMap; diff --git a/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java b/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java index f24577af0..3412f24e7 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java +++ b/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java @@ -21,16 +21,12 @@ package ch.eitchnet.privilege.helper; import java.io.File; -import org.apache.log4j.BasicConfigurator; -import org.apache.log4j.ConsoleAppender; -import org.apache.log4j.Level; -import org.apache.log4j.Logger; -import org.apache.log4j.PatternLayout; import org.dom4j.Document; import org.dom4j.DocumentFactory; import org.dom4j.Element; import ch.eitchnet.privilege.handler.PrivilegeHandler; +import ch.eitchnet.utils.helper.Log4jConfigurator; /** *

    @@ -51,7 +47,7 @@ import ch.eitchnet.privilege.handler.PrivilegeHandler; */ public class BootstrapConfigurationHelper { - // private static final Logger logger = Logger.getLogger(BootstrapConfigurationHelper.class); + // private static final Logger logger = LoggerFactory.getLogger(BootstrapConfigurationHelper.class); private static String path; @@ -70,9 +66,7 @@ public class BootstrapConfigurationHelper { * the args from the command line */ public static void main(String[] args) { - BasicConfigurator.resetConfiguration(); - BasicConfigurator.configure(new ConsoleAppender(new PatternLayout("%d %5p [%t] %C{1} %M - %m%n"))); - Logger.getRootLogger().setLevel(Level.INFO); + Log4jConfigurator.configure(); // get current directory BootstrapConfigurationHelper.path = System.getProperty("user.dir") + "/newConfig"; @@ -133,7 +127,8 @@ public class BootstrapConfigurationHelper { // create PersistenceHandler Element persistenceHandlerElem = factory.createElement(XmlConstants.XML_HANDLER_PERSISTENCE); containerElement.add(persistenceHandlerElem); - persistenceHandlerElem.addAttribute(XmlConstants.XML_ATTR_CLASS, BootstrapConfigurationHelper.defaultPersistenceHandler); + persistenceHandlerElem.addAttribute(XmlConstants.XML_ATTR_CLASS, + BootstrapConfigurationHelper.defaultPersistenceHandler); parametersElement = factory.createElement(XmlConstants.XML_PARAMETERS); persistenceHandlerElem.add(parametersElement); // Parameter basePath @@ -150,7 +145,8 @@ public class BootstrapConfigurationHelper { // create EncryptionHandler Element encryptionHandlerElem = factory.createElement(XmlConstants.XML_HANDLER_ENCRYPTION); containerElement.add(encryptionHandlerElem); - encryptionHandlerElem.addAttribute(XmlConstants.XML_ATTR_CLASS, BootstrapConfigurationHelper.defaultEncryptionHandler); + encryptionHandlerElem.addAttribute(XmlConstants.XML_ATTR_CLASS, + BootstrapConfigurationHelper.defaultEncryptionHandler); parametersElement = factory.createElement(XmlConstants.XML_PARAMETERS); encryptionHandlerElem.add(parametersElement); // Parameter hashAlgorithm @@ -160,7 +156,8 @@ public class BootstrapConfigurationHelper { parametersElement.add(parameterElement); // write the container file to disk - File privilegeContainerFile = new File(BootstrapConfigurationHelper.path + "/" + BootstrapConfigurationHelper.defaultPrivilegeContainerXmlFile); + File privilegeContainerFile = new File(BootstrapConfigurationHelper.path + "/" + + BootstrapConfigurationHelper.defaultPrivilegeContainerXmlFile); XmlHelper.writeDocument(doc, privilegeContainerFile); } } diff --git a/src/main/java/ch/eitchnet/privilege/helper/InitializationHelper.java b/src/main/java/ch/eitchnet/privilege/helper/InitializationHelper.java index 0a9164091..94ff3d9d2 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/InitializationHelper.java +++ b/src/main/java/ch/eitchnet/privilege/helper/InitializationHelper.java @@ -25,8 +25,9 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import org.apache.log4j.Logger; import org.dom4j.Element; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import ch.eitchnet.privilege.handler.DefaultPrivilegeHandler; import ch.eitchnet.privilege.handler.EncryptionHandler; @@ -44,7 +45,7 @@ import ch.eitchnet.utils.helper.StringHelper; */ public class InitializationHelper { - private static final Logger logger = Logger.getLogger(InitializationHelper.class); + private static final Logger logger = LoggerFactory.getLogger(InitializationHelper.class); /** * Initializes the {@link DefaultPrivilegeHandler} from the configuration file @@ -92,7 +93,7 @@ public class InitializationHelper { encryptionHandler.initialize(parameterMap); } catch (Exception e) { - InitializationHelper.logger.error(e, e); + InitializationHelper.logger.error(e.getMessage(), e); throw new PrivilegeException("EncryptionHandler " + encryptionHandlerClassName + " could not be initialized"); } @@ -107,7 +108,7 @@ public class InitializationHelper { persistenceHandler.initialize(parameterMap); } catch (Exception e) { - InitializationHelper.logger.error(e, e); + InitializationHelper.logger.error(e.getMessage(), e); throw new PrivilegeException("PersistenceHandler " + persistenceHandlerElement + " could not be initialized"); } @@ -122,7 +123,7 @@ public class InitializationHelper { privilegeHandler.initialize(parameterMap, encryptionHandler, persistenceHandler, policyMap); } catch (Exception e) { - InitializationHelper.logger.error(e, e); + InitializationHelper.logger.error(e.getMessage(), e); throw new PrivilegeException("PrivilegeHandler " + privilegeHandler.getClass().getName() + " could not be initialized"); } diff --git a/src/main/java/ch/eitchnet/privilege/helper/XmlHelper.java b/src/main/java/ch/eitchnet/privilege/helper/XmlHelper.java index ea86556b7..eec3c9eb2 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/XmlHelper.java +++ b/src/main/java/ch/eitchnet/privilege/helper/XmlHelper.java @@ -27,7 +27,6 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import org.apache.log4j.Logger; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.DocumentFactory; @@ -35,6 +34,8 @@ import org.dom4j.Element; import org.dom4j.io.OutputFormat; import org.dom4j.io.SAXReader; import org.dom4j.io.XMLWriter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import ch.eitchnet.privilege.i18n.PrivilegeException; @@ -50,7 +51,7 @@ public class XmlHelper { */ public static final String DEFAULT_ENCODING = "UTF-8"; - private static final Logger logger = Logger.getLogger(XmlHelper.class); + private static final Logger logger = LoggerFactory.getLogger(XmlHelper.class); /** * Parses an XML file on the file system using dom4j and returns the resulting {@link Document} object diff --git a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java index 778ac48fd..2fe79e34e 100644 --- a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -24,13 +24,10 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Map; -import org.apache.log4j.BasicConfigurator; -import org.apache.log4j.ConsoleAppender; -import org.apache.log4j.Level; -import org.apache.log4j.Logger; -import org.apache.log4j.PatternLayout; import org.junit.BeforeClass; import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import ch.eitchnet.privilege.handler.PrivilegeHandler; import ch.eitchnet.privilege.helper.CertificateThreadLocal; @@ -46,6 +43,7 @@ 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.Log4jConfigurator; /** * JUnit for performing Privilege tests. This JUnit is by no means complete, but checks the bare minimum. TODO add more @@ -67,7 +65,7 @@ public class PrivilegeTest { private static final byte[] PASS_BAD = "123".getBytes(); private static final byte[] PASS_TED = "12345".getBytes(); - private static final Logger logger = Logger.getLogger(PrivilegeTest.class); + private static final Logger logger = LoggerFactory.getLogger(PrivilegeTest.class); private static PrivilegeHandler privilegeHandler; @@ -80,16 +78,14 @@ public class PrivilegeTest { try { // set up log4j - BasicConfigurator.resetConfiguration(); - BasicConfigurator.configure(new ConsoleAppender(new PatternLayout("%d %5p [%t] %C{1} %M - %m%n"))); - Logger.getRootLogger().setLevel(Level.INFO); + Log4jConfigurator.configure(); // initialize container String pwd = System.getProperty("user.dir"); File privilegeContainerXmlFile = new File(pwd + "/config/Privilege.xml"); PrivilegeTest.privilegeHandler = InitializationHelper.initializeFromXml(privilegeContainerXmlFile); } catch (Exception e) { - PrivilegeTest.logger.error(e, e); + PrivilegeTest.logger.error(e.getMessage(), e); throw new RuntimeException("Initialization failed: " + e.getLocalizedMessage(), e); } @@ -102,7 +98,8 @@ public class PrivilegeTest { @Test public void testAuthenticationOk() throws Exception { - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, copyBytes(PrivilegeTest.PASS_ADMIN)); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, + copyBytes(PrivilegeTest.PASS_ADMIN)); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); PrivilegeTest.privilegeHandler.invalidateSession(certificate); } @@ -120,7 +117,8 @@ public class PrivilegeTest { @Test(expected = AccessDeniedException.class) public void testFailAuthenticationNOk() throws Exception { - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, copyBytes(PrivilegeTest.PASS_BAD)); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, + copyBytes(PrivilegeTest.PASS_BAD)); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); PrivilegeTest.privilegeHandler.invalidateSession(certificate); } @@ -144,16 +142,18 @@ public class PrivilegeTest { @Test public void testAddUserBobAsAdmin() throws Exception { - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, copyBytes(PrivilegeTest.PASS_ADMIN)); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, + copyBytes(PrivilegeTest.PASS_ADMIN)); // let's add a new user bob - UserRep userRep = new UserRep("1", PrivilegeTest.BOB, "Bob", "Newman", UserState.NEW, new HashSet(), null, - new HashMap()); + UserRep userRep = new UserRep("1", PrivilegeTest.BOB, "Bob", "Newman", UserState.NEW, new HashSet(), + null, new HashMap()); PrivilegeTest.privilegeHandler.addOrReplaceUser(certificate, userRep, null); PrivilegeTest.logger.info("Added user " + PrivilegeTest.BOB); // set bob's password - PrivilegeTest.privilegeHandler.setUserPassword(certificate, PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); + PrivilegeTest.privilegeHandler.setUserPassword(certificate, PrivilegeTest.BOB, + copyBytes(PrivilegeTest.PASS_BOB)); PrivilegeTest.logger.info("Set Bob's password"); PrivilegeTest.privilegeHandler.invalidateSession(certificate); } @@ -166,7 +166,8 @@ public class PrivilegeTest { */ @Test(expected = AccessDeniedException.class) public void testFailAuthAsBob() throws Exception { - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, + copyBytes(PrivilegeTest.PASS_BOB)); PrivilegeTest.privilegeHandler.invalidateSession(certificate); } @@ -176,7 +177,8 @@ public class PrivilegeTest { */ @Test public void testEnableUserBob() throws Exception { - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, copyBytes(PrivilegeTest.PASS_ADMIN)); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, + copyBytes(PrivilegeTest.PASS_ADMIN)); PrivilegeTest.privilegeHandler.setUserState(certificate, PrivilegeTest.BOB, UserState.ENABLED); PrivilegeTest.privilegeHandler.invalidateSession(certificate); } @@ -190,7 +192,8 @@ public class PrivilegeTest { @Test(expected = PrivilegeException.class) public void testFailAuthUserBob() throws Exception { - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, + copyBytes(PrivilegeTest.PASS_BOB)); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); PrivilegeTest.privilegeHandler.invalidateSession(certificate); } @@ -201,7 +204,8 @@ public class PrivilegeTest { */ @Test public void testAddRole() throws Exception { - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, copyBytes(PrivilegeTest.PASS_ADMIN)); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, + copyBytes(PrivilegeTest.PASS_ADMIN)); Map privilegeMap = new HashMap(); RoleRep roleRep = new RoleRep(PrivilegeTest.ROLE_USER, privilegeMap); @@ -216,7 +220,8 @@ public class PrivilegeTest { */ @Test public void testAddRoleToBob() throws Exception { - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, copyBytes(PrivilegeTest.PASS_ADMIN)); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, + copyBytes(PrivilegeTest.PASS_ADMIN)); PrivilegeTest.privilegeHandler.addRoleToUser(certificate, PrivilegeTest.BOB, PrivilegeTest.ROLE_USER); PrivilegeTest.privilegeHandler.invalidateSession(certificate); } @@ -227,7 +232,8 @@ public class PrivilegeTest { */ @Test public void testAuthAsBob() throws Exception { - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, + copyBytes(PrivilegeTest.PASS_BOB)); PrivilegeTest.privilegeHandler.invalidateSession(certificate); } @@ -241,12 +247,13 @@ public class PrivilegeTest { public void testFailAddUserTedAsBob() throws Exception { // auth as Bog - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, + copyBytes(PrivilegeTest.PASS_BOB)); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); // let's add a new user Ted - UserRep userRep = new UserRep("1", PrivilegeTest.TED, "Ted", "And then Some", UserState.NEW, new HashSet(), null, - new HashMap()); + UserRep userRep = new UserRep("1", PrivilegeTest.TED, "Ted", "And then Some", UserState.NEW, + new HashSet(), null, new HashMap()); PrivilegeTest.privilegeHandler.addOrReplaceUser(certificate, userRep, null); PrivilegeTest.logger.info("Added user " + PrivilegeTest.TED); PrivilegeTest.privilegeHandler.invalidateSession(certificate); @@ -259,8 +266,10 @@ public class PrivilegeTest { @Test public void testAddAdminRoleToBob() throws Exception { - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, copyBytes(PrivilegeTest.PASS_ADMIN)); - PrivilegeTest.privilegeHandler.addRoleToUser(certificate, PrivilegeTest.BOB, PrivilegeHandler.PRIVILEGE_ADMIN_ROLE); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, + copyBytes(PrivilegeTest.PASS_ADMIN)); + PrivilegeTest.privilegeHandler.addRoleToUser(certificate, PrivilegeTest.BOB, + PrivilegeHandler.PRIVILEGE_ADMIN_ROLE); PrivilegeTest.logger.info("Added " + PrivilegeHandler.PRIVILEGE_ADMIN_ROLE + " to " + PrivilegeTest.ADMIN); PrivilegeTest.privilegeHandler.invalidateSession(certificate); } @@ -272,7 +281,8 @@ public class PrivilegeTest { @Test public void testAddUserTedAsBob() throws Exception { - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, + copyBytes(PrivilegeTest.PASS_BOB)); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); // let's add a new user ted @@ -293,11 +303,13 @@ public class PrivilegeTest { @Test public void testSetTedPwdAsBob() throws Exception { - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, + copyBytes(PrivilegeTest.PASS_BOB)); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); // set ted's password to default - PrivilegeTest.privilegeHandler.setUserPassword(certificate, PrivilegeTest.TED, copyBytes(PrivilegeTest.PASS_DEF)); + PrivilegeTest.privilegeHandler.setUserPassword(certificate, PrivilegeTest.TED, + copyBytes(PrivilegeTest.PASS_DEF)); PrivilegeTest.privilegeHandler.invalidateSession(certificate); } @@ -308,8 +320,10 @@ public class PrivilegeTest { */ @Test public void testTedChangesOwnPwd() throws Exception { - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.TED, copyBytes(PrivilegeTest.PASS_DEF)); - PrivilegeTest.privilegeHandler.setUserPassword(certificate, PrivilegeTest.TED, copyBytes(PrivilegeTest.PASS_TED)); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.TED, + copyBytes(PrivilegeTest.PASS_DEF)); + PrivilegeTest.privilegeHandler.setUserPassword(certificate, PrivilegeTest.TED, + copyBytes(PrivilegeTest.PASS_TED)); PrivilegeTest.privilegeHandler.invalidateSession(certificate); } @@ -319,7 +333,8 @@ public class PrivilegeTest { */ @Test public void testAuthAsTed() throws Exception { - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.TED, copyBytes(PrivilegeTest.PASS_TED)); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.TED, + copyBytes(PrivilegeTest.PASS_TED)); PrivilegeTest.privilegeHandler.invalidateSession(certificate); } @@ -330,7 +345,8 @@ public class PrivilegeTest { @Test public void testPerformRestrictableAsAdmin() throws Exception { - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, copyBytes(PrivilegeTest.PASS_ADMIN)); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, + copyBytes(PrivilegeTest.PASS_ADMIN)); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); // see if eitch can perform restrictable @@ -347,7 +363,8 @@ public class PrivilegeTest { */ @Test(expected = AccessDeniedException.class) public void testFailPerformRestrictableAsBob() throws Exception { - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, + copyBytes(PrivilegeTest.PASS_BOB)); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); // see if bob can perform restrictable @@ -366,7 +383,8 @@ public class PrivilegeTest { @Test public void testAddAppRoleToBob() throws Exception { - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, copyBytes(PrivilegeTest.PASS_ADMIN)); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, + copyBytes(PrivilegeTest.PASS_ADMIN)); PrivilegeTest.privilegeHandler.addRoleToUser(certificate, PrivilegeTest.BOB, PrivilegeTest.ROLE_APP_USER); PrivilegeTest.logger.info("Added " + PrivilegeTest.ROLE_APP_USER + " to " + PrivilegeTest.BOB); PrivilegeTest.privilegeHandler.invalidateSession(certificate); @@ -380,7 +398,8 @@ public class PrivilegeTest { */ @Test public void testPerformRestrictableAsBob() throws Exception { - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, + copyBytes(PrivilegeTest.PASS_BOB)); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); // see if bob can perform restrictable @@ -433,13 +452,15 @@ public class PrivilegeTest { @Test(expected = AccessDeniedException.class) public void testLoginSystemUser() throws Exception { - PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.SYSTEM_USER_ADMIN, PrivilegeTest.SYSTEM_USER_ADMIN.getBytes()); + PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.SYSTEM_USER_ADMIN, + PrivilegeTest.SYSTEM_USER_ADMIN.getBytes()); } @Test public void testCertificateThreadLocal() { - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, copyBytes(PrivilegeTest.PASS_ADMIN)); + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, + copyBytes(PrivilegeTest.PASS_ADMIN)); org.junit.Assert.assertTrue("Certificate is null!", certificate != null); // set certificate into thread local From eac0782810c43fb89f41ecf1be2747e7ee433c8e Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 25 Nov 2012 00:44:56 +0100 Subject: [PATCH 080/173] [Minor] Removed dependency to ch.eitchnet.log4j and thus added log4j.xml The log4j.xml configuration file is in the test resources and now the project is completely free of a dependency to a concrete logging implementation as all logging of the sources is done over slf4j --- pom.xml | 6 +--- .../helper/BootstrapConfigurationHelper.java | 2 -- .../privilege/test/PrivilegeTest.java | 5 ---- src/test/resources/log4j.xml | 29 +++++++++++++++++++ 4 files changed, 30 insertions(+), 12 deletions(-) create mode 100644 src/test/resources/log4j.xml diff --git a/pom.xml b/pom.xml index 7605305ef..875dcab4f 100644 --- a/pom.xml +++ b/pom.xml @@ -91,11 +91,6 @@ ch.eitchnet.utils 0.1.0-SNAPSHOT - - ch.eitchnet - ch.eitchnet.log4j - 0.1.0-SNAPSHOT - org.slf4j slf4j-api @@ -105,6 +100,7 @@ org.slf4j slf4j-log4j12 1.7.2 + test diff --git a/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java b/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java index 3412f24e7..9f9bde839 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java +++ b/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java @@ -26,7 +26,6 @@ import org.dom4j.DocumentFactory; import org.dom4j.Element; import ch.eitchnet.privilege.handler.PrivilegeHandler; -import ch.eitchnet.utils.helper.Log4jConfigurator; /** *

    @@ -66,7 +65,6 @@ public class BootstrapConfigurationHelper { * the args from the command line */ public static void main(String[] args) { - Log4jConfigurator.configure(); // get current directory BootstrapConfigurationHelper.path = System.getProperty("user.dir") + "/newConfig"; diff --git a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java index 2fe79e34e..11837917d 100644 --- a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -43,7 +43,6 @@ 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.Log4jConfigurator; /** * JUnit for performing Privilege tests. This JUnit is by no means complete, but checks the bare minimum. TODO add more @@ -75,11 +74,7 @@ public class PrivilegeTest { */ @BeforeClass public static void init() throws Exception { - try { - // set up log4j - Log4jConfigurator.configure(); - // initialize container String pwd = System.getProperty("user.dir"); File privilegeContainerXmlFile = new File(pwd + "/config/Privilege.xml"); diff --git a/src/test/resources/log4j.xml b/src/test/resources/log4j.xml new file mode 100644 index 000000000..a35a3c351 --- /dev/null +++ b/src/test/resources/log4j.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From f73d829822c8240870f43e8672787c8768913e9c Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 1 Dec 2012 00:04:45 +0100 Subject: [PATCH 081/173] [Minor] moved exceptions to base package Exceptions were previously in package i18n, this didn't make sense, so they were move to base --- .../{i18n => base}/AccessDeniedException.java | 2 +- .../{i18n => base}/PrivilegeException.java | 2 +- .../handler/DefaultEncryptionHandler.java | 2 +- .../handler/DefaultPrivilegeHandler.java | 13 ++++--- .../privilege/handler/PersistenceHandler.java | 2 +- .../privilege/handler/PrivilegeHandler.java | 6 ++-- .../handler/XmlPersistenceHandler.java | 36 ++++++++++--------- .../privilege/helper/ClassHelper.java | 2 +- .../helper/InitializationHelper.java | 2 +- .../eitchnet/privilege/helper/XmlHelper.java | 2 +- .../eitchnet/privilege/model/Certificate.java | 2 +- .../privilege/model/PrivilegeRep.java | 2 +- .../privilege/model/internal/Privilege.java | 2 +- .../privilege/model/internal/Role.java | 2 +- .../privilege/model/internal/Session.java | 2 +- .../privilege/model/internal/User.java | 25 ++++++++----- .../privilege/policy/DefaultPrivilege.java | 4 +-- .../privilege/policy/PrivilegePolicy.java | 2 +- 18 files changed, 61 insertions(+), 49 deletions(-) rename src/main/java/ch/eitchnet/privilege/{i18n => base}/AccessDeniedException.java (96%) rename src/main/java/ch/eitchnet/privilege/{i18n => base}/PrivilegeException.java (97%) diff --git a/src/main/java/ch/eitchnet/privilege/i18n/AccessDeniedException.java b/src/main/java/ch/eitchnet/privilege/base/AccessDeniedException.java similarity index 96% rename from src/main/java/ch/eitchnet/privilege/i18n/AccessDeniedException.java rename to src/main/java/ch/eitchnet/privilege/base/AccessDeniedException.java index 818335103..0c8fa5283 100644 --- a/src/main/java/ch/eitchnet/privilege/i18n/AccessDeniedException.java +++ b/src/main/java/ch/eitchnet/privilege/base/AccessDeniedException.java @@ -17,7 +17,7 @@ * along with Privilege. If not, see . * */ -package ch.eitchnet.privilege.i18n; +package ch.eitchnet.privilege.base; /** * Exception thrown if access is denied during login, or if a certain privilege is not granted diff --git a/src/main/java/ch/eitchnet/privilege/i18n/PrivilegeException.java b/src/main/java/ch/eitchnet/privilege/base/PrivilegeException.java similarity index 97% rename from src/main/java/ch/eitchnet/privilege/i18n/PrivilegeException.java rename to src/main/java/ch/eitchnet/privilege/base/PrivilegeException.java index 0f9fe5111..dc254418a 100644 --- a/src/main/java/ch/eitchnet/privilege/i18n/PrivilegeException.java +++ b/src/main/java/ch/eitchnet/privilege/base/PrivilegeException.java @@ -17,7 +17,7 @@ * along with Privilege. If not, see . * */ -package ch.eitchnet.privilege.i18n; +package ch.eitchnet.privilege.base; /** * Main {@link RuntimeException} thrown if something goes wrong in Privilege diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java index 1dbd99a0f..fb614673e 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java @@ -28,9 +28,9 @@ import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import ch.eitchnet.privilege.base.PrivilegeException; import ch.eitchnet.privilege.helper.HashHelper; import ch.eitchnet.privilege.helper.XmlConstants; -import ch.eitchnet.privilege.i18n.PrivilegeException; /** *

    diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 9cc7e9127..a152e8cbd 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -31,9 +31,9 @@ import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import ch.eitchnet.privilege.base.AccessDeniedException; +import ch.eitchnet.privilege.base.PrivilegeException; import ch.eitchnet.privilege.helper.ClassHelper; -import ch.eitchnet.privilege.i18n.AccessDeniedException; -import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.Certificate; import ch.eitchnet.privilege.model.PrivilegeRep; import ch.eitchnet.privilege.model.Restrictable; @@ -718,7 +718,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { DefaultPrivilegeHandler.logger.info("User " + username + " authenticated: " + session); } catch (RuntimeException e) { - DefaultPrivilegeHandler.logger.error("User " + username + " Failed to authenticate: " + e.getLocalizedMessage()); + DefaultPrivilegeHandler.logger.error("User " + username + " Failed to authenticate: " + + e.getLocalizedMessage()); throw e; } finally { clearPassword(password); @@ -786,7 +787,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { Role role = this.persistenceHandler.getRole(roleName); if (role == null) { - DefaultPrivilegeHandler.logger.error("No role is defined with name " + roleName + " which is configured for user " + user); + DefaultPrivilegeHandler.logger.error("No role is defined with name " + roleName + + " which is configured for user " + user); continue; } @@ -1188,7 +1190,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { this.sessionMap.put(sessionId, new CertificateSessionPair(session, systemUserCertificate)); // log - DefaultPrivilegeHandler.logger.info("The system user " + systemUsername + " is logged in with session " + session); + DefaultPrivilegeHandler.logger.info("The system user " + systemUsername + " is logged in with session " + + session); return systemUserCertificate; } diff --git a/src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java b/src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java index bba8f20c0..498f6e8e7 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java @@ -118,7 +118,7 @@ public interface PersistenceHandler { /** * Informs this {@link PersistenceHandler} to persist any changes which need to be saved * - * @return true if changes were persisted successfully, false if something went wrong + * @return true if changes were persisted successfully, false if nothing needed to be persisted */ public boolean persist(); diff --git a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java index 4f2c54b2a..7889905e7 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java @@ -22,8 +22,8 @@ package ch.eitchnet.privilege.handler; import java.util.List; import java.util.Locale; -import ch.eitchnet.privilege.i18n.AccessDeniedException; -import ch.eitchnet.privilege.i18n.PrivilegeException; +import ch.eitchnet.privilege.base.AccessDeniedException; +import ch.eitchnet.privilege.base.PrivilegeException; import ch.eitchnet.privilege.model.Certificate; import ch.eitchnet.privilege.model.PrivilegeRep; import ch.eitchnet.privilege.model.Restrictable; @@ -401,7 +401,7 @@ public interface PrivilegeHandler { *

    * *

    - * If the user is not the administrator, then a {@link ch.eitchnet.privilege.i18n.PrivilegeException} is thrown + * If the user is not the administrator, then a {@link ch.eitchnet.privilege.base.PrivilegeException} is thrown *

    * * @param certificate diff --git a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java index 2d5fc12b8..6ea6fda2e 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java @@ -35,9 +35,9 @@ import org.dom4j.Element; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import ch.eitchnet.privilege.base.PrivilegeException; import ch.eitchnet.privilege.helper.XmlConstants; import ch.eitchnet.privilege.helper.XmlHelper; -import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.UserState; import ch.eitchnet.privilege.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.Role; @@ -150,11 +150,13 @@ public class XmlPersistenceHandler implements PersistenceHandler { throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " + XmlConstants.XML_PARAM_MODEL_FILE + " is invalid"); } + // get model file File modelFile = new File(this.modelPath); boolean modelFileUnchanged = modelFile.exists() && modelFile.lastModified() == this.modelsFileDate; - if (!(modelFileUnchanged && this.roleMapDirty && this.userMapDirty)) { - XmlPersistenceHandler.logger.warn("Not persisting as current file is unchanged and model data is not dirty"); + if (modelFileUnchanged && !this.roleMapDirty && !this.userMapDirty) { + XmlPersistenceHandler.logger + .warn("Not persisting as current file is unchanged and model data is not dirty"); return false; } @@ -181,19 +183,14 @@ public class XmlPersistenceHandler implements PersistenceHandler { } rootElement.add(rolesElement); - // reset dirty states and return if something was dirty, false otherwise - if (this.userMapDirty || this.roleMapDirty) { - this.userMapDirty = false; - this.roleMapDirty = false; - - return true; - - } + // now write the file + XmlHelper.writeElement(rootElement, modelFile); + // reset dirty states this.userMapDirty = false; this.roleMapDirty = false; - return false; + return true; } /** @@ -259,10 +256,13 @@ public class XmlPersistenceHandler implements PersistenceHandler { * @see ch.eitchnet.privilege.handler.PersistenceHandler#initialize(java.util.Map) */ @Override - public void initialize(Map parameterMap) { + public void initialize(Map paramsMap) { + + // copy parameter map + this.parameterMap = Collections.unmodifiableMap(new HashMap(paramsMap)); // get and validate base bath - String basePath = parameterMap.get(XmlConstants.XML_PARAM_BASE_PATH); + String basePath = this.parameterMap.get(XmlConstants.XML_PARAM_BASE_PATH); File basePathF = new File(basePath); if (!basePathF.exists() && !basePathF.isDirectory()) { throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " @@ -270,7 +270,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { } // get model file name - String modelFileName = parameterMap.get(XmlConstants.XML_PARAM_MODEL_FILE); + String modelFileName = this.parameterMap.get(XmlConstants.XML_PARAM_MODEL_FILE); if (modelFileName == null || modelFileName.isEmpty()) { throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " + XmlConstants.XML_PARAM_MODEL_FILE + " is invalid"); @@ -321,9 +321,11 @@ public class XmlPersistenceHandler implements PersistenceHandler { for (Element roleElement : rolesElementList) { String roleName = roleElement.getTextTrim(); if (roleName.isEmpty()) { - XmlPersistenceHandler.logger.error("User " + username + " has a role defined with no name, Skipped."); + XmlPersistenceHandler.logger.error("User " + username + + " has a role defined with no name, Skipped."); } else if (!this.roleMap.containsKey(roleName)) { - XmlPersistenceHandler.logger.error("User " + username + " has a inexistant role " + roleName + ", Skipped."); + XmlPersistenceHandler.logger.error("User " + username + " has a inexistant role " + roleName + + ", Skipped."); } else { roles.add(roleName); } diff --git a/src/main/java/ch/eitchnet/privilege/helper/ClassHelper.java b/src/main/java/ch/eitchnet/privilege/helper/ClassHelper.java index 41b10b775..5852805d8 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/ClassHelper.java +++ b/src/main/java/ch/eitchnet/privilege/helper/ClassHelper.java @@ -19,7 +19,7 @@ */ package ch.eitchnet.privilege.helper; -import ch.eitchnet.privilege.i18n.PrivilegeException; +import ch.eitchnet.privilege.base.PrivilegeException; /** * The {@link ClassHelper} class is a helper to instantiate classes using reflection diff --git a/src/main/java/ch/eitchnet/privilege/helper/InitializationHelper.java b/src/main/java/ch/eitchnet/privilege/helper/InitializationHelper.java index 94ff3d9d2..401cdb5fd 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/InitializationHelper.java +++ b/src/main/java/ch/eitchnet/privilege/helper/InitializationHelper.java @@ -29,11 +29,11 @@ import org.dom4j.Element; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +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.i18n.PrivilegeException; import ch.eitchnet.privilege.policy.PrivilegePolicy; import ch.eitchnet.utils.helper.StringHelper; diff --git a/src/main/java/ch/eitchnet/privilege/helper/XmlHelper.java b/src/main/java/ch/eitchnet/privilege/helper/XmlHelper.java index eec3c9eb2..900c8ca87 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/XmlHelper.java +++ b/src/main/java/ch/eitchnet/privilege/helper/XmlHelper.java @@ -37,7 +37,7 @@ import org.dom4j.io.XMLWriter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ch.eitchnet.privilege.i18n.PrivilegeException; +import ch.eitchnet.privilege.base.PrivilegeException; /** * Helper class for performing XML based tasks using Dom4J diff --git a/src/main/java/ch/eitchnet/privilege/model/Certificate.java b/src/main/java/ch/eitchnet/privilege/model/Certificate.java index 87d30a3da..0d165e57c 100644 --- a/src/main/java/ch/eitchnet/privilege/model/Certificate.java +++ b/src/main/java/ch/eitchnet/privilege/model/Certificate.java @@ -23,8 +23,8 @@ import java.io.Serializable; import java.util.Locale; import java.util.Map; +import ch.eitchnet.privilege.base.PrivilegeException; import ch.eitchnet.privilege.handler.PrivilegeHandler; -import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.internal.Session; /** diff --git a/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java b/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java index e13d6a1f4..ca3b596d9 100644 --- a/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java +++ b/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java @@ -22,8 +22,8 @@ package ch.eitchnet.privilege.model; import java.io.Serializable; import java.util.Set; +import ch.eitchnet.privilege.base.PrivilegeException; import ch.eitchnet.privilege.handler.PrivilegeHandler; -import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.Role; import ch.eitchnet.privilege.policy.PrivilegePolicy; diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/Privilege.java b/src/main/java/ch/eitchnet/privilege/model/internal/Privilege.java index c62456fbd..55c4fe1a5 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/Privilege.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/Privilege.java @@ -23,8 +23,8 @@ 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.i18n.PrivilegeException; import ch.eitchnet.privilege.model.PrivilegeRep; import ch.eitchnet.privilege.model.Restrictable; import ch.eitchnet.privilege.policy.PrivilegePolicy; diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/Role.java b/src/main/java/ch/eitchnet/privilege/model/internal/Role.java index ab929ffde..d6825717b 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/Role.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/Role.java @@ -23,7 +23,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; -import ch.eitchnet.privilege.i18n.PrivilegeException; +import ch.eitchnet.privilege.base.PrivilegeException; import ch.eitchnet.privilege.model.PrivilegeRep; import ch.eitchnet.privilege.model.RoleRep; diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/Session.java b/src/main/java/ch/eitchnet/privilege/model/internal/Session.java index 7e3bb8614..4abcd08d5 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/Session.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/Session.java @@ -19,8 +19,8 @@ */ package ch.eitchnet.privilege.model.internal; +import ch.eitchnet.privilege.base.PrivilegeException; import ch.eitchnet.privilege.handler.PrivilegeHandler; -import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.Certificate; /** diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/User.java b/src/main/java/ch/eitchnet/privilege/model/internal/User.java index 9e12026ac..0405ecec3 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/User.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/User.java @@ -26,7 +26,7 @@ import java.util.Locale; import java.util.Map; import java.util.Set; -import ch.eitchnet.privilege.i18n.PrivilegeException; +import ch.eitchnet.privilege.base.PrivilegeException; import ch.eitchnet.privilege.model.UserRep; import ch.eitchnet.privilege.model.UserState; @@ -91,9 +91,6 @@ public final class User { if (username == null || username.isEmpty()) { throw new PrivilegeException("No username defined!"); } - - // password may be null, meaning not able to login - if (firstname == null || firstname.isEmpty()) { throw new PrivilegeException("No firstname defined!"); } @@ -104,9 +101,10 @@ public final class User { throw new PrivilegeException("No userState defined!"); } + // password may be null, meaning not able to login // roles may be null, meaning not able to login and must be added later - - // local may be null, meaning use system default + // locale may be null, meaning use system default + // properties may be null, meaning no properties this.userId = userId; @@ -117,11 +115,20 @@ public final class User { this.firstname = firstname; this.surname = surname; - this.roles = Collections.unmodifiableSet(roles); + if (roles == null) + this.roles = Collections.emptySet(); + else + this.roles = Collections.unmodifiableSet(roles); - this.locale = locale; + if (locale == null) + this.locale = Locale.getDefault(); + else + this.locale = locale; - this.propertyMap = Collections.unmodifiableMap(propertyMap); + if (propertyMap == null) + this.propertyMap = Collections.emptyMap(); + else + this.propertyMap = Collections.unmodifiableMap(propertyMap); } /** diff --git a/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java b/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java index ebb061656..3db6be69b 100644 --- a/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java +++ b/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java @@ -19,8 +19,8 @@ */ package ch.eitchnet.privilege.policy; -import ch.eitchnet.privilege.i18n.AccessDeniedException; -import ch.eitchnet.privilege.i18n.PrivilegeException; +import ch.eitchnet.privilege.base.AccessDeniedException; +import ch.eitchnet.privilege.base.PrivilegeException; import ch.eitchnet.privilege.model.Restrictable; import ch.eitchnet.privilege.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.Role; diff --git a/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicy.java b/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicy.java index 50fc79837..62041e636 100644 --- a/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicy.java +++ b/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicy.java @@ -19,7 +19,7 @@ */ package ch.eitchnet.privilege.policy; -import ch.eitchnet.privilege.i18n.AccessDeniedException; +import ch.eitchnet.privilege.base.AccessDeniedException; import ch.eitchnet.privilege.model.Restrictable; import ch.eitchnet.privilege.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.Role; From 28a60b52f7ea4bad1f951fcb37a9ca296a7cdfba Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 1 Dec 2012 00:06:54 +0100 Subject: [PATCH 082/173] [New] implemented auto persist on password change Through configuration option it is now possible to enable automatic persisting after password change, no matter who the user is. --- config/Privilege.xml | 3 ++- .../handler/DefaultPrivilegeHandler.java | 26 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/config/Privilege.xml b/config/Privilege.xml index 2cfaf41e1..b37070a5a 100644 --- a/config/Privilege.xml +++ b/config/Privilege.xml @@ -28,6 +28,7 @@ along with Privilege. If not, see . + @@ -38,7 +39,7 @@ along with Privilege. If not, see . - + diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index a152e8cbd..4c45f060f 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -67,6 +67,11 @@ import ch.eitchnet.privilege.policy.PrivilegePolicy; */ public class DefaultPrivilegeHandler implements PrivilegeHandler { + /** + * configuration parameter to define automatic persisting on password change + */ + private static final String PARAM_AUTO_PERSIST_ON_PASSWORD_CHANGE = "autoPersistOnPasswordChange"; + /** * log4j logger */ @@ -107,6 +112,11 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { */ private boolean initialized; + /** + * flag to define if a persist should be performed after a user changes their password + */ + private boolean autoPersistOnPasswordChange; + /** * @see ch.eitchnet.privilege.handler.PrivilegeHandler#getRole(java.lang.String) */ @@ -624,6 +634,11 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // delegate user replacement to persistence handler this.persistenceHandler.addOrReplaceUser(newUser); + // perform automatic persisting, if enabled + if (this.autoPersistOnPasswordChange) { + this.persistenceHandler.persist(); + } + } finally { clearPassword(password); } @@ -997,6 +1012,17 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { this.encryptionHandler = encryptionHandler; this.persistenceHandler = persistenceHandler; + String autoPersistS = parameterMap.get(PARAM_AUTO_PERSIST_ON_PASSWORD_CHANGE); + if (autoPersistS == null || autoPersistS.equals(Boolean.FALSE.toString())) { + this.autoPersistOnPasswordChange = false; + } else if (autoPersistS.equals(Boolean.TRUE.toString())) { + this.autoPersistOnPasswordChange = true; + logger.info("Enabling automatic persistence on password change."); + } else { + logger.error("Parameter " + PARAM_AUTO_PERSIST_ON_PASSWORD_CHANGE + " has illegal value " + autoPersistS + + ". Overriding with " + Boolean.FALSE.toString()); + } + // validate policies on privileges of Roles for (Role role : persistenceHandler.getAllRoles()) { validatePolicies(role); From e0f0b67f605d1e49b0b408643a7c7d3f0e693127 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 1 Dec 2012 00:08:09 +0100 Subject: [PATCH 083/173] [Bugfix] fixed failing tests The tests did not work because of some previous refactoring or some other weirdness. Solved this issue now by initializing better the persistence handler for testing --- .../privilege/test/PrivilegeTest.java | 84 ++++++++++++++----- 1 file changed, 64 insertions(+), 20 deletions(-) diff --git a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java index 11837917d..5357faaa7 100644 --- a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -24,16 +24,19 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Map; +import junit.framework.Assert; + +import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; 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.helper.CertificateThreadLocal; import ch.eitchnet.privilege.helper.InitializationHelper; -import ch.eitchnet.privilege.i18n.AccessDeniedException; -import ch.eitchnet.privilege.i18n.PrivilegeException; import ch.eitchnet.privilege.model.Certificate; import ch.eitchnet.privilege.model.PrivilegeRep; import ch.eitchnet.privilege.model.Restrictable; @@ -43,10 +46,12 @@ 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.FileHelper; /** - * JUnit for performing Privilege tests. This JUnit is by no means complete, but checks the bare minimum. TODO add more - * tests, especially with deny and allow lists + * 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 */ @@ -75,10 +80,25 @@ public class PrivilegeTest { @BeforeClass public static void init() throws Exception { try { - // initialize container + // copy configuration to tmp String pwd = System.getProperty("user.dir"); - File privilegeContainerXmlFile = new File(pwd + "/config/Privilege.xml"); - PrivilegeTest.privilegeHandler = InitializationHelper.initializeFromXml(privilegeContainerXmlFile); + + File origPrivilegeModelFile = new File(pwd + "/config/PrivilegeModel.xml"); + File tmpPrivilegeModelFile = new File(pwd + "/target/test/PrivilegeModel.xml"); + if (tmpPrivilegeModelFile.exists() && !tmpPrivilegeModelFile.delete()) { + throw new RuntimeException("Tmp configuration still exists and can not be deleted at " + + tmpPrivilegeModelFile.getAbsolutePath()); + } + + File parentFile = tmpPrivilegeModelFile.getParentFile(); + if (!parentFile.exists()) { + if (!parentFile.mkdirs()) + throw new RuntimeException("Could not create parent for tmp " + tmpPrivilegeModelFile); + } + + if (!FileHelper.copy(origPrivilegeModelFile, tmpPrivilegeModelFile, true)) + throw new RuntimeException("Failed to copy " + origPrivilegeModelFile + " to " + tmpPrivilegeModelFile); + } catch (Exception e) { PrivilegeTest.logger.error(e.getMessage(), e); @@ -86,6 +106,24 @@ public class PrivilegeTest { } } + @Before + public void setup() throws Exception { + try { + + String pwd = System.getProperty("user.dir"); + + File privilegeConfigFile = new File(pwd + "/config/Privilege.xml"); + + // initialize privilege + PrivilegeTest.privilegeHandler = InitializationHelper.initializeFromXml(privilegeConfigFile); + + } catch (Exception e) { + PrivilegeTest.logger.error(e.getMessage(), e); + + throw new RuntimeException("Setup failed: " + e.getLocalizedMessage(), e); + } + } + /** * @throws Exception * if something goes wrong @@ -95,7 +133,7 @@ public class PrivilegeTest { Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, copyBytes(PrivilegeTest.PASS_ADMIN)); - org.junit.Assert.assertTrue("Certificate is null!", certificate != null); + Assert.assertTrue("Certificate is null!", certificate != null); PrivilegeTest.privilegeHandler.invalidateSession(certificate); } @@ -114,7 +152,7 @@ public class PrivilegeTest { Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, copyBytes(PrivilegeTest.PASS_BAD)); - org.junit.Assert.assertTrue("Certificate is null!", certificate != null); + Assert.assertTrue("Certificate is null!", certificate != null); PrivilegeTest.privilegeHandler.invalidateSession(certificate); } @@ -126,7 +164,7 @@ public class PrivilegeTest { public void testFailAuthenticationPWNull() throws Exception { Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, null); - org.junit.Assert.assertTrue("Certificate is null!", certificate != null); + Assert.assertTrue("Certificate is null!", certificate != null); PrivilegeTest.privilegeHandler.invalidateSession(certificate); } @@ -150,6 +188,7 @@ public class PrivilegeTest { PrivilegeTest.privilegeHandler.setUserPassword(certificate, PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); PrivilegeTest.logger.info("Set Bob's password"); + privilegeHandler.persist(certificate); PrivilegeTest.privilegeHandler.invalidateSession(certificate); } @@ -175,6 +214,7 @@ public class PrivilegeTest { Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, copyBytes(PrivilegeTest.PASS_ADMIN)); PrivilegeTest.privilegeHandler.setUserState(certificate, PrivilegeTest.BOB, UserState.ENABLED); + privilegeHandler.persist(certificate); PrivilegeTest.privilegeHandler.invalidateSession(certificate); } @@ -189,7 +229,7 @@ public class PrivilegeTest { Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); - org.junit.Assert.assertTrue("Certificate is null!", certificate != null); + Assert.assertTrue("Certificate is null!", certificate != null); PrivilegeTest.privilegeHandler.invalidateSession(certificate); } @@ -206,6 +246,7 @@ public class PrivilegeTest { RoleRep roleRep = new RoleRep(PrivilegeTest.ROLE_USER, privilegeMap); PrivilegeTest.privilegeHandler.addOrReplaceRole(certificate, roleRep); + privilegeHandler.persist(certificate); PrivilegeTest.privilegeHandler.invalidateSession(certificate); } @@ -218,6 +259,7 @@ public class PrivilegeTest { Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, copyBytes(PrivilegeTest.PASS_ADMIN)); PrivilegeTest.privilegeHandler.addRoleToUser(certificate, PrivilegeTest.BOB, PrivilegeTest.ROLE_USER); + privilegeHandler.persist(certificate); PrivilegeTest.privilegeHandler.invalidateSession(certificate); } @@ -244,7 +286,7 @@ public class PrivilegeTest { // auth as Bog Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); - org.junit.Assert.assertTrue("Certificate is null!", certificate != null); + Assert.assertTrue("Certificate is null!", certificate != null); // let's add a new user Ted UserRep userRep = new UserRep("1", PrivilegeTest.TED, "Ted", "And then Some", UserState.NEW, @@ -266,6 +308,7 @@ public class PrivilegeTest { PrivilegeTest.privilegeHandler.addRoleToUser(certificate, PrivilegeTest.BOB, PrivilegeHandler.PRIVILEGE_ADMIN_ROLE); PrivilegeTest.logger.info("Added " + PrivilegeHandler.PRIVILEGE_ADMIN_ROLE + " to " + PrivilegeTest.ADMIN); + privilegeHandler.persist(certificate); PrivilegeTest.privilegeHandler.invalidateSession(certificate); } @@ -278,7 +321,7 @@ public class PrivilegeTest { Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); - org.junit.Assert.assertTrue("Certificate is null!", certificate != null); + Assert.assertTrue("Certificate is null!", certificate != null); // let's add a new user ted HashSet roles = new HashSet(); @@ -287,7 +330,7 @@ public class PrivilegeTest { new HashMap()); PrivilegeTest.privilegeHandler.addOrReplaceUser(certificate, userRep, null); PrivilegeTest.logger.info("Added user " + PrivilegeTest.TED); - + privilegeHandler.persist(certificate); PrivilegeTest.privilegeHandler.invalidateSession(certificate); } @@ -300,12 +343,12 @@ public class PrivilegeTest { Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); - org.junit.Assert.assertTrue("Certificate is null!", certificate != null); + Assert.assertTrue("Certificate is null!", certificate != null); // set ted's password to default PrivilegeTest.privilegeHandler.setUserPassword(certificate, PrivilegeTest.TED, copyBytes(PrivilegeTest.PASS_DEF)); - + privilegeHandler.persist(certificate); PrivilegeTest.privilegeHandler.invalidateSession(certificate); } @@ -342,7 +385,7 @@ public class PrivilegeTest { Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, copyBytes(PrivilegeTest.PASS_ADMIN)); - org.junit.Assert.assertTrue("Certificate is null!", certificate != null); + Assert.assertTrue("Certificate is null!", certificate != null); // see if eitch can perform restrictable Restrictable restrictable = new TestRestrictable(); @@ -360,7 +403,7 @@ public class PrivilegeTest { public void testFailPerformRestrictableAsBob() throws Exception { Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); - org.junit.Assert.assertTrue("Certificate is null!", certificate != null); + Assert.assertTrue("Certificate is null!", certificate != null); // see if bob can perform restrictable Restrictable restrictable = new TestRestrictable(); @@ -382,6 +425,7 @@ public class PrivilegeTest { copyBytes(PrivilegeTest.PASS_ADMIN)); PrivilegeTest.privilegeHandler.addRoleToUser(certificate, PrivilegeTest.BOB, PrivilegeTest.ROLE_APP_USER); PrivilegeTest.logger.info("Added " + PrivilegeTest.ROLE_APP_USER + " to " + PrivilegeTest.BOB); + privilegeHandler.persist(certificate); PrivilegeTest.privilegeHandler.invalidateSession(certificate); } @@ -395,7 +439,7 @@ public class PrivilegeTest { public void testPerformRestrictableAsBob() throws Exception { Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); - org.junit.Assert.assertTrue("Certificate is null!", certificate != null); + Assert.assertTrue("Certificate is null!", certificate != null); // see if bob can perform restrictable Restrictable restrictable = new TestRestrictable(); @@ -456,7 +500,7 @@ public class PrivilegeTest { Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, copyBytes(PrivilegeTest.PASS_ADMIN)); - org.junit.Assert.assertTrue("Certificate is null!", certificate != null); + Assert.assertTrue("Certificate is null!", certificate != null); // set certificate into thread local CertificateThreadLocal.getInstance().set(certificate); From 717e2de556ae32c8baf09683f2fd7abc452ec4f8 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 1 Dec 2012 00:12:00 +0100 Subject: [PATCH 084/173] [Minor] cleaned up test tests left some state files, which are now cleaned up as well --- .../privilege/test/PrivilegeTest.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java index 5357faaa7..47e2c69e7 100644 --- a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -26,6 +26,7 @@ import java.util.Map; import junit.framework.Assert; +import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; @@ -106,6 +107,25 @@ public class PrivilegeTest { } } + @AfterClass + public static void destroy() throws Exception { + + // delete temporary file + String pwd = System.getProperty("user.dir"); + + File tmpPrivilegeModelFile = new File(pwd + "/target/test/PrivilegeModel.xml"); + if (tmpPrivilegeModelFile.exists() && !tmpPrivilegeModelFile.delete()) { + throw new RuntimeException("Tmp configuration still exists and can not be deleted at " + + tmpPrivilegeModelFile.getAbsolutePath()); + } + + // and temporary parent + File parentFile = tmpPrivilegeModelFile.getParentFile(); + if (parentFile.exists() && !parentFile.delete()) { + throw new RuntimeException("Could not remove temporary parent for tmp " + tmpPrivilegeModelFile); + } + } + @Before public void setup() throws Exception { try { From 9970055cf945ae1220b6223b2ff475def0c43510 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Tue, 4 Dec 2012 20:30:04 +0100 Subject: [PATCH 085/173] [New] added preliminary XML schemas for XML files --- src/test/resources/Privilege.xsd | 88 ++++++++++++++++++++++ src/test/resources/PrivilegeModel.xsd | 103 ++++++++++++++++++++++++++ 2 files changed, 191 insertions(+) create mode 100644 src/test/resources/Privilege.xsd create mode 100644 src/test/resources/PrivilegeModel.xsd diff --git a/src/test/resources/Privilege.xsd b/src/test/resources/Privilege.xsd new file mode 100644 index 000000000..0f9baa712 --- /dev/null +++ b/src/test/resources/Privilege.xsd @@ -0,0 +1,88 @@ + + + + + +Copyright (c) 2010, 2011 + +Robert von Burg <eitch@eitchnet.ch> + +This file is part of Privilege. + +Privilege is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Privilege is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with Privilege. If not, see <http://www.gnu.org/licenses/>. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/PrivilegeModel.xsd b/src/test/resources/PrivilegeModel.xsd new file mode 100644 index 000000000..cab4d1d3b --- /dev/null +++ b/src/test/resources/PrivilegeModel.xsd @@ -0,0 +1,103 @@ + + + + + +Copyright (c) 2010, 2011 + +Robert von Burg <eitch@eitchnet.ch> + +This file is part of Privilege. + +Privilege is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Privilege is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with Privilege. If not, see <http://www.gnu.org/licenses/>. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From e76d9f91214d75fab1dba5cd6f50a17c29afce04 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 20 Jan 2013 21:26:18 +0100 Subject: [PATCH 086/173] Implemented XML parsing and writing by using basic Java implementation --- config/PrivilegeModel.xml | 7 + pom.xml | 5 - .../handler/XmlPersistenceHandler.java | 495 +++--------------- .../helper/BootstrapConfigurationHelper.java | 102 +--- .../helper/InitializationHelper.java | 204 -------- .../eitchnet/privilege/helper/XmlHelper.java | 135 +++-- .../internal/PrivilegeContainerModel.java | 167 ++++++ .../eitchnet/privilege/xml/ElementParser.java | 36 ++ .../privilege/xml/ElementParserAdapter.java | 48 ++ .../privilege/xml/InitializationHelper.java | 106 ++++ .../xml/PrivilegeConfigDomWriter.java | 121 +++++ .../xml/PrivilegeConfigSaxReader.java | 183 +++++++ .../xml/PrivilegeModelDomWriter.java | 160 ++++++ .../xml/PrivilegeModelSaxReader.java | 347 ++++++++++++ .../privilege/test/PrivilegeTest.java | 2 +- .../ch/eitchnet/privilege/test/XmlTest.java | 219 ++++++++ 16 files changed, 1580 insertions(+), 757 deletions(-) delete mode 100644 src/main/java/ch/eitchnet/privilege/helper/InitializationHelper.java create mode 100644 src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java create mode 100644 src/main/java/ch/eitchnet/privilege/xml/ElementParser.java create mode 100644 src/main/java/ch/eitchnet/privilege/xml/ElementParserAdapter.java create mode 100644 src/main/java/ch/eitchnet/privilege/xml/InitializationHelper.java create mode 100644 src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigDomWriter.java create mode 100644 src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java create mode 100644 src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelDomWriter.java create mode 100644 src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java create mode 100644 src/test/java/ch/eitchnet/privilege/test/XmlTest.java diff --git a/config/PrivilegeModel.xml b/config/PrivilegeModel.xml index 396878bc4..4c788b66e 100644 --- a/config/PrivilegeModel.xml +++ b/config/PrivilegeModel.xml @@ -71,6 +71,13 @@ along with Privilege. If not, see . true + + + + hello + goodbye + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index 875dcab4f..2776e6b91 100644 --- a/pom.xml +++ b/pom.xml @@ -81,11 +81,6 @@ 4.10 test - - maven - dom4j - 1.7-20060614 - ch.eitchnet ch.eitchnet.utils diff --git a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java index 6ea6fda2e..60d5828c7 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java @@ -20,28 +20,22 @@ package ch.eitchnet.privilege.handler; import java.io.File; -import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; import java.util.LinkedList; import java.util.List; -import java.util.Locale; import java.util.Map; -import java.util.Set; -import org.dom4j.DocumentFactory; -import org.dom4j.Element; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ch.eitchnet.privilege.base.PrivilegeException; import ch.eitchnet.privilege.helper.XmlConstants; import ch.eitchnet.privilege.helper.XmlHelper; -import ch.eitchnet.privilege.model.UserState; -import ch.eitchnet.privilege.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.Role; import ch.eitchnet.privilege.model.internal.User; +import ch.eitchnet.privilege.xml.PrivilegeModelDomWriter; +import ch.eitchnet.privilege.xml.PrivilegeModelSaxReader; /** * {@link PersistenceHandler} implementation which reads the configuration from XML files. These configuration is passed @@ -138,120 +132,6 @@ public class XmlPersistenceHandler implements PersistenceHandler { this.roleMapDirty = true; } - /** - * @see ch.eitchnet.privilege.handler.PersistenceHandler#persist() - */ - @Override - public boolean persist() { - - // get models file name - String modelFileName = this.parameterMap.get(XmlConstants.XML_PARAM_MODEL_FILE); - if (modelFileName == null || modelFileName.isEmpty()) { - throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " - + XmlConstants.XML_PARAM_MODEL_FILE + " is invalid"); - } - - // get model file - File modelFile = new File(this.modelPath); - boolean modelFileUnchanged = modelFile.exists() && modelFile.lastModified() == this.modelsFileDate; - if (modelFileUnchanged && !this.roleMapDirty && !this.userMapDirty) { - XmlPersistenceHandler.logger - .warn("Not persisting as current file is unchanged and model data is not dirty"); - return false; - } - - DocumentFactory docFactory = DocumentFactory.getInstance(); - - // create root element - Element rootElement = docFactory.createElement(XmlConstants.XML_ROOT_PRIVILEGE_USERS_AND_ROLES); - - // USERS - // build XML DOM of users - List users = XmlPersistenceHandler.toDomUsers(this.userMap); - Element usersElement = docFactory.createElement(XmlConstants.XML_USERS); - for (Element userElement : users) { - usersElement.add(userElement); - } - rootElement.add(usersElement); - - // ROLES - // build XML DOM of roles - List roles = XmlPersistenceHandler.toDomRoles(this.roleMap); - Element rolesElement = docFactory.createElement(XmlConstants.XML_ROLES); - for (Element roleElement : roles) { - rolesElement.add(roleElement); - } - rootElement.add(rolesElement); - - // now write the file - XmlHelper.writeElement(rootElement, modelFile); - - // reset dirty states - this.userMapDirty = false; - this.roleMapDirty = false; - - return true; - } - - /** - * @see ch.eitchnet.privilege.handler.PersistenceHandler#reload() - */ - @Override - public boolean reload() { - - // validate file exists - File modelsFile = new File(this.modelPath); - if (!modelsFile.exists()) { - throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " - + XmlConstants.XML_PARAM_MODEL_FILE + " is invalid as models file does not exist at path " - + modelsFile.getAbsolutePath()); - } - - this.roleMap = Collections.synchronizedMap(new HashMap()); - this.userMap = Collections.synchronizedMap(new HashMap()); - - // parse models xml file to XML document - Element modelsRootElement = XmlHelper.parseDocument(modelsFile).getRootElement(); - this.modelsFileDate = modelsFile.lastModified(); - - // ROLES - // get roles element - Element rolesElement = modelsRootElement.element(XmlConstants.XML_ROLES); - // read roles - Map roles = readRoles(rolesElement); - this.roleMap = roles; - - // USERS - // get users element - Element usersElement = modelsRootElement.element(XmlConstants.XML_USERS); - // read users - Map users = readUsers(usersElement); - this.userMap = users; - - this.userMapDirty = false; - this.roleMapDirty = false; - - XmlPersistenceHandler.logger.info("Read " + this.userMap.size() + " Users"); - XmlPersistenceHandler.logger.info("Read " + this.roleMap.size() + " Roles"); - - // validate we have a user with PrivilegeAdmin access - boolean privilegeAdminExists = false; - for (String username : this.userMap.keySet()) { - User user = this.userMap.get(username); - if (user.hasRole(PrivilegeHandler.PRIVILEGE_ADMIN_ROLE)) { - privilegeAdminExists = true; - break; - } - } - - if (!privilegeAdminExists) { - XmlPersistenceHandler.logger.warn("No User with role '" + PrivilegeHandler.PRIVILEGE_ADMIN_ROLE - + "' exists. Privilege modifications will not be possible!"); - } - - return true; - } - /** * @see ch.eitchnet.privilege.handler.PersistenceHandler#initialize(java.util.Map) */ @@ -284,317 +164,94 @@ public class XmlPersistenceHandler implements PersistenceHandler { } /** - * Parses {@link User} objects from their XML representations - * - * @param usersRootElement - * the element containing suer elements - * - * @return the map of converted {@link User} objects + * @see ch.eitchnet.privilege.handler.PersistenceHandler#reload() */ - protected Map readUsers(Element usersRootElement) { + @Override + public boolean reload() { - Map userMap = new HashMap(); - - @SuppressWarnings("unchecked") - List userElements = usersRootElement.elements(XmlConstants.XML_USER); - for (Element userElement : userElements) { - - String userId = userElement.attributeValue(XmlConstants.XML_ATTR_USER_ID); - - String username = userElement.attributeValue(XmlConstants.XML_ATTR_USERNAME); - String password = userElement.attributeValue(XmlConstants.XML_ATTR_PASSWORD); - - String firstname = userElement.element(XmlConstants.XML_FIRSTNAME).getTextTrim(); - String surname = userElement.element(XmlConstants.XML_SURNAME).getTextTrim(); - - UserState userState = UserState.valueOf(userElement.element(XmlConstants.XML_STATE).getTextTrim()); - - // TODO better parsing needed - String localeName = userElement.element(XmlConstants.XML_LOCALE).getTextTrim(); - Locale locale = new Locale(localeName); - - // read roles - Element rolesElement = userElement.element(XmlConstants.XML_ROLES); - @SuppressWarnings("unchecked") - List rolesElementList = rolesElement.elements(XmlConstants.XML_ROLE); - Set roles = new HashSet(); - for (Element roleElement : rolesElementList) { - String roleName = roleElement.getTextTrim(); - if (roleName.isEmpty()) { - XmlPersistenceHandler.logger.error("User " + username - + " has a role defined with no name, Skipped."); - } else if (!this.roleMap.containsKey(roleName)) { - XmlPersistenceHandler.logger.error("User " + username + " has a inexistant role " + roleName - + ", Skipped."); - } else { - roles.add(roleName); - } - } - - // read properties - Element propertiesElement = userElement.element(XmlConstants.XML_PROPERTIES); - Map propertyMap = XmlPersistenceHandler.convertToPropertyMap(propertiesElement); - - // create user - User user = new User(userId, username, password, firstname, surname, userState, roles, locale, propertyMap); - - // put user in map - userMap.put(username, user); - XmlPersistenceHandler.logger.info("Loaded user " + user); + // validate file exists + File modelsFile = new File(this.modelPath); + if (!modelsFile.exists()) { + throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " + + XmlConstants.XML_PARAM_MODEL_FILE + " is invalid as models file does not exist at path " + + modelsFile.getAbsolutePath()); } - return userMap; + this.roleMap = Collections.synchronizedMap(new HashMap()); + this.userMap = Collections.synchronizedMap(new HashMap()); + + // parse models xml file to XML document + PrivilegeModelSaxReader xmlHandler = new PrivilegeModelSaxReader(); + XmlHelper.parseDocument(modelsFile, xmlHandler); + + this.modelsFileDate = modelsFile.lastModified(); + + // ROLES + List roles = xmlHandler.getRoles(); + for (Role role : roles) { + this.roleMap.put(role.getName(), role); + } + + // USERS + List users = xmlHandler.getUsers(); + for (User user : users) { + this.userMap.put(user.getUsername(), user); + } + + this.userMapDirty = false; + this.roleMapDirty = false; + + XmlPersistenceHandler.logger.info("Read " + this.userMap.size() + " Users"); + XmlPersistenceHandler.logger.info("Read " + this.roleMap.size() + " Roles"); + + // validate we have a user with PrivilegeAdmin access + boolean privilegeAdminExists = false; + for (String username : this.userMap.keySet()) { + User user = this.userMap.get(username); + if (user.hasRole(PrivilegeHandler.PRIVILEGE_ADMIN_ROLE)) { + privilegeAdminExists = true; + break; + } + } + + if (!privilegeAdminExists) { + XmlPersistenceHandler.logger.warn("No User with role '" + PrivilegeHandler.PRIVILEGE_ADMIN_ROLE + + "' exists. Privilege modifications will not be possible!"); + } + + return true; } /** - * Parses {@link Role} objects from their XML representations - * - * @param rolesRootElement - * the element containing role elements - * - * @return the map of converted {@link Role} objects + * @see ch.eitchnet.privilege.handler.PersistenceHandler#persist() */ - protected Map readRoles(Element rolesRootElement) { + @Override + public boolean persist() { - Map roleMap = new HashMap(); - - @SuppressWarnings("unchecked") - List roleElements = rolesRootElement.elements(XmlConstants.XML_ROLE); - for (Element roleElement : roleElements) { - - String roleName = roleElement.attributeValue(XmlConstants.XML_ATTR_NAME); - - Map privilegeMap = readPrivileges(roleElement); - - Role role = new Role(roleName, privilegeMap); - roleMap.put(roleName, role); + // get models file name + String modelFileName = this.parameterMap.get(XmlConstants.XML_PARAM_MODEL_FILE); + if (modelFileName == null || modelFileName.isEmpty()) { + throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " + + XmlConstants.XML_PARAM_MODEL_FILE + " is invalid"); } - return roleMap; - } - - /** - * Parses {@link Privilege} objects from their XML representation to their objects - * - * @param roleParentElement - * the parent on which the Privilege XML elements are - * - * @return the map of {@link Privilege} objects - */ - protected Map readPrivileges(Element roleParentElement) { - - Map privilegeMap = new HashMap(); - - @SuppressWarnings("unchecked") - List privilegeElements = roleParentElement.elements(XmlConstants.XML_PRIVILEGE); - for (Element privilegeElement : privilegeElements) { - - String privilegeName = privilegeElement.attributeValue(XmlConstants.XML_ATTR_NAME); - String privilegePolicy = privilegeElement.attributeValue(XmlConstants.XML_ATTR_POLICY); - - Element allAllowedE = privilegeElement.element(XmlConstants.XML_ALL_ALLOWED); - boolean allAllowed = false; - if (allAllowedE != null) { - allAllowed = Boolean.valueOf(allAllowedE.getTextTrim()).booleanValue(); - } - - @SuppressWarnings("unchecked") - List denyElements = privilegeElement.elements(XmlConstants.XML_DENY); - Set denyList = new HashSet(denyElements.size()); - for (Element denyElement : denyElements) { - String denyValue = denyElement.getTextTrim(); - if (!denyValue.isEmpty()) - denyList.add(denyValue); - } - - @SuppressWarnings("unchecked") - List allowElements = privilegeElement.elements(XmlConstants.XML_ALLOW); - Set allowList = new HashSet(allowElements.size()); - for (Element allowElement : allowElements) { - String allowValue = allowElement.getTextTrim(); - if (!allowValue.isEmpty()) - allowList.add(allowValue); - } - - Privilege privilege = new Privilege(privilegeName, privilegePolicy, allAllowed, denyList, allowList); - privilegeMap.put(privilegeName, privilege); + // get model file + File modelFile = new File(this.modelPath); + boolean modelFileUnchanged = modelFile.exists() && modelFile.lastModified() == this.modelsFileDate; + if (modelFileUnchanged && !this.roleMapDirty && !this.userMapDirty) { + XmlPersistenceHandler.logger + .warn("Not persisting as current file is unchanged and model data is not dirty"); + return false; } - return privilegeMap; - } + // delegate writing + PrivilegeModelDomWriter modelWriter = new PrivilegeModelDomWriter(getAllUsers(), getAllRoles(), modelFile); + modelWriter.write(); - /** - * Converts {@link User} objects to their XML representations - * - * @param userMap - * the map of users to convert - * - * @return the list of XML User elements - */ - protected static List toDomUsers(Map userMap) { + // reset dirty states + this.userMapDirty = false; + this.roleMapDirty = false; - List usersAsElements = new ArrayList(userMap.size()); - - DocumentFactory documentFactory = DocumentFactory.getInstance(); - - synchronized (userMap) { - for (String userName : userMap.keySet()) { - - // get the user object - User user = userMap.get(userName); - - // create the user element - Element userElement = documentFactory.createElement(XmlConstants.XML_USER); - userElement.addAttribute(XmlConstants.XML_ATTR_USER_ID, user.getUserId()); - userElement.addAttribute(XmlConstants.XML_ATTR_USERNAME, user.getUsername()); - userElement.addAttribute(XmlConstants.XML_ATTR_PASSWORD, user.getPassword()); - - // add first name element - Element firstnameElement = documentFactory.createElement(XmlConstants.XML_FIRSTNAME); - firstnameElement.setText(user.getFirstname()); - userElement.add(firstnameElement); - - // add surname element - Element surnameElement = documentFactory.createElement(XmlConstants.XML_SURNAME); - surnameElement.setText(user.getSurname()); - userElement.add(surnameElement); - - // add state element - Element stateElement = documentFactory.createElement(XmlConstants.XML_STATE); - stateElement.setText(user.getUserState().toString()); - userElement.add(stateElement); - - // add locale element - Element localeElement = documentFactory.createElement(XmlConstants.XML_LOCALE); - localeElement.setText(user.getLocale().toString()); - userElement.add(localeElement); - - // add all the role elements - Element rolesElement = documentFactory.createElement(XmlConstants.XML_ROLES); - userElement.add(rolesElement); - for (String roleName : user.getRoles()) { - Element roleElement = documentFactory.createElement(XmlConstants.XML_ROLE); - roleElement.setText(roleName); - rolesElement.add(roleElement); - } - - // add element to return list - usersAsElements.add(userElement); - } - } - - return usersAsElements; - } - - /** - * Converts {@link Role} objects to their XML representations - * - * @param roleMap - * the roles to convert - * - * @return the list of XML Role elements - */ - protected static List toDomRoles(Map roleMap) { - - List rolesAsElements = new ArrayList(roleMap.size()); - - DocumentFactory documentFactory = DocumentFactory.getInstance(); - - synchronized (roleMap) { - for (String roleName : roleMap.keySet()) { - - // get the role object - Role role = roleMap.get(roleName); - - // create the role element - Element roleElement = documentFactory.createElement(XmlConstants.XML_ROLE); - roleElement.addAttribute(XmlConstants.XML_ATTR_NAME, role.getName()); - - // add all the privileges - XmlPersistenceHandler.toDomPrivileges(roleElement, role.getPrivilegeMap()); - - // add element to return list - rolesAsElements.add(roleElement); - } - } - - return rolesAsElements; - } - - /** - * Converts {@link Privilege} objects to their XML representation - * - * @param roleParentElement - * the XML element of the parent {@link Role} - * @param privilegeMap - * the map of {@link Privilege}s to convert - */ - protected static void toDomPrivileges(Element roleParentElement, Map privilegeMap) { - - DocumentFactory documentFactory = DocumentFactory.getInstance(); - - for (Privilege privilege : privilegeMap.values()) { - - // create the privilege element - Element privilegeElement = documentFactory.createElement(XmlConstants.XML_PRIVILEGE); - privilegeElement.addAttribute(XmlConstants.XML_ATTR_NAME, privilege.getName()); - privilegeElement.addAttribute(XmlConstants.XML_ATTR_POLICY, privilege.getPolicy()); - - // add the all allowed element - Element allAllowedElement = documentFactory.createElement(XmlConstants.XML_ALL_ALLOWED); - allAllowedElement.setText(Boolean.toString(privilege.isAllAllowed())); - privilegeElement.add(allAllowedElement); - - // add all the deny values - for (String denyValue : privilege.getDenyList()) { - Element denyValueElement = documentFactory.createElement(XmlConstants.XML_DENY); - denyValueElement.setText(denyValue); - privilegeElement.add(denyValueElement); - } - - // add all the allow values - for (String allowValue : privilege.getAllowList()) { - Element allowValueElement = documentFactory.createElement(XmlConstants.XML_ALLOW); - allowValueElement.setText(allowValue); - privilegeElement.add(allowValueElement); - } - - // add element to parent - roleParentElement.add(privilegeElement); - } - } - - /** - * Converts an {@link XmlConstants#XML_PROPERTIES} element containing {@link XmlConstants#XML_PROPERTY} elements to - * a {@link Map} of String key/value pairs - * - * @param element - * the XML {@link Element} with name {@link XmlConstants#XML_PROPERTIES} containing - * {@link XmlConstants#XML_PROPERTY} elements - * - * @return the {@link Map} of the property name/value combinations from the given {@link Element} - */ - @SuppressWarnings("unchecked") - protected static Map convertToPropertyMap(Element element) { - - // if element is null then there are no properties, so return empty map - if (element == null) - return Collections.emptyMap(); - - List elements = element.elements(XmlConstants.XML_PROPERTY); - - // if elements is null or empty then there are no properties, so return empty map - if (elements == null || elements.isEmpty()) - return Collections.emptyMap(); - - Map propertyMap = new HashMap(); - - for (Element property : elements) { - String name = property.attributeValue(XmlConstants.XML_ATTR_NAME); - String value = property.attributeValue(XmlConstants.XML_ATTR_VALUE); - propertyMap.put(name, value); - } - - return propertyMap; + return true; } } diff --git a/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java b/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java index 9f9bde839..56970e63b 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java +++ b/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java @@ -20,12 +20,12 @@ package ch.eitchnet.privilege.helper; import java.io.File; - -import org.dom4j.Document; -import org.dom4j.DocumentFactory; -import org.dom4j.Element; +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; /** *

    @@ -46,7 +46,7 @@ import ch.eitchnet.privilege.handler.PrivilegeHandler; */ public class BootstrapConfigurationHelper { - // private static final Logger logger = LoggerFactory.getLogger(BootstrapConfigurationHelper.class); + // private static final Logger logger = Loggerdoc.getLogger(BootstrapConfigurationHelper.class); private static String path; @@ -81,81 +81,29 @@ public class BootstrapConfigurationHelper { 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: - BootstrapConfigurationHelper.createXmlPrivilegeContainer(); - BootstrapConfigurationHelper.createPolicyConfiguration(); - BootstrapConfigurationHelper.createModel(); - } - - /** - * - */ - private static void createModel() { - // TODO Auto-generated method stub - - } - - /** - * - */ - private static void createPolicyConfiguration() { - // TODO Auto-generated method stub - - } - - /** - * - */ - private static void createXmlPrivilegeContainer() { - - // create document root - DocumentFactory factory = DocumentFactory.getInstance(); - Document doc = factory.createDocument(XmlHelper.DEFAULT_ENCODING); - doc.setName(XmlConstants.XML_ROOT_PRIVILEGE); - Element rootElement = factory.createElement(XmlConstants.XML_ROOT_PRIVILEGE); - doc.setRootElement(rootElement); - - Element containerElement = factory.createElement(XmlConstants.XML_CONTAINER); - - Element parameterElement; - Element parametersElement; - - // create PersistenceHandler - Element persistenceHandlerElem = factory.createElement(XmlConstants.XML_HANDLER_PERSISTENCE); - containerElement.add(persistenceHandlerElem); - persistenceHandlerElem.addAttribute(XmlConstants.XML_ATTR_CLASS, - BootstrapConfigurationHelper.defaultPersistenceHandler); - parametersElement = factory.createElement(XmlConstants.XML_PARAMETERS); - persistenceHandlerElem.add(parametersElement); - // Parameter basePath - parameterElement = factory.createElement(XmlConstants.XML_PARAMETER); - parameterElement.addAttribute(XmlConstants.XML_ATTR_NAME, XmlConstants.XML_PARAM_BASE_PATH); - parameterElement.addAttribute(XmlConstants.XML_ATTR_VALUE, BootstrapConfigurationHelper.basePath); - parametersElement.add(parameterElement); - // Parameter modelXmlFile - parameterElement = factory.createElement(XmlConstants.XML_PARAMETER); - parameterElement.addAttribute(XmlConstants.XML_ATTR_NAME, XmlConstants.XML_PARAM_MODEL_FILE); - parameterElement.addAttribute(XmlConstants.XML_ATTR_VALUE, BootstrapConfigurationHelper.modelFileName); - parametersElement.add(parameterElement); - - // create EncryptionHandler - Element encryptionHandlerElem = factory.createElement(XmlConstants.XML_HANDLER_ENCRYPTION); - containerElement.add(encryptionHandlerElem); - encryptionHandlerElem.addAttribute(XmlConstants.XML_ATTR_CLASS, - BootstrapConfigurationHelper.defaultEncryptionHandler); - parametersElement = factory.createElement(XmlConstants.XML_PARAMETERS); - encryptionHandlerElem.add(parametersElement); - // Parameter hashAlgorithm - parameterElement = factory.createElement(XmlConstants.XML_PARAMETER); - parameterElement.addAttribute(XmlConstants.XML_ATTR_NAME, XmlConstants.XML_PARAM_HASH_ALGORITHM); - parameterElement.addAttribute(XmlConstants.XML_ATTR_VALUE, BootstrapConfigurationHelper.hashAlgorithm); - parametersElement.add(parameterElement); - - // write the container file to disk - File privilegeContainerFile = new File(BootstrapConfigurationHelper.path + "/" + File configFile = new File(BootstrapConfigurationHelper.path + "/" + BootstrapConfigurationHelper.defaultPrivilegeContainerXmlFile); - XmlHelper.writeDocument(doc, privilegeContainerFile); + PrivilegeConfigDomWriter configSaxWriter = new PrivilegeConfigDomWriter(containerModel, configFile); + configSaxWriter.write(); } } diff --git a/src/main/java/ch/eitchnet/privilege/helper/InitializationHelper.java b/src/main/java/ch/eitchnet/privilege/helper/InitializationHelper.java deleted file mode 100644 index 401cdb5fd..000000000 --- a/src/main/java/ch/eitchnet/privilege/helper/InitializationHelper.java +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Copyright (c) 2010 - 2012 - * - * This file is part of Privilege. - * - * Privilege is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . - * - */ -package ch.eitchnet.privilege.helper; - -import java.io.File; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.dom4j.Element; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -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.policy.PrivilegePolicy; -import ch.eitchnet.utils.helper.StringHelper; - -/** - * This class implements the initializing of the {@link PrivilegeHandler} by loading an XML file containing the - * configuration - * - * @author Robert von Burg - */ -public class InitializationHelper { - - private static final Logger logger = LoggerFactory.getLogger(InitializationHelper.class); - - /** - * 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 {@link PrivilegeHandler} instance loaded from the configuration file - */ - public static PrivilegeHandler initializeFromXml(File privilegeXmlFile) { - - // make sure file exists - if (!privilegeXmlFile.exists()) { - throw new PrivilegeException("Privilege file does not exist at path " + privilegeXmlFile.getAbsolutePath()); - } - - // parse container xml file to XML document - Element rootElement = XmlHelper.parseDocument(privilegeXmlFile).getRootElement(); - Element containerElement = rootElement.element(XmlConstants.XML_CONTAINER); - - // instantiate encryption handler - Element encryptionHandlerElement = containerElement.element(XmlConstants.XML_HANDLER_ENCRYPTION); - String encryptionHandlerClassName = encryptionHandlerElement.attributeValue(XmlConstants.XML_ATTR_CLASS); - EncryptionHandler encryptionHandler = ClassHelper.instantiateClass(encryptionHandlerClassName); - - // instantiate persistence handler - Element persistenceHandlerElement = containerElement.element(XmlConstants.XML_HANDLER_PERSISTENCE); - String persistenceHandlerClassName = persistenceHandlerElement.attributeValue(XmlConstants.XML_ATTR_CLASS); - PersistenceHandler persistenceHandler = ClassHelper.instantiateClass(persistenceHandlerClassName); - - // instantiate privilege handler - DefaultPrivilegeHandler privilegeHandler = new DefaultPrivilegeHandler(); - - // get policies - Element policiesElement = rootElement.element(XmlConstants.XML_POLICIES); - Map> policyMap = InitializationHelper.convertToPolicyMap(policiesElement); - - try { - - // get parameters - Element parameterElement = encryptionHandlerElement.element(XmlConstants.XML_PARAMETERS); - Map parameterMap = InitializationHelper.convertToParameterMap(parameterElement); - - // initialize encryption handler - encryptionHandler.initialize(parameterMap); - - } catch (Exception e) { - InitializationHelper.logger.error(e.getMessage(), e); - throw new PrivilegeException("EncryptionHandler " + encryptionHandlerClassName - + " could not be initialized"); - } - - try { - - // get parameters - Element parameterElement = persistenceHandlerElement.element(XmlConstants.XML_PARAMETERS); - Map parameterMap = InitializationHelper.convertToParameterMap(parameterElement); - - // initialize persistence handler - persistenceHandler.initialize(parameterMap); - - } catch (Exception e) { - InitializationHelper.logger.error(e.getMessage(), e); - throw new PrivilegeException("PersistenceHandler " + persistenceHandlerElement - + " could not be initialized"); - } - - try { - - // get parameters - Element parameterElement = containerElement.element(XmlConstants.XML_PARAMETERS); - Map parameterMap = InitializationHelper.convertToParameterMap(parameterElement); - - // initialize privilege handler - privilegeHandler.initialize(parameterMap, encryptionHandler, persistenceHandler, policyMap); - - } catch (Exception e) { - InitializationHelper.logger.error(e.getMessage(), e); - throw new PrivilegeException("PrivilegeHandler " + privilegeHandler.getClass().getName() - + " could not be initialized"); - } - - return privilegeHandler; - } - - /** - * Converts an {@link XmlConstants#XML_PARAMETERS} element containing {@link XmlConstants#XML_PARAMETER} elements to - * a {@link Map} of String key/value pairs - * - * @param element - * the XML {@link Element} with name {@link XmlConstants#XML_PARAMETERS} containing - * {@link XmlConstants#XML_PARAMETER} elements - * - * @return the {@link Map} of the parameter name/value combinations from the given {@link Element} - */ - @SuppressWarnings("unchecked") - public static Map convertToParameterMap(Element element) { - - // if element is null then there are no parameters, so return empty map - if (element == null) - return Collections.emptyMap(); - - List elements = element.elements(XmlConstants.XML_PARAMETER); - - // if elements is null or empty then there are no parameters, so return empty map - if (elements == null || elements.isEmpty()) - return Collections.emptyMap(); - - Map parameterMap = new HashMap(); - - for (Element parameter : elements) { - String name = parameter.attributeValue(XmlConstants.XML_ATTR_NAME); - String value = parameter.attributeValue(XmlConstants.XML_ATTR_VALUE); - - // replace any defined system properties - value = StringHelper.replaceSystemPropertiesIn(value); - - parameterMap.put(name, value); - } - - return parameterMap; - } - - /** - * Converts an {@link XmlConstants#XML_POLICIES} element containing {@link XmlConstants#XML_POLICY} elements to a - * {@link Map} of String/Class pairs - * - * @param element - * the XML {@link Element} with name {@link XmlConstants#XML_POLICIES} containing - * {@link XmlConstants#XML_POLICY} elements - * - * @return the {@link Map} of the policy name/class combinations from the given {@link Element} - */ - @SuppressWarnings("unchecked") - public static Map> convertToPolicyMap(Element element) { - - Map> policyMap = new HashMap>(); - - List policyElements = element.elements(XmlConstants.XML_POLICY); - for (Element policyElement : policyElements) { - String policyName = policyElement.attributeValue(XmlConstants.XML_ATTR_NAME); - String policyClass = policyElement.attributeValue(XmlConstants.XML_ATTR_CLASS); - - Class clazz; - try { - clazz = ClassHelper.loadClass(policyClass); - } catch (PrivilegeException e) { - throw new PrivilegeException("The Policy with name " + policyName + " does not exist", e); - } - - policyMap.put(policyName, clazz); - } - - return policyMap; - } -} diff --git a/src/main/java/ch/eitchnet/privilege/helper/XmlHelper.java b/src/main/java/ch/eitchnet/privilege/helper/XmlHelper.java index 900c8ca87..c3507efea 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/XmlHelper.java +++ b/src/main/java/ch/eitchnet/privilege/helper/XmlHelper.java @@ -20,24 +20,30 @@ package ch.eitchnet.privilege.helper; import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import org.dom4j.Document; -import org.dom4j.DocumentException; -import org.dom4j.DocumentFactory; -import org.dom4j.Element; -import org.dom4j.io.OutputFormat; -import org.dom4j.io.SAXReader; -import org.dom4j.io.XMLWriter; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.w3c.dom.DOMException; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; import ch.eitchnet.privilege.base.PrivilegeException; +import ch.eitchnet.utils.exceptions.XmlException; /** * Helper class for performing XML based tasks using Dom4J @@ -61,22 +67,22 @@ public class XmlHelper { * * @return a {@link Document} object containing the dom4j {@link Element}s of the XML file */ - public static Document parseDocument(File xmlFile) { + public static void parseDocument(File xmlFile, DefaultHandler xmlHandler) { try { - InputStream inStream = new FileInputStream(xmlFile); + SAXParserFactory spf = SAXParserFactory.newInstance(); - SAXReader reader = new SAXReader(); - Document document = reader.read(inStream); + SAXParser sp = spf.newSAXParser(); + XmlHelper.logger.info("Parsing XML document " + xmlFile.getAbsolutePath()); + sp.parse(xmlFile, xmlHandler); - XmlHelper.logger.info("Read XML document " + document.getRootElement().getName()); - return document; - - } catch (FileNotFoundException e) { - throw new PrivilegeException("The XML file does not exist or is not readable: " + xmlFile.getAbsolutePath()); - } catch (DocumentException e) { - throw new PrivilegeException("the XML file " + xmlFile.getAbsolutePath() + " is not parseable:", e); + } catch (ParserConfigurationException e) { + throw new PrivilegeException("Failed to initialize a SAX Parser: " + e.getLocalizedMessage(), e); + } catch (SAXException e) { + throw new PrivilegeException("The XML file " + xmlFile.getAbsolutePath() + " is not parseable:", e); + } catch (IOException e) { + throw new PrivilegeException("The XML could not be read: " + xmlFile.getAbsolutePath()); } } @@ -87,40 +93,40 @@ public class XmlHelper { * the {@link Document} to write to the file system * @param file * the {@link File} describing the path on the file system where the XML file should be written to + * + * @throws RuntimeException + * if something went wrong while creating the XML configuration, or writing the element */ - public static void writeDocument(Document document, File file) { + public static void writeDocument(Document document, File file) throws RuntimeException { - XmlHelper.logger.info("Exporting document element " + document.getName() + " to " + file.getAbsolutePath()); - - OutputStream fileOutputStream = null; + XmlHelper.logger.info("Exporting document element " + document.getNodeName() + " to " + file.getAbsolutePath()); try { - fileOutputStream = new FileOutputStream(file); - - String aEncodingScheme = document.getXMLEncoding(); - if (aEncodingScheme == null || aEncodingScheme.isEmpty()) { - aEncodingScheme = XmlHelper.DEFAULT_ENCODING; + String encoding = document.getInputEncoding(); + if (encoding == null || encoding.isEmpty()) { + encoding = XmlHelper.DEFAULT_ENCODING; } - OutputFormat outformat = OutputFormat.createPrettyPrint(); - outformat.setEncoding(aEncodingScheme); - XMLWriter writer = new XMLWriter(fileOutputStream, outformat); - writer.write(document); - writer.flush(); + + // Set up a transformer + TransformerFactory transfac = TransformerFactory.newInstance(); + Transformer transformer = transfac.newTransformer(); + transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no"); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty(OutputKeys.METHOD, "xml"); + transformer.setOutputProperty(OutputKeys.ENCODING, encoding); + transformer.setOutputProperty("{http://xml.apache.org/xalan}indent-amount", "2"); + //transformer.setOutputProperty("{http://xml.apache.org/xalan}line-separator", "\t"); + + // Transform to file + StreamResult result = new StreamResult(file); + Source xmlSource = new DOMSource(document); + transformer.transform(xmlSource, result); } catch (Exception e) { throw new PrivilegeException("Exception while exporting to file: " + e, e); - } finally { - - if (fileOutputStream != null) { - try { - fileOutputStream.close(); - } catch (IOException e) { - XmlHelper.logger.error("Could not close file output stream: " + e, e); - } - } } } @@ -131,13 +137,40 @@ public class XmlHelper { * the {@link Element} to write to the file system * @param file * the {@link File} describing the path on the file system where the XML file should be written to + * @param encoding + * encoding to use to write the file + * + * @throws RuntimeException + * if something went wrong while creating the XML configuration, or writing the element */ - public static void writeElement(Element rootElement, File file) { - - Document document = DocumentFactory.getInstance().createDocument(XmlHelper.DEFAULT_ENCODING); - document.setRootElement(rootElement); - document.setName(rootElement.getName()); + public static void writeElement(Element rootElement, File file, String encoding) throws RuntimeException { + Document document = createDocument(); + document.appendChild(rootElement); XmlHelper.writeDocument(document, file); } + + /** + * Returns a new document instance + * + * @return a new document instance + * + * @throws RuntimeException + * if something went wrong while creating the XML configuration + */ + public static Document createDocument() throws RuntimeException { + try { + + DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance(); + DocumentBuilder docBuilder = dbfac.newDocumentBuilder(); + Document document = docBuilder.newDocument(); + + return document; + + } catch (DOMException e) { + throw new XmlException("Failed to create Document: " + e.getLocalizedMessage(), e); + } catch (ParserConfigurationException e) { + throw new XmlException("Failed to create Document: " + e.getLocalizedMessage(), e); + } + } } diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java b/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java new file mode 100644 index 000000000..9fff19710 --- /dev/null +++ b/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.privilege.model.internal; + +import java.util.HashMap; +import java.util.Map; + +import ch.eitchnet.privilege.base.PrivilegeException; +import ch.eitchnet.privilege.policy.PrivilegePolicy; + +public class PrivilegeContainerModel { + + String encryptionHandlerClassName; + Map encryptionHandlerParameterMap; + String persistenceHandlerClassName; + Map persistenceHandlerParameterMap; + Map parameterMap; + + private Map> 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 name + * @param policyClass + */ + 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) { + throw new PrivilegeException("Configured Privilege Policy " + privilegeName + " with class " + + policyClassName + " could not be instantiated.", e); + } catch (IllegalAccessException e) { + throw new PrivilegeException("Configured Privilege Policy " + privilegeName + " with class " + + policyClassName + " can not be accessed.", e); + } catch (ClassNotFoundException e) { + throw new PrivilegeException("Configured Privilege Policy " + privilegeName + " with class " + + policyClassName + " does not exist.", e); + } + } + + /** + * @return the policies + */ + public Map> getPolicies() { + return this.policies; + } + + @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(); + } +} \ No newline at end of file diff --git a/src/main/java/ch/eitchnet/privilege/xml/ElementParser.java b/src/main/java/ch/eitchnet/privilege/xml/ElementParser.java new file mode 100644 index 000000000..35e75f251 --- /dev/null +++ b/src/main/java/ch/eitchnet/privilege/xml/ElementParser.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.privilege.xml; + +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; + +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/src/main/java/ch/eitchnet/privilege/xml/ElementParserAdapter.java b/src/main/java/ch/eitchnet/privilege/xml/ElementParserAdapter.java new file mode 100644 index 000000000..a881bc2b1 --- /dev/null +++ b/src/main/java/ch/eitchnet/privilege/xml/ElementParserAdapter.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +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/src/main/java/ch/eitchnet/privilege/xml/InitializationHelper.java b/src/main/java/ch/eitchnet/privilege/xml/InitializationHelper.java new file mode 100644 index 000000000..edc7ec37b --- /dev/null +++ b/src/main/java/ch/eitchnet/privilege/xml/InitializationHelper.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2010 - 2012 + * + * This file is part of Privilege. + * + * Privilege is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privilege is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Privilege. If not, see . + * + */ +package ch.eitchnet.privilege.xml; + +import java.io.File; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +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.helper.ClassHelper; +import ch.eitchnet.privilege.helper.XmlHelper; +import ch.eitchnet.privilege.model.internal.PrivilegeContainerModel; +import ch.eitchnet.privilege.policy.PrivilegePolicy; + +/** + * This class implements the initializing of the {@link PrivilegeHandler} by loading an XML file containing the + * configuration + * + * @author Robert von Burg + */ +public class InitializationHelper { + + private static final Logger logger = LoggerFactory.getLogger(InitializationHelper.class); + + /** + * 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 {@link PrivilegeHandler} instance loaded from the configuration file + */ + public static PrivilegeHandler initializeFromXml(File privilegeXmlFile) { + + // make sure file exists + if (!privilegeXmlFile.exists()) { + throw new PrivilegeException("Privilege file does not exist at path " + privilegeXmlFile.getAbsolutePath()); + } + + // parse configuration file + PrivilegeContainerModel containerModel = new PrivilegeContainerModel(); + PrivilegeConfigSaxReader xmlHandler = new PrivilegeConfigSaxReader(containerModel); + XmlHelper.parseDocument(privilegeXmlFile, xmlHandler); + + // initialize encryption handler + String encryptionHandlerClassName = containerModel.getEncryptionHandlerClassName(); + EncryptionHandler encryptionHandler = ClassHelper.instantiateClass(encryptionHandlerClassName); + Map parameterMap = containerModel.getEncryptionHandlerParameterMap(); + try { + encryptionHandler.initialize(parameterMap); + } catch (Exception e) { + InitializationHelper.logger.error(e.getMessage(), e); + throw new PrivilegeException("EncryptionHandler " + encryptionHandlerClassName + + " could not be initialized"); + } + + // initialize persistence handler + String persistenceHandlerClassName = containerModel.getPersistenceHandlerClassName(); + PersistenceHandler persistenceHandler = ClassHelper.instantiateClass(persistenceHandlerClassName); + parameterMap = containerModel.getPersistenceHandlerParameterMap(); + try { + persistenceHandler.initialize(parameterMap); + } catch (Exception e) { + InitializationHelper.logger.error(e.getMessage(), e); + throw new PrivilegeException("PersistenceHandler " + persistenceHandlerClassName + + " could not be initialized"); + } + + // initialize privilege handler + DefaultPrivilegeHandler privilegeHandler = new DefaultPrivilegeHandler(); + parameterMap = containerModel.getParameterMap(); + Map> policyMap = containerModel.getPolicies(); + try { + privilegeHandler.initialize(parameterMap, encryptionHandler, persistenceHandler, policyMap); + } catch (Exception e) { + InitializationHelper.logger.error(e.getMessage(), e); + throw new PrivilegeException("PrivilegeHandler " + privilegeHandler.getClass().getName() + + " could not be initialized"); + } + + return privilegeHandler; + } +} diff --git a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigDomWriter.java b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigDomWriter.java new file mode 100644 index 000000000..4826da315 --- /dev/null +++ b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigDomWriter.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +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.helper.XmlHelper; +import ch.eitchnet.privilege.model.internal.PrivilegeContainerModel; +import ch.eitchnet.privilege.policy.PrivilegePolicy; + +/** + * @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/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java new file mode 100644 index 000000000..95fb910d1 --- /dev/null +++ b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.privilege.xml; + +import java.util.HashMap; +import java.util.Map; +import java.util.Stack; + +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +import ch.eitchnet.privilege.model.internal.PrivilegeContainerModel; + +/** + * @author Robert von Burg + * + */ +public class PrivilegeConfigSaxReader extends DefaultHandler { + + // private static final Logger logger = LoggerFactory.getLogger(PrivilegeConfigSaxReader.class); + + private Stack buildersStack = new Stack(); + + private PrivilegeContainerModel containerModel; + + public PrivilegeConfigSaxReader(PrivilegeContainerModel containerModel) { + this.containerModel = containerModel; + } + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + + if (qName.equals("Container")) { + this.buildersStack.add(new ContainerParser()); + } else if (qName.equals("Parameters")) { + this.buildersStack.add(new ParametersParser()); + } else if (qName.equals("Policies")) { + this.buildersStack.add(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("Container")) { + elementParser = this.buildersStack.pop(); + } else if (qName.equals("Parameters")) { + elementParser = this.buildersStack.pop(); + } else if (qName.equals("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("Container")) { + this.currentElement = qName; + } else if (qName.equals("EncryptionHandler")) { + this.currentElement = qName; + PrivilegeConfigSaxReader.this.containerModel + .setEncryptionHandlerClassName(attributes.getValue("class")); + } else if (qName.equals("PersistenceHandler")) { + this.currentElement = qName; + PrivilegeConfigSaxReader.this.containerModel.setPersistenceHandlerClassName(attributes + .getValue("class")); + } + } + + @Override + public void notifyChild(ElementParser child) { + if (!(child instanceof ParametersParser)) + return; + + ParametersParser parametersChild = (ParametersParser) child; + + if (this.currentElement.equals("Container")) { + PrivilegeConfigSaxReader.this.containerModel.setParameterMap(parametersChild.getParameterMap()); + } else if (this.currentElement.equals("EncryptionHandler")) { + PrivilegeConfigSaxReader.this.containerModel.setEncryptionHandlerParameterMap(parametersChild + .getParameterMap()); + } else if (this.currentElement.equals("PersistenceHandler")) { + PrivilegeConfigSaxReader.this.containerModel.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("Parameter")) { + String key = attributes.getValue("name"); + String value = attributes.getValue("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("Policy")) { + String policyName = attributes.getValue("name"); + String policyClassName = attributes.getValue("class"); + + PrivilegeConfigSaxReader.this.containerModel.addPolicy(policyName, policyClassName); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelDomWriter.java b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelDomWriter.java new file mode 100644 index 000000000..e740dce35 --- /dev/null +++ b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelDomWriter.java @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +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.helper.XmlHelper; +import ch.eitchnet.privilege.model.internal.Privilege; +import ch.eitchnet.privilege.model.internal.Role; +import ch.eitchnet.privilege.model.internal.User; + +/** + * @author Robert von Burg + * + */ +public class PrivilegeModelDomWriter { + + private List users; + private List roles; + private File modelFile; + + /** + * + */ + public PrivilegeModelDomWriter(List users, List roles, File modelFile) { + this.users = users; + this.roles = roles; + this.modelFile = modelFile; + } + + public void write() { + + // create document root + Document doc = XmlHelper.createDocument(); + Element rootElement = doc.createElement(XmlConstants.XML_ROOT_PRIVILEGE_USERS_AND_ROLES); + doc.appendChild(rootElement); + + Element usersElement = doc.createElement(XmlConstants.XML_USERS); + rootElement.appendChild(usersElement); + + for (User user : this.users) { + + // create the user element + Element userElement = doc.createElement(XmlConstants.XML_USER); + usersElement.appendChild(userElement); + + userElement.setAttribute(XmlConstants.XML_ATTR_USER_ID, user.getUserId()); + userElement.setAttribute(XmlConstants.XML_ATTR_USERNAME, user.getUsername()); + userElement.setAttribute(XmlConstants.XML_ATTR_PASSWORD, user.getPassword()); + + // add first name element + Element firstnameElement = doc.createElement(XmlConstants.XML_FIRSTNAME); + firstnameElement.setTextContent(user.getFirstname()); + userElement.appendChild(firstnameElement); + + // add surname element + Element surnameElement = doc.createElement(XmlConstants.XML_SURNAME); + surnameElement.setTextContent(user.getSurname()); + userElement.appendChild(surnameElement); + + // 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 + Element parametersElement = doc.createElement(XmlConstants.XML_PARAMETERS); + userElement.appendChild(parametersElement); + for (Entry entry : user.getProperties().entrySet()) { + Element paramElement = doc.createElement(XmlConstants.XML_PARAMETER); + paramElement.setAttribute(XmlConstants.XML_ATTR_NAME, entry.getKey()); + paramElement.setAttribute(XmlConstants.XML_ATTR_VALUE, entry.getValue()); + parametersElement.appendChild(paramElement); + } + } + + Element rolesElement = doc.createElement(XmlConstants.XML_ROLES); + rootElement.appendChild(rolesElement); + + for (Role role : this.roles) { + + // create the role element + Element roleElement = doc.createElement(XmlConstants.XML_ROLE); + rolesElement.appendChild(roleElement); + + roleElement.setAttribute(XmlConstants.XML_ATTR_NAME, role.getName()); + + for (Privilege privilege : role.getPrivilegeMap().values()) { + + // 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 + 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/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java new file mode 100644 index 000000000..c79378489 --- /dev/null +++ b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java @@ -0,0 +1,347 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.privilege.xml; + +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 java.util.Stack; + +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.model.UserState; +import ch.eitchnet.privilege.model.internal.Privilege; +import ch.eitchnet.privilege.model.internal.Role; +import ch.eitchnet.privilege.model.internal.User; +import ch.eitchnet.utils.helper.StringHelper; + +/** + * @author Robert von Burg + * + */ +public class PrivilegeModelSaxReader extends DefaultHandler { + + private static final Logger logger = LoggerFactory.getLogger(PrivilegeModelSaxReader.class); + + private Stack buildersStack = new Stack(); + + private List users; + private List roles; + + private boolean insideUser; + + /** + * + */ + public PrivilegeModelSaxReader() { + this.users = new ArrayList(); + this.roles = new ArrayList(); + } + + /** + * @return the users + */ + public List getUsers() { + return this.users; + } + + /** + * @return the roles + */ + public List getRoles() { + return this.roles; + } + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + + if (qName.equals("Users")) { + this.buildersStack.add(new UserParser()); + this.insideUser = true; + } else if (qName.equals("Properties")) { + this.buildersStack.add(new PropertyParser()); + } else if (qName.equals("Roles") && !this.insideUser) { + this.buildersStack.add(new RoleParser()); + } + + 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("Users")) { + elementParser = this.buildersStack.pop(); + this.insideUser = false; + PrivilegeModelSaxReader.logger.info("Popping for Users"); + } else if (qName.equals("Properties")) { + elementParser = this.buildersStack.pop(); + PrivilegeModelSaxReader.logger.info("Popping for Properties"); + } else if (qName.equals("Roles") && !this.insideUser) { + elementParser = this.buildersStack.pop(); + PrivilegeModelSaxReader.logger.info("Popping for Roles"); + } + + 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("Role")) { + this.roleName = attributes.getValue("name"); + } else if (qName.equals("Privilege")) { + this.privilegeName = attributes.getValue("name"); + this.privilegePolicy = attributes.getValue("policy"); + } + } + + @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("AllAllowed")) { + this.allAllowed = StringHelper.parseBoolean(this.text.toString().trim()); + } else if (qName.equals("Allow")) { + this.allowList.add(this.text.toString().trim()); + } else if (qName.equals("Deny")) { + this.denyList.add(this.text.toString().trim()); + } else if (qName.equals("Privilege")) { + + Privilege privilege = new Privilege(this.privilegeName, this.privilegePolicy, this.allAllowed, + this.denyList, this.allowList); + this.privileges.put(this.privilegeName, privilege); + + } else if (qName.equals("Role")) { + + Role role = new Role(this.roleName, this.privileges); + + PrivilegeModelSaxReader.this.roles.add(role); + PrivilegeModelSaxReader.logger.info("New Role: " + role); + init(); + } + } + } + +// +// Application +// Administrator +// ENABLED +// en_GB +// +// PrivilegeAdmin +// AppUser +// +// +// +// +// +// + + public class UserParser extends ElementParserAdapter { + + StringBuilder text; + + String userId; + String username; + String password; + String firstName; + String surname; + 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("User")) { + this.userId = attributes.getValue("userId"); + this.username = attributes.getValue("username"); + this.password = attributes.getValue("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("Firstname")) { + this.firstName = this.text.toString().trim(); + } else if (qName.equals("Surname")) { + this.surname = this.text.toString().trim(); + } else if (qName.equals("State")) { + this.userState = UserState.valueOf(this.text.toString().trim()); + } else if (qName.equals("Locale")) { + this.locale = Locale.forLanguageTag(this.text.toString().trim()); + } else if (qName.equals("Role")) { + this.userRoles.add(this.text.toString().trim()); + } else if (qName.equals("User")) { + + User user = new User(this.userId, this.username, this.password, this.firstName, this.surname, + this.userState, this.userRoles, this.locale, this.parameters); + + StringBuilder builder = new StringBuilder(); + builder.append("UserParser [userId="); + builder.append(this.userId); + builder.append(", username="); + builder.append(this.username); + builder.append(", password="); + builder.append(this.password); + builder.append(", firstName="); + builder.append(this.firstName); + builder.append(", surname="); + builder.append(this.surname); + builder.append(", userState="); + builder.append(this.userState); + builder.append(", locale="); + builder.append(this.locale); + builder.append(", userRoles="); + builder.append(this.userRoles.size()); + builder.append(", parameters="); + builder.append(this.parameters.size()); + builder.append("]"); + PrivilegeModelSaxReader.logger.info(builder.toString()); + + PrivilegeModelSaxReader.this.users.add(user); + + } + } + + @Override + public void notifyChild(ElementParser child) { + if (child instanceof PropertyParser) { + this.parameters = ((PropertyParser) child).parameterMap; + } + } + } + + class PropertyParser extends ElementParserAdapter { + +// + + private Map parameterMap = new HashMap(); + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + if (qName.equals("Property")) { + String key = attributes.getValue("name"); + String value = attributes.getValue("value"); + this.parameterMap.put(key, value); + } + } + + /** + * @return the parameterMap + */ + public Map getParameterMap() { + return this.parameterMap; + } + } +} diff --git a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java index 47e2c69e7..e7854c128 100644 --- a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -37,7 +37,6 @@ import ch.eitchnet.privilege.base.AccessDeniedException; import ch.eitchnet.privilege.base.PrivilegeException; import ch.eitchnet.privilege.handler.PrivilegeHandler; import ch.eitchnet.privilege.helper.CertificateThreadLocal; -import ch.eitchnet.privilege.helper.InitializationHelper; import ch.eitchnet.privilege.model.Certificate; import ch.eitchnet.privilege.model.PrivilegeRep; import ch.eitchnet.privilege.model.Restrictable; @@ -47,6 +46,7 @@ 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.privilege.xml.InitializationHelper; import ch.eitchnet.utils.helper.FileHelper; /** diff --git a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java new file mode 100644 index 000000000..3b8de44f0 --- /dev/null +++ b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java @@ -0,0 +1,219 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +package ch.eitchnet.privilege.test; + +import java.io.File; +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 junit.framework.Assert; + +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.XmlPersistenceHandler; +import ch.eitchnet.privilege.helper.XmlHelper; +import ch.eitchnet.privilege.model.UserState; +import ch.eitchnet.privilege.model.internal.Privilege; +import ch.eitchnet.privilege.model.internal.PrivilegeContainerModel; +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.PrivilegeModelDomWriter; +import ch.eitchnet.privilege.xml.PrivilegeModelSaxReader; +import ch.eitchnet.utils.helper.FileHelper; +import ch.eitchnet.utils.helper.StringHelper; + +/** + * @author Robert von Burg + * + */ +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 { + + File tmpDir = new File("target/test"); + if (tmpDir.exists()) + FileHelper.deleteFile(tmpDir, false); + tmpDir.mkdirs(); + } + + @AfterClass + public static void destroy() 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/PrivilegeModelTest.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/Privilege.xml"); + XmlHelper.parseDocument(xmlFile, saxReader); + logger.info(containerModel.toString()); + + // assert all objects read + Assert.assertNotNull(containerModel.getParameterMap()); + Assert.assertNotNull(containerModel.getPolicies()); + Assert.assertNotNull(containerModel.getEncryptionHandlerClassName()); + Assert.assertNotNull(containerModel.getEncryptionHandlerParameterMap()); + Assert.assertNotNull(containerModel.getPersistenceHandlerClassName()); + Assert.assertNotNull(containerModel.getPersistenceHandlerParameterMap()); + + Assert.assertEquals(1, containerModel.getParameterMap().size()); + Assert.assertEquals(1, containerModel.getPolicies().size()); + Assert.assertEquals(1, containerModel.getEncryptionHandlerParameterMap().size()); + Assert.assertEquals(2, 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(); + + // 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.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)); + Assert.assertEquals("22d4ba39605d49c758184d9bd63beae5ccf8786f3dabbab45cd9f59c2afbcbd0", fileHash); + } + + @Test + public void canReadModel() { + + PrivilegeModelSaxReader xmlHandler = new PrivilegeModelSaxReader(); + File xmlFile = new File("config/PrivilegeModel.xml"); + XmlHelper.parseDocument(xmlFile, xmlHandler); + + List users = xmlHandler.getUsers(); + Assert.assertNotNull(users); + List roles = xmlHandler.getRoles(); + Assert.assertNotNull(roles); + + Assert.assertEquals(2, users.size()); + Assert.assertEquals(4, roles.size()); + + // TODO extend assertions to actual model + } + + @Test + public void canWriteModel() { + + Map propertyMap; + Set userRoles; + Map privilegeMap; + + List users = new ArrayList(); + propertyMap = new HashMap(); + propertyMap.put("prop1", "value1"); + userRoles = new HashSet(); + userRoles.add("role1"); + users.add(new User("1", "user1", "blabla", "Bob", "White", UserState.DISABLED, userRoles, Locale.ENGLISH, + propertyMap)); + + propertyMap = new HashMap(); + propertyMap.put("prop2", "value2"); + userRoles = new HashSet(); + userRoles.add("role2"); + users.add(new User("2", "user2", "haha", "Leonard", "Sheldon", UserState.ENABLED, userRoles, Locale.ENGLISH, + propertyMap)); + + List roles = new ArrayList(); + privilegeMap = new HashMap(); + privilegeMap.put("priv1", new Privilege("priv1", "DefaultPrivilege", true, null, null)); + roles.add(new Role("role1", privilegeMap)); + + privilegeMap = new HashMap(); + Set denyList = new HashSet(); + denyList.add("myself"); + Set allowList = new HashSet(); + allowList.add("other"); + privilegeMap.put("priv2", new Privilege("priv2", "DefaultPrivilege", false, denyList, allowList)); + roles.add(new Role("role2", privilegeMap)); + + File modelFile = new File("./target/test/PrivilegeModelTest.xml"); + PrivilegeModelDomWriter configSaxWriter = new PrivilegeModelDomWriter(users, roles, modelFile); + configSaxWriter.write(); + + String fileHash = StringHelper.getHexString(FileHelper.hashFileSha256(modelFile)); + Assert.assertEquals("8e1e82278162f21b1654c2e059570bbcb3cb63b053c1dd784bc8e225e8cfd04f", fileHash); + } +} From 5a9104ebbb39d93d8ad2594719582708d4abd64d Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 20 Jan 2013 22:21:14 +0100 Subject: [PATCH 087/173] Fixed failing tests due to a problem in Java 7 changing the byte code compilation sequence, thus the tests must have better sequencing --- .../handler/DefaultPrivilegeHandler.java | 10 +- .../privilege/test/PrivilegeTest.java | 525 +++++++++--------- 2 files changed, 274 insertions(+), 261 deletions(-) diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 4c45f060f..8aa236f38 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -420,7 +420,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // validate that role exists if (getRole(roleName) == null) { - throw new PrivilegeException("Role " + roleName + " doest not exist!"); + throw new PrivilegeException("Role " + roleName + " does not exist!"); } // create new user @@ -680,9 +680,9 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // create certificate Certificate certificate; try { - // username must be at least 3 characters in length - if (username == null || username.length() < 3) - throw new PrivilegeException("The given username is shorter than 3 characters"); + // username must be at least 2 characters in length + if (username == null || username.length() < 2) + throw new PrivilegeException("The given username '" + username + "' is shorter than 2 characters"); // and validate the password validatePassword(password); @@ -699,7 +699,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // validate password String pwHash = user.getPassword(); if (pwHash == null) - throw new AccessDeniedException("User has no password and may not login!"); + throw new AccessDeniedException("User " + username + " has no password and may not login!"); if (!pwHash.equals(passwordHash)) throw new AccessDeniedException("Password is incorrect for " + username); diff --git a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java index 47e2c69e7..7df2aba2f 100644 --- a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -65,6 +65,7 @@ public class PrivilegeTest { private static final String SYSTEM_USER_ADMIN = "system_admin"; private static final byte[] PASS_BOB = "admin1".getBytes(); private static final String ROLE_APP_USER = "AppUser"; + 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(); @@ -144,6 +145,12 @@ public class PrivilegeTest { } } + private byte[] copyBytes(byte[] bytes) { + byte[] copy = new byte[bytes.length]; + System.arraycopy(bytes, 0, copy, 0, bytes.length); + return copy; + } + /** * @throws Exception * if something goes wrong @@ -157,12 +164,6 @@ public class PrivilegeTest { PrivilegeTest.privilegeHandler.invalidateSession(certificate); } - private byte[] copyBytes(byte[] bytes) { - byte[] copy = new byte[bytes.length]; - System.arraycopy(bytes, 0, copy, 0, bytes.length); - return copy; - } - /** * @throws Exception * if something goes wrong @@ -193,209 +194,18 @@ public class PrivilegeTest { * if something goes wrong */ @Test - public void testAddUserBobAsAdmin() throws Exception { - - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, - copyBytes(PrivilegeTest.PASS_ADMIN)); - - // let's add a new user bob - UserRep userRep = new UserRep("1", PrivilegeTest.BOB, "Bob", "Newman", UserState.NEW, new HashSet(), - null, new HashMap()); - PrivilegeTest.privilegeHandler.addOrReplaceUser(certificate, userRep, null); - PrivilegeTest.logger.info("Added user " + PrivilegeTest.BOB); - - // set bob's password - PrivilegeTest.privilegeHandler.setUserPassword(certificate, PrivilegeTest.BOB, - copyBytes(PrivilegeTest.PASS_BOB)); - PrivilegeTest.logger.info("Set Bob's password"); - privilegeHandler.persist(certificate); - PrivilegeTest.privilegeHandler.invalidateSession(certificate); - } - - /** - * Will fail because user bob is not yet enabled - * - * @throws Exception - * if something goes wrong - */ - @Test(expected = AccessDeniedException.class) - public void testFailAuthAsBob() throws Exception { - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, - copyBytes(PrivilegeTest.PASS_BOB)); - PrivilegeTest.privilegeHandler.invalidateSession(certificate); - } - - /** - * @throws Exception - * if something goes wrong - */ - @Test - public void testEnableUserBob() throws Exception { - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, - copyBytes(PrivilegeTest.PASS_ADMIN)); - PrivilegeTest.privilegeHandler.setUserState(certificate, PrivilegeTest.BOB, UserState.ENABLED); - privilegeHandler.persist(certificate); - PrivilegeTest.privilegeHandler.invalidateSession(certificate); - } - - /** - * Will fail as user bob has no role - * - * @throws Exception - * if something goes wrong - */ - @Test(expected = PrivilegeException.class) - public void testFailAuthUserBob() throws Exception { - - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, - copyBytes(PrivilegeTest.PASS_BOB)); - Assert.assertTrue("Certificate is null!", certificate != null); - PrivilegeTest.privilegeHandler.invalidateSession(certificate); - } - - /** - * @throws Exception - * if something goes wrong - */ - @Test - public void testAddRole() throws Exception { + public void testAddRoleTemp() throws Exception { Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, copyBytes(PrivilegeTest.PASS_ADMIN)); Map privilegeMap = new HashMap(); - RoleRep roleRep = new RoleRep(PrivilegeTest.ROLE_USER, privilegeMap); + RoleRep roleRep = new RoleRep(PrivilegeTest.ROLE_TEMP, privilegeMap); PrivilegeTest.privilegeHandler.addOrReplaceRole(certificate, roleRep); privilegeHandler.persist(certificate); PrivilegeTest.privilegeHandler.invalidateSession(certificate); } - /** - * @throws Exception - * if something goes wrong - */ - @Test - public void testAddRoleToBob() throws Exception { - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, - copyBytes(PrivilegeTest.PASS_ADMIN)); - PrivilegeTest.privilegeHandler.addRoleToUser(certificate, PrivilegeTest.BOB, PrivilegeTest.ROLE_USER); - privilegeHandler.persist(certificate); - PrivilegeTest.privilegeHandler.invalidateSession(certificate); - } - - /** - * @throws Exception - * if something goes wrong - */ - @Test - public void testAuthAsBob() throws Exception { - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, - copyBytes(PrivilegeTest.PASS_BOB)); - PrivilegeTest.privilegeHandler.invalidateSession(certificate); - } - - /** - * Will fail because user bob does not have admin rights - * - * @throws Exception - * if something goes wrong - */ - @Test(expected = AccessDeniedException.class) - public void testFailAddUserTedAsBob() throws Exception { - - // auth as Bog - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, - copyBytes(PrivilegeTest.PASS_BOB)); - Assert.assertTrue("Certificate is null!", certificate != null); - - // let's add a new user Ted - UserRep userRep = new UserRep("1", PrivilegeTest.TED, "Ted", "And then Some", UserState.NEW, - new HashSet(), null, new HashMap()); - PrivilegeTest.privilegeHandler.addOrReplaceUser(certificate, userRep, null); - PrivilegeTest.logger.info("Added user " + PrivilegeTest.TED); - PrivilegeTest.privilegeHandler.invalidateSession(certificate); - } - - /** - * @throws Exception - * if something goes wrong - */ - @Test - public void testAddAdminRoleToBob() throws Exception { - - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, - copyBytes(PrivilegeTest.PASS_ADMIN)); - PrivilegeTest.privilegeHandler.addRoleToUser(certificate, PrivilegeTest.BOB, - PrivilegeHandler.PRIVILEGE_ADMIN_ROLE); - PrivilegeTest.logger.info("Added " + PrivilegeHandler.PRIVILEGE_ADMIN_ROLE + " to " + PrivilegeTest.ADMIN); - privilegeHandler.persist(certificate); - PrivilegeTest.privilegeHandler.invalidateSession(certificate); - } - - /** - * @throws Exception - * if something goes wrong - */ - @Test - public void testAddUserTedAsBob() throws Exception { - - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, - copyBytes(PrivilegeTest.PASS_BOB)); - Assert.assertTrue("Certificate is null!", certificate != null); - - // let's add a new user ted - HashSet roles = new HashSet(); - roles.add(PrivilegeTest.ROLE_USER); - UserRep userRep = new UserRep("2", PrivilegeTest.TED, "Ted", "Newman", UserState.ENABLED, roles, null, - new HashMap()); - PrivilegeTest.privilegeHandler.addOrReplaceUser(certificate, userRep, null); - PrivilegeTest.logger.info("Added user " + PrivilegeTest.TED); - privilegeHandler.persist(certificate); - PrivilegeTest.privilegeHandler.invalidateSession(certificate); - } - - /** - * @throws Exception - * if something goes wrong - */ - @Test - public void testSetTedPwdAsBob() throws Exception { - - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, - copyBytes(PrivilegeTest.PASS_BOB)); - Assert.assertTrue("Certificate is null!", certificate != null); - - // set ted's password to default - PrivilegeTest.privilegeHandler.setUserPassword(certificate, PrivilegeTest.TED, - copyBytes(PrivilegeTest.PASS_DEF)); - privilegeHandler.persist(certificate); - PrivilegeTest.privilegeHandler.invalidateSession(certificate); - } - - /** - * @throws Exception - * if something goes wrong - */ - @Test - public void testTedChangesOwnPwd() throws Exception { - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.TED, - copyBytes(PrivilegeTest.PASS_DEF)); - PrivilegeTest.privilegeHandler.setUserPassword(certificate, PrivilegeTest.TED, - copyBytes(PrivilegeTest.PASS_TED)); - PrivilegeTest.privilegeHandler.invalidateSession(certificate); - } - - /** - * @throws Exception - * if something goes wrong - */ - @Test - public void testAuthAsTed() throws Exception { - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.TED, - copyBytes(PrivilegeTest.PASS_TED)); - PrivilegeTest.privilegeHandler.invalidateSession(certificate); - } - /** * @throws Exception * if something goes wrong @@ -413,63 +223,6 @@ public class PrivilegeTest { PrivilegeTest.privilegeHandler.invalidateSession(certificate); } - /** - * Tests if the user bob, who does not have AppUser role can perform restrictable - * - * @throws Exception - * if something goes wrong - */ - @Test(expected = AccessDeniedException.class) - public void testFailPerformRestrictableAsBob() throws Exception { - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, - copyBytes(PrivilegeTest.PASS_BOB)); - Assert.assertTrue("Certificate is null!", certificate != null); - - // see if bob can perform restrictable - Restrictable restrictable = new TestRestrictable(); - try { - PrivilegeTest.privilegeHandler.actionAllowed(certificate, restrictable); - } finally { - PrivilegeTest.privilegeHandler.invalidateSession(certificate); - } - } - - /** - * @throws Exception - * if something goes wrong - */ - @Test - public void testAddAppRoleToBob() throws Exception { - - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, - copyBytes(PrivilegeTest.PASS_ADMIN)); - PrivilegeTest.privilegeHandler.addRoleToUser(certificate, PrivilegeTest.BOB, PrivilegeTest.ROLE_APP_USER); - PrivilegeTest.logger.info("Added " + PrivilegeTest.ROLE_APP_USER + " to " + PrivilegeTest.BOB); - privilegeHandler.persist(certificate); - PrivilegeTest.privilegeHandler.invalidateSession(certificate); - } - - /** - * Tests if the user bob, who now has AppUser role can perform restrictable - * - * @throws Exception - * if something goes wrong - */ - @Test - public void testPerformRestrictableAsBob() throws Exception { - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, - copyBytes(PrivilegeTest.PASS_BOB)); - Assert.assertTrue("Certificate is null!", certificate != null); - - // see if bob can perform restrictable - Restrictable restrictable = new TestRestrictable(); - try { - PrivilegeTest.privilegeHandler.actionAllowed(certificate, restrictable); - } finally { - PrivilegeTest.privilegeHandler.invalidateSession(certificate); - } - } - /** * Tests if an action can be performed as a system user * @@ -533,4 +286,264 @@ public class PrivilegeTest { PrivilegeTest.privilegeHandler.invalidateSession(certificate); } } + + /** + * 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
    • + *
    + * + * @throws Exception + * if something goes wrong + */ + @Test + public void testUserStory() throws Exception { + + addBobAsAdmin(); + failAuthAsBobNotEnabled(); + enableBob(); + failAuthAsBobNoRole(); + addRoleUser(); + addRoleUserToBob(); + authAsBob(); + failAddTedAsBobNotAdmin(); + addRoleAdminToBob(); + addTedAsBob(); + failAuthAsTedNoPass(); + setPassForTedAsBob(); + tedChangesOwnPass(); + authAsTed(); + failPerformRestrictableAsBobNoRoleApp(); + addRoleAppToBob(); + performRestrictableAsBob(); + } + + private void performRestrictableAsBob() { + Certificate certificate; + // testPerformRestrictableAsBob + // Tests if the user bob, who now has AppUser role can perform restrictable + certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); + Assert.assertTrue("Certificate is null!", certificate != null); + // see if bob can perform restrictable + Restrictable restrictable = new TestRestrictable(); + PrivilegeTest.privilegeHandler.actionAllowed(certificate, restrictable); + PrivilegeTest.privilegeHandler.invalidateSession(certificate); + } + + private void addRoleAppToBob() { + Certificate certificate; + // testAddAppRoleToBob + certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, + copyBytes(PrivilegeTest.PASS_ADMIN)); + PrivilegeTest.privilegeHandler.addRoleToUser(certificate, PrivilegeTest.BOB, PrivilegeTest.ROLE_APP_USER); + PrivilegeTest.logger.info("Added " + PrivilegeTest.ROLE_APP_USER + " to " + PrivilegeTest.BOB); + privilegeHandler.persist(certificate); + PrivilegeTest.privilegeHandler.invalidateSession(certificate); + } + + private void failPerformRestrictableAsBobNoRoleApp() { + Certificate certificate; + // 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 + certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); + Assert.assertTrue("Certificate is null!", certificate != null); + // see if bob can perform restrictable + Restrictable restrictable = new TestRestrictable(); + try { + PrivilegeTest.privilegeHandler.actionAllowed(certificate, restrictable); + Assert.fail("Should fail as bob does not have role app"); + } catch (AccessDeniedException e) { + String msg = "User bob does not have Privilege ch.eitchnet.privilege.test.model.TestRestrictable needed for Restrictable ch.eitchnet.privilege.test.model.TestRestrictable"; + Assert.assertEquals(msg, e.getLocalizedMessage()); + } finally { + PrivilegeTest.privilegeHandler.invalidateSession(certificate); + } + } + + private void authAsTed() { + Certificate certificate; + // testAuthAsTed + certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.TED, copyBytes(PrivilegeTest.PASS_TED)); + PrivilegeTest.privilegeHandler.invalidateSession(certificate); + } + + private void tedChangesOwnPass() { + Certificate certificate; + // testTedChangesOwnPwd + certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.TED, copyBytes(PrivilegeTest.PASS_DEF)); + PrivilegeTest.privilegeHandler.setUserPassword(certificate, PrivilegeTest.TED, + copyBytes(PrivilegeTest.PASS_TED)); + PrivilegeTest.privilegeHandler.invalidateSession(certificate); + } + + private void setPassForTedAsBob() { + Certificate certificate; + // testSetTedPwdAsBob + certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); + Assert.assertTrue("Certificate is null!", certificate != null); + // set ted's password to default + PrivilegeTest.privilegeHandler.setUserPassword(certificate, PrivilegeTest.TED, + copyBytes(PrivilegeTest.PASS_DEF)); + privilegeHandler.persist(certificate); + PrivilegeTest.privilegeHandler.invalidateSession(certificate); + } + + private void failAuthAsTedNoPass() { + // testFailAuthAsTedNoPass + // Will fail because user ted has no password + try { + PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.TED, copyBytes(PrivilegeTest.PASS_TED)); + org.junit.Assert.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!"; + Assert.assertEquals(msg, e.getMessage()); + } + } + + private void addTedAsBob() { + Certificate certificate; + UserRep userRep; + // testAddUserTedAsBob + certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); + Assert.assertTrue("Certificate is null!", certificate != null); + // let's add a new user ted + HashSet roles = new HashSet(); + roles.add(PrivilegeTest.ROLE_USER); + userRep = new UserRep("2", PrivilegeTest.TED, "Ted", "Newman", UserState.ENABLED, roles, null, + new HashMap()); + PrivilegeTest.privilegeHandler.addOrReplaceUser(certificate, userRep, null); + PrivilegeTest.logger.info("Added user " + PrivilegeTest.TED); + privilegeHandler.persist(certificate); + PrivilegeTest.privilegeHandler.invalidateSession(certificate); + } + + private void addRoleAdminToBob() { + Certificate certificate; + // testAddAdminRoleToBob + certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, + copyBytes(PrivilegeTest.PASS_ADMIN)); + PrivilegeTest.privilegeHandler.addRoleToUser(certificate, PrivilegeTest.BOB, + PrivilegeHandler.PRIVILEGE_ADMIN_ROLE); + PrivilegeTest.logger.info("Added " + PrivilegeHandler.PRIVILEGE_ADMIN_ROLE + " to " + PrivilegeTest.ADMIN); + privilegeHandler.persist(certificate); + PrivilegeTest.privilegeHandler.invalidateSession(certificate); + } + + private void failAddTedAsBobNotAdmin() { + Certificate certificate; + UserRep userRep; + // testFailAddUserTedAsBob + // Will fail because user bob does not have admin rights + // auth as Bob + certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); + // let's add a new user Ted + userRep = new UserRep("1", PrivilegeTest.TED, "Ted", "And then Some", UserState.NEW, new HashSet(), + null, new HashMap()); + try { + PrivilegeTest.privilegeHandler.addOrReplaceUser(certificate, userRep, null); + Assert.fail("User bob may not add a user as bob does not have admin rights!"); + } catch (PrivilegeException e) { + String msg = "User does not have PrivilegeAdmin role! Certificate: " + certificate.toString(); + Assert.assertEquals(msg, e.getMessage()); + } finally { + PrivilegeTest.privilegeHandler.invalidateSession(certificate); + } + } + + private void authAsBob() { + Certificate certificate; + // testAuthAsBob + certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); + PrivilegeTest.privilegeHandler.invalidateSession(certificate); + } + + private void addRoleUserToBob() { + Certificate certificate; + // testAddRoleUserToBob + certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, + copyBytes(PrivilegeTest.PASS_ADMIN)); + PrivilegeTest.privilegeHandler.addRoleToUser(certificate, PrivilegeTest.BOB, PrivilegeTest.ROLE_USER); + privilegeHandler.persist(certificate); + PrivilegeTest.privilegeHandler.invalidateSession(certificate); + } + + private void addRoleUser() { + Certificate certificate; + // add role user + certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, + copyBytes(PrivilegeTest.PASS_ADMIN)); + Map privilegeMap = new HashMap(); + RoleRep roleRep = new RoleRep(PrivilegeTest.ROLE_USER, privilegeMap); + PrivilegeTest.privilegeHandler.addOrReplaceRole(certificate, roleRep); + privilegeHandler.persist(certificate); + PrivilegeTest.privilegeHandler.invalidateSession(certificate); + } + + private void failAuthAsBobNoRole() { + // testFailAuthUserBob + // Will fail as user bob has no role + try { + PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); + org.junit.Assert.fail("User Bob may not authenticate because the user has no role"); + } catch (PrivilegeException e) { + String msg = "User bob does not have any roles defined!"; + Assert.assertEquals(msg, e.getMessage()); + } + } + + private void enableBob() { + Certificate certificate; + // testEnableUserBob + certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, + copyBytes(PrivilegeTest.PASS_ADMIN)); + PrivilegeTest.privilegeHandler.setUserState(certificate, PrivilegeTest.BOB, UserState.ENABLED); + privilegeHandler.persist(certificate); + PrivilegeTest.privilegeHandler.invalidateSession(certificate); + } + + private void failAuthAsBobNotEnabled() { + // testFailAuthAsBob + // Will fail because user bob is not yet enabled + try { + PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); + org.junit.Assert.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!"; + Assert.assertEquals(msg, e.getMessage()); + } + } + + private void addBobAsAdmin() { + Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, + copyBytes(PrivilegeTest.PASS_ADMIN)); + + // let's add a new user bob + UserRep userRep = new UserRep("1", PrivilegeTest.BOB, "Bob", "Newman", UserState.NEW, new HashSet(), + null, new HashMap()); + PrivilegeTest.privilegeHandler.addOrReplaceUser(certificate, userRep, null); + PrivilegeTest.logger.info("Added user " + PrivilegeTest.BOB); + + // set bob's password + PrivilegeTest.privilegeHandler.setUserPassword(certificate, PrivilegeTest.BOB, + copyBytes(PrivilegeTest.PASS_BOB)); + PrivilegeTest.logger.info("Set Bob's password"); + privilegeHandler.persist(certificate); + PrivilegeTest.privilegeHandler.invalidateSession(certificate); + } } From 7814b844a1adab13023900e93d67f5187613a478 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 20 Jan 2013 22:34:46 +0100 Subject: [PATCH 088/173] fixed an NPE when a user has no parameters during SAX parsing --- .../xml/PrivilegeModelSaxReader.java | 23 ------------------- 1 file changed, 23 deletions(-) diff --git a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java index c79378489..28c956700 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java +++ b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java @@ -287,30 +287,7 @@ public class PrivilegeModelSaxReader extends DefaultHandler { User user = new User(this.userId, this.username, this.password, this.firstName, this.surname, this.userState, this.userRoles, this.locale, this.parameters); - StringBuilder builder = new StringBuilder(); - builder.append("UserParser [userId="); - builder.append(this.userId); - builder.append(", username="); - builder.append(this.username); - builder.append(", password="); - builder.append(this.password); - builder.append(", firstName="); - builder.append(this.firstName); - builder.append(", surname="); - builder.append(this.surname); - builder.append(", userState="); - builder.append(this.userState); - builder.append(", locale="); - builder.append(this.locale); - builder.append(", userRoles="); - builder.append(this.userRoles.size()); - builder.append(", parameters="); - builder.append(this.parameters.size()); - builder.append("]"); - PrivilegeModelSaxReader.logger.info(builder.toString()); - PrivilegeModelSaxReader.this.users.add(user); - } } From b5b9465dbe2c061bc83e451db42a20adbb4f1c3a Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 20 Jan 2013 22:35:59 +0100 Subject: [PATCH 089/173] fixed compilation warning --- .../java/ch/eitchnet/privilege/helper/PasswordCreaterUI.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ch/eitchnet/privilege/helper/PasswordCreaterUI.java b/src/main/java/ch/eitchnet/privilege/helper/PasswordCreaterUI.java index f2c4231c8..122ba4011 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/PasswordCreaterUI.java +++ b/src/main/java/ch/eitchnet/privilege/helper/PasswordCreaterUI.java @@ -62,7 +62,7 @@ public class PasswordCreaterUI { 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); + final JComboBox digestCombo = new JComboBox(digests); digestCombo.setSelectedIndex(3); final JPasswordField passwordField = new JPasswordField(); final JTextField hashField = new JTextField(150); From f67fbe4edbb38d9ba0dd5d3bffeca98aad048650 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 20 Jan 2013 22:44:25 +0100 Subject: [PATCH 090/173] fixed XXX in copyright headers --- .../ch/eitchnet/privilege/helper/PasswordCreator.java | 2 +- .../privilege/model/internal/PrivilegeContainerModel.java | 8 ++++---- .../java/ch/eitchnet/privilege/xml/ElementParser.java | 8 ++++---- .../ch/eitchnet/privilege/xml/ElementParserAdapter.java | 8 ++++---- .../eitchnet/privilege/xml/PrivilegeConfigDomWriter.java | 8 ++++---- .../eitchnet/privilege/xml/PrivilegeConfigSaxReader.java | 8 ++++---- .../eitchnet/privilege/xml/PrivilegeModelDomWriter.java | 8 ++++---- .../eitchnet/privilege/xml/PrivilegeModelSaxReader.java | 8 ++++---- src/test/java/ch/eitchnet/privilege/test/XmlTest.java | 8 ++++---- 9 files changed, 33 insertions(+), 33 deletions(-) diff --git a/src/main/java/ch/eitchnet/privilege/helper/PasswordCreator.java b/src/main/java/ch/eitchnet/privilege/helper/PasswordCreator.java index 228cd529c..8430e9be0 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/PasswordCreator.java +++ b/src/main/java/ch/eitchnet/privilege/helper/PasswordCreator.java @@ -29,7 +29,7 @@ import java.security.MessageDigest; *

    * *

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

    * * @author Robert von Burg diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java b/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java index 9fff19710..a38ba337b 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java @@ -3,20 +3,20 @@ * * All rights reserved. * - * This file is part of the XXX. + * This file is part of the Privilege. * - * XXX is free software: you can redistribute + * Privilege is free software: you can redistribute * it and/or modify it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 3 of the License, * or (at your option) any later version. * - * XXX is distributed in the hope that it will + * Privilege is distributed in the hope that it will * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with XXX. If not, see + * along with Privilege. If not, see * . */ package ch.eitchnet.privilege.model.internal; diff --git a/src/main/java/ch/eitchnet/privilege/xml/ElementParser.java b/src/main/java/ch/eitchnet/privilege/xml/ElementParser.java index 35e75f251..0504a64b9 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/ElementParser.java +++ b/src/main/java/ch/eitchnet/privilege/xml/ElementParser.java @@ -3,20 +3,20 @@ * * All rights reserved. * - * This file is part of the XXX. + * This file is part of the Privilege. * - * XXX is free software: you can redistribute + * Privilege is free software: you can redistribute * it and/or modify it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 3 of the License, * or (at your option) any later version. * - * XXX is distributed in the hope that it will + * Privilege is distributed in the hope that it will * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with XXX. If not, see + * along with Privilege. If not, see * . */ package ch.eitchnet.privilege.xml; diff --git a/src/main/java/ch/eitchnet/privilege/xml/ElementParserAdapter.java b/src/main/java/ch/eitchnet/privilege/xml/ElementParserAdapter.java index a881bc2b1..5f90bb59c 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/ElementParserAdapter.java +++ b/src/main/java/ch/eitchnet/privilege/xml/ElementParserAdapter.java @@ -3,20 +3,20 @@ * * All rights reserved. * - * This file is part of the XXX. + * This file is part of the Privilege. * - * XXX is free software: you can redistribute + * Privilege is free software: you can redistribute * it and/or modify it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 3 of the License, * or (at your option) any later version. * - * XXX is distributed in the hope that it will + * Privilege is distributed in the hope that it will * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with XXX. If not, see + * along with Privilege. If not, see * . */ package ch.eitchnet.privilege.xml; diff --git a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigDomWriter.java b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigDomWriter.java index 4826da315..fab6e260e 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigDomWriter.java +++ b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigDomWriter.java @@ -3,20 +3,20 @@ * * All rights reserved. * - * This file is part of the XXX. + * This file is part of the Privilege. * - * XXX is free software: you can redistribute + * Privilege is free software: you can redistribute * it and/or modify it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 3 of the License, * or (at your option) any later version. * - * XXX is distributed in the hope that it will + * Privilege is distributed in the hope that it will * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with XXX. If not, see + * along with Privilege. If not, see * . */ package ch.eitchnet.privilege.xml; diff --git a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java index 95fb910d1..d8c8b12e7 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java +++ b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java @@ -3,20 +3,20 @@ * * All rights reserved. * - * This file is part of the XXX. + * This file is part of the Privilege. * - * XXX is free software: you can redistribute + * Privilege is free software: you can redistribute * it and/or modify it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 3 of the License, * or (at your option) any later version. * - * XXX is distributed in the hope that it will + * Privilege is distributed in the hope that it will * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with XXX. If not, see + * along with Privilege. If not, see * . */ package ch.eitchnet.privilege.xml; diff --git a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelDomWriter.java b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelDomWriter.java index e740dce35..8f136f2ea 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelDomWriter.java +++ b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelDomWriter.java @@ -3,20 +3,20 @@ * * All rights reserved. * - * This file is part of the XXX. + * This file is part of the Privilege. * - * XXX is free software: you can redistribute + * Privilege is free software: you can redistribute * it and/or modify it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 3 of the License, * or (at your option) any later version. * - * XXX is distributed in the hope that it will + * Privilege is distributed in the hope that it will * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with XXX. If not, see + * along with Privilege. If not, see * . */ package ch.eitchnet.privilege.xml; diff --git a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java index 28c956700..fa85e9843 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java +++ b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java @@ -3,20 +3,20 @@ * * All rights reserved. * - * This file is part of the XXX. + * This file is part of the Privilege. * - * XXX is free software: you can redistribute + * Privilege is free software: you can redistribute * it and/or modify it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 3 of the License, * or (at your option) any later version. * - * XXX is distributed in the hope that it will + * Privilege is distributed in the hope that it will * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with XXX. If not, see + * along with Privilege. If not, see * . */ package ch.eitchnet.privilege.xml; diff --git a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java index 3b8de44f0..b730d1370 100644 --- a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java @@ -3,20 +3,20 @@ * * All rights reserved. * - * This file is part of the XXX. + * This file is part of the Privilege. * - * XXX is free software: you can redistribute + * Privilege is free software: you can redistribute * it and/or modify it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 3 of the License, * or (at your option) any later version. * - * XXX is distributed in the hope that it will + * Privilege is distributed in the hope that it will * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with XXX. If not, see + * along with Privilege. If not, see * . */ package ch.eitchnet.privilege.test; From e2ebcd1fc6d3f8bf77f5c4ab05e261e510216d43 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 20 Jan 2013 22:50:03 +0100 Subject: [PATCH 091/173] Fixed some unnecessary TODOs --- .../handler/DefaultPrivilegeHandler.java | 1 - .../privilege/model/internal/User.java | 69 +++++++++---------- .../privilege/policy/DefaultPrivilege.java | 2 - 3 files changed, 32 insertions(+), 40 deletions(-) diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 8aa236f38..e7eab35f1 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -345,7 +345,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } // create new user - // XXX should the collections be recreated and the getRoles() and getProperties() methods be removed? User user = new User(userRep.getUserId(), userRep.getUsername(), passwordHash, userRep.getFirstname(), userRep.getSurname(), userRep.getUserState(), userRep.getRoles(), userRep.getLocale(), userRep.getProperties()); diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/User.java b/src/main/java/ch/eitchnet/privilege/model/internal/User.java index 0405ecec3..0428adf5f 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/User.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/User.java @@ -118,7 +118,7 @@ public final class User { if (roles == null) this.roles = Collections.emptySet(); else - this.roles = Collections.unmodifiableSet(roles); + this.roles = Collections.unmodifiableSet(new HashSet(roles)); if (locale == null) this.locale = Locale.getDefault(); @@ -128,7 +128,7 @@ public final class User { if (propertyMap == null) this.propertyMap = Collections.emptyMap(); else - this.propertyMap = Collections.unmodifiableMap(propertyMap); + this.propertyMap = Collections.unmodifiableMap(new HashMap(propertyMap)); } /** @@ -151,11 +151,6 @@ public final class User { * @return the hashed password for this {@link User} */ public String getPassword() { - - // TODO is it possible that there is a hidden way of accessing this - // field even though? The User object should be private, but maybe I - // forgot something? - return this.password; } @@ -206,6 +201,36 @@ public final class User { 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 */ @@ -268,34 +293,4 @@ public final class User { return false; return true; } - - /** - * 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; - } } diff --git a/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java b/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java index 3db6be69b..f2e0115b0 100644 --- a/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java +++ b/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java @@ -26,8 +26,6 @@ import ch.eitchnet.privilege.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.Role; /** - * XXX re-think this implementation... - * * @author Robert von Burg */ public class DefaultPrivilege implements PrivilegePolicy { From dfbae9b6cb1d7cee3136367fc1986070d69b0a3d Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 20 Jan 2013 22:53:58 +0100 Subject: [PATCH 092/173] moved XML schemas to src/main/resoures/ --- src/{test => main}/resources/Privilege.xsd | 0 src/{test => main}/resources/PrivilegeModel.xsd | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename src/{test => main}/resources/Privilege.xsd (100%) rename src/{test => main}/resources/PrivilegeModel.xsd (100%) diff --git a/src/test/resources/Privilege.xsd b/src/main/resources/Privilege.xsd similarity index 100% rename from src/test/resources/Privilege.xsd rename to src/main/resources/Privilege.xsd diff --git a/src/test/resources/PrivilegeModel.xsd b/src/main/resources/PrivilegeModel.xsd similarity index 100% rename from src/test/resources/PrivilegeModel.xsd rename to src/main/resources/PrivilegeModel.xsd From f92afab67181a406b6ce73607bb0cea55422e840 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 20 Jan 2013 23:21:56 +0100 Subject: [PATCH 093/173] Removed local XmlHelper and moved it to ch.eitchnet.utils --- .../handler/XmlPersistenceHandler.java | 2 +- .../eitchnet/privilege/helper/XmlHelper.java | 176 ------------------ .../privilege/xml/InitializationHelper.java | 2 +- .../xml/PrivilegeConfigDomWriter.java | 2 +- .../xml/PrivilegeModelDomWriter.java | 2 +- .../ch/eitchnet/privilege/test/XmlTest.java | 2 +- 6 files changed, 5 insertions(+), 181 deletions(-) delete mode 100644 src/main/java/ch/eitchnet/privilege/helper/XmlHelper.java diff --git a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java index 60d5828c7..b40820caa 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java @@ -31,11 +31,11 @@ import org.slf4j.LoggerFactory; import ch.eitchnet.privilege.base.PrivilegeException; import ch.eitchnet.privilege.helper.XmlConstants; -import ch.eitchnet.privilege.helper.XmlHelper; import ch.eitchnet.privilege.model.internal.Role; import ch.eitchnet.privilege.model.internal.User; import ch.eitchnet.privilege.xml.PrivilegeModelDomWriter; import ch.eitchnet.privilege.xml.PrivilegeModelSaxReader; +import ch.eitchnet.utils.helper.XmlHelper; /** * {@link PersistenceHandler} implementation which reads the configuration from XML files. These configuration is passed diff --git a/src/main/java/ch/eitchnet/privilege/helper/XmlHelper.java b/src/main/java/ch/eitchnet/privilege/helper/XmlHelper.java deleted file mode 100644 index c3507efea..000000000 --- a/src/main/java/ch/eitchnet/privilege/helper/XmlHelper.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright (c) 2010 - 2012 - * - * This file is part of Privilege. - * - * Privilege is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . - * - */ -package ch.eitchnet.privilege.helper; - -import java.io.File; -import java.io.IOException; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.parsers.SAXParser; -import javax.xml.parsers.SAXParserFactory; -import javax.xml.transform.OutputKeys; -import javax.xml.transform.Source; -import javax.xml.transform.Transformer; -import javax.xml.transform.TransformerFactory; -import javax.xml.transform.dom.DOMSource; -import javax.xml.transform.stream.StreamResult; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.w3c.dom.DOMException; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.xml.sax.SAXException; -import org.xml.sax.helpers.DefaultHandler; - -import ch.eitchnet.privilege.base.PrivilegeException; -import ch.eitchnet.utils.exceptions.XmlException; - -/** - * Helper class for performing XML based tasks using Dom4J - * - * @author Robert von Burg - */ -public class XmlHelper { - - /** - * DEFAULT_ENCODING = "UTF-8" : defines the default UTF-8 encoding expected of XML files - */ - public static final String DEFAULT_ENCODING = "UTF-8"; - - private static final Logger logger = LoggerFactory.getLogger(XmlHelper.class); - - /** - * Parses an XML file on the file system using dom4j and returns the resulting {@link Document} object - * - * @param xmlFile - * the {@link File} which has the path to the XML file to read - * - * @return a {@link Document} object containing the dom4j {@link Element}s of the XML file - */ - public static void parseDocument(File xmlFile, DefaultHandler xmlHandler) { - - try { - - SAXParserFactory spf = SAXParserFactory.newInstance(); - - SAXParser sp = spf.newSAXParser(); - XmlHelper.logger.info("Parsing XML document " + xmlFile.getAbsolutePath()); - sp.parse(xmlFile, xmlHandler); - - } catch (ParserConfigurationException e) { - throw new PrivilegeException("Failed to initialize a SAX Parser: " + e.getLocalizedMessage(), e); - } catch (SAXException e) { - throw new PrivilegeException("The XML file " + xmlFile.getAbsolutePath() + " is not parseable:", e); - } catch (IOException e) { - throw new PrivilegeException("The XML could not be read: " + xmlFile.getAbsolutePath()); - } - } - - /** - * Writes a dom4j {@link Document} to an XML file on the file system - * - * @param document - * the {@link Document} to write to the file system - * @param file - * the {@link File} describing the path on the file system where the XML file should be written to - * - * @throws RuntimeException - * if something went wrong while creating the XML configuration, or writing the element - */ - public static void writeDocument(Document document, File file) throws RuntimeException { - - XmlHelper.logger.info("Exporting document element " + document.getNodeName() + " to " + file.getAbsolutePath()); - - try { - - String encoding = document.getInputEncoding(); - if (encoding == null || encoding.isEmpty()) { - encoding = XmlHelper.DEFAULT_ENCODING; - } - - // Set up a transformer - TransformerFactory transfac = TransformerFactory.newInstance(); - Transformer transformer = transfac.newTransformer(); - transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no"); - transformer.setOutputProperty(OutputKeys.INDENT, "yes"); - transformer.setOutputProperty(OutputKeys.METHOD, "xml"); - transformer.setOutputProperty(OutputKeys.ENCODING, encoding); - transformer.setOutputProperty("{http://xml.apache.org/xalan}indent-amount", "2"); - //transformer.setOutputProperty("{http://xml.apache.org/xalan}line-separator", "\t"); - - // Transform to file - StreamResult result = new StreamResult(file); - Source xmlSource = new DOMSource(document); - transformer.transform(xmlSource, result); - - } catch (Exception e) { - - throw new PrivilegeException("Exception while exporting to file: " + e, e); - - } - } - - /** - * Writes a dom4j {@link Element} to an XML file on the file system - * - * @param rootElement - * the {@link Element} to write to the file system - * @param file - * the {@link File} describing the path on the file system where the XML file should be written to - * @param encoding - * encoding to use to write the file - * - * @throws RuntimeException - * if something went wrong while creating the XML configuration, or writing the element - */ - public static void writeElement(Element rootElement, File file, String encoding) throws RuntimeException { - - Document document = createDocument(); - document.appendChild(rootElement); - XmlHelper.writeDocument(document, file); - } - - /** - * Returns a new document instance - * - * @return a new document instance - * - * @throws RuntimeException - * if something went wrong while creating the XML configuration - */ - public static Document createDocument() throws RuntimeException { - try { - - DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance(); - DocumentBuilder docBuilder = dbfac.newDocumentBuilder(); - Document document = docBuilder.newDocument(); - - return document; - - } catch (DOMException e) { - throw new XmlException("Failed to create Document: " + e.getLocalizedMessage(), e); - } catch (ParserConfigurationException e) { - throw new XmlException("Failed to create Document: " + e.getLocalizedMessage(), e); - } - } -} diff --git a/src/main/java/ch/eitchnet/privilege/xml/InitializationHelper.java b/src/main/java/ch/eitchnet/privilege/xml/InitializationHelper.java index edc7ec37b..e5d1a1a24 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/InitializationHelper.java +++ b/src/main/java/ch/eitchnet/privilege/xml/InitializationHelper.java @@ -31,9 +31,9 @@ import ch.eitchnet.privilege.handler.EncryptionHandler; import ch.eitchnet.privilege.handler.PersistenceHandler; import ch.eitchnet.privilege.handler.PrivilegeHandler; import ch.eitchnet.privilege.helper.ClassHelper; -import ch.eitchnet.privilege.helper.XmlHelper; import ch.eitchnet.privilege.model.internal.PrivilegeContainerModel; import ch.eitchnet.privilege.policy.PrivilegePolicy; +import ch.eitchnet.utils.helper.XmlHelper; /** * This class implements the initializing of the {@link PrivilegeHandler} by loading an XML file containing the diff --git a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigDomWriter.java b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigDomWriter.java index fab6e260e..fae22d9f0 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigDomWriter.java +++ b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigDomWriter.java @@ -28,9 +28,9 @@ import org.w3c.dom.Document; import org.w3c.dom.Element; import ch.eitchnet.privilege.helper.XmlConstants; -import ch.eitchnet.privilege.helper.XmlHelper; import ch.eitchnet.privilege.model.internal.PrivilegeContainerModel; import ch.eitchnet.privilege.policy.PrivilegePolicy; +import ch.eitchnet.utils.helper.XmlHelper; /** * @author Robert von Burg diff --git a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelDomWriter.java b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelDomWriter.java index 8f136f2ea..18b0e7de7 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelDomWriter.java +++ b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelDomWriter.java @@ -29,10 +29,10 @@ import org.w3c.dom.Document; import org.w3c.dom.Element; import ch.eitchnet.privilege.helper.XmlConstants; -import ch.eitchnet.privilege.helper.XmlHelper; import ch.eitchnet.privilege.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.Role; import ch.eitchnet.privilege.model.internal.User; +import ch.eitchnet.utils.helper.XmlHelper; /** * @author Robert von Burg diff --git a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java index b730d1370..ec247fa45 100644 --- a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java @@ -40,7 +40,6 @@ import org.slf4j.LoggerFactory; import ch.eitchnet.privilege.handler.DefaultEncryptionHandler; import ch.eitchnet.privilege.handler.XmlPersistenceHandler; -import ch.eitchnet.privilege.helper.XmlHelper; import ch.eitchnet.privilege.model.UserState; import ch.eitchnet.privilege.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.PrivilegeContainerModel; @@ -52,6 +51,7 @@ import ch.eitchnet.privilege.xml.PrivilegeModelDomWriter; import ch.eitchnet.privilege.xml.PrivilegeModelSaxReader; import ch.eitchnet.utils.helper.FileHelper; import ch.eitchnet.utils.helper.StringHelper; +import ch.eitchnet.utils.helper.XmlHelper; /** * @author Robert von Burg From 08c70f5ec07fa69b6eab3d45dbcb138ec8a11c51 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Wed, 30 Jan 2013 15:02:17 +0100 Subject: [PATCH 094/173] Update pom.xml Added distribution management to deploy to nexus.eitchnet.ch --- pom.xml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/pom.xml b/pom.xml index 2776e6b91..bddc78dba 100644 --- a/pom.xml +++ b/pom.xml @@ -73,6 +73,19 @@ codehausSnapshots Codehaus Snapshots http://snapshots.maven.codehaus.org/maven2 default ... --> + + + + deployment + Internal Releases + http://nexus.eitchnet.ch/content/repositories/releases/ + + + deployment + Internal Releases + http://nexus.eitchnet.ch/content/repositories/snapshots/ + + From acef67a99f7e82886b45af1d0fa8f93150d981ce Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 31 Jan 2013 19:07:37 +0100 Subject: [PATCH 095/173] [Minor] code cleanup commented out unneeded code --- .../privilege/helper/BootstrapConfigurationHelper.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java b/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java index 56970e63b..31935e3ab 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java +++ b/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java @@ -52,10 +52,9 @@ public class BootstrapConfigurationHelper { 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 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"; From 17525a41d5f7a0a44c0867d22a0ca59ef0ad410e Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 17 Mar 2013 12:08:12 +0100 Subject: [PATCH 096/173] [Bugfix] fixed failing test due to uppercase/lowercase change of BASE16 --- src/test/java/ch/eitchnet/privilege/test/XmlTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java index ec247fa45..da7e2a1b5 100644 --- a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java @@ -153,7 +153,7 @@ public class XmlTest { configSaxWriter.write(); String fileHash = StringHelper.getHexString(FileHelper.hashFileSha256(configFile)); - Assert.assertEquals("22d4ba39605d49c758184d9bd63beae5ccf8786f3dabbab45cd9f59c2afbcbd0", fileHash); + Assert.assertEquals("22D4BA39605D49C758184D9BD63BEAE5CCF8786F3DABBAB45CD9F59C2AFBCBD0", fileHash); } @Test @@ -214,6 +214,6 @@ public class XmlTest { configSaxWriter.write(); String fileHash = StringHelper.getHexString(FileHelper.hashFileSha256(modelFile)); - Assert.assertEquals("8e1e82278162f21b1654c2e059570bbcb3cb63b053c1dd784bc8e225e8cfd04f", fileHash); + Assert.assertEquals("8E1E82278162F21B1654C2E059570BBCB3CB63B053C1DD784BC8E225E8CFD04F", fileHash); } } From 44cb14803602a1c4a8481c0907aae627ba6cba42 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 24 Mar 2013 12:23:29 +0100 Subject: [PATCH 097/173] [Minor] code cleanup - cleaned up JavaDoc for some classes - changed equals() to not use instanceOf but class==class - set some fields private which were package scope --- .../privilege/model/internal/Privilege.java | 9 +---- .../internal/PrivilegeContainerModel.java | 23 ++++++++--- .../privilege/model/internal/Role.java | 8 +--- .../privilege/model/internal/Session.java | 39 ++++++++----------- .../privilege/model/internal/User.java | 8 +--- 5 files changed, 40 insertions(+), 47 deletions(-) diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/Privilege.java b/src/main/java/ch/eitchnet/privilege/model/internal/Privilege.java index 55c4fe1a5..2bb456c0b 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/Privilege.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/Privilege.java @@ -166,6 +166,8 @@ public final class Privilege { } /** + * Returns a string representation of this object displaying its concrete type and its values + * * @see java.lang.Object#toString() */ @Override @@ -179,9 +181,6 @@ public final class Privilege { return builder.toString(); } - /** - * @see java.lang.Object#hashCode() - */ @Override public int hashCode() { final int prime = 31; @@ -190,9 +189,6 @@ public final class Privilege { return result; } - /** - * @see java.lang.Object#equals(java.lang.Object) - */ @Override public boolean equals(Object obj) { if (this == obj) @@ -209,5 +205,4 @@ public final class Privilege { return false; return true; } - } diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java b/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java index a38ba337b..c8e44343b 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java @@ -29,13 +29,19 @@ import ch.eitchnet.privilege.policy.PrivilegePolicy; public class PrivilegeContainerModel { - String encryptionHandlerClassName; - Map encryptionHandlerParameterMap; - String persistenceHandlerClassName; - Map persistenceHandlerParameterMap; - Map parameterMap; + private String encryptionHandlerClassName; + private Map encryptionHandlerParameterMap; + private String persistenceHandlerClassName; + private Map persistenceHandlerParameterMap; + private Map parameterMap; + private Map> policies; - private Map> policies = new HashMap>(); + /** + * Default constructor + */ + public PrivilegeContainerModel() { + this.policies = new HashMap>(); + } /** * @return the parameterMap @@ -146,6 +152,11 @@ public class PrivilegeContainerModel { return this.policies; } + /** + * Returns a string representation of this object displaying its concrete type and its values + * + * @see java.lang.Object#toString() + */ @Override public String toString() { StringBuilder builder = new StringBuilder(); diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/Role.java b/src/main/java/ch/eitchnet/privilege/model/internal/Role.java index d6825717b..70cd2b11d 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/Role.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/Role.java @@ -133,6 +133,8 @@ public final class Role { } /** + * Returns a string representation of this object displaying its concrete type and its values + * * @see java.lang.Object#toString() */ @Override @@ -146,9 +148,6 @@ public final class Role { return builder.toString(); } - /** - * @see java.lang.Object#hashCode() - */ @Override public int hashCode() { final int prime = 31; @@ -157,9 +156,6 @@ public final class Role { return result; } - /** - * @see java.lang.Object#equals(java.lang.Object) - */ @Override public boolean equals(Object obj) { if (this == obj) diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/Session.java b/src/main/java/ch/eitchnet/privilege/model/internal/Session.java index 4abcd08d5..2ab1e45df 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/Session.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/Session.java @@ -126,8 +126,23 @@ public final class Session { } /** - * @see java.lang.Object#hashCode() + * Returns a string representation of this object displaying its concrete type and its values + * + * @see java.lang.Object#toString() */ + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("Session [sessionId="); + builder.append(this.sessionId); + builder.append(", username="); + builder.append(this.username); + builder.append(", loginTime="); + builder.append(this.loginTime); + builder.append("]"); + return builder.toString(); + } + @Override public int hashCode() { final int prime = 31; @@ -140,16 +155,13 @@ public final class Session { return result; } - /** - * @see java.lang.Object#equals(java.lang.Object) - */ @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; - if (!(obj instanceof Session)) + if (getClass() != obj.getClass()) return false; Session other = (Session) obj; if (this.authPassword == null) { @@ -176,21 +188,4 @@ public final class Session { return false; return true; } - - /** - * @see java.lang.Object#toString() - */ - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append("Session [sessionId="); - builder.append(this.sessionId); - builder.append(", username="); - builder.append(this.username); - builder.append(", loginTime="); - builder.append(this.loginTime); - builder.append("]"); - return builder.toString(); - } - } diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/User.java b/src/main/java/ch/eitchnet/privilege/model/internal/User.java index 0428adf5f..eb50d4a58 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/User.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/User.java @@ -240,6 +240,8 @@ public final class User { } /** + * Returns a string representation of this object displaying its concrete type and its values + * * @see java.lang.Object#toString() */ @Override @@ -263,9 +265,6 @@ public final class User { return builder.toString(); } - /** - * @see java.lang.Object#hashCode() - */ @Override public int hashCode() { final int prime = 31; @@ -274,9 +273,6 @@ public final class User { return result; } - /** - * @see java.lang.Object#equals(java.lang.Object) - */ @Override public boolean equals(Object obj) { if (this == obj) From 9261494667608d36ebae64e80d33e3fefc0d985c Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Tue, 26 Mar 2013 19:42:17 +0100 Subject: [PATCH 098/173] [Minor] cleaned up doc and implemented missing toString() and equals() A number classes had JavaDocs with @see parameters on overriding methods. This was changed so that they were removed, as they are not needed. In some classes the toString() methods were missing, as well as equals() and hashcode() they were now added --- .../handler/DefaultEncryptionHandler.java | 14 -- .../handler/DefaultPrivilegeHandler.java | 121 +++++------------- .../privilege/handler/EncryptionHandler.java | 1 - .../privilege/handler/PersistenceHandler.java | 1 - .../privilege/handler/SystemUserAction.java | 12 +- .../handler/XmlPersistenceHandler.java | 37 ++---- .../helper/CertificateThreadLocal.java | 8 +- .../eitchnet/privilege/model/Certificate.java | 36 +++--- .../privilege/model/PrivilegeRep.java | 47 +++++++ .../ch/eitchnet/privilege/model/RoleRep.java | 41 +++++- .../ch/eitchnet/privilege/model/UserRep.java | 51 ++++++++ .../internal/PrivilegeContainerModel.java | 7 + .../privilege/model/internal/User.java | 1 - .../privilege/policy/DefaultPrivilege.java | 5 + .../eitchnet/privilege/xml/ElementParser.java | 1 + 15 files changed, 225 insertions(+), 158 deletions(-) diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java index fb614673e..237fee200 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java @@ -44,7 +44,6 @@ import ch.eitchnet.privilege.helper.XmlConstants; * * * @author Robert von Burg - * */ public class DefaultEncryptionHandler implements EncryptionHandler { @@ -63,9 +62,6 @@ public class DefaultEncryptionHandler implements EncryptionHandler { */ private String hashAlgorithm; - /** - * @see ch.eitchnet.privilege.handler.EncryptionHandler#convertToHash(java.lang.String) - */ @Override public String convertToHash(String string) { try { @@ -79,9 +75,6 @@ public class DefaultEncryptionHandler implements EncryptionHandler { } } - /** - * @see ch.eitchnet.privilege.handler.EncryptionHandler#convertToHash(java.lang.String) - */ @Override public String convertToHash(byte[] bytes) { try { @@ -95,21 +88,14 @@ public class DefaultEncryptionHandler implements EncryptionHandler { } } - /** - * @see ch.eitchnet.privilege.handler.EncryptionHandler#nextToken() - */ @Override public String nextToken() { byte[] bytes = new byte[16]; this.secureRandom.nextBytes(bytes); String randomString = new String(bytes); - //String randomString = new BigInteger(80, secureRandom).toString(32); // 80 big integer bits = 16 chars return randomString; } - /** - * @see ch.eitchnet.privilege.handler.EncryptionHandler#initialize(java.util.Map) - */ @Override public void initialize(Map parameterMap) { diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index e7eab35f1..0235341b0 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -63,7 +63,6 @@ import ch.eitchnet.privilege.policy.PrivilegePolicy; * * * @author Robert von Burg - * */ public class DefaultPrivilegeHandler implements PrivilegeHandler { @@ -117,9 +116,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { */ private boolean autoPersistOnPasswordChange; - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#getRole(java.lang.String) - */ @Override public RoleRep getRole(String roleName) { Role role = this.persistenceHandler.getRole(roleName); @@ -128,9 +124,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { return role.asRoleRep(); } - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#getUser(java.lang.String) - */ @Override public UserRep getUser(String username) { User user = this.persistenceHandler.getUser(username); @@ -174,9 +167,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { return policy; } - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#queryUsers(ch.eitchnet.privilege.model.UserRep) - */ @Override public List queryUsers(UserRep selectorRep) { @@ -268,22 +258,30 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } /** - * @param selPropertyMap + * 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 - * @return + * 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 selPropertyMap, Map properties) { + private boolean isSelectedByProperty(Map selectionMap, Map properties) { - if (selPropertyMap == null) + if (selectionMap == null) return true; - if (selPropertyMap.isEmpty() && properties.isEmpty()) + if (selectionMap.isEmpty() && properties.isEmpty()) return true; - for (String selKey : selPropertyMap.keySet()) { + for (String selKey : selectionMap.keySet()) { String value = properties.get(selKey); - if (value == null || !value.equals(selPropertyMap.get(selKey))) + if (value == null || !value.equals(selectionMap.get(selKey))) return false; } @@ -291,22 +289,25 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } /** - * @param selRoles + * 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 - * @return + * 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 selRoles, Set roles) { + private boolean isSelectedByRole(Set selectionRoles, Set roles) { - if (selRoles == null) + if (selectionRoles == null) return true; - return roles.containsAll(selRoles); + return roles.containsAll(selectionRoles); } - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#addOrReplaceRole(ch.eitchnet.privilege.model.Certificate, - * ch.eitchnet.privilege.model.RoleRep) - */ @Override public void addOrReplaceRole(Certificate certificate, RoleRep roleRep) { @@ -323,10 +324,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { this.persistenceHandler.addOrReplaceRole(role); } - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#addOrReplaceUser(ch.eitchnet.privilege.model.Certificate, - * ch.eitchnet.privilege.model.UserRep, java.lang.String) - */ @Override public void addOrReplaceUser(Certificate certificate, UserRep userRep, byte[] password) { try { @@ -357,10 +354,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } } - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#addOrReplacePrivilegeOnRole(ch.eitchnet.privilege.model.Certificate, - * java.lang.String, ch.eitchnet.privilege.model.PrivilegeRep) - */ @Override public void addOrReplacePrivilegeOnRole(Certificate certificate, String roleName, PrivilegeRep privilegeRep) { @@ -394,10 +387,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { this.persistenceHandler.addOrReplaceRole(newRole); } - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#addRoleToUser(ch.eitchnet.privilege.model.Certificate, - * java.lang.String, java.lang.String) - */ @Override public void addRoleToUser(Certificate certificate, String username, String roleName) { @@ -433,10 +422,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { this.persistenceHandler.addOrReplaceUser(newUser); } - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#removePrivilegeFromRole(ch.eitchnet.privilege.model.Certificate, - * java.lang.String, java.lang.String) - */ @Override public void removePrivilegeFromRole(Certificate certificate, String roleName, String privilegeName) { @@ -467,10 +452,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { this.persistenceHandler.addOrReplaceRole(newRole); } - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#removeRole(ch.eitchnet.privilege.model.Certificate, - * java.lang.String) - */ @Override public RoleRep removeRole(Certificate certificate, String roleName) { @@ -487,10 +468,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { return removedRole.asRoleRep(); } - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#removeRoleFromUser(ch.eitchnet.privilege.model.Certificate, - * java.lang.String, java.lang.String) - */ @Override public void removeRoleFromUser(Certificate certificate, String username, String roleName) { @@ -520,10 +497,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { this.persistenceHandler.addOrReplaceUser(newUser); } - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#removeUser(ch.eitchnet.privilege.model.Certificate, - * java.lang.String) - */ @Override public UserRep removeUser(Certificate certificate, String username) { @@ -542,10 +515,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setUserLocale(ch.eitchnet.privilege.model.Certificate, - * java.lang.String, java.util.Locale) - */ @Override public void setUserLocale(Certificate certificate, String username, Locale locale) { @@ -566,10 +535,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { this.persistenceHandler.addOrReplaceUser(newUser); } - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setUserName(ch.eitchnet.privilege.model.Certificate, - * java.lang.String, java.lang.String, java.lang.String) - */ @Override public void setUserName(Certificate certificate, String username, String firstname, String surname) { @@ -590,10 +555,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { this.persistenceHandler.addOrReplaceUser(newUser); } - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setUserPassword(ch.eitchnet.privilege.model.Certificate, - * java.lang.String, java.lang.String) - */ @Override public void setUserPassword(Certificate certificate, String username, byte[] password) { try { @@ -643,10 +604,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } } - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setUserState(ch.eitchnet.privilege.model.Certificate, - * java.lang.String, ch.eitchnet.privilege.model.UserState) - */ @Override public void setUserState(Certificate certificate, String username, UserState state) { @@ -743,9 +700,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { return certificate; } - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#invalidateSession(ch.eitchnet.privilege.model.Certificate) - */ @Override public boolean invalidateSession(Certificate certificate) { @@ -852,7 +806,9 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { * {@link PrivilegePolicy#actionAllowed(Role, Privilege, Restrictable)} * * @param role + * the role which wants to perform the action * @param restrictable + * the {@link Restrictable} which is to be checked for authorization */ private boolean actionAllowed(Role role, Restrictable restrictable) { @@ -892,9 +848,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { return true; } - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#isCertificateValid(ch.eitchnet.privilege.model.Certificate) - */ @Override public void isCertificateValid(Certificate certificate) { @@ -931,9 +884,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // everything is ok } - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#validateIsPrivilegeAdmin(ch.eitchnet.privilege.model.Certificate) - */ @Override public void validateIsPrivilegeAdmin(Certificate certificate) throws PrivilegeException { @@ -972,9 +922,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } } - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#persist(ch.eitchnet.privilege.model.Certificate) - */ @Override public boolean persist(Certificate certificate) { @@ -1062,10 +1009,12 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { * @author Robert von Burg */ protected class CertificateSessionPair { + /** * The {@link Session} */ public final Session session; + /** * The {@link Certificate} */ @@ -1086,7 +1035,11 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } /** + * 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) { @@ -1096,10 +1049,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } } - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#runAsSystem(java.lang.String, - * ch.eitchnet.privilege.handler.SystemUserAction) - */ @Override public void runAsSystem(String systemUsername, SystemUserAction action) throws PrivilegeException { diff --git a/src/main/java/ch/eitchnet/privilege/handler/EncryptionHandler.java b/src/main/java/ch/eitchnet/privilege/handler/EncryptionHandler.java index b25dbd82a..f6b734213 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/EncryptionHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/EncryptionHandler.java @@ -26,7 +26,6 @@ import java.util.Map; * for certificates and so forth * * @author Robert von Burg - * */ public interface EncryptionHandler { diff --git a/src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java b/src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java index 498f6e8e7..557d1fc48 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java @@ -41,7 +41,6 @@ import ch.eitchnet.privilege.policy.PrivilegePolicy; *

    * * @author Robert von Burg - * */ public interface PersistenceHandler { diff --git a/src/main/java/ch/eitchnet/privilege/handler/SystemUserAction.java b/src/main/java/ch/eitchnet/privilege/handler/SystemUserAction.java index 0aac16d1d..d59aba87b 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/SystemUserAction.java +++ b/src/main/java/ch/eitchnet/privilege/handler/SystemUserAction.java @@ -22,10 +22,20 @@ package ch.eitchnet.privilege.handler; import ch.eitchnet.privilege.model.Certificate; /** - * @author Robert von Burg + * 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 interface SystemUserAction { + /** + * 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 + * + * @param certificate + * the {@link Certificate} which was generated for a valid system user + */ public void execute(Certificate certificate); } diff --git a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java index b40820caa..84ddf5435 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java @@ -58,9 +58,6 @@ public class XmlPersistenceHandler implements PersistenceHandler { private String modelPath; - /** - * @see ch.eitchnet.privilege.handler.PersistenceHandler#getAllUsers() - */ @Override public List getAllUsers() { synchronized (this.userMap) { @@ -68,9 +65,6 @@ public class XmlPersistenceHandler implements PersistenceHandler { } } - /** - * @see ch.eitchnet.privilege.handler.PersistenceHandler#getAllRoles() - */ @Override public List getAllRoles() { synchronized (this.roleMap) { @@ -78,25 +72,16 @@ public class XmlPersistenceHandler implements PersistenceHandler { } } - /** - * @see ch.eitchnet.privilege.handler.PersistenceHandler#getUser(java.lang.String) - */ @Override public User getUser(String username) { return this.userMap.get(username); } - /** - * @see ch.eitchnet.privilege.handler.PersistenceHandler#getRole(java.lang.String) - */ @Override public Role getRole(String roleName) { return this.roleMap.get(roleName); } - /** - * @see ch.eitchnet.privilege.handler.PersistenceHandler#removeUser(java.lang.String) - */ @Override public User removeUser(String username) { User user = this.userMap.remove(username); @@ -104,9 +89,6 @@ public class XmlPersistenceHandler implements PersistenceHandler { return user; } - /** - * @see ch.eitchnet.privilege.handler.PersistenceHandler#removeRole(java.lang.String) - */ @Override public Role removeRole(String roleName) { Role role = this.roleMap.remove(roleName); @@ -114,18 +96,12 @@ public class XmlPersistenceHandler implements PersistenceHandler { return role; } - /** - * @see ch.eitchnet.privilege.handler.PersistenceHandler#addOrReplaceUser(ch.eitchnet.privilege.model.internal.User) - */ @Override public void addOrReplaceUser(User user) { this.userMap.put(user.getUsername(), user); this.userMapDirty = true; } - /** - * @see ch.eitchnet.privilege.handler.PersistenceHandler#addOrReplaceRole(ch.eitchnet.privilege.model.internal.Role) - */ @Override public void addOrReplaceRole(Role role) { this.roleMap.put(role.getName(), role); @@ -133,7 +109,11 @@ public class XmlPersistenceHandler implements PersistenceHandler { } /** - * @see ch.eitchnet.privilege.handler.PersistenceHandler#initialize(java.util.Map) + * 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) { @@ -164,7 +144,10 @@ public class XmlPersistenceHandler implements PersistenceHandler { } /** - * @see ch.eitchnet.privilege.handler.PersistenceHandler#reload() + * 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() { @@ -223,7 +206,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { } /** - * @see ch.eitchnet.privilege.handler.PersistenceHandler#persist() + * Writes the model to the XML files. Where the files are written to was defined in the {@link #initialize(Map)} */ @Override public boolean persist() { diff --git a/src/main/java/ch/eitchnet/privilege/helper/CertificateThreadLocal.java b/src/main/java/ch/eitchnet/privilege/helper/CertificateThreadLocal.java index 161687258..fe75d24c0 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/CertificateThreadLocal.java +++ b/src/main/java/ch/eitchnet/privilege/helper/CertificateThreadLocal.java @@ -22,8 +22,10 @@ package ch.eitchnet.privilege.helper; import ch.eitchnet.privilege.model.Certificate; /** - * @author Robert von Burg + * This {@link ThreadLocal} holds a reference to the {@link Certificate} which allows any code segment to perform + * further authorization before executing * + * @author Robert von Burg */ public class CertificateThreadLocal extends ThreadLocal { @@ -36,11 +38,11 @@ public class CertificateThreadLocal extends ThreadLocal { return CertificateThreadLocal.instance; } - public static Certificate getCert() { + public static Certificate getCertificate() { return CertificateThreadLocal.instance.get(); } - public static void setCert(Certificate certificate) { + public static void setCertificate(Certificate certificate) { CertificateThreadLocal.instance.set(certificate); } } diff --git a/src/main/java/ch/eitchnet/privilege/model/Certificate.java b/src/main/java/ch/eitchnet/privilege/model/Certificate.java index 0d165e57c..ca7710313 100644 --- a/src/main/java/ch/eitchnet/privilege/model/Certificate.java +++ b/src/main/java/ch/eitchnet/privilege/model/Certificate.java @@ -146,8 +146,23 @@ public final class Certificate implements Serializable { } /** - * @see java.lang.Object#hashCode() + * Returns a string representation of this object displaying its concrete type and its values + * + * @see java.lang.Object#toString() */ + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("Certificate [sessionId="); + builder.append(this.sessionId); + builder.append(", username="); + builder.append(this.username); + builder.append(", locale="); + builder.append(this.locale); + builder.append("]"); + return builder.toString(); + } + @Override public int hashCode() { final int prime = 31; @@ -160,9 +175,6 @@ public final class Certificate implements Serializable { return result; } - /** - * @see java.lang.Object#equals(java.lang.Object) - */ @Override public boolean equals(Object obj) { if (this == obj) @@ -199,20 +211,4 @@ public final class Certificate implements Serializable { return false; return true; } - - /** - * @see java.lang.Object#toString() - */ - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append("Certificate [sessionId="); - builder.append(this.sessionId); - builder.append(", username="); - builder.append(this.username); - builder.append(", locale="); - builder.append(this.locale); - builder.append("]"); - return builder.toString(); - } } diff --git a/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java b/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java index ca3b596d9..c74725a80 100644 --- a/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java +++ b/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java @@ -184,4 +184,51 @@ public class PrivilegeRep implements Serializable { } } } + + /** + * Returns a string representation of this object displaying its concrete type and its values + * + * @see java.lang.Object#toString() + */ + @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/src/main/java/ch/eitchnet/privilege/model/RoleRep.java b/src/main/java/ch/eitchnet/privilege/model/RoleRep.java index f7a37a0f5..06fd4a9bb 100644 --- a/src/main/java/ch/eitchnet/privilege/model/RoleRep.java +++ b/src/main/java/ch/eitchnet/privilege/model/RoleRep.java @@ -74,10 +74,43 @@ public class RoleRep implements Serializable { } /** - * @param privilegeMap - * the privilegeMap to set + * Returns a string representation of this object displaying its concrete type and its values + * + * @see java.lang.Object#toString() */ - public void setPrivileges(Map privilegeMap) { - this.privilegeMap = privilegeMap; + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("RoleRep [name="); + builder.append(this.name); + builder.append(", privilegeMap="); + builder.append((this.privilegeMap == null ? "null" : this.privilegeMap)); + 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/src/main/java/ch/eitchnet/privilege/model/UserRep.java b/src/main/java/ch/eitchnet/privilege/model/UserRep.java index 66b27284c..01f0f786a 100644 --- a/src/main/java/ch/eitchnet/privilege/model/UserRep.java +++ b/src/main/java/ch/eitchnet/privilege/model/UserRep.java @@ -217,4 +217,55 @@ public class UserRep implements Serializable { public Map getProperties() { return this.propertyMap; } + + /** + * Returns a string representation of this object displaying its concrete type and its values + * + * @see java.lang.Object#toString() + */ + @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(", surname="); + builder.append(this.surname); + 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/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java b/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java index c8e44343b..1b1d118cf 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java @@ -25,8 +25,15 @@ 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} + * + * @author Robert von Burg + */ public class PrivilegeContainerModel { private String encryptionHandlerClassName; diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/User.java b/src/main/java/ch/eitchnet/privilege/model/internal/User.java index eb50d4a58..1cd67bc2e 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/User.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/User.java @@ -40,7 +40,6 @@ import ch.eitchnet.privilege.model.UserState; *

    * * @author Robert von Burg - * */ public final class User { diff --git a/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java b/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java index f2e0115b0..478169cdb 100644 --- a/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java +++ b/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java @@ -26,11 +26,16 @@ import ch.eitchnet.privilege.model.internal.Privilege; 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#actionAllowed(ch.eitchnet.privilege.model.internal.Role, * ch.eitchnet.privilege.model.internal.Privilege, ch.eitchnet.privilege.model.Restrictable) */ diff --git a/src/main/java/ch/eitchnet/privilege/xml/ElementParser.java b/src/main/java/ch/eitchnet/privilege/xml/ElementParser.java index 0504a64b9..82d513343 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/ElementParser.java +++ b/src/main/java/ch/eitchnet/privilege/xml/ElementParser.java @@ -24,6 +24,7 @@ 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; From 93022ba559be08c8463a6a8cf1c1be87ac505345 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Tue, 9 Apr 2013 07:33:32 +0200 Subject: [PATCH 099/173] [Major] major rewrite of the privilege validation. Now the PrivilegeContext object is central and once the user logged in, this object is bound to a ThreadLocal. From then there is no further need to interact with the PrivilegeHandler - this allows for authenticated users to get a remote copy of the PrivilegeContext so that on a remote client, the user can check for permissions, without having to do the round trip to the server. A code review of this change would be good, but preliminary tests show that it works. A test should now be implemented to check if getting a remote copy also allows for authorization. --- config/Model.xml | 55 +++ config/Privilege.xml | 2 +- config/PrivilegeModel.xml | 42 +- pom.xml | 2 +- .../handler/DefaultPrivilegeHandler.java | 392 ++++++--------- .../privilege/handler/PersistenceHandler.java | 4 +- .../privilege/handler/PrivilegeHandler.java | 63 +-- .../privilege/handler/SystemUserAction.java | 10 +- .../helper/CertificateThreadLocal.java | 48 -- .../privilege/i18n/PrivilegeMessages.java | 46 ++ .../i18n/PrivilegeMessages.properties | 6 + .../eitchnet/privilege/model/Certificate.java | 28 +- .../eitchnet/privilege/model/IPrivilege.java | 92 ++++ .../privilege/model/PrivilegeContext.java | 161 ++++++ .../privilege/model/PrivilegeRep.java | 77 ++- .../privilege/model/Restrictable.java | 7 +- .../ch/eitchnet/privilege/model/RoleRep.java | 13 + .../ch/eitchnet/privilege/model/UserRep.java | 28 ++ .../internal/PrivilegeContainerModel.java | 4 + .../{Privilege.java => PrivilegeImpl.java} | 123 +++-- .../privilege/model/internal/Role.java | 40 +- .../privilege/model/internal/Session.java | 191 -------- .../privilege/model/internal/User.java | 9 +- .../privilege/policy/DefaultPrivilege.java | 66 ++- .../privilege/policy/PrivilegePolicy.java | 15 +- .../xml/PrivilegeModelDomWriter.java | 5 +- .../xml/PrivilegeModelSaxReader.java | 9 +- .../privilege/test/PrivilegeTest.java | 463 +++++++++--------- .../ch/eitchnet/privilege/test/XmlTest.java | 18 +- .../test/model/TestSystemUserAction.java | 26 +- .../test/model/TestSystemUserActionDeny.java | 20 +- 31 files changed, 1061 insertions(+), 1004 deletions(-) create mode 100644 config/Model.xml delete mode 100644 src/main/java/ch/eitchnet/privilege/helper/CertificateThreadLocal.java create mode 100644 src/main/java/ch/eitchnet/privilege/i18n/PrivilegeMessages.java create mode 100644 src/main/java/ch/eitchnet/privilege/i18n/PrivilegeMessages.properties create mode 100644 src/main/java/ch/eitchnet/privilege/model/IPrivilege.java create mode 100644 src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java rename src/main/java/ch/eitchnet/privilege/model/internal/{Privilege.java => PrivilegeImpl.java} (63%) delete mode 100644 src/main/java/ch/eitchnet/privilege/model/internal/Session.java diff --git a/config/Model.xml b/config/Model.xml new file mode 100644 index 000000000..74dabc4f4 --- /dev/null +++ b/config/Model.xml @@ -0,0 +1,55 @@ + + + + + + + + + + true + + + + + + true + + + true + + + + + + + + + Application + Administrator + ENABLED + en_GB + + PrivilegeAdmin + AppUser + + + + + + + + + System User + Administrator + SYSTEM + en_GB + + system_admin_privileges + + + + + + + diff --git a/config/Privilege.xml b/config/Privilege.xml index b37070a5a..20eda653e 100644 --- a/config/Privilege.xml +++ b/config/Privilege.xml @@ -39,7 +39,7 @@ along with Privilege. If not, see . - + diff --git a/config/PrivilegeModel.xml b/config/PrivilegeModel.xml index 4c788b66e..a58e6830c 100644 --- a/config/PrivilegeModel.xml +++ b/config/PrivilegeModel.xml @@ -1,26 +1,10 @@ - + @@ -39,7 +23,7 @@ along with Privilege. If not, see .
    - + System User Administrator @@ -48,7 +32,7 @@ along with Privilege. If not, see . system_admin_privileges - + @@ -58,20 +42,20 @@ along with Privilege. If not, see . - + true - + - + true - + true - + hello diff --git a/pom.xml b/pom.xml index bddc78dba..2d601786a 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ ch.eitchnet ch.eitchnet.privilege jar - 0.1.0-SNAPSHOT + 0.2.0-SNAPSHOT ch.eitchnet.privilege https://github.com/eitch/ch.eitchnet.privilege diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 0235341b0..43691ca2a 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -19,6 +19,7 @@ */ package ch.eitchnet.privilege.handler; +import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -35,14 +36,14 @@ import ch.eitchnet.privilege.base.AccessDeniedException; import ch.eitchnet.privilege.base.PrivilegeException; import ch.eitchnet.privilege.helper.ClassHelper; 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.Restrictable; import ch.eitchnet.privilege.model.RoleRep; import ch.eitchnet.privilege.model.UserRep; import ch.eitchnet.privilege.model.UserState; -import ch.eitchnet.privilege.model.internal.Privilege; +import ch.eitchnet.privilege.model.internal.PrivilegeImpl; import ch.eitchnet.privilege.model.internal.Role; -import ch.eitchnet.privilege.model.internal.Session; import ch.eitchnet.privilege.model.internal.User; import ch.eitchnet.privilege.policy.PrivilegePolicy; @@ -72,7 +73,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { private static final String PARAM_AUTO_PERSIST_ON_PASSWORD_CHANGE = "autoPersistOnPasswordChange"; /** - * log4j logger + * slf4j logger */ protected static final Logger logger = LoggerFactory.getLogger(DefaultPrivilegeHandler.class); @@ -82,14 +83,9 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { private long lastSessionId; /** - * This map stores certificates for system users as a cache + * Map keeping a reference to all active sessions */ - private Map systemUserCertificateMap; - - /** - * Map keeping a reference to all active sessions with their certificates - */ - private Map sessionMap; + private Map privilegeContextMap; /** * Map of {@link PrivilegePolicy} classes @@ -132,41 +128,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { return user.asUserRep(); } - /** - *

    - * 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 = ClassHelper.instantiateClass(policyClazz); - } catch (Exception e) { - throw new PrivilegeException("The class for the policy with the name " + policyName + " does not exist!" - + policyName, e); - } - - return policy; - } - @Override public List queryUsers(UserRep selectorRep) { @@ -377,8 +338,15 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } // create new role with the additional privilege - Privilege newPrivilege = new Privilege(privilegeRep); - Map privilegeMap = new HashMap(role.getPrivilegeMap()); + IPrivilege newPrivilege = new PrivilegeImpl(privilegeRep); + // copy existing privileges + Set existingPrivilegeNames = role.getPrivilegeNames(); + Map privilegeMap = new HashMap(existingPrivilegeNames.size() + 1); + for (String name : existingPrivilegeNames) { + IPrivilege privilege = role.getPrivilege(name); + privilegeMap.put(name, privilege); + } + // add new one privilegeMap.put(newPrivilege.getName(), newPrivilege); Role newRole = new Role(role.getName(), privilegeMap); @@ -438,9 +406,11 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { if (!role.hasPrivilege(privilegeName)) throw new PrivilegeException("Role " + roleName + " does not have Privilege " + privilegeName); - // create new set of privileges with out the to remove privilege - Map newPrivileges = new HashMap(role.getPrivilegeMap().size() - 1); - for (Privilege privilege : role.getPrivilegeMap().values()) { + // create new set of privileges with out the to removed privilege + Set privilegeNames = role.getPrivilegeNames(); + Map newPrivileges = new HashMap(privilegeNames.size() - 1); + for (String name : privilegeNames) { + IPrivilege privilege = role.getPrivilege(name); if (!privilege.getName().equals(privilegeName)) newPrivileges.put(privilege.getName(), privilege); } @@ -666,7 +636,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { + " and can not login!"); // validate user has at least one role - if (user.getRoles().isEmpty()) { + Set userRoles = user.getRoles(); + if (userRoles.isEmpty()) { throw new PrivilegeException("User " + username + " does not have any roles defined!"); } @@ -678,15 +649,14 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { String sessionId = nextSessionId(); // create a new certificate, with details of the user - certificate = new Certificate(sessionId, username, authToken, authPassword, user.getLocale(), - new HashMap(user.getProperties())); + certificate = new Certificate(sessionId, System.currentTimeMillis(), username, authToken, authPassword, + user.getLocale(), new HashMap(user.getProperties())); - // create and save a new session - Session session = new Session(sessionId, username, authToken, authPassword, System.currentTimeMillis()); - this.sessionMap.put(sessionId, new CertificateSessionPair(session, certificate)); + PrivilegeContext privilegeContext = buildPrivilegeContext(certificate, user); + this.privilegeContextMap.put(sessionId, privilegeContext); // log - DefaultPrivilegeHandler.logger.info("User " + username + " authenticated: " + session); + DefaultPrivilegeHandler.logger.info("User " + username + " authenticated: " + certificate); } catch (RuntimeException e) { DefaultPrivilegeHandler.logger.error("User " + username + " Failed to authenticate: " @@ -700,6 +670,56 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { return certificate; } + /** + * 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) { + + // cache the privilege + if (privileges.containsKey(privilegeName)) + continue; + + IPrivilege privilege = role.getPrivilege(privilegeName); + if (privilege == null) { + throw new PrivilegeException(MessageFormat.format("The Privilege {0} does not exist for role {1}", + privilegeName, roleName)); + } + 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) { + throw new PrivilegeException(MessageFormat.format( + "The Policy {0} does not exist for Privilege {1}", policyName, privilegeName)); + } + policies.put(policyName, policy); + } + } + + UserRep userRep = user.asUserRep(); + PrivilegeContext privilegeContext = new PrivilegeContext(userRep, certificate, privileges, policies); + return privilegeContext; + } + @Override public boolean invalidateSession(Certificate certificate) { @@ -707,10 +727,10 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { isCertificateValid(certificate); // remove registration - CertificateSessionPair certificateSessionPair = this.sessionMap.remove(certificate.getSessionId()); + PrivilegeContext privilegeContext = this.privilegeContextMap.remove(certificate.getSessionId()); // return true if object was really removed - boolean loggedOut = certificateSessionPair != null; + boolean loggedOut = privilegeContext != null; if (loggedOut) DefaultPrivilegeHandler.logger.info("User " + certificate.getUsername() + " logged out."); else @@ -718,136 +738,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { return loggedOut; } - /** - * Checks if the action is allowed by iterating the roles of the certificates user and then delegating to - * {@link #actionAllowed(Role, Restrictable)} - * - * @throws AccessDeniedException - * if the {@link Certificate} is not for a currently logged in {@link User} or if the user may not - * perform the action defined by the {@link Restrictable} implementation - * - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#actionAllowed(ch.eitchnet.privilege.model.Certificate, - * ch.eitchnet.privilege.model.Restrictable) - */ - @Override - public void actionAllowed(Certificate certificate, Restrictable restrictable) { - - // TODO What is better, validate from {@link Restrictable} to {@link User} or the opposite direction? - - // first validate certificate - isCertificateValid(certificate); - - // restrictable must not be null - if (restrictable == null) - throw new PrivilegeException("Restrictable may not be null!"); - - // get user object - User user = this.persistenceHandler.getUser(certificate.getUsername()); - if (user == null) { - throw new PrivilegeException( - "Oh boy, how did this happen: No User in user map although the certificate is valid!"); - } - - String privilegeName = restrictable.getPrivilegeName(); - - // now iterate roles and validate on policies - for (String roleName : user.getRoles()) { - - Role role = this.persistenceHandler.getRole(roleName); - if (role == null) { - DefaultPrivilegeHandler.logger.error("No role is defined with name " + roleName - + " which is configured for user " + user); - continue; - } - - // ignore if this role does not have the privilege - if (!role.hasPrivilege(privilegeName)) - continue; - - // if action is allowed, then break iteration as a privilege match has been made - if (actionAllowed(role, restrictable)) - return; - } - - throw new AccessDeniedException("User " + user.getUsername() + " does not have Privilege " + privilegeName - + " needed for Restrictable " + restrictable.getClass().getName()); - } - - /** - * Checks if the {@link RoleRep} has access to the {@link Restrictable} by delegating to - * {@link PrivilegePolicy#actionAllowed(Role, Privilege, Restrictable)} - * - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#actionAllowed(ch.eitchnet.privilege.model.RoleRep, - * ch.eitchnet.privilege.model.Restrictable) - */ - @Override - public void actionAllowed(RoleRep roleRep, Restrictable restrictable) { - - // user and restrictable must not be null - if (roleRep == null) - throw new PrivilegeException("Role may not be null!"); - else if (restrictable == null) - throw new PrivilegeException("Restrictable may not be null!"); - - // get role for the roleRep - Role role = this.persistenceHandler.getRole(roleRep.getName()); - - // validate that the role exists - if (role == null) { - throw new PrivilegeException("No Role exists with the name " + roleRep.getName()); - } - - // delegate to method with Role - actionAllowed(role, restrictable); - } - - /** - * Checks if the {@link Role} has access to the {@link Restrictable} by delegating to - * {@link PrivilegePolicy#actionAllowed(Role, Privilege, Restrictable)} - * - * @param role - * the role which wants to perform the action - * @param restrictable - * the {@link Restrictable} which is to be checked for authorization - */ - private boolean actionAllowed(Role role, Restrictable restrictable) { - - // validate PrivilegeName for this restrictable - String privilegeName = restrictable.getPrivilegeName(); - if (privilegeName == null || privilegeName.length() < 3) { - throw new PrivilegeException( - "The PrivilegeName may not be shorter than 3 characters. Invalid Restrictable " - + restrictable.getClass().getName()); - } - - // If the role does not have this privilege, then stop as another role might have this privilege - if (!role.hasPrivilege(privilegeName)) { - return false; - } - - // get the privilege for this restrictable - Privilege privilege = role.getPrivilegeMap().get(privilegeName); - - // check if all is allowed - if (privilege.isAllAllowed()) { - return true; - } - - // otherwise delegate checking to the policy configured for this privilege - PrivilegePolicy policy = getPolicy(privilege.getPolicy()); - if (policy == null) { - throw new PrivilegeException("PrivilegePolicy " + privilege.getPolicy() + " does not exist for Privilege " - + privilegeName); - } - - // delegate checking to privilege policy - policy.actionAllowed(role, privilege, restrictable); - - // the policy must throw an exception if action not allowed, - // so return true if we arrive here - return true; - } - @Override public void isCertificateValid(Certificate certificate) { @@ -856,24 +746,17 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { throw new PrivilegeException("Certificate may not be null!"); // first see if a session exists for this certificate - CertificateSessionPair certificateSessionPair = this.sessionMap.get(certificate.getSessionId()); - if (certificateSessionPair == null) + PrivilegeContext privilegeContext = this.privilegeContextMap.get(certificate.getSessionId()); + if (privilegeContext == null) throw new AccessDeniedException("There is no session information for " + certificate.toString()); // validate certificate has not been tampered with - Certificate sessionCertificate = certificateSessionPair.certificate; + Certificate sessionCertificate = privilegeContext.getCertificate(); if (!sessionCertificate.equals(certificate)) throw new PrivilegeException("Received illegal certificate for session id " + certificate.getSessionId()); - // TODO is validating authToken overkill since the two certificates have already been checked on equality? - // validate authToken from certificate using the sessions authPassword - String authToken = certificate.getAuthToken(certificateSessionPair.session.getAuthPassword()); - if (authToken == null || !authToken.equals(certificateSessionPair.session.getAuthToken())) - throw new PrivilegeException("Received illegal certificate data for session id " - + certificate.getSessionId()); - // get user object - User user = this.persistenceHandler.getUser(certificateSessionPair.session.getUsername()); + User user = this.persistenceHandler.getUser(privilegeContext.getUsername()); // if user exists, then certificate is valid if (user == null) { @@ -884,6 +767,15 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // everything is ok } + @Override + public PrivilegeContext getPrivilegeContext(Certificate certificate) throws PrivilegeException { + + // first validate certificate + isCertificateValid(certificate); + + return this.privilegeContextMap.get(certificate.getSessionId()); + } + @Override public void validateIsPrivilegeAdmin(Certificate certificate) throws PrivilegeException { @@ -975,8 +867,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } this.lastSessionId = 0l; - this.sessionMap = Collections.synchronizedMap(new HashMap()); - this.systemUserCertificateMap = Collections.synchronizedMap(new HashMap()); + this.privilegeContextMap = Collections.synchronizedMap(new HashMap()); this.initialized = true; } @@ -987,7 +878,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { * the role for which the policies are to be checked */ private void validatePolicies(Role role) { - for (Privilege privilege : role.getPrivilegeMap().values()) { + for (String privilegeName : role.getPrivilegeNames()) { + IPrivilege privilege = role.getPrivilege(privilegeName); String policy = privilege.getPolicy(); if (policy != null && !this.policyMap.containsKey(policy)) { throw new PrivilegeException("Policy " + policy + " for Privilege " + privilege.getName() @@ -1003,37 +895,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { return Long.toString(++this.lastSessionId % Long.MAX_VALUE); } - /** - * An internal class used to keep a record of sessions with the certificate - * - * @author Robert von Burg - */ - protected class CertificateSessionPair { - - /** - * The {@link Session} - */ - public final Session session; - - /** - * The {@link Certificate} - */ - public final Certificate certificate; - - /** - * Creates a new {@link CertificateSessionPair} with the given session and certificate - * - * @param session - * the session - * @param certificate - * the certificate - */ - public CertificateSessionPair(Session session, Certificate certificate) { - this.session = session; - this.certificate = certificate; - } - } - /** * 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 @@ -1071,10 +932,10 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { checkPrivilege(actionClassname, systemUser); // get certificate for this system user - Certificate systemUserCertificate = getSystemUserCertificate(systemUsername); + PrivilegeContext systemUserPrivilegeContext = getSystemUserPrivilegeContext(systemUsername); // perform the action - action.execute(systemUserCertificate); + action.execute(systemUserPrivilegeContext); } /** @@ -1113,14 +974,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { * * @return the {@link Certificate} for this system user */ - private Certificate getSystemUserCertificate(String systemUsername) { - - // see if a certificate has already been created for this system user - Certificate systemUserCertificate = this.systemUserCertificateMap.get(systemUsername); - if (systemUserCertificate != null) - return systemUserCertificate; - - // otherwise log this system user in, by performing a slightly different authentication + private PrivilegeContext getSystemUserPrivilegeContext(String systemUsername) { // we only work with hashed passwords String passwordHash = this.encryptionHandler.convertToHash(systemUsername.getBytes()); @@ -1156,17 +1010,51 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { String sessionId = nextSessionId(); // create a new certificate, with details of the user - systemUserCertificate = new Certificate(sessionId, systemUsername, authToken, authPassword, user.getLocale(), - new HashMap(user.getProperties())); + Certificate systemUserCertificate = new Certificate(sessionId, System.currentTimeMillis(), systemUsername, + authToken, authPassword, user.getLocale(), new HashMap(user.getProperties())); - // create and save a new session - Session session = new Session(sessionId, systemUsername, authToken, authPassword, System.currentTimeMillis()); - this.sessionMap.put(sessionId, new CertificateSessionPair(session, systemUserCertificate)); + // create and save a new privilege context + PrivilegeContext privilegeContext = buildPrivilegeContext(systemUserCertificate, user); // log DefaultPrivilegeHandler.logger.info("The system user " + systemUsername + " is logged in with session " - + session); + + systemUserCertificate); - return systemUserCertificate; + 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 = ClassHelper.instantiateClass(policyClazz); + } catch (Exception e) { + throw new PrivilegeException("The class for the policy with the name " + policyName + " does not exist!" + + policyName, e); + } + + return policy; } } diff --git a/src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java b/src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java index 557d1fc48..a7dcc8b43 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java @@ -22,8 +22,8 @@ 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.Privilege; import ch.eitchnet.privilege.model.internal.Role; import ch.eitchnet.privilege.model.internal.User; import ch.eitchnet.privilege.policy.PrivilegePolicy; @@ -37,7 +37,7 @@ import ch.eitchnet.privilege.policy.PrivilegePolicy; *

    * 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 Privilege} + * and {@link IPrivilege} *

    * * @author Robert von Burg diff --git a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java index 7889905e7..ec0d5376a 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java @@ -25,14 +25,13 @@ import java.util.Locale; import ch.eitchnet.privilege.base.AccessDeniedException; 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.Restrictable; import ch.eitchnet.privilege.model.RoleRep; import ch.eitchnet.privilege.model.UserRep; import ch.eitchnet.privilege.model.UserState; -import ch.eitchnet.privilege.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.Role; -import ch.eitchnet.privilege.model.internal.Session; import ch.eitchnet.privilege.model.internal.User; /** @@ -222,7 +221,7 @@ public interface PrivilegeHandler { * @param roleName * the roleName of the {@link Role} to which the privilege should be added * @param privilegeRep - * the representation of the {@link Privilege} which should be added to the {@link Role} + * the representation of the {@link IPrivilege} which should be added to the {@link Role} * * @throws AccessDeniedException * if the user for this certificate may not perform the action @@ -335,49 +334,15 @@ public interface PrivilegeHandler { public Certificate authenticate(String username, byte[] password) throws AccessDeniedException; /** - * Invalidates the {@link Session} for the given {@link Certificate}, effectively logging out the user who was - * authenticated with the credentials associated to the given {@link Certificate} + * 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 {@link Session} is to be invalidated - * @return true if the {@link Session} was still valid and is now invalidated, false otherwise + * 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 {@link User} registered to the given {@link Certificate} is allowed to access the - * {@link Restrictable} - * - * @param certificate - * the {@link Certificate} of the user which has the privilege to perform this action - * @param restrictable - * the {@link Restrictable} to which the user wants access - * - * @throws AccessDeniedException - * if the user for this certificate may not perform the action defined by the {@link Restrictable} - * implementation - * @throws PrivilegeException - * if there is anything wrong with this certificate - */ - public void actionAllowed(Certificate certificate, Restrictable restrictable) throws AccessDeniedException, - PrivilegeException; - - /** - * Checks if the {@link RoleRep} is allowed to access the {@link Restrictable} - * - * @param roleRep - * the {@link RoleRep} for which access to the {@link Restrictable} is to be checked - * @param restrictable - * the {@link Restrictable} to which access is to be checked - * - * @throws PrivilegeException - * if the {@link Role} does not exist - * @throws AccessDeniedException - * if the role may not perform the action defined by the {@link Restrictable} implementation - */ - public void actionAllowed(RoleRep roleRep, Restrictable restrictable) throws PrivilegeException, - AccessDeniedException; - /** * 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 @@ -390,6 +355,20 @@ public interface PrivilegeHandler { */ public void isCertificateValid(Certificate certificate) 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; + /** *

    * Validates if this {@link Certificate} is for a {@link ch.eitchnet.privilege.model.internal.User} with diff --git a/src/main/java/ch/eitchnet/privilege/handler/SystemUserAction.java b/src/main/java/ch/eitchnet/privilege/handler/SystemUserAction.java index d59aba87b..724d8a32d 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/SystemUserAction.java +++ b/src/main/java/ch/eitchnet/privilege/handler/SystemUserAction.java @@ -20,6 +20,7 @@ package ch.eitchnet.privilege.handler; import ch.eitchnet.privilege.model.Certificate; +import ch.eitchnet.privilege.model.PrivilegeContext; /** * With this interface system actions, which are to be performed in an automated fashion, i.e. by cron jobs, can be @@ -34,8 +35,11 @@ public interface SystemUserAction { * 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 * - * @param certificate - * the {@link Certificate} which was generated for a valid system user + * 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 void execute(Certificate certificate); + public void execute(PrivilegeContext privilegeContext); } diff --git a/src/main/java/ch/eitchnet/privilege/helper/CertificateThreadLocal.java b/src/main/java/ch/eitchnet/privilege/helper/CertificateThreadLocal.java deleted file mode 100644 index fe75d24c0..000000000 --- a/src/main/java/ch/eitchnet/privilege/helper/CertificateThreadLocal.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2012 - * - * This file is part of ??????????????? - * - * ?????????????? is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with ????????????????. If not, see . - * - */ -package ch.eitchnet.privilege.helper; - -import ch.eitchnet.privilege.model.Certificate; - -/** - * This {@link ThreadLocal} holds a reference to the {@link Certificate} which allows any code segment to perform - * further authorization before executing - * - * @author Robert von Burg - */ -public class CertificateThreadLocal extends ThreadLocal { - - private static final CertificateThreadLocal instance; - static { - instance = new CertificateThreadLocal(); - } - - public static CertificateThreadLocal getInstance() { - return CertificateThreadLocal.instance; - } - - public static Certificate getCertificate() { - return CertificateThreadLocal.instance.get(); - } - - public static void setCertificate(Certificate certificate) { - CertificateThreadLocal.instance.set(certificate); - } -} diff --git a/src/main/java/ch/eitchnet/privilege/i18n/PrivilegeMessages.java b/src/main/java/ch/eitchnet/privilege/i18n/PrivilegeMessages.java new file mode 100644 index 000000000..c84da0b69 --- /dev/null +++ b/src/main/java/ch/eitchnet/privilege/i18n/PrivilegeMessages.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +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 = "ch.eitchnet.privilege.i18n.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/src/main/java/ch/eitchnet/privilege/i18n/PrivilegeMessages.properties b/src/main/java/ch/eitchnet/privilege/i18n/PrivilegeMessages.properties new file mode 100644 index 000000000..62f19745c --- /dev/null +++ b/src/main/java/ch/eitchnet/privilege/i18n/PrivilegeMessages.properties @@ -0,0 +1,6 @@ +Privilege.accessdenied.noprivilege=User {0} does not have Privilege {1} needed for Restrictable {2} +Privilege.illegalArgument.nonstring=\ {1} has returned a non-string privilege value\! +Privilege.illegalArgument.privilegeNameMismatch=The passed privilege has the name {0} but the restrictable is referencing privilege {1} +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\! diff --git a/src/main/java/ch/eitchnet/privilege/model/Certificate.java b/src/main/java/ch/eitchnet/privilege/model/Certificate.java index ca7710313..5b6bebfe0 100644 --- a/src/main/java/ch/eitchnet/privilege/model/Certificate.java +++ b/src/main/java/ch/eitchnet/privilege/model/Certificate.java @@ -25,7 +25,7 @@ import java.util.Map; import ch.eitchnet.privilege.base.PrivilegeException; import ch.eitchnet.privilege.handler.PrivilegeHandler; -import ch.eitchnet.privilege.model.internal.Session; +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 @@ -39,6 +39,7 @@ public final class Certificate implements Serializable { private static final long serialVersionUID = 1L; private final String sessionId; + private final long loginTime; private final String username; private final String authToken; private final String authPassword; @@ -72,15 +73,25 @@ public final class Certificate implements Serializable { * 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 authToken, String authPassword, Locale locale, - Map propertyMap) { + public Certificate(String sessionId, long loginTime, String username, String authToken, String authPassword, + Locale locale, Map propertyMap) { // validate arguments are not null - if (sessionId == null || username == null || authToken == null || authPassword == null) { - throw new PrivilegeException("One of the arguments is null!"); + if (StringHelper.isEmpty(sessionId)) { + throw new PrivilegeException("sessionId is null!"); + } + if (StringHelper.isEmpty(username)) { + throw new PrivilegeException("username is null!"); + } + if (StringHelper.isEmpty(authToken)) { + throw new PrivilegeException("authToken is null!"); + } + if (StringHelper.isEmpty(authPassword)) { + throw new PrivilegeException("authPassword is null!"); } this.sessionId = sessionId; + this.loginTime = loginTime; this.username = username; this.authToken = authToken; this.authPassword = authPassword; @@ -130,6 +141,13 @@ public final class Certificate implements Serializable { return this.username; } + /** + * @return the loginTime + */ + public long getLoginTime() { + return this.loginTime; + } + /** * Returns the authToken if the given authPassword is correct, null otherwise * diff --git a/src/main/java/ch/eitchnet/privilege/model/IPrivilege.java b/src/main/java/ch/eitchnet/privilege/model/IPrivilege.java new file mode 100644 index 000000000..f9ddbe93f --- /dev/null +++ b/src/main/java/ch/eitchnet/privilege/model/IPrivilege.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +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/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java b/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java new file mode 100644 index 000000000..661434a18 --- /dev/null +++ b/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2012, Robert von Burg + * + * All rights reserved. + * + * This file is part of the XXX. + * + * XXX is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * XXX is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XXX. If not, see + * . + */ +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(); + } + + // + // 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 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"), + getUsername(), privilegeName, restrictable.getClass().getName()); + throw new AccessDeniedException(msg); + } + + // get the policy referenced by the restrictable + String policyName = privilege.getPolicy(); + PrivilegePolicy policy = this.policies.get(policyName); + if (policy == null) { + String msg = "The PrivilegePolicy {0} does not exist on the PrivilegeContext!"; + throw new PrivilegeException(MessageFormat.format(msg, policyName)); + } + + // delegate to the policy + policy.validateAction(privilege, restrictable); + } + + // + // ThreadLocal binding + // + + private static final ThreadLocal threadLocal = new ThreadLocal(); + + /** + * Returns the currently {@link ThreadLocal} bound {@link PrivilegeContext} or throws a {@link PrivilegeException} + * if none is set + * + * @return the currently {@link ThreadLocal} bound {@link PrivilegeContext} or throws a {@link PrivilegeException} + * if none is set + * + * @throws PrivilegeException + * if no {@link PrivilegeContext} is set + */ + public static PrivilegeContext get() throws PrivilegeException { + PrivilegeContext privilegeContext = PrivilegeContext.threadLocal.get(); + if (privilegeContext == null) { + throw new PrivilegeException("There is no PrivilegeContext currently bound to the ThreadLocal!"); + } + return privilegeContext; + } + + /** + * Bind a {@link PrivilegeContext} to the {@link ThreadLocal} or throws a {@link PrivilegeException} if one is + * already bound + * + * @param privilegeContext + * the {@link PrivilegeContext} to bind + * + * @throws PrivilegeException + * if e {@link PrivilegeContext} is already bound + */ + public static void set(PrivilegeContext privilegeContext) throws PrivilegeException { + PrivilegeContext currentContext = PrivilegeContext.threadLocal.get(); + if (privilegeContext != null && currentContext != null) { + throw new PrivilegeException("There already is a PrivilegeContext bound to the ThreadLocal!"); + } + PrivilegeContext.threadLocal.set(privilegeContext); + } +} diff --git a/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java b/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java index c74725a80..b2a316c5b 100644 --- a/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java +++ b/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java @@ -24,14 +24,14 @@ import java.util.Set; import ch.eitchnet.privilege.base.PrivilegeException; import ch.eitchnet.privilege.handler.PrivilegeHandler; -import ch.eitchnet.privilege.model.internal.Privilege; 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 Privilege} itself hidden from remote clients and make sure instances are only + * 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 Privilege} + * and simply wraps all public data from the {@link IPrivilege} * * @author Robert von Burg */ @@ -53,12 +53,12 @@ public class PrivilegeRep implements Serializable { * @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 Privilege} has unrestricted access to a + * 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 Privilege} + * a list of deny rules for this {@link IPrivilege} * @param allowList - * a list of allow rules for this {@link Privilege} + * a list of allow rules for this {@link IPrivilege} */ public PrivilegeRep(String name, String policy, boolean allAllowed, Set denyList, Set allowList) { this.name = name; @@ -66,6 +66,29 @@ public class PrivilegeRep implements Serializable { this.allAllowed = allAllowed; this.denyList = denyList; this.allowList = allowList; + + validate(); + } + + /** + * Validates that all required fields are set + */ + public void validate() { + + if (StringHelper.isEmpty(this.name)) { + throw new PrivilegeException("No name defined!"); + } + + if (StringHelper.isEmpty(this.policy)) { + throw new PrivilegeException("policy is null!"); + } + + if (this.denyList == null) { + throw new PrivilegeException("denyList is null"); + } + if (this.allowList == null) { + throw new PrivilegeException("allowList is null"); + } } /** @@ -143,48 +166,6 @@ public class PrivilegeRep implements Serializable { this.allowList = allowList; } - /** - *

    - * Validates this {@link PrivilegeRep} so that a {@link Privilege} object can later be created from it - *

    - * - * TODO write comment on how validation is done - */ - public void validate() { - - if (this.name == null || this.name.isEmpty()) { - throw new PrivilegeException("No name defined!"); - } - - // if not all allowed, then validate that deny and allow lists are defined - if (this.allAllowed) { - - // all allowed means no policy will be used - if (this.policy != null && !this.policy.isEmpty()) { - throw new PrivilegeException("All is allowed, so Policy may not be set!"); - } - - if (this.denyList != null && !this.denyList.isEmpty()) - throw new PrivilegeException("All is allowed, so deny list must be null"); - if (this.allowList != null && !this.allowList.isEmpty()) - throw new PrivilegeException("All is allowed, so allow list must be null"); - - } else { - - // not all allowed, so policy must be set - if (this.policy == null || this.policy.isEmpty()) { - throw new PrivilegeException("All is not allowed, so Policy must be defined!"); - } - - if (this.denyList == null) { - throw new PrivilegeException("All is not allowed, so deny list must be set, even if empty"); - } - if (this.allowList == null) { - throw new PrivilegeException("All is not allowed, so allow list must be set, even if empty"); - } - } - } - /** * Returns a string representation of this object displaying its concrete type and its values * diff --git a/src/main/java/ch/eitchnet/privilege/model/Restrictable.java b/src/main/java/ch/eitchnet/privilege/model/Restrictable.java index 95ef2e3c4..99795bb9e 100644 --- a/src/main/java/ch/eitchnet/privilege/model/Restrictable.java +++ b/src/main/java/ch/eitchnet/privilege/model/Restrictable.java @@ -19,14 +19,13 @@ */ package ch.eitchnet.privilege.model; -import ch.eitchnet.privilege.model.internal.Privilege; 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 Privilege} which has the associated {@link PrivilegePolicy} + * {@link #getPrivilegeName()} is used to find the {@link IPrivilege} which has the associated {@link PrivilegePolicy} * for evaluating access *

    * @@ -36,9 +35,9 @@ import ch.eitchnet.privilege.policy.PrivilegePolicy; public interface Restrictable { /** - * Returns the name of the {@link Privilege} which is to be used to validate privileges against + * Returns the name of the {@link IPrivilege} which is to be used to validate privileges against * - * @return the name of the {@link Privilege} 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(); diff --git a/src/main/java/ch/eitchnet/privilege/model/RoleRep.java b/src/main/java/ch/eitchnet/privilege/model/RoleRep.java index 06fd4a9bb..dfee0572c 100644 --- a/src/main/java/ch/eitchnet/privilege/model/RoleRep.java +++ b/src/main/java/ch/eitchnet/privilege/model/RoleRep.java @@ -22,7 +22,9 @@ package ch.eitchnet.privilege.model; import java.io.Serializable; import java.util.Map; +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 @@ -47,8 +49,19 @@ public class RoleRep implements Serializable { * the map of privileges granted to this role */ public RoleRep(String name, Map privilegeMap) { + this.name = name; this.privilegeMap = privilegeMap; + + validate(); + } + + /** + * validates that all required fields are set + */ + public void validate() { + if (StringHelper.isEmpty(this.name)) + throw new PrivilegeException("name is null"); } /** diff --git a/src/main/java/ch/eitchnet/privilege/model/UserRep.java b/src/main/java/ch/eitchnet/privilege/model/UserRep.java index 01f0f786a..118cb727c 100644 --- a/src/main/java/ch/eitchnet/privilege/model/UserRep.java +++ b/src/main/java/ch/eitchnet/privilege/model/UserRep.java @@ -24,8 +24,10 @@ import java.util.Locale; import java.util.Map; import java.util.Set; +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; /** * To keep certain details of the {@link User} itself hidden from remote clients and make sure instances are only edited @@ -77,6 +79,32 @@ public class UserRep implements Serializable { this.roles = roles; this.locale = locale; this.propertyMap = propertyMap; + + validate(); + } + + /** + * Validates that all required fields are set + */ + public void validate() { + + if (StringHelper.isEmpty(this.userId)) + throw new PrivilegeException("userId is null or empty"); + + if (StringHelper.isEmpty(this.username)) + throw new PrivilegeException("username is null or empty"); + + if (StringHelper.isEmpty(this.firstname)) + throw new PrivilegeException("firstname is null or empty"); + + if (StringHelper.isEmpty(this.surname)) + throw new PrivilegeException("surname is null or empty"); + + if (this.userState == null) + throw new PrivilegeException("userState is null"); + + if (this.roles == null) + throw new PrivilegeException("roles is null"); } /** diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java b/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java index 1b1d118cf..a30db711c 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java @@ -32,6 +32,10 @@ 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 { diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/Privilege.java b/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeImpl.java similarity index 63% rename from src/main/java/ch/eitchnet/privilege/model/internal/Privilege.java rename to src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeImpl.java index 2bb456c0b..c32623f41 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/Privilege.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeImpl.java @@ -25,20 +25,22 @@ 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 Privilege} 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 Privilege} 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} 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 Privilege}s have allow and deny rules which the configured {@link PrivilegeHandler} uses to + * {@link IPrivilege}s have allow and deny rules which the configured {@link PrivilegeHandler} uses to *

    * *

    @@ -48,7 +50,7 @@ import ch.eitchnet.privilege.policy.PrivilegePolicy; * * @author Robert von Burg */ -public final class Privilege { +public final class PrivilegeImpl implements IPrivilege { private final String name; private final String policy; @@ -65,66 +67,59 @@ public final class Privilege { * 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 Privilege} has unrestricted access to a + * 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 Privilege}, can be null if all allowed + * 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 Privilege}, can be null if all allowed + * a list of allow rules for this {@link PrivilegeImpl}, can be null if all allowed */ - public Privilege(String name, String policy, boolean allAllowed, Set denyList, Set allowList) { + public PrivilegeImpl(String name, String policy, boolean allAllowed, Set denyList, Set allowList) { - if (name == null || name.isEmpty()) { + if (StringHelper.isEmpty(name)) { throw new PrivilegeException("No name defined!"); } - this.name = name; - - // if not all allowed, then validate that deny and allow lists are defined - if (allAllowed) { - - this.allAllowed = true; - - // all allowed means no policy will be used - this.policy = null; - - this.denyList = Collections.emptySet(); - this.allowList = Collections.emptySet(); - } else { - - this.allAllowed = false; - - // not all allowed, so policy must be set - if (policy == null || policy.isEmpty()) { - throw new PrivilegeException("All is not allowed and no Policy defined!"); - } - this.policy = policy; - - if (denyList == null) { - throw new PrivilegeException("All is not allowed and no denyList defined!"); - } - this.denyList = Collections.unmodifiableSet(denyList); - - if (allowList == null) { - throw new PrivilegeException("All is not allowed and no allowList defined!"); - } - this.allowList = Collections.unmodifiableSet(allowList); + if (StringHelper.isEmpty(policy)) { + throw new PrivilegeException("Policy may not be empty!"); } + if (denyList == null) { + throw new PrivilegeException("denyList is null!"); + } + if (allowList == null) { + throw new PrivilegeException("allowList is null!"); + } + + this.name = name; + this.allAllowed = allAllowed; + this.policy = policy; + this.denyList = Collections.unmodifiableSet(denyList); + this.allowList = Collections.unmodifiableSet(allowList); } /** - * Constructs a {@link Privilege} from the {@link PrivilegeRep} + * Constructs a {@link PrivilegeImpl} from the {@link PrivilegeRep} * * @param privilegeRep - * the {@link PrivilegeRep} from which to create the {@link Privilege} + * the {@link PrivilegeRep} from which to create the {@link PrivilegeImpl} */ - public Privilege(PrivilegeRep privilegeRep) { + public PrivilegeImpl(PrivilegeRep privilegeRep) { this(privilegeRep.getName(), privilegeRep.getPolicy(), privilegeRep.isAllAllowed(), privilegeRep.getDenyList(), privilegeRep.getDenyList()); } + /** + * @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; } @@ -132,6 +127,7 @@ public final class Privilege { /** * @return the policy */ + @Override public String getPolicy() { return this.policy; } @@ -139,6 +135,7 @@ public final class Privilege { /** * @return the allAllowed */ + @Override public boolean isAllAllowed() { return this.allAllowed; } @@ -146,6 +143,7 @@ public final class Privilege { /** * @return the allowList */ + @Override public Set getAllowList() { return this.allowList; } @@ -153,16 +151,41 @@ public final class Privilege { /** * @return the denyList */ + @Override public Set getDenyList() { return this.denyList; } /** - * @return a {@link PrivilegeRep} which is a representation of this object used to serialize and view on clients + * @return true if there are values in the allow list */ - public PrivilegeRep asPrivilegeRep() { - return new PrivilegeRep(this.name, this.policy, this.allAllowed, new HashSet(this.denyList), - new HashSet(this.allowList)); + @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); } /** @@ -197,7 +220,7 @@ public final class Privilege { return false; if (getClass() != obj.getClass()) return false; - Privilege other = (Privilege) obj; + PrivilegeImpl other = (PrivilegeImpl) obj; if (this.name == null) { if (other.name != null) return false; diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/Role.java b/src/main/java/ch/eitchnet/privilege/model/internal/Role.java index 70cd2b11d..9e85710de 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/Role.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/Role.java @@ -22,10 +22,13 @@ package ch.eitchnet.privilege.model.internal; import java.util.Collections; import java.util.HashMap; import java.util.Map; +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; /** *

    @@ -43,7 +46,7 @@ import ch.eitchnet.privilege.model.RoleRep; public final class Role { private final String name; - private final Map privilegeMap; + private final Map privilegeMap; /** * Default constructor @@ -51,11 +54,11 @@ public final class Role { * @param name * the name of the role * @param privilegeMap - * a map of {@link Privilege}s granted to this role + * a map of {@link IPrivilege}s granted to this role */ - public Role(String name, Map privilegeMap) { + public Role(String name, Map privilegeMap) { - if (name == null || name.isEmpty()) { + if (StringHelper.isEmpty(name)) { throw new PrivilegeException("No name defined!"); } if (privilegeMap == null) { @@ -75,7 +78,7 @@ public final class Role { public Role(RoleRep roleRep) { String name = roleRep.getName(); - if (name == null || name.isEmpty()) { + if (StringHelper.isEmpty(name)) { throw new PrivilegeException("No name defined!"); } @@ -84,9 +87,9 @@ public final class Role { } // build privileges from reps - Map privilegeMap = new HashMap(roleRep.getPrivilegeMap().size()); + Map privilegeMap = new HashMap(roleRep.getPrivilegeMap().size()); for (String privilegeName : roleRep.getPrivilegeMap().keySet()) { - privilegeMap.put(privilegeName, new Privilege(roleRep.getPrivilegeMap().get(privilegeName))); + privilegeMap.put(privilegeName, new PrivilegeImpl(roleRep.getPrivilegeMap().get(privilegeName))); } this.name = name; @@ -101,21 +104,30 @@ public final class Role { } /** - * Returns the {@link Map} of {@link Privilege}s which are granted to this {@link Role} + * Returns the {@link Set} of names for the currently stored {@link IPrivilege Privileges} * - * @return the {@link Map} of {@link Privilege}s which are granted to this {@link Role} + * @return the {@link Set} of names for the currently stored {@link IPrivilege Privileges} */ - public Map getPrivilegeMap() { - return this.privilegeMap; + public Set getPrivilegeNames() { + return this.privilegeMap.keySet(); } /** - * Determines if this {@link Role} has the {@link Privilege} with the given name + * 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 Privilege} + * the name of the {@link IPrivilege} * - * @return true if this {@link Role} has the {@link Privilege} with the given name + * @return true if this {@link Role} has the {@link IPrivilege} with the given name */ public boolean hasPrivilege(String name) { return this.privilegeMap.containsKey(name); diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/Session.java b/src/main/java/ch/eitchnet/privilege/model/internal/Session.java deleted file mode 100644 index 2ab1e45df..000000000 --- a/src/main/java/ch/eitchnet/privilege/model/internal/Session.java +++ /dev/null @@ -1,191 +0,0 @@ -/* - * Copyright (c) 2010 - 2012 - * - * This file is part of Privilege. - * - * Privilege is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . - * - */ -package ch.eitchnet.privilege.model.internal; - -import ch.eitchnet.privilege.base.PrivilegeException; -import ch.eitchnet.privilege.handler.PrivilegeHandler; -import ch.eitchnet.privilege.model.Certificate; - -/** - *

    - * A {@link Session} is linked to currently logged in user. The {@link PrivilegeHandler} creates an instance of this - * class once a {@link User} has successfully logged in and keeps this object private but hands out a - * {@link Certificate} which the user must use every time a privilege needs to be granted - *

    - * - *

    - * Note: This is an internal object which is not to be serialized or passed to clients, the client must keep his - * {@link Certificate} which is used for accessing privileges - *

    - * - * @author Robert von Burg - */ -public final class Session { - - private final String sessionId; - private final String username; - private final long loginTime; - - private final String authToken; - private final String authPassword; - - /** - * Default constructor - * - *

    - * 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 authToken - * the authentication token defining the users unique session and is a private field of this session. It - * corresponds with the authentication token on the {@link Certificate} - * @param authPassword - * the password to access the authentication token, this is not known to the client but set by the - * {@link PrivilegeHandler} on authentication. It corresponds with the authentication password on the - * {@link Certificate} - * @param loginTime - * the time the user logged in - */ - public Session(String sessionId, String username, String authToken, String authPassword, long loginTime) { - - if (sessionId == null || sessionId.isEmpty()) { - throw new PrivilegeException("No sessionId defined!"); - } - if (username == null || username.isEmpty()) { - throw new PrivilegeException("No username defined!"); - } - if (authToken == null || authToken.isEmpty()) { - throw new PrivilegeException("No authToken defined!"); - } - if (authPassword == null || authPassword.isEmpty()) { - throw new PrivilegeException("No authPassword defined!"); - } - - this.sessionId = sessionId; - this.username = username; - this.authToken = authToken; - this.authPassword = authPassword; - this.loginTime = loginTime; - } - - /** - * @return the sessionId - */ - public String getSessionId() { - return this.sessionId; - } - - /** - * @return the authToken - */ - public String getAuthToken() { - return this.authToken; - } - - /** - * @return the authPassword - */ - public String getAuthPassword() { - return this.authPassword; - } - - /** - * @return the username - */ - public String getUsername() { - return this.username; - } - - /** - * @return the loginTime - */ - public long getLoginTime() { - return this.loginTime; - } - - /** - * Returns a string representation of this object displaying its concrete type and its values - * - * @see java.lang.Object#toString() - */ - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append("Session [sessionId="); - builder.append(this.sessionId); - builder.append(", username="); - builder.append(this.username); - builder.append(", loginTime="); - builder.append(this.loginTime); - builder.append("]"); - return builder.toString(); - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((this.authPassword == null) ? 0 : this.authPassword.hashCode()); - result = prime * result + ((this.authToken == null) ? 0 : this.authToken.hashCode()); - result = prime * result + (int) (this.loginTime ^ (this.loginTime >>> 32)); - 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 (getClass() != obj.getClass()) - return false; - Session other = (Session) obj; - if (this.authPassword == null) { - if (other.authPassword != null) - return false; - } else if (!this.authPassword.equals(other.authPassword)) - return false; - if (this.authToken == null) { - if (other.authToken != null) - return false; - } else if (!this.authToken.equals(other.authToken)) - return false; - if (this.loginTime != other.loginTime) - 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/src/main/java/ch/eitchnet/privilege/model/internal/User.java b/src/main/java/ch/eitchnet/privilege/model/internal/User.java index 1cd67bc2e..7e50da571 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/User.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/User.java @@ -29,6 +29,7 @@ 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 @@ -84,16 +85,16 @@ public final class User { public User(String userId, String username, String password, String firstname, String surname, UserState userState, Set roles, Locale locale, Map propertyMap) { - if (userId == null || userId.isEmpty()) { + if (StringHelper.isEmpty(userId)) { throw new PrivilegeException("No UserId defined!"); } - if (username == null || username.isEmpty()) { + if (StringHelper.isEmpty(username)) { throw new PrivilegeException("No username defined!"); } - if (firstname == null || firstname.isEmpty()) { + if (StringHelper.isEmpty(firstname)) { throw new PrivilegeException("No firstname defined!"); } - if (surname == null || surname.isEmpty()) { + if (StringHelper.isEmpty(surname)) { throw new PrivilegeException("No surname defined!"); } if (userState == null) { diff --git a/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java b/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java index 478169cdb..3a360be94 100644 --- a/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java +++ b/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java @@ -19,11 +19,16 @@ */ 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.privilege.model.internal.Privilege; import ch.eitchnet.privilege.model.internal.Role; +import ch.eitchnet.utils.helper.StringHelper; /** * This is a simple implementation of {@link PrivilegePolicy} which uses the {@link Restrictable#getPrivilegeName()} to @@ -36,53 +41,62 @@ 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#actionAllowed(ch.eitchnet.privilege.model.internal.Role, - * ch.eitchnet.privilege.model.internal.Privilege, ch.eitchnet.privilege.model.Restrictable) + * @see ch.eitchnet.privilege.policy.PrivilegePolicy#validateAction(PrivilegeContext, Restrictable) */ @Override - public void actionAllowed(Role role, Privilege privilege, Restrictable restrictable) { + public void validateAction(IPrivilege privilege, Restrictable restrictable) { - // validate user is not null - if (role == null) - throw new PrivilegeException("Role may not be null!"); + 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 (privilegeName == null || privilegeName.isEmpty()) { - throw new PrivilegeException("The PrivilegeName for the Restrictable is null or empty: " + restrictable); + 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)); + } + + // if everything is allowed, then no need to carry on + if (privilege.isAllAllowed()) + return; + // 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)) { - throw new PrivilegeException(Restrictable.class.getName() + " " + restrictable.getClass().getSimpleName() - + " has returned a non-string privilege value!"); + String msg = Restrictable.class.getName() + + PrivilegeMessages.getString("Privilege.illegalArgument.nonstring"); //$NON-NLS-1$ + msg = MessageFormat.format(msg, restrictable.getClass().getSimpleName()); + throw new PrivilegeException(msg); } String privilegeValue = (String) object; // first check values not allowed - for (String denied : privilege.getDenyList()) { - - // if value in deny list - if (denied.equals(privilegeValue)) { - - // then throw access denied - throw new AccessDeniedException("Role " + role.getName() + " does not have Privilege " + privilegeName - + " needed for Restrictable " + restrictable.getClass().getName()); - } + if (privilege.isDenied(privilegeValue)) { + // then throw access denied + String msg = MessageFormat.format(PrivilegeMessages.getString("Privilege.accessdenied.noprivilege"), + PrivilegeContext.get().getUsername(), privilegeName, restrictable.getClass().getName()); + throw new AccessDeniedException(msg); } // now check values allowed - for (String allowed : privilege.getAllowList()) { - if (allowed.equals(privilegeValue)) - return; - } + if (privilege.isAllowed(privilegeValue)) + return; // default is not allowed - throw new AccessDeniedException("Role " + role.getName() + " does not have Privilege " + privilegeName - + " needed for Restrictable " + restrictable.getClass().getName()); + String msg = MessageFormat.format(PrivilegeMessages.getString("Privilege.accessdenied.noprivilege"), + PrivilegeContext.get().getUsername(), privilegeName, restrictable.getClass().getName()); + throw new AccessDeniedException(msg); } } diff --git a/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicy.java b/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicy.java index 62041e636..4f0372c25 100644 --- a/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicy.java +++ b/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicy.java @@ -20,20 +20,19 @@ package ch.eitchnet.privilege.policy; import ch.eitchnet.privilege.base.AccessDeniedException; +import ch.eitchnet.privilege.model.IPrivilege; import ch.eitchnet.privilege.model.Restrictable; -import ch.eitchnet.privilege.model.internal.Privilege; 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 Privilege} has access to the given {@link Restrictable} + * given {@link IPrivilege} has access to the given {@link Restrictable} *

    * *

    - * Re-think this interface and especially the {@link #actionAllowed(Role, Privilege, Restrictable)}-method... maybe we - * need one with out the {@link Privilege} in its signature? + * TODO *

    * * @author Robert von Burg @@ -41,17 +40,15 @@ import ch.eitchnet.privilege.model.internal.User; public interface PrivilegePolicy { /** - * Checks if the given {@link Role} and the given {@link Privilege} has access to the given {@link Restrictable} + * Checks if the given {@link Role} and the given {@link IPrivilege} has access to the given {@link Restrictable} * - * @param role - * the {@link Role} trying to access the {@link Restrictable} * @param privilege - * the {@link Privilege} to check with + * the {@link IPrivilege} containing the permissions * @param restrictable * the {@link Restrictable} to which the user wants access * * @throws AccessDeniedException * if action not allowed */ - public void actionAllowed(Role role, Privilege privilege, Restrictable restrictable) throws AccessDeniedException; + public void validateAction(IPrivilege privilege, Restrictable restrictable) throws AccessDeniedException; } diff --git a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelDomWriter.java b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelDomWriter.java index 18b0e7de7..03ef69f77 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelDomWriter.java +++ b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelDomWriter.java @@ -29,7 +29,7 @@ import org.w3c.dom.Document; import org.w3c.dom.Element; import ch.eitchnet.privilege.helper.XmlConstants; -import ch.eitchnet.privilege.model.internal.Privilege; +import ch.eitchnet.privilege.model.IPrivilege; import ch.eitchnet.privilege.model.internal.Role; import ch.eitchnet.privilege.model.internal.User; import ch.eitchnet.utils.helper.XmlHelper; @@ -124,7 +124,8 @@ public class PrivilegeModelDomWriter { roleElement.setAttribute(XmlConstants.XML_ATTR_NAME, role.getName()); - for (Privilege privilege : role.getPrivilegeMap().values()) { + for (String privilegeName : role.getPrivilegeNames()) { + IPrivilege privilege = role.getPrivilege(privilegeName); // create the privilege element Element privilegeElement = doc.createElement(XmlConstants.XML_PRIVILEGE); diff --git a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java index fa85e9843..edc0490a2 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java +++ b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java @@ -36,8 +36,9 @@ import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; +import ch.eitchnet.privilege.model.IPrivilege; import ch.eitchnet.privilege.model.UserState; -import ch.eitchnet.privilege.model.internal.Privilege; +import ch.eitchnet.privilege.model.internal.PrivilegeImpl; import ch.eitchnet.privilege.model.internal.Role; import ch.eitchnet.privilege.model.internal.User; import ch.eitchnet.utils.helper.StringHelper; @@ -149,7 +150,7 @@ public class PrivilegeModelSaxReader extends DefaultHandler { private Set denyList; private Set allowList; - private Map privileges; + private Map privileges; /** * @@ -162,7 +163,7 @@ public class PrivilegeModelSaxReader extends DefaultHandler { * */ private void init() { - this.privileges = new HashMap(); + this.privileges = new HashMap(); this.text = null; @@ -204,7 +205,7 @@ public class PrivilegeModelSaxReader extends DefaultHandler { this.denyList.add(this.text.toString().trim()); } else if (qName.equals("Privilege")) { - Privilege privilege = new Privilege(this.privilegeName, this.privilegePolicy, this.allAllowed, + IPrivilege privilege = new PrivilegeImpl(this.privilegeName, this.privilegePolicy, this.allAllowed, this.denyList, this.allowList); this.privileges.put(this.privilegeName, privilege); diff --git a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java index f1f9406af..06ee3ca96 100644 --- a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -36,8 +36,8 @@ 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.helper.CertificateThreadLocal; import ch.eitchnet.privilege.model.Certificate; +import ch.eitchnet.privilege.model.PrivilegeContext; import ch.eitchnet.privilege.model.PrivilegeRep; import ch.eitchnet.privilege.model.Restrictable; import ch.eitchnet.privilege.model.RoleRep; @@ -47,6 +47,7 @@ import ch.eitchnet.privilege.test.model.TestRestrictable; import ch.eitchnet.privilege.test.model.TestSystemUserAction; import ch.eitchnet.privilege.test.model.TestSystemUserActionDeny; import ch.eitchnet.privilege.xml.InitializationHelper; +import ch.eitchnet.utils.helper.ArraysHelper; import ch.eitchnet.utils.helper.FileHelper; /** @@ -82,11 +83,13 @@ public class PrivilegeTest { @BeforeClass public static void init() throws Exception { try { + destroy(); + // copy configuration to tmp String pwd = System.getProperty("user.dir"); File origPrivilegeModelFile = new File(pwd + "/config/PrivilegeModel.xml"); - File tmpPrivilegeModelFile = new File(pwd + "/target/test/PrivilegeModel.xml"); + File tmpPrivilegeModelFile = new File(pwd + "/target/testPrivilege/PrivilegeModel.xml"); if (tmpPrivilegeModelFile.exists() && !tmpPrivilegeModelFile.delete()) { throw new RuntimeException("Tmp configuration still exists and can not be deleted at " + tmpPrivilegeModelFile.getAbsolutePath()); @@ -102,7 +105,7 @@ public class PrivilegeTest { throw new RuntimeException("Failed to copy " + origPrivilegeModelFile + " to " + tmpPrivilegeModelFile); } catch (Exception e) { - PrivilegeTest.logger.error(e.getMessage(), e); + logger.error(e.getMessage(), e); throw new RuntimeException("Initialization failed: " + e.getLocalizedMessage(), e); } @@ -114,7 +117,7 @@ public class PrivilegeTest { // delete temporary file String pwd = System.getProperty("user.dir"); - File tmpPrivilegeModelFile = new File(pwd + "/target/test/PrivilegeModel.xml"); + File tmpPrivilegeModelFile = new File(pwd + "/target/testPrivilege/PrivilegeModel.xml"); if (tmpPrivilegeModelFile.exists() && !tmpPrivilegeModelFile.delete()) { throw new RuntimeException("Tmp configuration still exists and can not be deleted at " + tmpPrivilegeModelFile.getAbsolutePath()); @@ -136,154 +139,139 @@ public class PrivilegeTest { File privilegeConfigFile = new File(pwd + "/config/Privilege.xml"); // initialize privilege - PrivilegeTest.privilegeHandler = InitializationHelper.initializeFromXml(privilegeConfigFile); + privilegeHandler = InitializationHelper.initializeFromXml(privilegeConfigFile); } catch (Exception e) { - PrivilegeTest.logger.error(e.getMessage(), e); + logger.error(e.getMessage(), e); throw new RuntimeException("Setup failed: " + e.getLocalizedMessage(), e); } } - private byte[] copyBytes(byte[] bytes) { - byte[] copy = new byte[bytes.length]; - System.arraycopy(bytes, 0, copy, 0, bytes.length); - return copy; + private void login(String username, byte[] password) { + Certificate certificate = privilegeHandler.authenticate(username, password); + Assert.assertTrue("Certificate is null!", certificate != null); + PrivilegeContext privilegeContext = privilegeHandler.getPrivilegeContext(certificate); + PrivilegeContext.set(privilegeContext); + } + + private void logout() { + try { + PrivilegeContext privilegeContext = PrivilegeContext.get(); + 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; + } finally { + PrivilegeContext.set(null); + } } - /** - * @throws Exception - * if something goes wrong - */ @Test public void testAuthenticationOk() throws Exception { - - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, - copyBytes(PrivilegeTest.PASS_ADMIN)); - Assert.assertTrue("Certificate is null!", certificate != null); - PrivilegeTest.privilegeHandler.invalidateSession(certificate); + try { + login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); + } finally { + logout(); + } } - /** - * @throws Exception - * if something goes wrong - */ @Test(expected = AccessDeniedException.class) public void testFailAuthenticationNOk() throws Exception { - - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, - copyBytes(PrivilegeTest.PASS_BAD)); - Assert.assertTrue("Certificate is null!", certificate != null); - PrivilegeTest.privilegeHandler.invalidateSession(certificate); + try { + login(ADMIN, ArraysHelper.copyOf(PASS_BAD)); + } finally { + logout(); + } } - /** - * @throws Exception - * if something goes wrong - */ @Test(expected = PrivilegeException.class) public void testFailAuthenticationPWNull() throws Exception { - - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, null); - Assert.assertTrue("Certificate is null!", certificate != null); - PrivilegeTest.privilegeHandler.invalidateSession(certificate); + try { + login(ADMIN, null); + } finally { + logout(); + } } - /** - * @throws Exception - * if something goes wrong - */ @Test public void testAddRoleTemp() throws Exception { - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, - copyBytes(PrivilegeTest.PASS_ADMIN)); + try { + login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); - Map privilegeMap = new HashMap(); - RoleRep roleRep = new RoleRep(PrivilegeTest.ROLE_TEMP, privilegeMap); + Map privilegeMap = new HashMap(); + RoleRep roleRep = new RoleRep(ROLE_TEMP, privilegeMap); - PrivilegeTest.privilegeHandler.addOrReplaceRole(certificate, roleRep); - privilegeHandler.persist(certificate); - PrivilegeTest.privilegeHandler.invalidateSession(certificate); + Certificate certificate = PrivilegeContext.get().getCertificate(); + privilegeHandler.addOrReplaceRole(certificate, roleRep); + privilegeHandler.persist(certificate); + } finally { + logout(); + } } - /** - * @throws Exception - * if something goes wrong - */ @Test public void testPerformRestrictableAsAdmin() throws Exception { + try { + login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, - copyBytes(PrivilegeTest.PASS_ADMIN)); - Assert.assertTrue("Certificate is null!", certificate != null); + // see if admin can perform restrictable + Restrictable restrictable = new TestRestrictable(); + PrivilegeContext.get().validateAction(restrictable); - // see if eitch can perform restrictable - Restrictable restrictable = new TestRestrictable(); - PrivilegeTest.privilegeHandler.actionAllowed(certificate, restrictable); - PrivilegeTest.privilegeHandler.invalidateSession(certificate); + } finally { + logout(); + } } /** * Tests if an action can be performed as a system user - * - * @throws Exception - * if something goes wrong */ @Test public void testPerformSystemRestrictable() throws Exception { - // create the action to be performed as a system user - TestSystemUserAction action = new TestSystemUserAction(PrivilegeTest.privilegeHandler); - - // and then perform the action - PrivilegeTest.privilegeHandler.runAsSystem(PrivilegeTest.SYSTEM_USER_ADMIN, action); + // 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 - * - * @throws Exception - * if something goes wrong */ @Test(expected = PrivilegeException.class) public void testPerformSystemRestrictableFailPrivilege() throws Exception { + try { + // create the action to be performed as a system user + TestSystemUserActionDeny action = new TestSystemUserActionDeny(); - // create the action to be performed as a system user - TestSystemUserActionDeny action = new TestSystemUserActionDeny(PrivilegeTest.privilegeHandler); - - // and then perform the action - PrivilegeTest.privilegeHandler.runAsSystem(PrivilegeTest.SYSTEM_USER_ADMIN, action); + // and then perform the action + privilegeHandler.runAsSystem(SYSTEM_USER_ADMIN, action); + } finally { + logout(); + } } /** * System user may not login - * - * @throws Exception - * if something goes wrong */ @Test(expected = AccessDeniedException.class) public void testLoginSystemUser() throws Exception { - - PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.SYSTEM_USER_ADMIN, - PrivilegeTest.SYSTEM_USER_ADMIN.getBytes()); + try { + login(SYSTEM_USER_ADMIN, SYSTEM_USER_ADMIN.getBytes()); + } finally { + logout(); + } } @Test - public void testCertificateThreadLocal() { - - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, - copyBytes(PrivilegeTest.PASS_ADMIN)); - Assert.assertTrue("Certificate is null!", certificate != null); - - // set certificate into thread local - CertificateThreadLocal.getInstance().set(certificate); - - // see if bob can perform restrictable by returning certificate from CertificateThreadLocal - Restrictable restrictable = new TestRestrictable(); + public void testPrivilegeContext() { try { - PrivilegeTest.privilegeHandler.actionAllowed(CertificateThreadLocal.getInstance().get(), restrictable); + login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); + Restrictable restrictable = new TestRestrictable(); + PrivilegeContext.get().validateAction(restrictable); } finally { - PrivilegeTest.privilegeHandler.invalidateSession(certificate); + logout(); } } @@ -308,8 +296,6 @@ public class PrivilegeTest { *
  • perform restrictable as bob
  • * * - * @throws Exception - * if something goes wrong */ @Test public void testUserStory() throws Exception { @@ -334,216 +320,243 @@ public class PrivilegeTest { } private void performRestrictableAsBob() { - Certificate certificate; - // testPerformRestrictableAsBob - // Tests if the user bob, who now has AppUser role can perform restrictable - certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); - Assert.assertTrue("Certificate is null!", certificate != null); - // see if bob can perform restrictable - Restrictable restrictable = new TestRestrictable(); - PrivilegeTest.privilegeHandler.actionAllowed(certificate, restrictable); - PrivilegeTest.privilegeHandler.invalidateSession(certificate); + 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(); + PrivilegeContext.get().validateAction(restrictable); + } finally { + logout(); + } } private void addRoleAppToBob() { - Certificate certificate; - // testAddAppRoleToBob - certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, - copyBytes(PrivilegeTest.PASS_ADMIN)); - PrivilegeTest.privilegeHandler.addRoleToUser(certificate, PrivilegeTest.BOB, PrivilegeTest.ROLE_APP_USER); - PrivilegeTest.logger.info("Added " + PrivilegeTest.ROLE_APP_USER + " to " + PrivilegeTest.BOB); - privilegeHandler.persist(certificate); - PrivilegeTest.privilegeHandler.invalidateSession(certificate); + try { + // testAddAppRoleToBob + login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); + Certificate certificate = PrivilegeContext.get().getCertificate(); + privilegeHandler.addRoleToUser(certificate, BOB, ROLE_APP_USER); + logger.info("Added " + ROLE_APP_USER + " to " + BOB); + privilegeHandler.persist(certificate); + } finally { + logout(); + } } private void failPerformRestrictableAsBobNoRoleApp() { - Certificate certificate; - // 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 - certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); - Assert.assertTrue("Certificate is null!", certificate != null); - // see if bob can perform restrictable - Restrictable restrictable = new TestRestrictable(); try { - PrivilegeTest.privilegeHandler.actionAllowed(certificate, restrictable); + // 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(); + PrivilegeContext.get().validateAction(restrictable); Assert.fail("Should fail as bob does not have role app"); } catch (AccessDeniedException e) { String msg = "User bob does not have Privilege ch.eitchnet.privilege.test.model.TestRestrictable needed for Restrictable ch.eitchnet.privilege.test.model.TestRestrictable"; Assert.assertEquals(msg, e.getLocalizedMessage()); } finally { - PrivilegeTest.privilegeHandler.invalidateSession(certificate); + logout(); } } private void authAsTed() { - Certificate certificate; - // testAuthAsTed - certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.TED, copyBytes(PrivilegeTest.PASS_TED)); - PrivilegeTest.privilegeHandler.invalidateSession(certificate); + try { + // testAuthAsTed + login(TED, ArraysHelper.copyOf(PASS_TED)); + } finally { + logout(); + } } private void tedChangesOwnPass() { - Certificate certificate; - // testTedChangesOwnPwd - certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.TED, copyBytes(PrivilegeTest.PASS_DEF)); - PrivilegeTest.privilegeHandler.setUserPassword(certificate, PrivilegeTest.TED, - copyBytes(PrivilegeTest.PASS_TED)); - PrivilegeTest.privilegeHandler.invalidateSession(certificate); + try { + // testTedChangesOwnPwd + login(TED, ArraysHelper.copyOf(PASS_DEF)); + Certificate certificate = PrivilegeContext.get().getCertificate(); + privilegeHandler.setUserPassword(certificate, TED, ArraysHelper.copyOf(PASS_TED)); + } finally { + logout(); + } } private void setPassForTedAsBob() { - Certificate certificate; - // testSetTedPwdAsBob - certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); - Assert.assertTrue("Certificate is null!", certificate != null); - // set ted's password to default - PrivilegeTest.privilegeHandler.setUserPassword(certificate, PrivilegeTest.TED, - copyBytes(PrivilegeTest.PASS_DEF)); - privilegeHandler.persist(certificate); - PrivilegeTest.privilegeHandler.invalidateSession(certificate); + try { + // testSetTedPwdAsBob + login(BOB, ArraysHelper.copyOf(PASS_BOB)); + // set ted's password to default + Certificate certificate = PrivilegeContext.get().getCertificate(); + privilegeHandler.setUserPassword(certificate, TED, ArraysHelper.copyOf(PASS_DEF)); + privilegeHandler.persist(certificate); + } finally { + logout(); + } } private void failAuthAsTedNoPass() { - // testFailAuthAsTedNoPass - // Will fail because user ted has no password try { - PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.TED, copyBytes(PrivilegeTest.PASS_TED)); + // testFailAuthAsTedNoPass + // Will fail because user ted has no password + login(TED, ArraysHelper.copyOf(PASS_TED)); org.junit.Assert.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!"; Assert.assertEquals(msg, e.getMessage()); + } finally { + logout(); } } private void addTedAsBob() { - Certificate certificate; - UserRep userRep; - // testAddUserTedAsBob - certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); - Assert.assertTrue("Certificate is null!", certificate != null); - // let's add a new user ted - HashSet roles = new HashSet(); - roles.add(PrivilegeTest.ROLE_USER); - userRep = new UserRep("2", PrivilegeTest.TED, "Ted", "Newman", UserState.ENABLED, roles, null, - new HashMap()); - PrivilegeTest.privilegeHandler.addOrReplaceUser(certificate, userRep, null); - PrivilegeTest.logger.info("Added user " + PrivilegeTest.TED); - privilegeHandler.persist(certificate); - PrivilegeTest.privilegeHandler.invalidateSession(certificate); + 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("2", TED, "Ted", "Newman", UserState.ENABLED, roles, null, + new HashMap()); + Certificate certificate = PrivilegeContext.get().getCertificate(); + privilegeHandler.addOrReplaceUser(certificate, userRep, null); + logger.info("Added user " + TED); + privilegeHandler.persist(certificate); + } finally { + logout(); + } } private void addRoleAdminToBob() { - Certificate certificate; - // testAddAdminRoleToBob - certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, - copyBytes(PrivilegeTest.PASS_ADMIN)); - PrivilegeTest.privilegeHandler.addRoleToUser(certificate, PrivilegeTest.BOB, - PrivilegeHandler.PRIVILEGE_ADMIN_ROLE); - PrivilegeTest.logger.info("Added " + PrivilegeHandler.PRIVILEGE_ADMIN_ROLE + " to " + PrivilegeTest.ADMIN); - privilegeHandler.persist(certificate); - PrivilegeTest.privilegeHandler.invalidateSession(certificate); + try { + // testAddAdminRoleToBob + login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); + Certificate certificate = PrivilegeContext.get().getCertificate(); + privilegeHandler.addRoleToUser(certificate, BOB, PrivilegeHandler.PRIVILEGE_ADMIN_ROLE); + logger.info("Added " + PrivilegeHandler.PRIVILEGE_ADMIN_ROLE + " to " + ADMIN); + privilegeHandler.persist(certificate); + } finally { + logout(); + } } private void failAddTedAsBobNotAdmin() { - Certificate certificate; - UserRep userRep; - // testFailAddUserTedAsBob - // Will fail because user bob does not have admin rights - // auth as Bob - certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); - // let's add a new user Ted - userRep = new UserRep("1", PrivilegeTest.TED, "Ted", "And then Some", UserState.NEW, new HashSet(), - null, new HashMap()); + Certificate certificate = null; try { - PrivilegeTest.privilegeHandler.addOrReplaceUser(certificate, userRep, null); + 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 = PrivilegeContext.get().getCertificate(); + privilegeHandler.addOrReplaceUser(certificate, userRep, null); Assert.fail("User bob may not add a user as bob does not have admin rights!"); } catch (PrivilegeException e) { - String msg = "User does not have PrivilegeAdmin role! Certificate: " + certificate.toString(); + String msg = "User does not have PrivilegeAdmin role! Certificate: " + certificate; Assert.assertEquals(msg, e.getMessage()); } finally { - PrivilegeTest.privilegeHandler.invalidateSession(certificate); + logout(); } } private void authAsBob() { - Certificate certificate; - // testAuthAsBob - certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); - PrivilegeTest.privilegeHandler.invalidateSession(certificate); + try { + // testAuthAsBob + login(BOB, ArraysHelper.copyOf(PASS_BOB)); + } finally { + logout(); + } } private void addRoleUserToBob() { - Certificate certificate; - // testAddRoleUserToBob - certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, - copyBytes(PrivilegeTest.PASS_ADMIN)); - PrivilegeTest.privilegeHandler.addRoleToUser(certificate, PrivilegeTest.BOB, PrivilegeTest.ROLE_USER); - privilegeHandler.persist(certificate); - PrivilegeTest.privilegeHandler.invalidateSession(certificate); + try { + // testAddRoleUserToBob + login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); + Certificate certificate = PrivilegeContext.get().getCertificate(); + privilegeHandler.addRoleToUser(certificate, BOB, ROLE_USER); + privilegeHandler.persist(certificate); + logout(); + } finally { + logout(); + } } private void addRoleUser() { - Certificate certificate; - // add role user - certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, - copyBytes(PrivilegeTest.PASS_ADMIN)); - Map privilegeMap = new HashMap(); - RoleRep roleRep = new RoleRep(PrivilegeTest.ROLE_USER, privilegeMap); - PrivilegeTest.privilegeHandler.addOrReplaceRole(certificate, roleRep); - privilegeHandler.persist(certificate); - PrivilegeTest.privilegeHandler.invalidateSession(certificate); + try { + // add role user + login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); + Map privilegeMap = new HashMap(); + RoleRep roleRep = new RoleRep(ROLE_USER, privilegeMap); + Certificate certificate = PrivilegeContext.get().getCertificate(); + privilegeHandler.addOrReplaceRole(certificate, roleRep); + privilegeHandler.persist(certificate); + } finally { + logout(); + } } private void failAuthAsBobNoRole() { - // testFailAuthUserBob - // Will fail as user bob has no role try { - PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); + // testFailAuthUserBob + // Will fail as user bob has no role + privilegeHandler.authenticate(BOB, ArraysHelper.copyOf(PASS_BOB)); org.junit.Assert.fail("User Bob may not authenticate because the user has no role"); } catch (PrivilegeException e) { String msg = "User bob does not have any roles defined!"; Assert.assertEquals(msg, e.getMessage()); + } finally { + logout(); } } private void enableBob() { - Certificate certificate; - // testEnableUserBob - certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, - copyBytes(PrivilegeTest.PASS_ADMIN)); - PrivilegeTest.privilegeHandler.setUserState(certificate, PrivilegeTest.BOB, UserState.ENABLED); - privilegeHandler.persist(certificate); - PrivilegeTest.privilegeHandler.invalidateSession(certificate); + try { + // testEnableUserBob + login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); + Certificate certificate = PrivilegeContext.get().getCertificate(); + privilegeHandler.setUserState(certificate, BOB, UserState.ENABLED); + privilegeHandler.persist(certificate); + } finally { + logout(); + } } private void failAuthAsBobNotEnabled() { - // testFailAuthAsBob - // Will fail because user bob is not yet enabled try { - PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.BOB, copyBytes(PrivilegeTest.PASS_BOB)); + // testFailAuthAsBob + // Will fail because user bob is not yet enabled + privilegeHandler.authenticate(BOB, ArraysHelper.copyOf(PASS_BOB)); org.junit.Assert.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!"; Assert.assertEquals(msg, e.getMessage()); + } finally { + logout(); } } private void addBobAsAdmin() { - Certificate certificate = PrivilegeTest.privilegeHandler.authenticate(PrivilegeTest.ADMIN, - copyBytes(PrivilegeTest.PASS_ADMIN)); + try { + login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); - // let's add a new user bob - UserRep userRep = new UserRep("1", PrivilegeTest.BOB, "Bob", "Newman", UserState.NEW, new HashSet(), - null, new HashMap()); - PrivilegeTest.privilegeHandler.addOrReplaceUser(certificate, userRep, null); - PrivilegeTest.logger.info("Added user " + PrivilegeTest.BOB); + // let's add a new user bob + UserRep userRep = new UserRep("1", BOB, "Bob", "Newman", UserState.NEW, new HashSet(), null, + new HashMap()); + Certificate certificate = PrivilegeContext.get().getCertificate(); + privilegeHandler.addOrReplaceUser(certificate, userRep, null); + logger.info("Added user " + BOB); - // set bob's password - PrivilegeTest.privilegeHandler.setUserPassword(certificate, PrivilegeTest.BOB, - copyBytes(PrivilegeTest.PASS_BOB)); - PrivilegeTest.logger.info("Set Bob's password"); - privilegeHandler.persist(certificate); - PrivilegeTest.privilegeHandler.invalidateSession(certificate); + // 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/src/test/java/ch/eitchnet/privilege/test/XmlTest.java b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java index da7e2a1b5..23db59a65 100644 --- a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java @@ -23,6 +23,7 @@ package ch.eitchnet.privilege.test; import java.io.File; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -40,9 +41,10 @@ import org.slf4j.LoggerFactory; import ch.eitchnet.privilege.handler.DefaultEncryptionHandler; import ch.eitchnet.privilege.handler.XmlPersistenceHandler; +import ch.eitchnet.privilege.model.IPrivilege; import ch.eitchnet.privilege.model.UserState; -import ch.eitchnet.privilege.model.internal.Privilege; 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; @@ -71,6 +73,7 @@ public class XmlTest { */ @BeforeClass public static void init() throws Exception { + destroy(); File tmpDir = new File("target/test"); if (tmpDir.exists()) @@ -179,7 +182,7 @@ public class XmlTest { Map propertyMap; Set userRoles; - Map privilegeMap; + Map privilegeMap; List users = new ArrayList(); propertyMap = new HashMap(); @@ -197,16 +200,17 @@ public class XmlTest { propertyMap)); List roles = new ArrayList(); - privilegeMap = new HashMap(); - privilegeMap.put("priv1", new Privilege("priv1", "DefaultPrivilege", true, null, null)); + Set list = Collections.emptySet(); + privilegeMap = new HashMap(); + privilegeMap.put("priv1", new PrivilegeImpl("priv1", "DefaultPrivilege", true, list, list)); roles.add(new Role("role1", privilegeMap)); - privilegeMap = new HashMap(); + privilegeMap = new HashMap(); Set denyList = new HashSet(); denyList.add("myself"); Set allowList = new HashSet(); allowList.add("other"); - privilegeMap.put("priv2", new Privilege("priv2", "DefaultPrivilege", false, denyList, allowList)); + privilegeMap.put("priv2", new PrivilegeImpl("priv2", "DefaultPrivilege", false, denyList, allowList)); roles.add(new Role("role2", privilegeMap)); File modelFile = new File("./target/test/PrivilegeModelTest.xml"); @@ -214,6 +218,6 @@ public class XmlTest { configSaxWriter.write(); String fileHash = StringHelper.getHexString(FileHelper.hashFileSha256(modelFile)); - Assert.assertEquals("8E1E82278162F21B1654C2E059570BBCB3CB63B053C1DD784BC8E225E8CFD04F", fileHash); + Assert.assertEquals("9007F172BBD7BA51BA3E67199CE0AFCBC8645AF0AC02028ABE54BA6A2FC134B0", fileHash); } } diff --git a/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java b/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java index f247d680b..c86a5fdb0 100644 --- a/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java +++ b/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java @@ -19,34 +19,20 @@ */ package ch.eitchnet.privilege.test.model; -import ch.eitchnet.privilege.handler.PrivilegeHandler; import ch.eitchnet.privilege.handler.SystemUserAction; -import ch.eitchnet.privilege.model.Certificate; +import ch.eitchnet.privilege.model.PrivilegeContext; /** * @author Robert von Burg - * + * */ public class TestSystemUserAction implements SystemUserAction { - private PrivilegeHandler handler; - - /** - * - */ - public TestSystemUserAction(PrivilegeHandler handler) { - this.handler = handler; - } - - /** - * @see ch.eitchnet.privilege.handler.SystemUserAction#execute(ch.eitchnet.privilege.model.Certificate) - */ @Override - public void execute(Certificate certificate) { - + public void execute(PrivilegeContext context) { TestSystemRestrictable restrictable = new TestSystemRestrictable(); - - this.handler.actionAllowed(certificate, restrictable); + PrivilegeContext.set(context); + context.validateAction(restrictable); + PrivilegeContext.set(null); } - } diff --git a/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserActionDeny.java b/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserActionDeny.java index 14f6e566a..d8ed59616 100644 --- a/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserActionDeny.java +++ b/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserActionDeny.java @@ -19,9 +19,8 @@ */ package ch.eitchnet.privilege.test.model; -import ch.eitchnet.privilege.handler.PrivilegeHandler; import ch.eitchnet.privilege.handler.SystemUserAction; -import ch.eitchnet.privilege.model.Certificate; +import ch.eitchnet.privilege.model.PrivilegeContext; /** * @author Robert von Burg @@ -29,22 +28,9 @@ import ch.eitchnet.privilege.model.Certificate; */ public class TestSystemUserActionDeny implements SystemUserAction { - private PrivilegeHandler handler; - - /** - * - */ - public TestSystemUserActionDeny(PrivilegeHandler handler) { - this.handler = handler; - } - - /** - * @see ch.eitchnet.privilege.handler.SystemUserAction#execute(ch.eitchnet.privilege.model.Certificate) - */ @Override - public void execute(Certificate certificate) { - + public void execute(PrivilegeContext privilegeContext) { TestRestrictable restrictable = new TestRestrictable(); - this.handler.actionAllowed(certificate, restrictable); + privilegeContext.validateAction(restrictable); } } From 6eb34ff532957f159e4fb1452aacf35098b989c6 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 19 Apr 2013 21:33:18 +0200 Subject: [PATCH 100/173] [Minor] fixed problem with build on mvn cli The problem was due to a naming problem with the PrivilegeMessages class and property bundle having the same name and being in the same package. Moved the properties file now to the resources directory --- src/main/java/ch/eitchnet/privilege/i18n/PrivilegeMessages.java | 2 +- .../privilege/i18n => resources}/PrivilegeMessages.properties | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename src/main/{java/ch/eitchnet/privilege/i18n => resources}/PrivilegeMessages.properties (100%) diff --git a/src/main/java/ch/eitchnet/privilege/i18n/PrivilegeMessages.java b/src/main/java/ch/eitchnet/privilege/i18n/PrivilegeMessages.java index c84da0b69..da80d9f63 100644 --- a/src/main/java/ch/eitchnet/privilege/i18n/PrivilegeMessages.java +++ b/src/main/java/ch/eitchnet/privilege/i18n/PrivilegeMessages.java @@ -29,7 +29,7 @@ import java.util.ResourceBundle; * */ public class PrivilegeMessages { - private static final String BUNDLE_NAME = "ch.eitchnet.privilege.i18n.PrivilegeMessages"; //$NON-NLS-1$ + private static final String BUNDLE_NAME = "PrivilegeMessages"; //$NON-NLS-1$ private static final ResourceBundle RESOURCE_BUNDLE = ResourceBundle.getBundle(BUNDLE_NAME); diff --git a/src/main/java/ch/eitchnet/privilege/i18n/PrivilegeMessages.properties b/src/main/resources/PrivilegeMessages.properties similarity index 100% rename from src/main/java/ch/eitchnet/privilege/i18n/PrivilegeMessages.properties rename to src/main/resources/PrivilegeMessages.properties From d7f10a731f9b2a95cecb32baccf1a7b6e4b088dd Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 8 Aug 2013 00:32:39 +0200 Subject: [PATCH 101/173] [Minor] fixed compiler warnings fixed multiple compiler warnings about broken JavaDocs and pom which was still set to Java 1.6, now changed to 1.7 --- pom.xml | 6 +++--- .../privilege/handler/DefaultPrivilegeHandler.java | 14 +++++++++++--- .../privilege/handler/PrivilegeHandler.java | 10 +++++----- .../ch/eitchnet/privilege/model/Certificate.java | 6 ++---- .../model/internal/PrivilegeContainerModel.java | 6 +++--- .../privilege/policy/DefaultPrivilege.java | 2 +- .../privilege/xml/PrivilegeModelSaxReader.java | 4 ---- 7 files changed, 25 insertions(+), 23 deletions(-) diff --git a/pom.xml b/pom.xml index 2d601786a..fdef06dff 100644 --- a/pom.xml +++ b/pom.xml @@ -97,7 +97,7 @@ ch.eitchnet ch.eitchnet.utils - 0.1.0-SNAPSHOT + 0.2.0-SNAPSHOT org.slf4j @@ -129,8 +129,8 @@ maven-compiler-plugin 3.0 - 1.6 - 1.6 + 1.7 + 1.7 diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 43691ca2a..8f1a7aeb2 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -78,7 +78,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { protected static final Logger logger = LoggerFactory.getLogger(DefaultPrivilegeHandler.class); /** - * last assigned id for the {@link Session}s + * last assigned id for the {@link Certificate}s */ private long lastSessionId; @@ -285,6 +285,10 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { this.persistenceHandler.addOrReplaceRole(role); } + /** + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#addOrReplaceUser(ch.eitchnet.privilege.model.Certificate, + * ch.eitchnet.privilege.model.UserRep, byte[]) + */ @Override public void addOrReplaceUser(Certificate certificate, UserRep userRep, byte[] password) { try { @@ -525,6 +529,10 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { this.persistenceHandler.addOrReplaceUser(newUser); } + /** + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setUserPassword(ch.eitchnet.privilege.model.Certificate, + * java.lang.String, byte[]) + */ @Override public void setUserPassword(Certificate certificate, String username, byte[] password) { try { @@ -595,7 +603,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#authenticate(java.lang.String, java.lang.String) + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#authenticate(java.lang.String, byte[]) * * @throws AccessDeniedException * if the user credentials are not valid @@ -800,7 +808,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { /** * 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(java.lang.String) + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#validatePassword(byte[]) */ @Override public void validatePassword(byte[] password) throws PrivilegeException { diff --git a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java index ec0d5376a..5c0e397bd 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java @@ -159,7 +159,7 @@ public interface PrivilegeHandler { * *

    * 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(String)} + * the requirements of the implementation under {@link PrivilegeHandler#validatePassword(byte[])} *

    * * @param certificate @@ -169,7 +169,7 @@ public interface PrivilegeHandler { * @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(String)} + * {@link PrivilegeHandler#validatePassword(byte[])} * * @throws AccessDeniedException * if the user for this certificate may not perform the action @@ -235,7 +235,7 @@ public interface PrivilegeHandler { *

    * 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(String)} + * {@link PrivilegeHandler#validatePassword(byte[])} *

    * *

    @@ -249,7 +249,7 @@ public interface PrivilegeHandler { * @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(String)} + * {@link PrivilegeHandler#validatePassword(byte[])} * * @throws AccessDeniedException * if the user for this certificate may not perform the action @@ -324,7 +324,7 @@ public interface PrivilegeHandler { * 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(String)}-method + * must meet the requirements of the {@link #validatePassword(byte[])}-method * * @return a {@link Certificate} with which this user may then perform actions * diff --git a/src/main/java/ch/eitchnet/privilege/model/Certificate.java b/src/main/java/ch/eitchnet/privilege/model/Certificate.java index 5b6bebfe0..afff0b2eb 100644 --- a/src/main/java/ch/eitchnet/privilege/model/Certificate.java +++ b/src/main/java/ch/eitchnet/privilege/model/Certificate.java @@ -30,7 +30,7 @@ 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, String)} + * {@link PrivilegeHandler#authenticate(String, byte[])} * * @author Robert von Burg */ @@ -62,11 +62,9 @@ public final class Certificate implements Serializable { * the users login name * @param authToken * the authentication token defining the users unique session and is a private field of this certificate. - * It corresponds with the authentication token on the {@link Session} * @param authPassword * the password to access the authentication token, this is not known to the client but set by the - * {@link PrivilegeHandler} on authentication. It corresponds with the authentication password on the - * {@link Session} + * {@link PrivilegeHandler} on authentication. * @param locale * the users {@link Locale} * @param propertyMap diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java b/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java index a30db711c..b4fb6e929 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java @@ -130,8 +130,8 @@ public class PrivilegeContainerModel { } /** - * @param name - * @param policyClass + * @param privilegeName + * @param policyClassName */ public void addPolicy(String privilegeName, String policyClassName) { @@ -186,4 +186,4 @@ public class PrivilegeContainerModel { builder.append("]"); return builder.toString(); } -} \ No newline at end of file +} diff --git a/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java b/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java index 3a360be94..818d1e4c6 100644 --- a/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java +++ b/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java @@ -41,7 +41,7 @@ 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(PrivilegeContext, Restrictable) + * @see ch.eitchnet.privilege.policy.PrivilegePolicy#validateAction(IPrivilege, Restrictable) */ @Override public void validateAction(IPrivilege privilege, Restrictable restrictable) { diff --git a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java index edc0490a2..ea2bb5058 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java +++ b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java @@ -45,7 +45,6 @@ import ch.eitchnet.utils.helper.StringHelper; /** * @author Robert von Burg - * */ public class PrivilegeModelSaxReader extends DefaultHandler { @@ -58,9 +57,6 @@ public class PrivilegeModelSaxReader extends DefaultHandler { private boolean insideUser; - /** - * - */ public PrivilegeModelSaxReader() { this.users = new ArrayList(); this.roles = new ArrayList(); From c4e6c5e65d4ac1d1bd63a096a01a56ae528f0272 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Mon, 12 Aug 2013 11:04:21 +0200 Subject: [PATCH 102/173] Refactored InitializationHelper so it can take an InputStream Also renamed it to PrivilegeInitializationHelper --- .../PrivilegeInitializationHelper.java} | 42 +++++++++++++++---- .../privilege/test/PrivilegeTest.java | 4 +- .../ch/eitchnet/privilege/test/XmlTest.java | 3 +- 3 files changed, 36 insertions(+), 13 deletions(-) rename src/main/java/ch/eitchnet/privilege/{xml/InitializationHelper.java => helper/PrivilegeInitializationHelper.java} (70%) diff --git a/src/main/java/ch/eitchnet/privilege/xml/InitializationHelper.java b/src/main/java/ch/eitchnet/privilege/helper/PrivilegeInitializationHelper.java similarity index 70% rename from src/main/java/ch/eitchnet/privilege/xml/InitializationHelper.java rename to src/main/java/ch/eitchnet/privilege/helper/PrivilegeInitializationHelper.java index e5d1a1a24..35529dde9 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/InitializationHelper.java +++ b/src/main/java/ch/eitchnet/privilege/helper/PrivilegeInitializationHelper.java @@ -17,9 +17,11 @@ * along with Privilege. If not, see . * */ -package ch.eitchnet.privilege.xml; +package ch.eitchnet.privilege.helper; import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; import java.util.Map; import org.slf4j.Logger; @@ -30,9 +32,9 @@ 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.helper.ClassHelper; import ch.eitchnet.privilege.model.internal.PrivilegeContainerModel; import ch.eitchnet.privilege.policy.PrivilegePolicy; +import ch.eitchnet.privilege.xml.PrivilegeConfigSaxReader; import ch.eitchnet.utils.helper.XmlHelper; /** @@ -41,9 +43,9 @@ import ch.eitchnet.utils.helper.XmlHelper; * * @author Robert von Burg */ -public class InitializationHelper { +public class PrivilegeInitializationHelper { - private static final Logger logger = LoggerFactory.getLogger(InitializationHelper.class); + private static final Logger logger = LoggerFactory.getLogger(PrivilegeInitializationHelper.class); /** * Initializes the {@link DefaultPrivilegeHandler} from the configuration file @@ -51,7 +53,8 @@ public class InitializationHelper { * @param privilegeXmlFile * a {@link File} reference to the XML file containing the configuration for Privilege * - * @return the {@link PrivilegeHandler} instance loaded from the configuration file + * @return the initialized {@link PrivilegeHandler} where the {@link EncryptionHandler} and + * {@link PersistenceHandler} are set and initialized as well */ public static PrivilegeHandler initializeFromXml(File privilegeXmlFile) { @@ -60,10 +63,31 @@ public class InitializationHelper { throw new PrivilegeException("Privilege file does not exist at path " + privilegeXmlFile.getAbsolutePath()); } + // delegate using input stream + try { + return initializeFromXml(new FileInputStream(privilegeXmlFile)); + } catch (Exception e) { + PrivilegeInitializationHelper.logger.error(e.getMessage(), e); + throw new PrivilegeException("Failed to load configuration from " + privilegeXmlFile.getAbsolutePath()); + } + } + + /** + * 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(privilegeXmlFile, xmlHandler); + XmlHelper.parseDocument(privilegeConfigInputStream, xmlHandler); // initialize encryption handler String encryptionHandlerClassName = containerModel.getEncryptionHandlerClassName(); @@ -72,7 +96,7 @@ public class InitializationHelper { try { encryptionHandler.initialize(parameterMap); } catch (Exception e) { - InitializationHelper.logger.error(e.getMessage(), e); + PrivilegeInitializationHelper.logger.error(e.getMessage(), e); throw new PrivilegeException("EncryptionHandler " + encryptionHandlerClassName + " could not be initialized"); } @@ -84,7 +108,7 @@ public class InitializationHelper { try { persistenceHandler.initialize(parameterMap); } catch (Exception e) { - InitializationHelper.logger.error(e.getMessage(), e); + PrivilegeInitializationHelper.logger.error(e.getMessage(), e); throw new PrivilegeException("PersistenceHandler " + persistenceHandlerClassName + " could not be initialized"); } @@ -96,7 +120,7 @@ public class InitializationHelper { try { privilegeHandler.initialize(parameterMap, encryptionHandler, persistenceHandler, policyMap); } catch (Exception e) { - InitializationHelper.logger.error(e.getMessage(), e); + PrivilegeInitializationHelper.logger.error(e.getMessage(), e); throw new PrivilegeException("PrivilegeHandler " + privilegeHandler.getClass().getName() + " could not be initialized"); } diff --git a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java index 06ee3ca96..d3e58bb41 100644 --- a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -36,6 +36,7 @@ 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.helper.PrivilegeInitializationHelper; import ch.eitchnet.privilege.model.Certificate; import ch.eitchnet.privilege.model.PrivilegeContext; import ch.eitchnet.privilege.model.PrivilegeRep; @@ -46,7 +47,6 @@ 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.privilege.xml.InitializationHelper; import ch.eitchnet.utils.helper.ArraysHelper; import ch.eitchnet.utils.helper.FileHelper; @@ -139,7 +139,7 @@ public class PrivilegeTest { File privilegeConfigFile = new File(pwd + "/config/Privilege.xml"); // initialize privilege - privilegeHandler = InitializationHelper.initializeFromXml(privilegeConfigFile); + privilegeHandler = PrivilegeInitializationHelper.initializeFromXml(privilegeConfigFile); } catch (Exception e) { logger.error(e.getMessage(), e); diff --git a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java index 23db59a65..13c684834 100644 --- a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java @@ -33,7 +33,6 @@ import java.util.Set; import junit.framework.Assert; -import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import org.slf4j.Logger; @@ -81,7 +80,7 @@ public class XmlTest { tmpDir.mkdirs(); } - @AfterClass + //@AfterClass public static void destroy() throws Exception { File tmpDir = new File("target/test"); From 874acc582824e6eb8d52c0059dfb4df8fa05b1ea Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Mon, 12 Aug 2013 17:00:05 +0200 Subject: [PATCH 103/173] fixed wrong namespace in PrivilegeModel.xsd --- src/main/resources/PrivilegeModel.xsd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/PrivilegeModel.xsd b/src/main/resources/PrivilegeModel.xsd index cab4d1d3b..5ec6fd17c 100644 --- a/src/main/resources/PrivilegeModel.xsd +++ b/src/main/resources/PrivilegeModel.xsd @@ -1,5 +1,5 @@ + xmlns:pr="http://www.eitchnet.ch/dev/privilege/PrivilegeModel" elementFormDefault="qualified"> From 31c6acec6c335468793a79fecec7767011716e1d Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 15 Sep 2013 21:57:42 +0200 Subject: [PATCH 104/173] [Minor] fixed failing test due to different formatting of xml writing --- .../java/ch/eitchnet/privilege/test/XmlTest.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java index 13c684834..9ff57422d 100644 --- a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java @@ -33,6 +33,7 @@ import java.util.Set; import junit.framework.Assert; +import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import org.slf4j.Logger; @@ -72,7 +73,8 @@ public class XmlTest { */ @BeforeClass public static void init() throws Exception { - destroy(); + + cleanUp(); File tmpDir = new File("target/test"); if (tmpDir.exists()) @@ -80,8 +82,8 @@ public class XmlTest { tmpDir.mkdirs(); } - //@AfterClass - public static void destroy() throws Exception { + @AfterClass + public static void cleanUp() throws Exception { File tmpDir = new File("target/test"); if (!tmpDir.exists()) @@ -135,7 +137,6 @@ public class XmlTest { 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); @@ -155,7 +156,7 @@ public class XmlTest { configSaxWriter.write(); String fileHash = StringHelper.getHexString(FileHelper.hashFileSha256(configFile)); - Assert.assertEquals("22D4BA39605D49C758184D9BD63BEAE5CCF8786F3DABBAB45CD9F59C2AFBCBD0", fileHash); + Assert.assertEquals("2ABD3442EEC8BCEC5BEE365AAB6DB2FD4E1789325425CB1E017E900582525685", fileHash); } @Test @@ -217,6 +218,6 @@ public class XmlTest { configSaxWriter.write(); String fileHash = StringHelper.getHexString(FileHelper.hashFileSha256(modelFile)); - Assert.assertEquals("9007F172BBD7BA51BA3E67199CE0AFCBC8645AF0AC02028ABE54BA6A2FC134B0", fileHash); + Assert.assertEquals("A2127D20A61E00BCDBB61569CD2B200C4F0F111C972BAC3B1E54DF3B2FCDC8BE", fileHash); } } From 3d8fa992d41cbe2ba330837a150e8b9384b9d6dc Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 21 Sep 2013 09:03:05 +0200 Subject: [PATCH 105/173] [Project] moved some project details to parent ch.eitchnet.parent This simplifies project setup and maintenance --- pom.xml | 127 ++++---------------------------------------------------- 1 file changed, 7 insertions(+), 120 deletions(-) diff --git a/pom.xml b/pom.xml index fdef06dff..6a32d001d 100644 --- a/pom.xml +++ b/pom.xml @@ -2,113 +2,34 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> 4.0.0 - ch.eitchnet + + ch.eitchnet + ch.eitchnet.parent + 0.0.1-SNAPSHOT + ../ch.eitchnet.parent/pom.xml + + ch.eitchnet.privilege jar 0.2.0-SNAPSHOT ch.eitchnet.privilege https://github.com/eitch/ch.eitchnet.privilege - - UTF-8 - - - - - 2011 - - - GNU Lesser General Public License - http://www.gnu.org/licenses/lgpl.html - repo - - - - eitchnet.ch - http://blog.eitchnet.ch - - - - eitch - Robert von Vurg - eitch@eitchnet.ch - http://blog.eitchnet.ch - eitchnet.ch - http://blog.eitchnet.ch - - architect - developer - - +1 - - http://localhost - - - - Github Issues https://github.com/eitch/ch.eitchnet.privilege/issues - - scm:git:https://github.com/eitch/ch.eitchnet.privilege.git scm:git:git@github.com:eitch/ch.eitchnet.privilege.git https://github.com/eitch/ch.eitchnet.privilege - - - - - deployment - Internal Releases - http://nexus.eitchnet.ch/content/repositories/releases/ - - - deployment - Internal Releases - http://nexus.eitchnet.ch/content/repositories/snapshots/ - - - - - junit - junit - 4.10 - test - ch.eitchnet ch.eitchnet.utils - 0.2.0-SNAPSHOT - - - org.slf4j - slf4j-api - 1.7.2 - - - org.slf4j - slf4j-log4j12 - 1.7.2 - test @@ -117,60 +38,26 @@ org.apache.maven.plugins maven-eclipse-plugin - 2.9 - - true - true - org.apache.maven.plugins maven-compiler-plugin - 3.0 - - 1.7 - 1.7 - org.apache.maven.plugins maven-source-plugin - 2.1.2 - - - attach-sources - verify - - jar-no-fork - - - org.apache.maven.plugins maven-jar-plugin - 2.4 - - - - true - true - - - - org.apache.maven.plugins maven-site-plugin - 2.3 - - UTF-8 - From a68c44c9fdf3212004a33b838bcc9b72a12f1e95 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 21 Sep 2013 09:11:34 +0200 Subject: [PATCH 106/173] [Project] moved some project details to parent ch.eitchnet.parent This simplifies project setup and maintenance --- pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.xml b/pom.xml index 6a32d001d..bf615f1ef 100644 --- a/pom.xml +++ b/pom.xml @@ -14,6 +14,7 @@ 0.2.0-SNAPSHOT ch.eitchnet.privilege https://github.com/eitch/ch.eitchnet.privilege + 2011 Github Issues From 6f0c623d20848d0c22579e8d653b654ebcd18af6 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 21 Sep 2013 10:07:31 +0200 Subject: [PATCH 107/173] [Project] added dependency of ch.eitchnet.utils:0.2.0-SNAPSHOT --- pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.xml b/pom.xml index bf615f1ef..bd92a91a2 100644 --- a/pom.xml +++ b/pom.xml @@ -31,6 +31,7 @@ ch.eitchnet ch.eitchnet.utils + 0.2.0-SNAPSHOT From bac94f6075afa4858f73929ecce0e533be96511a Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 27 Oct 2013 23:06:19 +0100 Subject: [PATCH 108/173] [Minor] set parent to 0.1.0-SNAPSHOT --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index bd92a91a2..110c03a32 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ ch.eitchnet ch.eitchnet.parent - 0.0.1-SNAPSHOT + 0.1.0-SNAPSHOT ../ch.eitchnet.parent/pom.xml From 33d7fb2e3367eba058dd1062eaed1b3fc14ec5f4 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Mon, 28 Oct 2013 18:56:38 +0100 Subject: [PATCH 109/173] [Minor] cleaned up .gitignore --- .gitignore | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 8d6b8f627..2945707a2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,4 @@ -bin -dist - -# Maven target target/ - -# Eclipse settings .classpath .project -.settings/ - +.settings/ \ No newline at end of file From f2fd1233d961eb9cddfa2a40f589d215f52476f3 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Mon, 28 Oct 2013 22:00:33 +0100 Subject: [PATCH 110/173] [Minor] renamed validateIsPrivilegeAdmin to assertIsPrivilegeAdmin --- .../handler/DefaultPrivilegeHandler.java | 30 +++++++++---------- .../privilege/handler/PrivilegeHandler.java | 2 +- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 8f1a7aeb2..31014d2a5 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -55,7 +55,7 @@ import ch.eitchnet.privilege.policy.PrivilegePolicy; * 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 #validateIsPrivilegeAdmin(Certificate)}
    • + * 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
    • @@ -273,7 +273,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { public void addOrReplaceRole(Certificate certificate, RoleRep roleRep) { // validate who is doing this - validateIsPrivilegeAdmin(certificate); + assertIsPrivilegeAdmin(certificate); // create new role from RoleRep Role role = new Role(roleRep); @@ -294,7 +294,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { try { // validate who is doing this - validateIsPrivilegeAdmin(certificate); + assertIsPrivilegeAdmin(certificate); String passwordHash = null; if (password != null) { @@ -323,7 +323,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { public void addOrReplacePrivilegeOnRole(Certificate certificate, String roleName, PrivilegeRep privilegeRep) { // validate who is doing this - validateIsPrivilegeAdmin(certificate); + assertIsPrivilegeAdmin(certificate); // validate PrivilegeRep privilegeRep.validate(); @@ -363,7 +363,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { public void addRoleToUser(Certificate certificate, String username, String roleName) { // validate who is doing this - validateIsPrivilegeAdmin(certificate); + assertIsPrivilegeAdmin(certificate); // get user User user = this.persistenceHandler.getUser(username); @@ -398,7 +398,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { public void removePrivilegeFromRole(Certificate certificate, String roleName, String privilegeName) { // validate who is doing this - validateIsPrivilegeAdmin(certificate); + assertIsPrivilegeAdmin(certificate); // get role Role role = this.persistenceHandler.getRole(roleName); @@ -430,7 +430,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { public RoleRep removeRole(Certificate certificate, String roleName) { // validate who is doing this - validateIsPrivilegeAdmin(certificate); + assertIsPrivilegeAdmin(certificate); // delegate role removal to persistence handler Role removedRole = this.persistenceHandler.removeRole(roleName); @@ -446,7 +446,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { public void removeRoleFromUser(Certificate certificate, String username, String roleName) { // validate who is doing this - validateIsPrivilegeAdmin(certificate); + assertIsPrivilegeAdmin(certificate); // get User User user = this.persistenceHandler.getUser(username); @@ -475,7 +475,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { public UserRep removeUser(Certificate certificate, String username) { // validate who is doing this - validateIsPrivilegeAdmin(certificate); + assertIsPrivilegeAdmin(certificate); // delegate user removal to persistence handler User removedUser = this.persistenceHandler.removeUser(username); @@ -493,7 +493,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { public void setUserLocale(Certificate certificate, String username, Locale locale) { // validate who is doing this - validateIsPrivilegeAdmin(certificate); + assertIsPrivilegeAdmin(certificate); // get User User user = this.persistenceHandler.getUser(username); @@ -513,7 +513,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { public void setUserName(Certificate certificate, String username, String firstname, String surname) { // validate who is doing this - validateIsPrivilegeAdmin(certificate); + assertIsPrivilegeAdmin(certificate); // get User User user = this.persistenceHandler.getUser(username); @@ -546,7 +546,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } else { // otherwise validate the the certificate is for a privilege admin - validateIsPrivilegeAdmin(certificate); + assertIsPrivilegeAdmin(certificate); } // get User @@ -586,7 +586,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { public void setUserState(Certificate certificate, String username, UserState state) { // validate who is doing this - validateIsPrivilegeAdmin(certificate); + assertIsPrivilegeAdmin(certificate); // get User User user = this.persistenceHandler.getUser(username); @@ -785,7 +785,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } @Override - public void validateIsPrivilegeAdmin(Certificate certificate) throws PrivilegeException { + public void assertIsPrivilegeAdmin(Certificate certificate) throws PrivilegeException { // validate certificate isCertificateValid(certificate); @@ -826,7 +826,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { public boolean persist(Certificate certificate) { // validate who is doing this - validateIsPrivilegeAdmin(certificate); + assertIsPrivilegeAdmin(certificate); return this.persistenceHandler.persist(); } diff --git a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java index 5c0e397bd..eac3b4422 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java @@ -389,7 +389,7 @@ public interface PrivilegeHandler { * @throws AccessDeniedException * if the user does not not have admin privileges */ - public void validateIsPrivilegeAdmin(Certificate certificate) throws AccessDeniedException; + public void assertIsPrivilegeAdmin(Certificate certificate) throws AccessDeniedException; /** * Validate that the given password meets certain requirements. What these requirements are is a decision made by From 01884029b84f4555a8b0fc4afc0ab46b4ed1d0e8 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 15 Nov 2013 18:44:55 +0100 Subject: [PATCH 111/173] [Minor] removed unused file config/Model.xml --- config/Model.xml | 55 ------------------------------------------------ 1 file changed, 55 deletions(-) delete mode 100644 config/Model.xml diff --git a/config/Model.xml b/config/Model.xml deleted file mode 100644 index 74dabc4f4..000000000 --- a/config/Model.xml +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - - - - - true - - - - - - true - - - true - - - - - - - - - Application - Administrator - ENABLED - en_GB - - PrivilegeAdmin - AppUser - - - - - - - - - System User - Administrator - SYSTEM - en_GB - - system_admin_privileges - - - - - - - From a8b0f378346cb5650fd4e0e05bc4bff6b0ee1de9 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 16 Nov 2013 01:08:27 +0100 Subject: [PATCH 112/173] [Minor] Changed exceptions to contain cause and code clean up --- .../handler/XmlPersistenceHandler.java | 62 ++++++++++--------- .../helper/PrivilegeInitializationHelper.java | 33 +++++----- .../privilege/model/PrivilegeContext.java | 2 +- 3 files changed, 51 insertions(+), 46 deletions(-) diff --git a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java index 84ddf5435..f3db54aba 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java @@ -20,6 +20,7 @@ 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; @@ -56,7 +57,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { private Map parameterMap; - private String modelPath; + private File modelPath; @Override public List getAllUsers() { @@ -125,22 +126,35 @@ public class XmlPersistenceHandler implements PersistenceHandler { String basePath = this.parameterMap.get(XmlConstants.XML_PARAM_BASE_PATH); File basePathF = new File(basePath); if (!basePathF.exists() && !basePathF.isDirectory()) { - throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " - + XmlConstants.XML_PARAM_BASE_PATH + " is invalid"); + 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 model file name String modelFileName = this.parameterMap.get(XmlConstants.XML_PARAM_MODEL_FILE); if (modelFileName == null || modelFileName.isEmpty()) { - throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " - + XmlConstants.XML_PARAM_MODEL_FILE + " is invalid"); + 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_MODEL_FILE); + throw new PrivilegeException(msg); + } + + // validate file exists + String modelPathS = basePath + "/" + modelFileName; //$NON-NLS-1$ + File modelPath = new File(modelPathS); + if (!modelPath.exists()) { + String msg = "[{0}] Defined parameter {1} is invalid as model file does not exist at path {2}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, PersistenceHandler.class.getName(), XmlConstants.XML_PARAM_MODEL_FILE, + modelPath.getAbsolutePath()); + throw new PrivilegeException(msg); } // save path to model - this.modelPath = basePath + "/" + modelFileName; + this.modelPath = modelPath; if (reload()) - XmlPersistenceHandler.logger.info("Privilege Data loaded."); + logger.info("Privilege Data loaded."); //$NON-NLS-1$ } /** @@ -152,22 +166,14 @@ public class XmlPersistenceHandler implements PersistenceHandler { @Override public boolean reload() { - // validate file exists - File modelsFile = new File(this.modelPath); - if (!modelsFile.exists()) { - throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " - + XmlConstants.XML_PARAM_MODEL_FILE + " is invalid as models file does not exist at path " - + modelsFile.getAbsolutePath()); - } - this.roleMap = Collections.synchronizedMap(new HashMap()); this.userMap = Collections.synchronizedMap(new HashMap()); // parse models xml file to XML document PrivilegeModelSaxReader xmlHandler = new PrivilegeModelSaxReader(); - XmlHelper.parseDocument(modelsFile, xmlHandler); + XmlHelper.parseDocument(this.modelPath, xmlHandler); - this.modelsFileDate = modelsFile.lastModified(); + this.modelsFileDate = this.modelPath.lastModified(); // ROLES List roles = xmlHandler.getRoles(); @@ -184,8 +190,8 @@ public class XmlPersistenceHandler implements PersistenceHandler { this.userMapDirty = false; this.roleMapDirty = false; - XmlPersistenceHandler.logger.info("Read " + this.userMap.size() + " Users"); - XmlPersistenceHandler.logger.info("Read " + this.roleMap.size() + " Roles"); + 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 we have a user with PrivilegeAdmin access boolean privilegeAdminExists = false; @@ -198,8 +204,9 @@ public class XmlPersistenceHandler implements PersistenceHandler { } if (!privilegeAdminExists) { - XmlPersistenceHandler.logger.warn("No User with role '" + PrivilegeHandler.PRIVILEGE_ADMIN_ROLE - + "' exists. Privilege modifications will not be possible!"); + String msg = "No User with role ''{0}'' exists. Privilege modifications will not be possible!"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, PrivilegeHandler.PRIVILEGE_ADMIN_ROLE); + logger.warn(msg); } return true; @@ -214,21 +221,20 @@ public class XmlPersistenceHandler implements PersistenceHandler { // get models file name String modelFileName = this.parameterMap.get(XmlConstants.XML_PARAM_MODEL_FILE); if (modelFileName == null || modelFileName.isEmpty()) { - throw new PrivilegeException("[" + PersistenceHandler.class.getName() + "] Defined parameter " - + XmlConstants.XML_PARAM_MODEL_FILE + " is invalid"); + String msg = "[{0}] Defined parameter {1} is invalid"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, PersistenceHandler.class.getName(), XmlConstants.XML_PARAM_MODEL_FILE); + throw new PrivilegeException(msg); } // get model file - File modelFile = new File(this.modelPath); - boolean modelFileUnchanged = modelFile.exists() && modelFile.lastModified() == this.modelsFileDate; + boolean modelFileUnchanged = this.modelPath.exists() && this.modelPath.lastModified() == this.modelsFileDate; if (modelFileUnchanged && !this.roleMapDirty && !this.userMapDirty) { - XmlPersistenceHandler.logger - .warn("Not persisting as current file is unchanged and model data is not dirty"); + logger.warn("Not persisting as current file is unchanged and model data is not dirty"); //$NON-NLS-1$ return false; } // delegate writing - PrivilegeModelDomWriter modelWriter = new PrivilegeModelDomWriter(getAllUsers(), getAllRoles(), modelFile); + PrivilegeModelDomWriter modelWriter = new PrivilegeModelDomWriter(getAllUsers(), getAllRoles(), this.modelPath); modelWriter.write(); // reset dirty states diff --git a/src/main/java/ch/eitchnet/privilege/helper/PrivilegeInitializationHelper.java b/src/main/java/ch/eitchnet/privilege/helper/PrivilegeInitializationHelper.java index 35529dde9..0dde63e8c 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/PrivilegeInitializationHelper.java +++ b/src/main/java/ch/eitchnet/privilege/helper/PrivilegeInitializationHelper.java @@ -22,11 +22,9 @@ 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 org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import ch.eitchnet.privilege.base.PrivilegeException; import ch.eitchnet.privilege.handler.DefaultPrivilegeHandler; import ch.eitchnet.privilege.handler.EncryptionHandler; @@ -45,8 +43,6 @@ import ch.eitchnet.utils.helper.XmlHelper; */ public class PrivilegeInitializationHelper { - private static final Logger logger = LoggerFactory.getLogger(PrivilegeInitializationHelper.class); - /** * Initializes the {@link DefaultPrivilegeHandler} from the configuration file * @@ -60,15 +56,18 @@ public class PrivilegeInitializationHelper { // make sure file exists if (!privilegeXmlFile.exists()) { - throw new PrivilegeException("Privilege file does not exist at path " + privilegeXmlFile.getAbsolutePath()); + 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 { return initializeFromXml(new FileInputStream(privilegeXmlFile)); } catch (Exception e) { - PrivilegeInitializationHelper.logger.error(e.getMessage(), e); - throw new PrivilegeException("Failed to load configuration from " + privilegeXmlFile.getAbsolutePath()); + String msg = "Failed to load configuration from {0}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, privilegeXmlFile.getAbsolutePath()); + throw new PrivilegeException(msg, e); } } @@ -96,9 +95,9 @@ public class PrivilegeInitializationHelper { try { encryptionHandler.initialize(parameterMap); } catch (Exception e) { - PrivilegeInitializationHelper.logger.error(e.getMessage(), e); - throw new PrivilegeException("EncryptionHandler " + encryptionHandlerClassName - + " could not be initialized"); + String msg = "EncryptionHandler {0} could not be initialized"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, encryptionHandlerClassName); + throw new PrivilegeException(msg, e); } // initialize persistence handler @@ -108,9 +107,9 @@ public class PrivilegeInitializationHelper { try { persistenceHandler.initialize(parameterMap); } catch (Exception e) { - PrivilegeInitializationHelper.logger.error(e.getMessage(), e); - throw new PrivilegeException("PersistenceHandler " + persistenceHandlerClassName - + " could not be initialized"); + String msg = "PersistenceHandler {0} could not be initialized"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, persistenceHandlerClassName); + throw new PrivilegeException(msg, e); } // initialize privilege handler @@ -120,9 +119,9 @@ public class PrivilegeInitializationHelper { try { privilegeHandler.initialize(parameterMap, encryptionHandler, persistenceHandler, policyMap); } catch (Exception e) { - PrivilegeInitializationHelper.logger.error(e.getMessage(), e); - throw new PrivilegeException("PrivilegeHandler " + privilegeHandler.getClass().getName() - + " could not be initialized"); + 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/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java b/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java index 661434a18..00a27d189 100644 --- a/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java +++ b/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java @@ -96,7 +96,7 @@ public class PrivilegeContext { */ public void validateAction(Restrictable restrictable) throws AccessDeniedException, PrivilegeException { - // the the privilege for the restrictable + // the privilege for the restrictable String privilegeName = restrictable.getPrivilegeName(); IPrivilege privilege = this.privileges.get(privilegeName); if (privilege == null) { From 036c725a52e90d0642cfe649482c371e16ca3649 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 15 Dec 2013 13:38:29 +0100 Subject: [PATCH 113/173] [Project] Changed all licence references to Apache License 2.0 --- COPYING | 674 ------------------ COPYING.LESSER | 165 ----- LICENSE | 202 ++++++ config/Privilege.xml | 23 - config/PrivilegeModel.xml | 6 - docs/TODO | 5 - .../privilege/base/AccessDeniedException.java | 26 +- .../privilege/base/PrivilegeException.java | 26 +- .../handler/DefaultEncryptionHandler.java | 26 +- .../handler/DefaultPrivilegeHandler.java | 26 +- .../privilege/handler/EncryptionHandler.java | 26 +- .../privilege/handler/PersistenceHandler.java | 26 +- .../privilege/handler/PrivilegeHandler.java | 26 +- .../privilege/handler/SystemUserAction.java | 28 +- .../handler/XmlPersistenceHandler.java | 26 +- .../helper/BootstrapConfigurationHelper.java | 26 +- .../privilege/helper/ClassHelper.java | 26 +- .../eitchnet/privilege/helper/HashHelper.java | 26 +- .../privilege/helper/PasswordCreaterUI.java | 26 +- .../privilege/helper/PasswordCreator.java | 26 +- .../helper/PrivilegeInitializationHelper.java | 26 +- .../privilege/helper/XmlConstants.java | 26 +- .../privilege/i18n/PrivilegeMessages.java | 32 +- .../eitchnet/privilege/model/Certificate.java | 26 +- .../eitchnet/privilege/model/IPrivilege.java | 32 +- .../privilege/model/PrivilegeContext.java | 32 +- .../privilege/model/PrivilegeRep.java | 26 +- .../privilege/model/Restrictable.java | 26 +- .../ch/eitchnet/privilege/model/RoleRep.java | 26 +- .../ch/eitchnet/privilege/model/UserRep.java | 26 +- .../eitchnet/privilege/model/UserState.java | 26 +- .../internal/PrivilegeContainerModel.java | 32 +- .../model/internal/PrivilegeImpl.java | 26 +- .../privilege/model/internal/Role.java | 26 +- .../privilege/model/internal/User.java | 26 +- .../privilege/policy/DefaultPrivilege.java | 26 +- .../privilege/policy/PrivilegePolicy.java | 26 +- .../eitchnet/privilege/xml/ElementParser.java | 32 +- .../privilege/xml/ElementParserAdapter.java | 32 +- .../xml/PrivilegeConfigDomWriter.java | 32 +- .../xml/PrivilegeConfigSaxReader.java | 32 +- .../xml/PrivilegeModelDomWriter.java | 32 +- .../xml/PrivilegeModelSaxReader.java | 32 +- src/main/resources/Privilege.xsd | 31 +- src/main/resources/PrivilegeModel.xsd | 31 +- .../privilege/test/PrivilegeTest.java | 26 +- .../ch/eitchnet/privilege/test/XmlTest.java | 32 +- .../test/model/TestRestrictable.java | 26 +- .../test/model/TestSystemRestrictable.java | 26 +- .../test/model/TestSystemUserAction.java | 28 +- .../test/model/TestSystemUserActionDeny.java | 28 +- 51 files changed, 726 insertions(+), 1601 deletions(-) delete mode 100644 COPYING delete mode 100644 COPYING.LESSER create mode 100644 LICENSE diff --git a/COPYING b/COPYING deleted file mode 100644 index 94a9ed024..000000000 --- a/COPYING +++ /dev/null @@ -1,674 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. diff --git a/COPYING.LESSER b/COPYING.LESSER deleted file mode 100644 index 65c5ca88a..000000000 --- a/COPYING.LESSER +++ /dev/null @@ -1,165 +0,0 @@ - GNU LESSER GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - - This version of the GNU Lesser General Public License incorporates -the terms and conditions of version 3 of the GNU General Public -License, supplemented by the additional permissions listed below. - - 0. Additional Definitions. - - As used herein, "this License" refers to version 3 of the GNU Lesser -General Public License, and the "GNU GPL" refers to version 3 of the GNU -General Public License. - - "The Library" refers to a covered work governed by this License, -other than an Application or a Combined Work as defined below. - - An "Application" is any work that makes use of an interface provided -by the Library, but which is not otherwise based on the Library. -Defining a subclass of a class defined by the Library is deemed a mode -of using an interface provided by the Library. - - A "Combined Work" is a work produced by combining or linking an -Application with the Library. The particular version of the Library -with which the Combined Work was made is also called the "Linked -Version". - - The "Minimal Corresponding Source" for a Combined Work means the -Corresponding Source for the Combined Work, excluding any source code -for portions of the Combined Work that, considered in isolation, are -based on the Application, and not on the Linked Version. - - The "Corresponding Application Code" for a Combined Work means the -object code and/or source code for the Application, including any data -and utility programs needed for reproducing the Combined Work from the -Application, but excluding the System Libraries of the Combined Work. - - 1. Exception to Section 3 of the GNU GPL. - - You may convey a covered work under sections 3 and 4 of this License -without being bound by section 3 of the GNU GPL. - - 2. Conveying Modified Versions. - - If you modify a copy of the Library, and, in your modifications, a -facility refers to a function or data to be supplied by an Application -that uses the facility (other than as an argument passed when the -facility is invoked), then you may convey a copy of the modified -version: - - a) under this License, provided that you make a good faith effort to - ensure that, in the event an Application does not supply the - function or data, the facility still operates, and performs - whatever part of its purpose remains meaningful, or - - b) under the GNU GPL, with none of the additional permissions of - this License applicable to that copy. - - 3. Object Code Incorporating Material from Library Header Files. - - The object code form of an Application may incorporate material from -a header file that is part of the Library. You may convey such object -code under terms of your choice, provided that, if the incorporated -material is not limited to numerical parameters, data structure -layouts and accessors, or small macros, inline functions and templates -(ten or fewer lines in length), you do both of the following: - - a) Give prominent notice with each copy of the object code that the - Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the object code with a copy of the GNU GPL and this license - document. - - 4. Combined Works. - - You may convey a Combined Work under terms of your choice that, -taken together, effectively do not restrict modification of the -portions of the Library contained in the Combined Work and reverse -engineering for debugging such modifications, if you also do each of -the following: - - a) Give prominent notice with each copy of the Combined Work that - the Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the Combined Work with a copy of the GNU GPL and this license - document. - - c) For a Combined Work that displays copyright notices during - execution, include the copyright notice for the Library among - these notices, as well as a reference directing the user to the - copies of the GNU GPL and this license document. - - d) Do one of the following: - - 0) Convey the Minimal Corresponding Source under the terms of this - License, and the Corresponding Application Code in a form - suitable for, and under terms that permit, the user to - recombine or relink the Application with a modified version of - the Linked Version to produce a modified Combined Work, in the - manner specified by section 6 of the GNU GPL for conveying - Corresponding Source. - - 1) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (a) uses at run time - a copy of the Library already present on the user's computer - system, and (b) will operate properly with a modified version - of the Library that is interface-compatible with the Linked - Version. - - e) Provide Installation Information, but only if you would otherwise - be required to provide such information under section 6 of the - GNU GPL, and only to the extent that such information is - necessary to install and execute a modified version of the - Combined Work produced by recombining or relinking the - Application with a modified version of the Linked Version. (If - you use option 4d0, the Installation Information must accompany - the Minimal Corresponding Source and Corresponding Application - Code. If you use option 4d1, you must provide the Installation - Information in the manner specified by section 6 of the GNU GPL - for conveying Corresponding Source.) - - 5. Combined Libraries. - - You may place library facilities that are a work based on the -Library side by side in a single library together with other library -facilities that are not Applications and are not covered by this -License, and convey such a combined library under terms of your -choice, if you do both of the following: - - a) Accompany the combined library with a copy of the same work based - on the Library, uncombined with any other library facilities, - conveyed under the terms of this License. - - b) Give prominent notice with the combined library that part of it - is a work based on the Library, and explaining where to find the - accompanying uncombined form of the same work. - - 6. Revised Versions of the GNU Lesser General Public License. - - The Free Software Foundation may publish revised and/or new versions -of the GNU Lesser General Public License from time to time. Such new -versions will be similar in spirit to the present version, but may -differ in detail to address new problems or concerns. - - Each version is given a distinguishing version number. If the -Library as you received it specifies that a certain numbered version -of the GNU Lesser General Public License "or any later version" -applies to it, you have the option of following the terms and -conditions either of that published version or of any later version -published by the Free Software Foundation. If the Library as you -received it does not specify a version number of the GNU Lesser -General Public License, you may choose any version of the GNU Lesser -General Public License ever published by the Free Software Foundation. - - If the Library as you received it specifies that a proxy can decide -whether future versions of the GNU Lesser General Public License shall -apply, that proxy's public statement of acceptance of any version is -permanent authorization for you to choose that version for the -Library. diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/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/config/Privilege.xml b/config/Privilege.xml index 20eda653e..2c523ead3 100644 --- a/config/Privilege.xml +++ b/config/Privilege.xml @@ -1,27 +1,4 @@ - - diff --git a/config/PrivilegeModel.xml b/config/PrivilegeModel.xml index a58e6830c..2d1659459 100644 --- a/config/PrivilegeModel.xml +++ b/config/PrivilegeModel.xml @@ -1,10 +1,4 @@ - diff --git a/docs/TODO b/docs/TODO index 319d89b1f..e6c36ace6 100644 --- a/docs/TODO +++ b/docs/TODO @@ -4,13 +4,8 @@ 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 -- Re-think the PrivilegePolicy and its - actionAllowed(Role, Privilege, Restrictable)-method. Maybe the Privilege - argument is not needed, as this should be on the Role anyhow... - - i18n for any messages and exceptions! - Finish the JavaDoc - Set up a website =) - diff --git a/src/main/java/ch/eitchnet/privilege/base/AccessDeniedException.java b/src/main/java/ch/eitchnet/privilege/base/AccessDeniedException.java index 0c8fa5283..2a14841d7 100644 --- a/src/main/java/ch/eitchnet/privilege/base/AccessDeniedException.java +++ b/src/main/java/ch/eitchnet/privilege/base/AccessDeniedException.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2010 - 2012 + * Copyright 2013 Robert von Burg * - * This file is part of Privilege. - * - * Privilege is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * 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; diff --git a/src/main/java/ch/eitchnet/privilege/base/PrivilegeException.java b/src/main/java/ch/eitchnet/privilege/base/PrivilegeException.java index dc254418a..9d34cd7ed 100644 --- a/src/main/java/ch/eitchnet/privilege/base/PrivilegeException.java +++ b/src/main/java/ch/eitchnet/privilege/base/PrivilegeException.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2010 - 2012 + * Copyright 2013 Robert von Burg * - * This file is part of Privilege. - * - * Privilege is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * 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; diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java index 237fee200..792e6b9bb 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2010 - 2012 + * Copyright 2013 Robert von Burg * - * This file is part of Privilege. - * - * Privilege is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * 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; diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 31014d2a5..efdfd9f2d 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2010 - 2012 + * Copyright 2013 Robert von Burg * - * This file is part of Privilege. - * - * Privilege is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * 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; diff --git a/src/main/java/ch/eitchnet/privilege/handler/EncryptionHandler.java b/src/main/java/ch/eitchnet/privilege/handler/EncryptionHandler.java index f6b734213..186789f4d 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/EncryptionHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/EncryptionHandler.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2010 - 2012 + * Copyright 2013 Robert von Burg * - * This file is part of Privilege. - * - * Privilege is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * 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; diff --git a/src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java b/src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java index a7dcc8b43..85b009a33 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2010 - 2012 + * Copyright 2013 Robert von Burg * - * This file is part of Privilege. - * - * Privilege is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * 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; diff --git a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java index eac3b4422..52832c2c8 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2010 - 2012 + * Copyright 2013 Robert von Burg * - * This file is part of Privilege. - * - * Privilege is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * 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; diff --git a/src/main/java/ch/eitchnet/privilege/handler/SystemUserAction.java b/src/main/java/ch/eitchnet/privilege/handler/SystemUserAction.java index 724d8a32d..caf484f8f 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/SystemUserAction.java +++ b/src/main/java/ch/eitchnet/privilege/handler/SystemUserAction.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2012 - * - * This file is part of ??????????????? - * - * ?????????????? is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with ????????????????. If not, see . + * 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; diff --git a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java index f3db54aba..d94c6b246 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2010 - 2012 + * Copyright 2013 Robert von Burg * - * This file is part of Privilege. - * - * Privilege is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * 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; diff --git a/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java b/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java index 31935e3ab..c6a052942 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java +++ b/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2010 - 2012 + * Copyright 2013 Robert von Burg * - * This file is part of Privilege. - * - * Privilege is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * 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; diff --git a/src/main/java/ch/eitchnet/privilege/helper/ClassHelper.java b/src/main/java/ch/eitchnet/privilege/helper/ClassHelper.java index 5852805d8..cad6e5fb5 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/ClassHelper.java +++ b/src/main/java/ch/eitchnet/privilege/helper/ClassHelper.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2010 - 2012 + * Copyright 2013 Robert von Burg * - * This file is part of Privilege. - * - * Privilege is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * 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; diff --git a/src/main/java/ch/eitchnet/privilege/helper/HashHelper.java b/src/main/java/ch/eitchnet/privilege/helper/HashHelper.java index c038740bd..01fef9e5f 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/HashHelper.java +++ b/src/main/java/ch/eitchnet/privilege/helper/HashHelper.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2010 - 2012 + * Copyright 2013 Robert von Burg * - * This file is part of Privilege. - * - * Privilege is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * 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; diff --git a/src/main/java/ch/eitchnet/privilege/helper/PasswordCreaterUI.java b/src/main/java/ch/eitchnet/privilege/helper/PasswordCreaterUI.java index 122ba4011..fe58b86f5 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/PasswordCreaterUI.java +++ b/src/main/java/ch/eitchnet/privilege/helper/PasswordCreaterUI.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2010 - 2012 + * Copyright 2013 Robert von Burg * - * This file is part of Privilege. - * - * Privilege is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * 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; diff --git a/src/main/java/ch/eitchnet/privilege/helper/PasswordCreator.java b/src/main/java/ch/eitchnet/privilege/helper/PasswordCreator.java index 8430e9be0..6285b4d5a 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/PasswordCreator.java +++ b/src/main/java/ch/eitchnet/privilege/helper/PasswordCreator.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2010 - 2012 + * Copyright 2013 Robert von Burg * - * This file is part of Privilege. - * - * Privilege is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * 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; diff --git a/src/main/java/ch/eitchnet/privilege/helper/PrivilegeInitializationHelper.java b/src/main/java/ch/eitchnet/privilege/helper/PrivilegeInitializationHelper.java index 0dde63e8c..435f4faec 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/PrivilegeInitializationHelper.java +++ b/src/main/java/ch/eitchnet/privilege/helper/PrivilegeInitializationHelper.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2010 - 2012 + * Copyright 2013 Robert von Burg * - * This file is part of Privilege. - * - * Privilege is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * 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; diff --git a/src/main/java/ch/eitchnet/privilege/helper/XmlConstants.java b/src/main/java/ch/eitchnet/privilege/helper/XmlConstants.java index 3a9a16bc3..068fc01a7 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/XmlConstants.java +++ b/src/main/java/ch/eitchnet/privilege/helper/XmlConstants.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2010 - 2012 + * Copyright 2013 Robert von Burg * - * This file is part of Privilege. - * - * Privilege is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * 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; diff --git a/src/main/java/ch/eitchnet/privilege/i18n/PrivilegeMessages.java b/src/main/java/ch/eitchnet/privilege/i18n/PrivilegeMessages.java index da80d9f63..5b39df545 100644 --- a/src/main/java/ch/eitchnet/privilege/i18n/PrivilegeMessages.java +++ b/src/main/java/ch/eitchnet/privilege/i18n/PrivilegeMessages.java @@ -1,23 +1,17 @@ /* - * Copyright (c) 2012, Robert von Burg - * - * All rights reserved. - * - * This file is part of the XXX. - * - * XXX is free software: you can redistribute - * it and/or modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the License, - * or (at your option) any later version. - * - * XXX is distributed in the hope that it will - * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with XXX. If not, see - * . + * 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; diff --git a/src/main/java/ch/eitchnet/privilege/model/Certificate.java b/src/main/java/ch/eitchnet/privilege/model/Certificate.java index afff0b2eb..28ea2420f 100644 --- a/src/main/java/ch/eitchnet/privilege/model/Certificate.java +++ b/src/main/java/ch/eitchnet/privilege/model/Certificate.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2010 - 2012 + * Copyright 2013 Robert von Burg * - * This file is part of Privilege. - * - * Privilege is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * 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; diff --git a/src/main/java/ch/eitchnet/privilege/model/IPrivilege.java b/src/main/java/ch/eitchnet/privilege/model/IPrivilege.java index f9ddbe93f..3d22e7b34 100644 --- a/src/main/java/ch/eitchnet/privilege/model/IPrivilege.java +++ b/src/main/java/ch/eitchnet/privilege/model/IPrivilege.java @@ -1,23 +1,17 @@ /* - * Copyright (c) 2012, Robert von Burg - * - * All rights reserved. - * - * This file is part of the XXX. - * - * XXX is free software: you can redistribute - * it and/or modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the License, - * or (at your option) any later version. - * - * XXX is distributed in the hope that it will - * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with XXX. If not, see - * . + * 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; diff --git a/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java b/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java index 00a27d189..3889bfe11 100644 --- a/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java +++ b/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java @@ -1,23 +1,17 @@ /* - * Copyright (c) 2012, Robert von Burg - * - * All rights reserved. - * - * This file is part of the XXX. - * - * XXX is free software: you can redistribute - * it and/or modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the License, - * or (at your option) any later version. - * - * XXX is distributed in the hope that it will - * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with XXX. If not, see - * . + * 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; diff --git a/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java b/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java index b2a316c5b..10c20da2e 100644 --- a/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java +++ b/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2010 - 2012 + * Copyright 2013 Robert von Burg * - * This file is part of Privilege. - * - * Privilege is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * 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; diff --git a/src/main/java/ch/eitchnet/privilege/model/Restrictable.java b/src/main/java/ch/eitchnet/privilege/model/Restrictable.java index 99795bb9e..87f889818 100644 --- a/src/main/java/ch/eitchnet/privilege/model/Restrictable.java +++ b/src/main/java/ch/eitchnet/privilege/model/Restrictable.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2010 - 2012 + * Copyright 2013 Robert von Burg * - * This file is part of Privilege. - * - * Privilege is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * 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; diff --git a/src/main/java/ch/eitchnet/privilege/model/RoleRep.java b/src/main/java/ch/eitchnet/privilege/model/RoleRep.java index dfee0572c..9a4fe7548 100644 --- a/src/main/java/ch/eitchnet/privilege/model/RoleRep.java +++ b/src/main/java/ch/eitchnet/privilege/model/RoleRep.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2010 - 2012 + * Copyright 2013 Robert von Burg * - * This file is part of Privilege. - * - * Privilege is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * 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; diff --git a/src/main/java/ch/eitchnet/privilege/model/UserRep.java b/src/main/java/ch/eitchnet/privilege/model/UserRep.java index 118cb727c..2dd875b40 100644 --- a/src/main/java/ch/eitchnet/privilege/model/UserRep.java +++ b/src/main/java/ch/eitchnet/privilege/model/UserRep.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2010 - 2012 + * Copyright 2013 Robert von Burg * - * This file is part of Privilege. - * - * Privilege is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * 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; diff --git a/src/main/java/ch/eitchnet/privilege/model/UserState.java b/src/main/java/ch/eitchnet/privilege/model/UserState.java index c34782e1b..4922ff67e 100644 --- a/src/main/java/ch/eitchnet/privilege/model/UserState.java +++ b/src/main/java/ch/eitchnet/privilege/model/UserState.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2010 - 2012 + * Copyright 2013 Robert von Burg * - * This file is part of Privilege. - * - * Privilege is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * 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; diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java b/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java index b4fb6e929..39aabc5ae 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java @@ -1,23 +1,17 @@ /* - * Copyright (c) 2012, Robert von Burg - * - * All rights reserved. - * - * This file is part of the Privilege. - * - * Privilege is free software: you can redistribute - * it and/or modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the License, - * or (at your option) any later version. - * - * Privilege is distributed in the hope that it will - * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Privilege. If not, see - * . + * 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; diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeImpl.java b/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeImpl.java index c32623f41..8b28c442d 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeImpl.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeImpl.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2010 - 2012 + * Copyright 2013 Robert von Burg * - * This file is part of Privilege. - * - * Privilege is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * 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; diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/Role.java b/src/main/java/ch/eitchnet/privilege/model/internal/Role.java index 9e85710de..91ddbeb6a 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/Role.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/Role.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2010 - 2012 + * Copyright 2013 Robert von Burg * - * This file is part of Privilege. - * - * Privilege is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * 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; diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/User.java b/src/main/java/ch/eitchnet/privilege/model/internal/User.java index 7e50da571..7fe3a0ed2 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/User.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/User.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2010 - 2012 + * Copyright 2013 Robert von Burg * - * This file is part of Privilege. - * - * Privilege is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * 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; diff --git a/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java b/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java index 818d1e4c6..98c170bd6 100644 --- a/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java +++ b/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2010 - 2012 + * Copyright 2013 Robert von Burg * - * This file is part of Privilege. - * - * Privilege is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * 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; diff --git a/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicy.java b/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicy.java index 4f0372c25..a585ef589 100644 --- a/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicy.java +++ b/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicy.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2010 - 2012 + * Copyright 2013 Robert von Burg * - * This file is part of Privilege. - * - * Privilege is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * 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; diff --git a/src/main/java/ch/eitchnet/privilege/xml/ElementParser.java b/src/main/java/ch/eitchnet/privilege/xml/ElementParser.java index 82d513343..f35a1980e 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/ElementParser.java +++ b/src/main/java/ch/eitchnet/privilege/xml/ElementParser.java @@ -1,23 +1,17 @@ /* - * Copyright (c) 2012, Robert von Burg - * - * All rights reserved. - * - * This file is part of the Privilege. - * - * Privilege is free software: you can redistribute - * it and/or modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the License, - * or (at your option) any later version. - * - * Privilege is distributed in the hope that it will - * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Privilege. If not, see - * . + * 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; diff --git a/src/main/java/ch/eitchnet/privilege/xml/ElementParserAdapter.java b/src/main/java/ch/eitchnet/privilege/xml/ElementParserAdapter.java index 5f90bb59c..11a11200e 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/ElementParserAdapter.java +++ b/src/main/java/ch/eitchnet/privilege/xml/ElementParserAdapter.java @@ -1,23 +1,17 @@ /* - * Copyright (c) 2012, Robert von Burg - * - * All rights reserved. - * - * This file is part of the Privilege. - * - * Privilege is free software: you can redistribute - * it and/or modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the License, - * or (at your option) any later version. - * - * Privilege is distributed in the hope that it will - * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Privilege. If not, see - * . + * 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; diff --git a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigDomWriter.java b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigDomWriter.java index fae22d9f0..e2745a7eb 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigDomWriter.java +++ b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigDomWriter.java @@ -1,23 +1,17 @@ /* - * Copyright (c) 2012, Robert von Burg - * - * All rights reserved. - * - * This file is part of the Privilege. - * - * Privilege is free software: you can redistribute - * it and/or modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the License, - * or (at your option) any later version. - * - * Privilege is distributed in the hope that it will - * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Privilege. If not, see - * . + * 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; diff --git a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java index d8c8b12e7..74547ea9e 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java +++ b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java @@ -1,23 +1,17 @@ /* - * Copyright (c) 2012, Robert von Burg - * - * All rights reserved. - * - * This file is part of the Privilege. - * - * Privilege is free software: you can redistribute - * it and/or modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the License, - * or (at your option) any later version. - * - * Privilege is distributed in the hope that it will - * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Privilege. If not, see - * . + * 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; diff --git a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelDomWriter.java b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelDomWriter.java index 03ef69f77..6e419a542 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelDomWriter.java +++ b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelDomWriter.java @@ -1,23 +1,17 @@ /* - * Copyright (c) 2012, Robert von Burg - * - * All rights reserved. - * - * This file is part of the Privilege. - * - * Privilege is free software: you can redistribute - * it and/or modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the License, - * or (at your option) any later version. - * - * Privilege is distributed in the hope that it will - * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Privilege. If not, see - * . + * 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; diff --git a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java index ea2bb5058..b3add0f1b 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java +++ b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java @@ -1,23 +1,17 @@ /* - * Copyright (c) 2012, Robert von Burg - * - * All rights reserved. - * - * This file is part of the Privilege. - * - * Privilege is free software: you can redistribute - * it and/or modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the License, - * or (at your option) any later version. - * - * Privilege is distributed in the hope that it will - * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Privilege. If not, see - * . + * 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; diff --git a/src/main/resources/Privilege.xsd b/src/main/resources/Privilege.xsd index 0f9baa712..2fbcbdb1d 100644 --- a/src/main/resources/Privilege.xsd +++ b/src/main/resources/Privilege.xsd @@ -4,24 +4,19 @@ -Copyright (c) 2010, 2011 - -Robert von Burg <eitch@eitchnet.ch> - -This file is part of Privilege. - -Privilege is free software: you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Privilege is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License -along with Privilege. If not, see <http://www.gnu.org/licenses/>. + 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. diff --git a/src/main/resources/PrivilegeModel.xsd b/src/main/resources/PrivilegeModel.xsd index 5ec6fd17c..7d0ea3d40 100644 --- a/src/main/resources/PrivilegeModel.xsd +++ b/src/main/resources/PrivilegeModel.xsd @@ -4,24 +4,19 @@ -Copyright (c) 2010, 2011 - -Robert von Burg <eitch@eitchnet.ch> - -This file is part of Privilege. - -Privilege is free software: you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Privilege is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License -along with Privilege. If not, see <http://www.gnu.org/licenses/>. +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. diff --git a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java index d3e58bb41..c01a404c1 100644 --- a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2010 - 2012 + * Copyright 2013 Robert von Burg * - * This file is part of Privilege. - * - * Privilege is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * 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; diff --git a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java index 9ff57422d..719e271d9 100644 --- a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java @@ -1,23 +1,17 @@ /* - * Copyright (c) 2012, Robert von Burg - * - * All rights reserved. - * - * This file is part of the Privilege. - * - * Privilege is free software: you can redistribute - * it and/or modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the License, - * or (at your option) any later version. - * - * Privilege is distributed in the hope that it will - * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Privilege. If not, see - * . + * 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; diff --git a/src/test/java/ch/eitchnet/privilege/test/model/TestRestrictable.java b/src/test/java/ch/eitchnet/privilege/test/model/TestRestrictable.java index 7b677373c..af7bb4e7f 100644 --- a/src/test/java/ch/eitchnet/privilege/test/model/TestRestrictable.java +++ b/src/test/java/ch/eitchnet/privilege/test/model/TestRestrictable.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2010 - 2012 + * Copyright 2013 Robert von Burg * - * This file is part of Privilege. - * - * Privilege is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * 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; diff --git a/src/test/java/ch/eitchnet/privilege/test/model/TestSystemRestrictable.java b/src/test/java/ch/eitchnet/privilege/test/model/TestSystemRestrictable.java index d0e6145ba..d73c731f5 100644 --- a/src/test/java/ch/eitchnet/privilege/test/model/TestSystemRestrictable.java +++ b/src/test/java/ch/eitchnet/privilege/test/model/TestSystemRestrictable.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2010 - 2012 + * Copyright 2013 Robert von Burg * - * This file is part of Privilege. - * - * Privilege is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Privilege. If not, see . + * 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; diff --git a/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java b/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java index c86a5fdb0..86c1613e2 100644 --- a/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java +++ b/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2012 - * - * This file is part of ??????????????? - * - * ?????????????? is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with ????????????????. If not, see . + * 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; diff --git a/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserActionDeny.java b/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserActionDeny.java index d8ed59616..5cd8092f9 100644 --- a/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserActionDeny.java +++ b/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserActionDeny.java @@ -1,21 +1,17 @@ /* - * Copyright (c) 2012 - * - * This file is part of ??????????????? - * - * ?????????????? is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Privilege is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with ????????????????. If not, see . + * 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; From 744920a62be08eac7d96c21f4b54425b173b26ee Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Tue, 24 Dec 2013 02:42:29 +0100 Subject: [PATCH 114/173] [Minor] removed use of deprecated JUnit classes --- .../privilege/test/PrivilegeTest.java | 31 +++++++------- .../ch/eitchnet/privilege/test/XmlTest.java | 42 +++++++++---------- 2 files changed, 37 insertions(+), 36 deletions(-) diff --git a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java index c01a404c1..6d3b1b3ea 100644 --- a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -15,13 +15,15 @@ */ package ch.eitchnet.privilege.test; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + import java.io.File; import java.util.HashMap; import java.util.HashSet; import java.util.Map; -import junit.framework.Assert; - import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; @@ -53,6 +55,7 @@ import ch.eitchnet.utils.helper.FileHelper; * * @author Robert von Burg */ +@SuppressWarnings("nls") public class PrivilegeTest { private static final String ADMIN = "admin"; @@ -80,7 +83,7 @@ public class PrivilegeTest { public static void init() throws Exception { try { destroy(); - + // copy configuration to tmp String pwd = System.getProperty("user.dir"); @@ -146,7 +149,7 @@ public class PrivilegeTest { private void login(String username, byte[] password) { Certificate certificate = privilegeHandler.authenticate(username, password); - Assert.assertTrue("Certificate is null!", certificate != null); + assertTrue("Certificate is null!", certificate != null); PrivilegeContext privilegeContext = privilegeHandler.getPrivilegeContext(certificate); PrivilegeContext.set(privilegeContext); } @@ -350,10 +353,10 @@ public class PrivilegeTest { // see if bob can perform restrictable Restrictable restrictable = new TestRestrictable(); PrivilegeContext.get().validateAction(restrictable); - Assert.fail("Should fail as bob does not have role app"); + fail("Should fail as bob does not have role app"); } catch (AccessDeniedException e) { String msg = "User bob does not have Privilege ch.eitchnet.privilege.test.model.TestRestrictable needed for Restrictable ch.eitchnet.privilege.test.model.TestRestrictable"; - Assert.assertEquals(msg, e.getLocalizedMessage()); + assertEquals(msg, e.getLocalizedMessage()); } finally { logout(); } @@ -397,10 +400,10 @@ public class PrivilegeTest { // testFailAuthAsTedNoPass // Will fail because user ted has no password login(TED, ArraysHelper.copyOf(PASS_TED)); - org.junit.Assert.fail("User Ted may not authenticate because the user has no password!"); + 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!"; - Assert.assertEquals(msg, e.getMessage()); + assertEquals(msg, e.getMessage()); } finally { logout(); } @@ -451,10 +454,10 @@ public class PrivilegeTest { new HashMap()); certificate = PrivilegeContext.get().getCertificate(); privilegeHandler.addOrReplaceUser(certificate, userRep, null); - Assert.fail("User bob may not add a user as bob does not have admin rights!"); + fail("User bob may not add a user as bob does not have admin rights!"); } catch (PrivilegeException e) { String msg = "User does not have PrivilegeAdmin role! Certificate: " + certificate; - Assert.assertEquals(msg, e.getMessage()); + assertEquals(msg, e.getMessage()); } finally { logout(); } @@ -501,10 +504,10 @@ public class PrivilegeTest { // testFailAuthUserBob // Will fail as user bob has no role privilegeHandler.authenticate(BOB, ArraysHelper.copyOf(PASS_BOB)); - org.junit.Assert.fail("User Bob may not authenticate because the user has no role"); + fail("User Bob may not authenticate because the user has no role"); } catch (PrivilegeException e) { String msg = "User bob does not have any roles defined!"; - Assert.assertEquals(msg, e.getMessage()); + assertEquals(msg, e.getMessage()); } finally { logout(); } @@ -527,10 +530,10 @@ public class PrivilegeTest { // testFailAuthAsBob // Will fail because user bob is not yet enabled privilegeHandler.authenticate(BOB, ArraysHelper.copyOf(PASS_BOB)); - org.junit.Assert.fail("User Bob may not authenticate because the user is not yet enabled!"); + 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!"; - Assert.assertEquals(msg, e.getMessage()); + assertEquals(msg, e.getMessage()); } finally { logout(); } diff --git a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java index 719e271d9..7506a933b 100644 --- a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java @@ -15,6 +15,9 @@ */ package ch.eitchnet.privilege.test; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + import java.io.File; import java.util.ArrayList; import java.util.Collections; @@ -25,8 +28,6 @@ import java.util.Locale; import java.util.Map; import java.util.Set; -import junit.framework.Assert; - import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; @@ -51,13 +52,10 @@ 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); @@ -109,17 +107,17 @@ public class XmlTest { logger.info(containerModel.toString()); // assert all objects read - Assert.assertNotNull(containerModel.getParameterMap()); - Assert.assertNotNull(containerModel.getPolicies()); - Assert.assertNotNull(containerModel.getEncryptionHandlerClassName()); - Assert.assertNotNull(containerModel.getEncryptionHandlerParameterMap()); - Assert.assertNotNull(containerModel.getPersistenceHandlerClassName()); - Assert.assertNotNull(containerModel.getPersistenceHandlerParameterMap()); + assertNotNull(containerModel.getParameterMap()); + assertNotNull(containerModel.getPolicies()); + assertNotNull(containerModel.getEncryptionHandlerClassName()); + assertNotNull(containerModel.getEncryptionHandlerParameterMap()); + assertNotNull(containerModel.getPersistenceHandlerClassName()); + assertNotNull(containerModel.getPersistenceHandlerParameterMap()); - Assert.assertEquals(1, containerModel.getParameterMap().size()); - Assert.assertEquals(1, containerModel.getPolicies().size()); - Assert.assertEquals(1, containerModel.getEncryptionHandlerParameterMap().size()); - Assert.assertEquals(2, containerModel.getPersistenceHandlerParameterMap().size()); + assertEquals(1, containerModel.getParameterMap().size()); + assertEquals(1, containerModel.getPolicies().size()); + assertEquals(1, containerModel.getEncryptionHandlerParameterMap().size()); + assertEquals(2, containerModel.getPersistenceHandlerParameterMap().size()); // TODO extend assertions to actual model } @@ -150,7 +148,7 @@ public class XmlTest { configSaxWriter.write(); String fileHash = StringHelper.getHexString(FileHelper.hashFileSha256(configFile)); - Assert.assertEquals("2ABD3442EEC8BCEC5BEE365AAB6DB2FD4E1789325425CB1E017E900582525685", fileHash); + assertEquals("2ABD3442EEC8BCEC5BEE365AAB6DB2FD4E1789325425CB1E017E900582525685", fileHash); } @Test @@ -161,12 +159,12 @@ public class XmlTest { XmlHelper.parseDocument(xmlFile, xmlHandler); List users = xmlHandler.getUsers(); - Assert.assertNotNull(users); + assertNotNull(users); List roles = xmlHandler.getRoles(); - Assert.assertNotNull(roles); + assertNotNull(roles); - Assert.assertEquals(2, users.size()); - Assert.assertEquals(4, roles.size()); + assertEquals(2, users.size()); + assertEquals(4, roles.size()); // TODO extend assertions to actual model } @@ -212,6 +210,6 @@ public class XmlTest { configSaxWriter.write(); String fileHash = StringHelper.getHexString(FileHelper.hashFileSha256(modelFile)); - Assert.assertEquals("A2127D20A61E00BCDBB61569CD2B200C4F0F111C972BAC3B1E54DF3B2FCDC8BE", fileHash); + assertEquals("A2127D20A61E00BCDBB61569CD2B200C4F0F111C972BAC3B1E54DF3B2FCDC8BE", fileHash); } } From 85db07a002adf1c70982e69af743e2da3124718b Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Wed, 25 Dec 2013 11:55:55 +0100 Subject: [PATCH 115/173] [Minor] fixed issue where eclipse couldn't validate log4j.xml --- src/test/resources/log4j.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/resources/log4j.xml b/src/test/resources/log4j.xml index a35a3c351..0a2a73d06 100644 --- a/src/test/resources/log4j.xml +++ b/src/test/resources/log4j.xml @@ -1,5 +1,6 @@ - + From 15a245d94e50bbb443a4feb284ae1a4620efb2a3 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Wed, 25 Dec 2013 13:10:58 +0100 Subject: [PATCH 116/173] [Minor] added new initialization to PrivilegeInitializationHelper Configuration can now be parsed and then passed to an initialization method --- .../helper/PrivilegeInitializationHelper.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/main/java/ch/eitchnet/privilege/helper/PrivilegeInitializationHelper.java b/src/main/java/ch/eitchnet/privilege/helper/PrivilegeInitializationHelper.java index 435f4faec..ba51d5556 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/PrivilegeInitializationHelper.java +++ b/src/main/java/ch/eitchnet/privilege/helper/PrivilegeInitializationHelper.java @@ -84,6 +84,20 @@ public class PrivilegeInitializationHelper { 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); From 3727d3545fc9d7793051431403f319212fa1c450 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Wed, 25 Dec 2013 14:37:22 +0100 Subject: [PATCH 117/173] [Minor] cleaned up all compiler warnings --- .../handler/DefaultEncryptionHandler.java | 46 +++-- .../handler/DefaultPrivilegeHandler.java | 190 +++++++++++------- .../privilege/handler/PrivilegeHandler.java | 2 +- .../helper/BootstrapConfigurationHelper.java | 1 + .../privilege/helper/ClassHelper.java | 101 ---------- .../eitchnet/privilege/helper/HashHelper.java | 88 -------- .../privilege/helper/PasswordCreaterUI.java | 5 +- .../privilege/helper/PasswordCreator.java | 5 +- .../helper/PrivilegeInitializationHelper.java | 1 + .../privilege/helper/XmlConstants.java | 1 + .../eitchnet/privilege/model/Certificate.java | 9 +- .../privilege/model/PrivilegeContext.java | 8 +- .../privilege/model/PrivilegeRep.java | 9 +- .../ch/eitchnet/privilege/model/RoleRep.java | 3 +- .../ch/eitchnet/privilege/model/UserRep.java | 13 +- .../internal/PrivilegeContainerModel.java | 17 +- .../model/internal/PrivilegeImpl.java | 9 +- .../privilege/model/internal/Role.java | 9 +- .../privilege/model/internal/User.java | 11 +- .../privilege/policy/DefaultPrivilege.java | 4 +- .../xml/PrivilegeConfigSaxReader.java | 60 +++--- .../xml/PrivilegeModelSaxReader.java | 85 ++++---- .../ch/eitchnet/privilege/test/XmlTest.java | 4 +- 23 files changed, 275 insertions(+), 406 deletions(-) delete mode 100644 src/main/java/ch/eitchnet/privilege/helper/ClassHelper.java delete mode 100644 src/main/java/ch/eitchnet/privilege/helper/HashHelper.java diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java index 792e6b9bb..5c083dbbe 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java @@ -19,14 +19,15 @@ 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.HashHelper; import ch.eitchnet.privilege.helper.XmlConstants; +import ch.eitchnet.utils.helper.StringHelper; /** *

      @@ -60,27 +61,25 @@ public class DefaultEncryptionHandler implements EncryptionHandler { @Override public String convertToHash(String string) { - try { - - return HashHelper.stringToHash(this.hashAlgorithm, string); - - } catch (NoSuchAlgorithmException e) { - throw new PrivilegeException("Algorithm " + this.hashAlgorithm + " was not found!", e); - } catch (UnsupportedEncodingException e) { - throw new PrivilegeException("Charset ASCII is not supported!", e); - } + return convertToHash(string.getBytes()); } @Override public String convertToHash(byte[] bytes) { try { - return HashHelper.stringToHash(this.hashAlgorithm, bytes); + return StringHelper.hashAsHex(this.hashAlgorithm, bytes); - } catch (NoSuchAlgorithmException e) { - throw new PrivilegeException("Algorithm " + this.hashAlgorithm + " was not found!", e); - } catch (UnsupportedEncodingException e) { - throw new PrivilegeException("Charset ASCII is not supported!", e); + } 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; } } @@ -100,18 +99,21 @@ public class DefaultEncryptionHandler implements EncryptionHandler { // get hash algorithm parameters this.hashAlgorithm = parameterMap.get(XmlConstants.XML_PARAM_HASH_ALGORITHM); if (this.hashAlgorithm == null || this.hashAlgorithm.isEmpty()) { - throw new PrivilegeException("[" + EncryptionHandler.class.getName() + "] Defined parameter " - + XmlConstants.XML_PARAM_HASH_ALGORITHM + " is invalid"); + 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"); - DefaultEncryptionHandler.logger.info("Using hashing algorithm " + this.hashAlgorithm); + convertToHash("test"); //$NON-NLS-1$ + DefaultEncryptionHandler.logger.info(MessageFormat + .format("Using hashing algorithm {0}", this.hashAlgorithm)); //$NON-NLS-1$ } catch (Exception e) { - throw new PrivilegeException("[" + EncryptionHandler.class.getName() + "] Defined parameter " - + XmlConstants.XML_PARAM_HASH_ALGORITHM + " is invalid because of underlying exception: " - + e.getLocalizedMessage(), 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/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index efdfd9f2d..66fdb4ef4 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -30,7 +30,6 @@ import org.slf4j.LoggerFactory; import ch.eitchnet.privilege.base.AccessDeniedException; import ch.eitchnet.privilege.base.PrivilegeException; -import ch.eitchnet.privilege.helper.ClassHelper; import ch.eitchnet.privilege.model.Certificate; import ch.eitchnet.privilege.model.IPrivilege; import ch.eitchnet.privilege.model.PrivilegeContext; @@ -42,6 +41,7 @@ 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.utils.helper.ClassHelper; /** *

      @@ -66,7 +66,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { /** * configuration parameter to define automatic persisting on password change */ - private static final String PARAM_AUTO_PERSIST_ON_PASSWORD_CHANGE = "autoPersistOnPasswordChange"; + private static final String PARAM_AUTO_PERSIST_ON_PASSWORD_CHANGE = "autoPersistOnPasswordChange"; //$NON-NLS-1$ /** * slf4j logger @@ -327,14 +327,16 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // get role Role role = this.persistenceHandler.getRole(roleName); if (role == null) { - throw new PrivilegeException("Role " + roleName + " does not exist!"); + 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)) { - throw new PrivilegeException("Policy " + policy + " for Privilege " + privilegeRep.getName() - + " does not exist"); + 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 @@ -364,19 +366,21 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // get user User user = this.persistenceHandler.getUser(username); if (user == null) { - throw new PrivilegeException("User " + username + " does not exist!"); + throw new PrivilegeException(MessageFormat.format("User {0} does not exist!", username)); //$NON-NLS-1$ } // ignore if user already has role Set currentRoles = user.getRoles(); if (currentRoles.contains(roleName)) { - DefaultPrivilegeHandler.logger.error("User " + username + " already has role " + roleName); + String msg = MessageFormat.format("User {0} already has role {1}", username, roleName); //$NON-NLS-1$ + DefaultPrivilegeHandler.logger.error(msg); return; } // validate that role exists if (getRole(roleName) == null) { - throw new PrivilegeException("Role " + roleName + " does not exist!"); + String msg = MessageFormat.format("Role {0} does not exist!", roleName); //$NON-NLS-1$ + throw new PrivilegeException(msg); } // create new user @@ -399,12 +403,14 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // get role Role role = this.persistenceHandler.getRole(roleName); if (role == null) { - throw new PrivilegeException("Role " + roleName + " does not exist!"); + throw new PrivilegeException(MessageFormat.format("Role {0} does not exist!", roleName)); //$NON-NLS-1$ } // ignore if role does not have privilege - if (!role.hasPrivilege(privilegeName)) - throw new PrivilegeException("Role " + roleName + " does not have Privilege " + privilegeName); + if (!role.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 = role.getPrivilegeNames(); @@ -447,13 +453,14 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // get User User user = this.persistenceHandler.getUser(username); if (user == null) { - throw new PrivilegeException("User " + username + " does not exist!"); + throw new PrivilegeException(MessageFormat.format("User {0} does not exist!", username)); //$NON-NLS-1$ } // ignore if user does not have role Set currentRoles = user.getRoles(); if (!currentRoles.contains(roleName)) { - DefaultPrivilegeHandler.logger.error("User " + user + " does not have role " + roleName); + String msg = MessageFormat.format("User {0} does not have role {1}", user, roleName); //$NON-NLS-1$ + logger.error(msg); return; } @@ -494,7 +501,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // get User User user = this.persistenceHandler.getUser(username); if (user == null) { - throw new PrivilegeException("User " + username + " does not exist!"); + throw new PrivilegeException(MessageFormat.format("User {0} does not exist!", username)); //$NON-NLS-1$ } // create new user @@ -514,7 +521,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // get User User user = this.persistenceHandler.getUser(username); if (user == null) { - throw new PrivilegeException("User " + username + " does not exist!"); + throw new PrivilegeException(MessageFormat.format("User {0} does not exist!", username)); //$NON-NLS-1$ } // create new user @@ -548,7 +555,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // get User User user = this.persistenceHandler.getUser(username); if (user == null) { - throw new PrivilegeException("User " + username + " does not exist!"); + throw new PrivilegeException(MessageFormat.format("User {0} does not exist!", username)); //$NON-NLS-1$ } String passwordHash = null; @@ -587,7 +594,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // get User User user = this.persistenceHandler.getUser(username); if (user == null) { - throw new PrivilegeException("User " + username + " does not exist!"); + throw new PrivilegeException(MessageFormat.format("User {0} does not exist!", username)); //$NON-NLS-1$ } // create new user @@ -611,8 +618,10 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { Certificate certificate; try { // username must be at least 2 characters in length - if (username == null || username.length() < 2) - throw new PrivilegeException("The given username '" + username + "' is shorter than 2 characters"); + 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); + } // and validate the password validatePassword(password); @@ -623,26 +632,32 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // get user object User user = this.persistenceHandler.getUser(username); // no user means no authentication - if (user == null) - throw new AccessDeniedException("There is no user defined with the username " + username); + if (user == null) { + String msg = MessageFormat.format("There is no user defined with the username {0}", username); //$NON-NLS-1$ + throw new AccessDeniedException(msg); + } // validate password String pwHash = user.getPassword(); if (pwHash == null) - throw new AccessDeniedException("User " + username + " has no password and may not login!"); + throw new AccessDeniedException(MessageFormat.format( + "User {0} has no password and may not login!", username)); //$NON-NLS-1$ if (!pwHash.equals(passwordHash)) - throw new AccessDeniedException("Password is incorrect for " + username); + throw new AccessDeniedException(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) - throw new AccessDeniedException("User " + username + " does not have state " + UserState.ENABLED - + " and can not login!"); + 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); + } // validate user has at least one role Set userRoles = user.getRoles(); if (userRoles.isEmpty()) { - throw new PrivilegeException("User " + username + " does not have any roles defined!"); + throw new PrivilegeException( + MessageFormat.format("User {0} does not have any roles defined!", username)); //$NON-NLS-1$ } // get 2 auth tokens @@ -660,11 +675,13 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { this.privilegeContextMap.put(sessionId, privilegeContext); // log - DefaultPrivilegeHandler.logger.info("User " + username + " authenticated: " + certificate); + DefaultPrivilegeHandler.logger.info(MessageFormat.format( + "User {0} authenticated: {1}", username, certificate)); //$NON-NLS-1$ } catch (RuntimeException e) { - DefaultPrivilegeHandler.logger.error("User " + username + " Failed to authenticate: " - + e.getLocalizedMessage()); + 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); @@ -700,8 +717,9 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { IPrivilege privilege = role.getPrivilege(privilegeName); if (privilege == null) { - throw new PrivilegeException(MessageFormat.format("The Privilege {0} does not exist for role {1}", - privilegeName, roleName)); + String msg = "The Privilege {0} does not exist for role {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, privilegeName, roleName); + throw new PrivilegeException(msg); } privileges.put(privilegeName, privilege); @@ -712,8 +730,9 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { PrivilegePolicy policy = getPolicy(policyName); if (policy == null) { - throw new PrivilegeException(MessageFormat.format( - "The Policy {0} does not exist for Privilege {1}", policyName, privilegeName)); + 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); } @@ -736,9 +755,10 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // return true if object was really removed boolean loggedOut = privilegeContext != null; if (loggedOut) - DefaultPrivilegeHandler.logger.info("User " + certificate.getUsername() + " logged out."); + DefaultPrivilegeHandler.logger + .info(MessageFormat.format("User {0} logged out.", certificate.getUsername())); //$NON-NLS-1$ else - DefaultPrivilegeHandler.logger.warn("User already logged out!"); + DefaultPrivilegeHandler.logger.warn("User already logged out!"); //$NON-NLS-1$ return loggedOut; } @@ -747,25 +767,30 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // certificate must not be null if (certificate == null) - throw new PrivilegeException("Certificate may not be 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) - throw new AccessDeniedException("There is no session information for " + certificate.toString()); + 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)) - throw new PrivilegeException("Received illegal certificate for session id " + certificate.getSessionId()); + 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) { - throw new PrivilegeException( - "Oh boy, how did this happen: No User in user map although the certificate is valid!"); + 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 @@ -789,15 +814,16 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // get user object User user = this.persistenceHandler.getUser(certificate.getUsername()); if (user == null) { - throw new PrivilegeException( - "Oh boy, how did this happen: No User in user map although the certificate is valid! Certificate: " - + certificate); + String msg = "Oh boy, how did this happen: No User in user map although the certificate is valid! Certificate: {0}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, certificate); + throw new PrivilegeException(msg); } // validate user has PrivilegeAdmin role if (!user.hasRole(PrivilegeHandler.PRIVILEGE_ADMIN_ROLE)) { - throw new AccessDeniedException("User does not have " + PrivilegeHandler.PRIVILEGE_ADMIN_ROLE - + " role! Certificate: " + certificate); + String msg = "User does not have {0} role! Certificate: {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, PrivilegeHandler.PRIVILEGE_ADMIN_ROLE, certificate); + throw new AccessDeniedException(msg); } } @@ -810,11 +836,11 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { public void validatePassword(byte[] password) throws PrivilegeException { if (password == null || password.length == 0) { - throw new PrivilegeException("A password may not be empty!"); + 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"); + throw new PrivilegeException("The given password is shorter than 3 characters"); //$NON-NLS-1$ } } @@ -848,7 +874,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { PersistenceHandler persistenceHandler, Map> policyMap) { if (this.initialized) - throw new PrivilegeException("Already initialized!"); + throw new PrivilegeException("Already initialized!"); //$NON-NLS-1$ this.policyMap = policyMap; this.encryptionHandler = encryptionHandler; @@ -859,10 +885,11 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { this.autoPersistOnPasswordChange = false; } else if (autoPersistS.equals(Boolean.TRUE.toString())) { this.autoPersistOnPasswordChange = true; - logger.info("Enabling automatic persistence on password change."); + logger.info("Enabling automatic persistence on password change."); //$NON-NLS-1$ } else { - logger.error("Parameter " + PARAM_AUTO_PERSIST_ON_PASSWORD_CHANGE + " has illegal value " + autoPersistS - + ". Overriding with " + Boolean.FALSE.toString()); + String msg = "Parameter {0} has illegal value {1}. Overriding with {2}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, PARAM_AUTO_PERSIST_ON_PASSWORD_CHANGE, autoPersistS, Boolean.FALSE); + logger.error(msg); } // validate policies on privileges of Roles @@ -886,8 +913,9 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { IPrivilege privilege = role.getPrivilege(privilegeName); String policy = privilege.getPolicy(); if (policy != null && !this.policyMap.containsKey(policy)) { - throw new PrivilegeException("Policy " + policy + " for Privilege " + privilege.getName() - + " does not exist on role " + role); + 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); } } } @@ -918,18 +946,18 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { public void runAsSystem(String systemUsername, SystemUserAction action) throws PrivilegeException { if (systemUsername == null) - throw new PrivilegeException("systemUsername may not be null!"); + throw new PrivilegeException("systemUsername may not be null!"); //$NON-NLS-1$ if (action == null) - throw new PrivilegeException("action may not be 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("System user " + systemUsername + " does not exist!"); + 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("User " + systemUsername + " is not a System user!"); + throw new PrivilegeException(MessageFormat.format("User {0} is not a System user!", systemUsername)); //$NON-NLS-1$ // validate this system user may perform the given action String actionClassname = action.getClass().getName(); @@ -966,7 +994,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } // default throw exception, as the user does not have the privilege - throw new PrivilegeException("User " + user.getUsername() + " does not have Privilege " + privilegeName); + String msg = MessageFormat.format("User {0} does not have Privilege {1}", user.getUsername(), privilegeName); //$NON-NLS-1$ + throw new PrivilegeException(msg); } /** @@ -986,24 +1015,33 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // get user object User user = this.persistenceHandler.getUser(systemUsername); // no user means no authentication - if (user == null) - throw new AccessDeniedException("The system user with username " + systemUsername + " does not exist!"); + 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) - throw new AccessDeniedException("System user " + systemUsername + " has no password and may not login!"); - if (!pwHash.equals(passwordHash)) - throw new AccessDeniedException("System user " + systemUsername + " has an incorrect password defined!"); + if (pwHash == null) { + String msg = MessageFormat.format("System user {0} has no password and may not login!", systemUsername); //$NON-NLS-1$ + throw new AccessDeniedException(msg); + } + if (!pwHash.equals(passwordHash)) { + String msg = MessageFormat.format("System user {0} has an incorrect password defined!", systemUsername); //$NON-NLS-1$ + throw new AccessDeniedException(msg); + } // validate user state is system - if (user.getUserState() != UserState.SYSTEM) - throw new PrivilegeException("The system " + systemUsername + " user does not have expected user state " - + UserState.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()) { - throw new PrivilegeException("The system user " + systemUsername + " does not have any roles defined!"); + 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 @@ -1021,8 +1059,9 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { PrivilegeContext privilegeContext = buildPrivilegeContext(systemUserCertificate, user); // log - DefaultPrivilegeHandler.logger.info("The system user " + systemUsername + " is logged in with session " - + systemUserCertificate); + String msg = "The system user {0} is logged in with session {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, systemUsername, systemUserCertificate); + DefaultPrivilegeHandler.logger.info(msg); return privilegeContext; } @@ -1055,8 +1094,9 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { try { policy = ClassHelper.instantiateClass(policyClazz); } catch (Exception e) { - throw new PrivilegeException("The class for the policy with the name " + policyName + " does not exist!" - + policyName, 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/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java index 52832c2c8..fafd54bdc 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java @@ -43,7 +43,7 @@ public interface PrivilegeHandler { /** * PRIVILEGE_ADMIN_ROLE = PrivilegeAdmin: This is the role users must have, if they are allowed to modify objects */ - public static final String PRIVILEGE_ADMIN_ROLE = "PrivilegeAdmin"; + public static final String PRIVILEGE_ADMIN_ROLE = "PrivilegeAdmin"; //$NON-NLS-1$ /** * Returns a {@link UserRep} for the given username diff --git a/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java b/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java index c6a052942..f9d3b158b 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java +++ b/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java @@ -40,6 +40,7 @@ import ch.eitchnet.privilege.xml.PrivilegeConfigDomWriter; * * @author Robert von Burg */ +@SuppressWarnings("nls") public class BootstrapConfigurationHelper { // private static final Logger logger = Loggerdoc.getLogger(BootstrapConfigurationHelper.class); diff --git a/src/main/java/ch/eitchnet/privilege/helper/ClassHelper.java b/src/main/java/ch/eitchnet/privilege/helper/ClassHelper.java deleted file mode 100644 index cad6e5fb5..000000000 --- a/src/main/java/ch/eitchnet/privilege/helper/ClassHelper.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * 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 ch.eitchnet.privilege.base.PrivilegeException; - -/** - * The {@link ClassHelper} class is a helper to instantiate classes using reflection - * - * @author Robert von Burg - */ -public class ClassHelper { - - /** - * Returns an instance of the class' name given by instantiating the class through an empty arguments constructor - * - * @param - * the type of the class to return - * @param className - * the name of a class to instantiate through an empty arguments constructor - * - * @return the newly instantiated object from the given class name - * - * @throws PrivilegeException - * if the class could not be instantiated - */ - @SuppressWarnings("unchecked") - public static T instantiateClass(String className) throws PrivilegeException { - try { - - Class clazz = (Class) Class.forName(className); - - return clazz.getConstructor().newInstance(); - - } catch (Exception e) { - throw new PrivilegeException("The class " + className + " could not be instantiated: ", e); - } - } - - /** - * Instantiates an object for the given {@link Class} using an empty arguments constructor - * - * @param - * the type of the class to return - * @param clazz - * the {@link Class} from which a new object is to be instantiated using an empty arguments constructor - * - * @return the newly instantiated object from the given {@link Class} - * - * @throws PrivilegeException - * if the {@link Class} could not be instantiated - */ - public static T instantiateClass(Class clazz) throws PrivilegeException { - try { - - return clazz.getConstructor().newInstance(); - - } catch (Exception e) { - throw new PrivilegeException("The class " + clazz.getName() + " could not be instantiated: ", e); - } - } - - /** - * Loads the {@link Class} object for the given class name - * - * @param - * the type of {@link Class} to return - * @param className - * the name of the {@link Class} to load and return - * - * @return the {@link Class} object for the given class name - * - * @throws PrivilegeException - * if the class could not be instantiated - */ - @SuppressWarnings("unchecked") - public static Class loadClass(String className) throws PrivilegeException { - try { - - Class clazz = (Class) Class.forName(className); - - return clazz; - - } catch (Exception e) { - throw new PrivilegeException("The class " + className + " could not be instantiated: ", e); - } - } -} diff --git a/src/main/java/ch/eitchnet/privilege/helper/HashHelper.java b/src/main/java/ch/eitchnet/privilege/helper/HashHelper.java deleted file mode 100644 index 01fef9e5f..000000000 --- a/src/main/java/ch/eitchnet/privilege/helper/HashHelper.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * 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.UnsupportedEncodingException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; - -/** - * Helper class to hash a String for a certain hash algorithm, using the Java {@link MessageDigest} classes - * - * @author Robert von Burg - */ -public class HashHelper { - - /** - * Hex char table for fast calculating of hex values - */ - private static final byte[] HEX_CHAR_TABLE = { (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', - (byte) '5', (byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', - (byte) 'e', (byte) 'f' }; - - /** - * Creates the hash of the given string using {@link MessageDigest} and the defined hash algorithm - * - * @param hashAlgorithm - * the algorithm to use for hashing - * @param string - * the string to hash - * - * @return a new string encrypted by the defined algorithm - * - * @throws NoSuchAlgorithmException - * if the algorithm is not found - * @throws UnsupportedEncodingException - * if something is wrong with the given string to hash - */ - public static String stringToHash(String hashAlgorithm, String string) throws NoSuchAlgorithmException, - UnsupportedEncodingException { - return HashHelper.stringToHash(hashAlgorithm, string.getBytes()); - } - - /** - * Creates the hash of the given string using {@link MessageDigest} and the defined hash algorithm - * - * @param hashAlgorithm - * the algorithm to use for hashing - * @param bytes - * the bytes to hash - * - * @return a new string encrypted by the defined algorithm - * - * @throws NoSuchAlgorithmException - * if the algorithm is not found - * @throws UnsupportedEncodingException - * if something is wrong with the given string to hash - */ - public static String stringToHash(String hashAlgorithm, byte[] bytes) throws NoSuchAlgorithmException, - UnsupportedEncodingException { - - MessageDigest digest = MessageDigest.getInstance(hashAlgorithm); - byte[] hashArray = digest.digest(bytes); - - byte[] hex = new byte[2 * hashArray.length]; - int index = 0; - - for (byte b : hashArray) { - int v = b & 0xFF; - hex[index++] = HashHelper.HEX_CHAR_TABLE[v >>> 4]; - hex[index++] = HashHelper.HEX_CHAR_TABLE[v & 0xF]; - } - - return new String(hex, "ASCII"); - } -} diff --git a/src/main/java/ch/eitchnet/privilege/helper/PasswordCreaterUI.java b/src/main/java/ch/eitchnet/privilege/helper/PasswordCreaterUI.java index fe58b86f5..d409a196e 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/PasswordCreaterUI.java +++ b/src/main/java/ch/eitchnet/privilege/helper/PasswordCreaterUI.java @@ -31,11 +31,14 @@ 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 { /** @@ -91,7 +94,7 @@ public class PasswordCreaterUI { String digest = (String) digestCombo.getSelectedItem(); char[] passwordChar = passwordField.getPassword(); String password = new String(passwordChar); - String hash = HashHelper.stringToHash(digest, password); + String hash = StringHelper.hashAsHex(digest, password); hashField.setText(hash); } catch (Exception e1) { e1.printStackTrace(); diff --git a/src/main/java/ch/eitchnet/privilege/helper/PasswordCreator.java b/src/main/java/ch/eitchnet/privilege/helper/PasswordCreator.java index 6285b4d5a..81e3bf567 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/PasswordCreator.java +++ b/src/main/java/ch/eitchnet/privilege/helper/PasswordCreator.java @@ -19,6 +19,8 @@ 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 @@ -38,6 +40,7 @@ public class PasswordCreator { * @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)); @@ -63,7 +66,7 @@ public class PasswordCreator { System.out.print("Password: "); String password = r.readLine().trim(); - System.out.print("Hash is: " + HashHelper.stringToHash(hashAlgorithm, password)); + System.out.print("Hash is: " + StringHelper.hashAsHex(hashAlgorithm, password)); } } diff --git a/src/main/java/ch/eitchnet/privilege/helper/PrivilegeInitializationHelper.java b/src/main/java/ch/eitchnet/privilege/helper/PrivilegeInitializationHelper.java index ba51d5556..03bc8d21e 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/PrivilegeInitializationHelper.java +++ b/src/main/java/ch/eitchnet/privilege/helper/PrivilegeInitializationHelper.java @@ -29,6 +29,7 @@ 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; /** diff --git a/src/main/java/ch/eitchnet/privilege/helper/XmlConstants.java b/src/main/java/ch/eitchnet/privilege/helper/XmlConstants.java index 068fc01a7..079fe433d 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/XmlConstants.java +++ b/src/main/java/ch/eitchnet/privilege/helper/XmlConstants.java @@ -20,6 +20,7 @@ package ch.eitchnet.privilege.helper; * * @author Robert von Burg */ +@SuppressWarnings("nls") public class XmlConstants { /** diff --git a/src/main/java/ch/eitchnet/privilege/model/Certificate.java b/src/main/java/ch/eitchnet/privilege/model/Certificate.java index 28ea2420f..503f49515 100644 --- a/src/main/java/ch/eitchnet/privilege/model/Certificate.java +++ b/src/main/java/ch/eitchnet/privilege/model/Certificate.java @@ -72,16 +72,16 @@ public final class Certificate implements Serializable { // validate arguments are not null if (StringHelper.isEmpty(sessionId)) { - throw new PrivilegeException("sessionId is null!"); + throw new PrivilegeException("sessionId is null!"); //$NON-NLS-1$ } if (StringHelper.isEmpty(username)) { - throw new PrivilegeException("username is null!"); + throw new PrivilegeException("username is null!"); //$NON-NLS-1$ } if (StringHelper.isEmpty(authToken)) { - throw new PrivilegeException("authToken is null!"); + throw new PrivilegeException("authToken is null!"); //$NON-NLS-1$ } if (StringHelper.isEmpty(authPassword)) { - throw new PrivilegeException("authPassword is null!"); + throw new PrivilegeException("authPassword is null!"); //$NON-NLS-1$ } this.sessionId = sessionId; @@ -162,6 +162,7 @@ public final class Certificate implements Serializable { * * @see java.lang.Object#toString() */ + @SuppressWarnings("nls") @Override public String toString() { StringBuilder builder = new StringBuilder(); diff --git a/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java b/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java index 3889bfe11..d7780b635 100644 --- a/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java +++ b/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java @@ -94,7 +94,7 @@ public class PrivilegeContext { String privilegeName = restrictable.getPrivilegeName(); IPrivilege privilege = this.privileges.get(privilegeName); if (privilege == null) { - String msg = MessageFormat.format(PrivilegeMessages.getString("Privilege.accessdenied.noprivilege"), + String msg = MessageFormat.format(PrivilegeMessages.getString("Privilege.accessdenied.noprivilege"), //$NON-NLS-1$ getUsername(), privilegeName, restrictable.getClass().getName()); throw new AccessDeniedException(msg); } @@ -103,7 +103,7 @@ public class PrivilegeContext { String policyName = privilege.getPolicy(); PrivilegePolicy policy = this.policies.get(policyName); if (policy == null) { - String msg = "The PrivilegePolicy {0} does not exist on the PrivilegeContext!"; + String msg = "The PrivilegePolicy {0} does not exist on the PrivilegeContext!"; //$NON-NLS-1$ throw new PrivilegeException(MessageFormat.format(msg, policyName)); } @@ -130,7 +130,7 @@ public class PrivilegeContext { public static PrivilegeContext get() throws PrivilegeException { PrivilegeContext privilegeContext = PrivilegeContext.threadLocal.get(); if (privilegeContext == null) { - throw new PrivilegeException("There is no PrivilegeContext currently bound to the ThreadLocal!"); + throw new PrivilegeException("There is no PrivilegeContext currently bound to the ThreadLocal!"); //$NON-NLS-1$ } return privilegeContext; } @@ -148,7 +148,7 @@ public class PrivilegeContext { public static void set(PrivilegeContext privilegeContext) throws PrivilegeException { PrivilegeContext currentContext = PrivilegeContext.threadLocal.get(); if (privilegeContext != null && currentContext != null) { - throw new PrivilegeException("There already is a PrivilegeContext bound to the ThreadLocal!"); + throw new PrivilegeException("There already is a PrivilegeContext bound to the ThreadLocal!"); //$NON-NLS-1$ } PrivilegeContext.threadLocal.set(privilegeContext); } diff --git a/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java b/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java index 10c20da2e..e2c1a3a39 100644 --- a/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java +++ b/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java @@ -72,18 +72,18 @@ public class PrivilegeRep implements Serializable { public void validate() { if (StringHelper.isEmpty(this.name)) { - throw new PrivilegeException("No name defined!"); + throw new PrivilegeException("No name defined!"); //$NON-NLS-1$ } if (StringHelper.isEmpty(this.policy)) { - throw new PrivilegeException("policy is null!"); + throw new PrivilegeException("policy is null!"); //$NON-NLS-1$ } if (this.denyList == null) { - throw new PrivilegeException("denyList is null"); + throw new PrivilegeException("denyList is null"); //$NON-NLS-1$ } if (this.allowList == null) { - throw new PrivilegeException("allowList is null"); + throw new PrivilegeException("allowList is null"); //$NON-NLS-1$ } } @@ -167,6 +167,7 @@ public class PrivilegeRep implements Serializable { * * @see java.lang.Object#toString() */ + @SuppressWarnings("nls") @Override public String toString() { StringBuilder builder = new StringBuilder(); diff --git a/src/main/java/ch/eitchnet/privilege/model/RoleRep.java b/src/main/java/ch/eitchnet/privilege/model/RoleRep.java index 9a4fe7548..c32a226bc 100644 --- a/src/main/java/ch/eitchnet/privilege/model/RoleRep.java +++ b/src/main/java/ch/eitchnet/privilege/model/RoleRep.java @@ -57,7 +57,7 @@ public class RoleRep implements Serializable { */ public void validate() { if (StringHelper.isEmpty(this.name)) - throw new PrivilegeException("name is null"); + throw new PrivilegeException("name is null"); //$NON-NLS-1$ } /** @@ -87,6 +87,7 @@ public class RoleRep implements Serializable { * * @see java.lang.Object#toString() */ + @SuppressWarnings("nls") @Override public String toString() { StringBuilder builder = new StringBuilder(); diff --git a/src/main/java/ch/eitchnet/privilege/model/UserRep.java b/src/main/java/ch/eitchnet/privilege/model/UserRep.java index 2dd875b40..26bdc141e 100644 --- a/src/main/java/ch/eitchnet/privilege/model/UserRep.java +++ b/src/main/java/ch/eitchnet/privilege/model/UserRep.java @@ -85,22 +85,22 @@ public class UserRep implements Serializable { public void validate() { if (StringHelper.isEmpty(this.userId)) - throw new PrivilegeException("userId is null or empty"); + throw new PrivilegeException("userId is null or empty"); //$NON-NLS-1$ if (StringHelper.isEmpty(this.username)) - throw new PrivilegeException("username is null or empty"); + throw new PrivilegeException("username is null or empty"); //$NON-NLS-1$ if (StringHelper.isEmpty(this.firstname)) - throw new PrivilegeException("firstname is null or empty"); + throw new PrivilegeException("firstname is null or empty"); //$NON-NLS-1$ if (StringHelper.isEmpty(this.surname)) - throw new PrivilegeException("surname is null or empty"); + throw new PrivilegeException("surname is null or empty"); //$NON-NLS-1$ if (this.userState == null) - throw new PrivilegeException("userState is null"); + throw new PrivilegeException("userState is null"); //$NON-NLS-1$ if (this.roles == null) - throw new PrivilegeException("roles is null"); + throw new PrivilegeException("roles is null"); //$NON-NLS-1$ } /** @@ -247,6 +247,7 @@ public class UserRep implements Serializable { * * @see java.lang.Object#toString() */ + @SuppressWarnings("nls") @Override public String toString() { StringBuilder builder = new StringBuilder(); diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java b/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java index 39aabc5ae..efc2d7954 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java @@ -15,6 +15,7 @@ */ package ch.eitchnet.privilege.model.internal; +import java.text.MessageFormat; import java.util.HashMap; import java.util.Map; @@ -139,14 +140,17 @@ public class PrivilegeContainerModel { this.policies.put(privilegeName, clazz); } catch (InstantiationException e) { - throw new PrivilegeException("Configured Privilege Policy " + privilegeName + " with class " - + policyClassName + " could not be instantiated.", 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) { - throw new PrivilegeException("Configured Privilege Policy " + privilegeName + " with class " - + policyClassName + " can not be accessed.", 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) { - throw new PrivilegeException("Configured Privilege Policy " + privilegeName + " with class " - + policyClassName + " does not exist.", 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); } } @@ -162,6 +166,7 @@ public class PrivilegeContainerModel { * * @see java.lang.Object#toString() */ + @SuppressWarnings("nls") @Override public String toString() { StringBuilder builder = new StringBuilder(); diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeImpl.java b/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeImpl.java index 8b28c442d..e9d2b937c 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeImpl.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeImpl.java @@ -73,16 +73,16 @@ public final class PrivilegeImpl implements IPrivilege { public PrivilegeImpl(String name, String policy, boolean allAllowed, Set denyList, Set allowList) { if (StringHelper.isEmpty(name)) { - throw new PrivilegeException("No name defined!"); + throw new PrivilegeException("No name defined!"); //$NON-NLS-1$ } if (StringHelper.isEmpty(policy)) { - throw new PrivilegeException("Policy may not be empty!"); + throw new PrivilegeException("Policy may not be empty!"); //$NON-NLS-1$ } if (denyList == null) { - throw new PrivilegeException("denyList is null!"); + throw new PrivilegeException("denyList is null!"); //$NON-NLS-1$ } if (allowList == null) { - throw new PrivilegeException("allowList is null!"); + throw new PrivilegeException("allowList is null!"); //$NON-NLS-1$ } this.name = name; @@ -189,6 +189,7 @@ public final class PrivilegeImpl implements IPrivilege { * * @see java.lang.Object#toString() */ + @SuppressWarnings("nls") @Override public String toString() { StringBuilder builder = new StringBuilder(); diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/Role.java b/src/main/java/ch/eitchnet/privilege/model/internal/Role.java index 91ddbeb6a..b962095b7 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/Role.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/Role.java @@ -55,10 +55,10 @@ public final class Role { public Role(String name, Map privilegeMap) { if (StringHelper.isEmpty(name)) { - throw new PrivilegeException("No name defined!"); + throw new PrivilegeException("No name defined!"); //$NON-NLS-1$ } if (privilegeMap == null) { - throw new PrivilegeException("No privileges defined!"); + throw new PrivilegeException("No privileges defined!"); //$NON-NLS-1$ } this.name = name; @@ -75,11 +75,11 @@ public final class Role { String name = roleRep.getName(); if (StringHelper.isEmpty(name)) { - throw new PrivilegeException("No name defined!"); + throw new PrivilegeException("No name defined!"); //$NON-NLS-1$ } if (roleRep.getPrivilegeMap() == null) { - throw new PrivilegeException("No privileges defined!"); + throw new PrivilegeException("No privileges defined!"); //$NON-NLS-1$ } // build privileges from reps @@ -145,6 +145,7 @@ public final class Role { * * @see java.lang.Object#toString() */ + @SuppressWarnings("nls") @Override public String toString() { StringBuilder builder = new StringBuilder(); diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/User.java b/src/main/java/ch/eitchnet/privilege/model/internal/User.java index 7fe3a0ed2..7d1402234 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/User.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/User.java @@ -82,19 +82,19 @@ public final class User { Set roles, Locale locale, Map propertyMap) { if (StringHelper.isEmpty(userId)) { - throw new PrivilegeException("No UserId defined!"); + throw new PrivilegeException("No UserId defined!"); //$NON-NLS-1$ } if (StringHelper.isEmpty(username)) { - throw new PrivilegeException("No username defined!"); + throw new PrivilegeException("No username defined!"); //$NON-NLS-1$ } if (StringHelper.isEmpty(firstname)) { - throw new PrivilegeException("No firstname defined!"); + throw new PrivilegeException("No firstname defined!"); //$NON-NLS-1$ } if (StringHelper.isEmpty(surname)) { - throw new PrivilegeException("No surname defined!"); + throw new PrivilegeException("No surname defined!"); //$NON-NLS-1$ } if (userState == null) { - throw new PrivilegeException("No userState defined!"); + throw new PrivilegeException("No userState defined!"); //$NON-NLS-1$ } // password may be null, meaning not able to login @@ -240,6 +240,7 @@ public final class User { * * @see java.lang.Object#toString() */ + @SuppressWarnings("nls") @Override public String toString() { StringBuilder builder = new StringBuilder(); diff --git a/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java b/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java index 98c170bd6..2a10f3270 100644 --- a/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java +++ b/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java @@ -81,7 +81,7 @@ public class DefaultPrivilege implements PrivilegePolicy { // first check values not allowed if (privilege.isDenied(privilegeValue)) { // then throw access denied - String msg = MessageFormat.format(PrivilegeMessages.getString("Privilege.accessdenied.noprivilege"), + String msg = MessageFormat.format(PrivilegeMessages.getString("Privilege.accessdenied.noprivilege"), //$NON-NLS-1$ PrivilegeContext.get().getUsername(), privilegeName, restrictable.getClass().getName()); throw new AccessDeniedException(msg); } @@ -91,7 +91,7 @@ public class DefaultPrivilege implements PrivilegePolicy { return; // default is not allowed - String msg = MessageFormat.format(PrivilegeMessages.getString("Privilege.accessdenied.noprivilege"), + String msg = MessageFormat.format(PrivilegeMessages.getString("Privilege.accessdenied.noprivilege"), //$NON-NLS-1$ PrivilegeContext.get().getUsername(), privilegeName, restrictable.getClass().getName()); throw new AccessDeniedException(msg); } diff --git a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java index 74547ea9e..f7e09a822 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java +++ b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java @@ -23,11 +23,11 @@ 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 { @@ -41,14 +41,18 @@ public class PrivilegeConfigSaxReader extends DefaultHandler { 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("Container")) { + if (qName.equals(XmlConstants.XML_CONTAINER)) { this.buildersStack.add(new ContainerParser()); - } else if (qName.equals("Parameters")) { + } else if (qName.equals(XmlConstants.XML_PARAMETERS)) { this.buildersStack.add(new ParametersParser()); - } else if (qName.equals("Policies")) { + } else if (qName.equals(XmlConstants.XML_POLICIES)) { this.buildersStack.add(new PoliciesParser()); } @@ -69,11 +73,11 @@ public class PrivilegeConfigSaxReader extends DefaultHandler { this.buildersStack.peek().endElement(uri, localName, qName); ElementParser elementParser = null; - if (qName.equals("Container")) { + if (qName.equals(XmlConstants.XML_CONTAINER)) { elementParser = this.buildersStack.pop(); - } else if (qName.equals("Parameters")) { + } else if (qName.equals(XmlConstants.XML_PARAMETERS)) { elementParser = this.buildersStack.pop(); - } else if (qName.equals("Policies")) { + } else if (qName.equals(XmlConstants.XML_POLICIES)) { elementParser = this.buildersStack.pop(); } @@ -105,16 +109,16 @@ public class PrivilegeConfigSaxReader extends DefaultHandler { @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { - if (qName.equals("Container")) { + if (qName.equals(XmlConstants.XML_CONTAINER)) { this.currentElement = qName; - } else if (qName.equals("EncryptionHandler")) { + } else if (qName.equals(XmlConstants.XML_HANDLER_ENCRYPTION)) { this.currentElement = qName; - PrivilegeConfigSaxReader.this.containerModel - .setEncryptionHandlerClassName(attributes.getValue("class")); - } else if (qName.equals("PersistenceHandler")) { + String className = attributes.getValue(XmlConstants.XML_ATTR_CLASS); + getContainerModel().setEncryptionHandlerClassName(className); + } else if (qName.equals(XmlConstants.XML_HANDLER_PERSISTENCE)) { this.currentElement = qName; - PrivilegeConfigSaxReader.this.containerModel.setPersistenceHandlerClassName(attributes - .getValue("class")); + String className = attributes.getValue(XmlConstants.XML_ATTR_CLASS); + getContainerModel().setPersistenceHandlerClassName(className); } } @@ -125,14 +129,12 @@ public class PrivilegeConfigSaxReader extends DefaultHandler { ParametersParser parametersChild = (ParametersParser) child; - if (this.currentElement.equals("Container")) { - PrivilegeConfigSaxReader.this.containerModel.setParameterMap(parametersChild.getParameterMap()); - } else if (this.currentElement.equals("EncryptionHandler")) { - PrivilegeConfigSaxReader.this.containerModel.setEncryptionHandlerParameterMap(parametersChild - .getParameterMap()); - } else if (this.currentElement.equals("PersistenceHandler")) { - PrivilegeConfigSaxReader.this.containerModel.setPersistenceHandlerParameterMap(parametersChild - .getParameterMap()); + 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()); } } } @@ -145,9 +147,9 @@ public class PrivilegeConfigSaxReader extends DefaultHandler { @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { - if (qName.equals("Parameter")) { - String key = attributes.getValue("name"); - String value = attributes.getValue("value"); + 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); } } @@ -166,11 +168,11 @@ public class PrivilegeConfigSaxReader extends DefaultHandler { @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { - if (qName.equals("Policy")) { - String policyName = attributes.getValue("name"); - String policyClassName = attributes.getValue("class"); + if (qName.equals(XmlConstants.XML_POLICY)) { + String policyName = attributes.getValue(XmlConstants.XML_ATTR_NAME); + String policyClassName = attributes.getValue(XmlConstants.XML_ATTR_CLASS); - PrivilegeConfigSaxReader.this.containerModel.addPolicy(policyName, policyClassName); + getContainerModel().addPolicy(policyName, policyClassName); } } } diff --git a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java index b3add0f1b..df0b05460 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java +++ b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java @@ -15,6 +15,7 @@ */ package ch.eitchnet.privilege.xml; +import java.text.MessageFormat; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -30,6 +31,7 @@ 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.UserState; import ch.eitchnet.privilege.model.internal.PrivilegeImpl; @@ -42,7 +44,7 @@ import ch.eitchnet.utils.helper.StringHelper; */ public class PrivilegeModelSaxReader extends DefaultHandler { - private static final Logger logger = LoggerFactory.getLogger(PrivilegeModelSaxReader.class); + protected static final Logger logger = LoggerFactory.getLogger(PrivilegeModelSaxReader.class); private Stack buildersStack = new Stack(); @@ -73,12 +75,12 @@ public class PrivilegeModelSaxReader extends DefaultHandler { @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { - if (qName.equals("Users")) { + if (qName.equals(XmlConstants.XML_USERS)) { this.buildersStack.add(new UserParser()); this.insideUser = true; - } else if (qName.equals("Properties")) { + } else if (qName.equals(XmlConstants.XML_PROPERTIES)) { this.buildersStack.add(new PropertyParser()); - } else if (qName.equals("Roles") && !this.insideUser) { + } else if (qName.equals(XmlConstants.XML_ROLES) && !this.insideUser) { this.buildersStack.add(new RoleParser()); } @@ -99,16 +101,16 @@ public class PrivilegeModelSaxReader extends DefaultHandler { this.buildersStack.peek().endElement(uri, localName, qName); ElementParser elementParser = null; - if (qName.equals("Users")) { + if (qName.equals(XmlConstants.XML_USERS)) { elementParser = this.buildersStack.pop(); this.insideUser = false; - PrivilegeModelSaxReader.logger.info("Popping for Users"); - } else if (qName.equals("Properties")) { + logger.info("Popping for Users"); //$NON-NLS-1$ + } else if (qName.equals(XmlConstants.XML_PROPERTIES)) { elementParser = this.buildersStack.pop(); - PrivilegeModelSaxReader.logger.info("Popping for Properties"); - } else if (qName.equals("Roles") && !this.insideUser) { + logger.info("Popping for Properties"); //$NON-NLS-1$ + } else if (qName.equals(XmlConstants.XML_ROLES) && !this.insideUser) { elementParser = this.buildersStack.pop(); - PrivilegeModelSaxReader.logger.info("Popping for Roles"); + logger.info("Popping for Roles"); //$NON-NLS-1$ } if (!this.buildersStack.isEmpty() && elementParser != null) @@ -142,16 +144,10 @@ public class PrivilegeModelSaxReader extends DefaultHandler { private Map privileges; - /** - * - */ public RoleParser() { init(); } - /** - * - */ private void init() { this.privileges = new HashMap(); @@ -170,11 +166,11 @@ public class PrivilegeModelSaxReader extends DefaultHandler { this.text = new StringBuilder(); - if (qName.equals("Role")) { - this.roleName = attributes.getValue("name"); - } else if (qName.equals("Privilege")) { - this.privilegeName = attributes.getValue("name"); - this.privilegePolicy = attributes.getValue("policy"); + 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); } } @@ -187,24 +183,24 @@ public class PrivilegeModelSaxReader extends DefaultHandler { @Override public void endElement(String uri, String localName, String qName) throws SAXException { - if (qName.equals("AllAllowed")) { + if (qName.equals(XmlConstants.XML_ALL_ALLOWED)) { this.allAllowed = StringHelper.parseBoolean(this.text.toString().trim()); - } else if (qName.equals("Allow")) { + } else if (qName.equals(XmlConstants.XML_ALLOW)) { this.allowList.add(this.text.toString().trim()); - } else if (qName.equals("Deny")) { + } else if (qName.equals(XmlConstants.XML_DENY)) { this.denyList.add(this.text.toString().trim()); - } else if (qName.equals("Privilege")) { + } 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); - } else if (qName.equals("Role")) { + } else if (qName.equals(XmlConstants.XML_ROLE)) { Role role = new Role(this.roleName, this.privileges); - PrivilegeModelSaxReader.this.roles.add(role); - PrivilegeModelSaxReader.logger.info("New Role: " + role); + getRoles().add(role); + logger.info(MessageFormat.format("New Role: {0}", role)); //$NON-NLS-1$ init(); } } @@ -248,10 +244,10 @@ public class PrivilegeModelSaxReader extends DefaultHandler { this.text = new StringBuilder(); - if (qName.equals("User")) { - this.userId = attributes.getValue("userId"); - this.username = attributes.getValue("username"); - this.password = attributes.getValue("password"); + 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); } } @@ -263,22 +259,22 @@ public class PrivilegeModelSaxReader extends DefaultHandler { @Override public void endElement(String uri, String localName, String qName) throws SAXException { - if (qName.equals("Firstname")) { + if (qName.equals(XmlConstants.XML_FIRSTNAME)) { this.firstName = this.text.toString().trim(); - } else if (qName.equals("Surname")) { + } else if (qName.equals(XmlConstants.XML_SURNAME)) { this.surname = this.text.toString().trim(); - } else if (qName.equals("State")) { + } else if (qName.equals(XmlConstants.XML_STATE)) { this.userState = UserState.valueOf(this.text.toString().trim()); - } else if (qName.equals("Locale")) { + } else if (qName.equals(XmlConstants.XML_LOCALE)) { this.locale = Locale.forLanguageTag(this.text.toString().trim()); - } else if (qName.equals("Role")) { + } else if (qName.equals(XmlConstants.XML_ROLE)) { this.userRoles.add(this.text.toString().trim()); - } else if (qName.equals("User")) { + } else if (qName.equals(XmlConstants.XML_USER)) { User user = new User(this.userId, this.username, this.password, this.firstName, this.surname, this.userState, this.userRoles, this.locale, this.parameters); - PrivilegeModelSaxReader.this.users.add(user); + getUsers().add(user); } } @@ -294,20 +290,17 @@ public class PrivilegeModelSaxReader extends DefaultHandler { // - private Map parameterMap = new HashMap(); + public Map parameterMap = new HashMap(); @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { - if (qName.equals("Property")) { - String key = attributes.getValue("name"); - String value = attributes.getValue("value"); + 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); } } - /** - * @return the parameterMap - */ public Map getParameterMap() { return this.parameterMap; } diff --git a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java index 7506a933b..cac5aef26 100644 --- a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java @@ -148,7 +148,7 @@ public class XmlTest { configSaxWriter.write(); String fileHash = StringHelper.getHexString(FileHelper.hashFileSha256(configFile)); - assertEquals("2ABD3442EEC8BCEC5BEE365AAB6DB2FD4E1789325425CB1E017E900582525685", fileHash); + assertEquals("2abd3442eec8bcec5bee365aab6db2fd4e1789325425cb1e017e900582525685", fileHash); } @Test @@ -210,6 +210,6 @@ public class XmlTest { configSaxWriter.write(); String fileHash = StringHelper.getHexString(FileHelper.hashFileSha256(modelFile)); - assertEquals("A2127D20A61E00BCDBB61569CD2B200C4F0F111C972BAC3B1E54DF3B2FCDC8BE", fileHash); + assertEquals("a2127d20a61e00bcdbb61569cd2b200c4f0f111c972bac3b1e54df3b2fcdc8be", fileHash); } } From 5d59a52eeb2aad2860b52b5b628b2bed2e3b8c7a Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 28 Dec 2013 10:55:58 +0100 Subject: [PATCH 118/173] [Minor] Throwing explicit exception system user tries to login Further enforcing that a system user may not have a password --- config/PrivilegeModel.xml | 3 +-- .../handler/DefaultPrivilegeHandler.java | 18 +++++++++--------- .../privilege/model/internal/User.java | 2 +- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/config/PrivilegeModel.xml b/config/PrivilegeModel.xml index 2d1659459..9f15dcc53 100644 --- a/config/PrivilegeModel.xml +++ b/config/PrivilegeModel.xml @@ -18,7 +18,7 @@ - + System User Administrator SYSTEM @@ -26,7 +26,6 @@ system_admin_privileges - diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 66fdb4ef4..62d260d95 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -637,6 +637,13 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { throw new AccessDeniedException(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 no login!"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, username); + throw new AccessDeniedException(msg); + } + // validate password String pwHash = user.getPassword(); if (pwHash == null) @@ -1009,9 +1016,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { */ private PrivilegeContext getSystemUserPrivilegeContext(String systemUsername) { - // we only work with hashed passwords - String passwordHash = this.encryptionHandler.convertToHash(systemUsername.getBytes()); - // get user object User user = this.persistenceHandler.getUser(systemUsername); // no user means no authentication @@ -1022,12 +1026,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // validate password String pwHash = user.getPassword(); - if (pwHash == null) { - String msg = MessageFormat.format("System user {0} has no password and may not login!", systemUsername); //$NON-NLS-1$ - throw new AccessDeniedException(msg); - } - if (!pwHash.equals(passwordHash)) { - String msg = MessageFormat.format("System user {0} has an incorrect password defined!", systemUsername); //$NON-NLS-1$ + if (pwHash != null) { + String msg = MessageFormat.format("System users must not have a password: {0}", systemUsername); //$NON-NLS-1$ throw new AccessDeniedException(msg); } diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/User.java b/src/main/java/ch/eitchnet/privilege/model/internal/User.java index 7d1402234..0e260ccaf 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/User.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/User.java @@ -105,7 +105,7 @@ public final class User { this.userId = userId; this.username = username; - this.password = password; + this.password = StringHelper.isEmpty(password) ? null : password; this.userState = userState; this.firstname = firstname; From bab1e4b119c1dd42c5bf3c2765707e7d1a52b69a Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 28 Dec 2013 12:16:48 +0100 Subject: [PATCH 119/173] [Bugfix] fixed a bug where the role were not properly read from XML Added tests for reading the model from XML --- .../handler/DefaultPrivilegeHandler.java | 11 +- .../xml/PrivilegeModelSaxReader.java | 15 +- .../ch/eitchnet/privilege/test/XmlTest.java | 130 +++++++++++++++++- 3 files changed, 143 insertions(+), 13 deletions(-) diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 62d260d95..295541f58 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -972,9 +972,14 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // get certificate for this system user PrivilegeContext systemUserPrivilegeContext = getSystemUserPrivilegeContext(systemUsername); - - // perform the action - action.execute(systemUserPrivilegeContext); + String sessionId = systemUserPrivilegeContext.getCertificate().getSessionId(); + this.privilegeContextMap.put(sessionId, systemUserPrivilegeContext); + try { + // perform the action + action.execute(systemUserPrivilegeContext); + } finally { + this.privilegeContextMap.remove(sessionId); + } } /** diff --git a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java index df0b05460..dd5b83278 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java +++ b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java @@ -75,12 +75,12 @@ public class PrivilegeModelSaxReader extends DefaultHandler { @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { - if (qName.equals(XmlConstants.XML_USERS)) { + if (qName.equals(XmlConstants.XML_USER)) { this.buildersStack.add(new UserParser()); this.insideUser = true; } else if (qName.equals(XmlConstants.XML_PROPERTIES)) { this.buildersStack.add(new PropertyParser()); - } else if (qName.equals(XmlConstants.XML_ROLES) && !this.insideUser) { + } else if (qName.equals(XmlConstants.XML_ROLE) && !this.insideUser) { this.buildersStack.add(new RoleParser()); } @@ -101,16 +101,13 @@ public class PrivilegeModelSaxReader extends DefaultHandler { this.buildersStack.peek().endElement(uri, localName, qName); ElementParser elementParser = null; - if (qName.equals(XmlConstants.XML_USERS)) { + if (qName.equals(XmlConstants.XML_USER)) { elementParser = this.buildersStack.pop(); this.insideUser = false; - logger.info("Popping for Users"); //$NON-NLS-1$ } else if (qName.equals(XmlConstants.XML_PROPERTIES)) { elementParser = this.buildersStack.pop(); - logger.info("Popping for Properties"); //$NON-NLS-1$ - } else if (qName.equals(XmlConstants.XML_ROLES) && !this.insideUser) { + } else if (qName.equals(XmlConstants.XML_ROLE) && !this.insideUser) { elementParser = this.buildersStack.pop(); - logger.info("Popping for Roles"); //$NON-NLS-1$ } if (!this.buildersStack.isEmpty() && elementParser != null) @@ -266,14 +263,14 @@ public class PrivilegeModelSaxReader extends DefaultHandler { } else if (qName.equals(XmlConstants.XML_STATE)) { this.userState = UserState.valueOf(this.text.toString().trim()); } else if (qName.equals(XmlConstants.XML_LOCALE)) { - this.locale = Locale.forLanguageTag(this.text.toString().trim()); + 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_USER)) { User user = new User(this.userId, this.username, this.password, this.firstName, this.surname, this.userState, this.userRoles, this.locale, this.parameters); - + logger.info(MessageFormat.format("New User: {0}", user)); //$NON-NLS-1$ getUsers().add(user); } } diff --git a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java index cac5aef26..dbdc7bc6f 100644 --- a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java @@ -15,11 +15,16 @@ */ 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; @@ -166,7 +171,130 @@ public class XmlTest { assertEquals(2, users.size()); assertEquals(4, roles.size()); - // TODO extend assertions to actual model + // assert model + + // + // 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.getSurname()); + 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.getSurname()); + assertEquals(UserState.SYSTEM, systemAdmin.getUserState()); + assertEquals("en_gb", systemAdmin.getLocale().toString()); + assertThat(systemAdmin.getRoles(), containsInAnyOrder("system_admin_privileges")); + assertTrue(systemAdmin.getProperties().isEmpty()); + + // + // roles + // + + // PrivilegeAdmin + Role privilegeAdmin = findRole("PrivilegeAdmin", roles); + assertEquals("PrivilegeAdmin", privilegeAdmin.getName()); + assertTrue(privilegeAdmin.getPrivilegeNames().isEmpty()); + + // 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.test.model.TestSystemUserAction", + "ch.eitchnet.privilege.test.model.TestSystemRestrictable")); + + IPrivilege testSystemUserAction = systemAdminPrivileges + .getPrivilege("ch.eitchnet.privilege.test.model.TestSystemUserAction"); + assertEquals("ch.eitchnet.privilege.test.model.TestSystemUserAction", testSystemUserAction.getName()); + assertEquals("DefaultPrivilege", testSystemUserAction.getPolicy()); + assertTrue(testSystemUserAction.isAllAllowed()); + assertEquals(0, testSystemUserAction.getAllowList().size()); + assertEquals(0, 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.test.model.TestSystemUserAction")); + + IPrivilege testSystemUserAction2 = restrictedRole + .getPrivilege("ch.eitchnet.privilege.test.model.TestSystemUserAction"); + assertEquals("ch.eitchnet.privilege.test.model.TestSystemUserAction", 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 From 11f53cb2721d466280e628abfdf1d6d6b5e7bbdb Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 29 Dec 2013 23:12:37 +0100 Subject: [PATCH 120/173] [Minor] removed the use of the authPass in the certificate --- .../handler/DefaultPrivilegeHandler.java | 10 +++---- .../eitchnet/privilege/model/Certificate.java | 28 +++---------------- 2 files changed, 8 insertions(+), 30 deletions(-) diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 295541f58..0d2e284b8 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -668,15 +668,14 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } // get 2 auth tokens - String authToken = this.encryptionHandler.nextToken(); - String authPassword = this.encryptionHandler.nextToken(); + String authToken = this.encryptionHandler.convertToHash(this.encryptionHandler.nextToken()); // get next session id String sessionId = nextSessionId(); // create a new certificate, with details of the user - certificate = new Certificate(sessionId, System.currentTimeMillis(), username, authToken, authPassword, - user.getLocale(), new HashMap(user.getProperties())); + certificate = new Certificate(sessionId, System.currentTimeMillis(), username, authToken, user.getLocale(), + new HashMap(user.getProperties())); PrivilegeContext privilegeContext = buildPrivilegeContext(certificate, user); this.privilegeContextMap.put(sessionId, privilegeContext); @@ -1051,14 +1050,13 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // get 2 auth tokens String authToken = this.encryptionHandler.nextToken(); - String authPassword = this.encryptionHandler.nextToken(); // get next session id String sessionId = nextSessionId(); // create a new certificate, with details of the user Certificate systemUserCertificate = new Certificate(sessionId, System.currentTimeMillis(), systemUsername, - authToken, authPassword, user.getLocale(), new HashMap(user.getProperties())); + authToken, user.getLocale(), new HashMap(user.getProperties())); // create and save a new privilege context PrivilegeContext privilegeContext = buildPrivilegeContext(systemUserCertificate, user); diff --git a/src/main/java/ch/eitchnet/privilege/model/Certificate.java b/src/main/java/ch/eitchnet/privilege/model/Certificate.java index 503f49515..2d417f86c 100644 --- a/src/main/java/ch/eitchnet/privilege/model/Certificate.java +++ b/src/main/java/ch/eitchnet/privilege/model/Certificate.java @@ -38,7 +38,6 @@ public final class Certificate implements Serializable { private final long loginTime; private final String username; private final String authToken; - private final String authPassword; private Locale locale; @@ -58,17 +57,14 @@ public final class Certificate implements Serializable { * the users login name * @param authToken * the authentication token defining the users unique session and is a private field of this certificate. - * @param authPassword - * the password to access the authentication token, this is not known to the client but set by the - * {@link PrivilegeHandler} on authentication. * @param locale * the users {@link Locale} * @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, long loginTime, String username, String authToken, String authPassword, - Locale locale, Map propertyMap) { + public Certificate(String sessionId, long loginTime, String username, String authToken, Locale locale, + Map propertyMap) { // validate arguments are not null if (StringHelper.isEmpty(sessionId)) { @@ -80,15 +76,11 @@ public final class Certificate implements Serializable { if (StringHelper.isEmpty(authToken)) { throw new PrivilegeException("authToken is null!"); //$NON-NLS-1$ } - if (StringHelper.isEmpty(authPassword)) { - throw new PrivilegeException("authPassword is null!"); //$NON-NLS-1$ - } this.sessionId = sessionId; this.loginTime = loginTime; this.username = username; this.authToken = authToken; - this.authPassword = authPassword; // if no locale is given, set default if (locale == null) @@ -145,16 +137,10 @@ public final class Certificate implements Serializable { /** * Returns the authToken if the given authPassword is correct, null otherwise * - * @param authPassword - * the authentication password with which this certificate was created - * * @return the authToken if the given authPassword is correct, null otherwise */ - public String getAuthToken(String authPassword) { - if (this.authPassword.equals(authPassword)) - return this.authToken; - - return null; + public String getAuthToken() { + return this.authToken; } /** @@ -180,7 +166,6 @@ public final class Certificate implements Serializable { public int hashCode() { final int prime = 31; int result = 1; - result = prime * result + ((this.authPassword == null) ? 0 : this.authPassword.hashCode()); 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()); @@ -197,11 +182,6 @@ public final class Certificate implements Serializable { if (!(obj instanceof Certificate)) return false; Certificate other = (Certificate) obj; - if (this.authPassword == null) { - if (other.authPassword != null) - return false; - } else if (!this.authPassword.equals(other.authPassword)) - return false; if (this.authToken == null) { if (other.authToken != null) return false; From 1f28237091cc580a4d2f1db057fb9bc42f5baf0f Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 23 Jan 2014 22:59:42 +0100 Subject: [PATCH 121/173] [Project] added Jenkins build badge to README.md --- README => README.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) rename README => README.md (83%) diff --git a/README b/README.md similarity index 83% rename from README rename to README.md index 73ec1b402..ba63eaf11 100644 --- a/README +++ b/README.md @@ -1,8 +1,10 @@ +ch.eitchnet.privilege +================== -Privilege README file +[![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 @@ -23,7 +25,7 @@ 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 @@ -39,7 +41,7 @@ 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? @@ -64,13 +66,13 @@ 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: @@ -78,7 +80,7 @@ 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. From 4627f59a808749225d0771d8031d2d0336b4057f Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Tue, 28 Jan 2014 22:07:04 +0100 Subject: [PATCH 122/173] [Minor] properties are read-only on Certificate --- .../ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java | 2 +- src/main/java/ch/eitchnet/privilege/model/Certificate.java | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 0d2e284b8..fe9f2a175 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -639,7 +639,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // 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 no login!"; //$NON-NLS-1$ + String msg = "User {0} is a system user and may not login!"; //$NON-NLS-1$ msg = MessageFormat.format(msg, username); throw new AccessDeniedException(msg); } diff --git a/src/main/java/ch/eitchnet/privilege/model/Certificate.java b/src/main/java/ch/eitchnet/privilege/model/Certificate.java index 2d417f86c..1bac8c70e 100644 --- a/src/main/java/ch/eitchnet/privilege/model/Certificate.java +++ b/src/main/java/ch/eitchnet/privilege/model/Certificate.java @@ -16,6 +16,7 @@ package ch.eitchnet.privilege.model; import java.io.Serializable; +import java.util.Collections; import java.util.Locale; import java.util.Map; @@ -88,7 +89,7 @@ public final class Certificate implements Serializable { else this.locale = locale; - this.propertyMap = propertyMap; + this.propertyMap = Collections.unmodifiableMap(propertyMap); } /** From 5e6423c44319fa1b1cfb8382adda0d753cd1cf83 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Tue, 28 Jan 2014 22:19:15 +0100 Subject: [PATCH 123/173] [Bugfix] fixed NPE when Certificate is created with null properties --- src/main/java/ch/eitchnet/privilege/model/Certificate.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/ch/eitchnet/privilege/model/Certificate.java b/src/main/java/ch/eitchnet/privilege/model/Certificate.java index 1bac8c70e..ba2f74bc1 100644 --- a/src/main/java/ch/eitchnet/privilege/model/Certificate.java +++ b/src/main/java/ch/eitchnet/privilege/model/Certificate.java @@ -89,7 +89,10 @@ public final class Certificate implements Serializable { else this.locale = locale; - this.propertyMap = Collections.unmodifiableMap(propertyMap); + if (propertyMap == null) + this.propertyMap = Collections.emptyMap(); + else + this.propertyMap = Collections.unmodifiableMap(propertyMap); } /** From 58b742d5d9b057732741a97fc9c76ee30269b148 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 1 Feb 2014 13:47:04 +0100 Subject: [PATCH 124/173] [Minor] using Deque instead of Stack --- .../privilege/xml/PrivilegeConfigSaxReader.java | 11 ++++++----- .../privilege/xml/PrivilegeModelSaxReader.java | 11 ++++++----- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java index f7e09a822..c1b31c5a2 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java +++ b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java @@ -15,9 +15,10 @@ */ package ch.eitchnet.privilege.xml; +import java.util.ArrayDeque; +import java.util.Deque; import java.util.HashMap; import java.util.Map; -import java.util.Stack; import org.xml.sax.Attributes; import org.xml.sax.SAXException; @@ -33,7 +34,7 @@ public class PrivilegeConfigSaxReader extends DefaultHandler { // private static final Logger logger = LoggerFactory.getLogger(PrivilegeConfigSaxReader.class); - private Stack buildersStack = new Stack(); + private Deque buildersStack = new ArrayDeque(); private PrivilegeContainerModel containerModel; @@ -49,11 +50,11 @@ public class PrivilegeConfigSaxReader extends DefaultHandler { public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { if (qName.equals(XmlConstants.XML_CONTAINER)) { - this.buildersStack.add(new ContainerParser()); + this.buildersStack.push(new ContainerParser()); } else if (qName.equals(XmlConstants.XML_PARAMETERS)) { - this.buildersStack.add(new ParametersParser()); + this.buildersStack.push(new ParametersParser()); } else if (qName.equals(XmlConstants.XML_POLICIES)) { - this.buildersStack.add(new PoliciesParser()); + this.buildersStack.push(new PoliciesParser()); } if (!this.buildersStack.isEmpty()) diff --git a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java index dd5b83278..1ac89649e 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java +++ b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java @@ -16,14 +16,15 @@ 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 java.util.Stack; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -46,7 +47,7 @@ public class PrivilegeModelSaxReader extends DefaultHandler { protected static final Logger logger = LoggerFactory.getLogger(PrivilegeModelSaxReader.class); - private Stack buildersStack = new Stack(); + private Deque buildersStack = new ArrayDeque(); private List users; private List roles; @@ -76,12 +77,12 @@ public class PrivilegeModelSaxReader extends DefaultHandler { public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { if (qName.equals(XmlConstants.XML_USER)) { - this.buildersStack.add(new UserParser()); + this.buildersStack.push(new UserParser()); this.insideUser = true; } else if (qName.equals(XmlConstants.XML_PROPERTIES)) { - this.buildersStack.add(new PropertyParser()); + this.buildersStack.push(new PropertyParser()); } else if (qName.equals(XmlConstants.XML_ROLE) && !this.insideUser) { - this.buildersStack.add(new RoleParser()); + this.buildersStack.push(new RoleParser()); } if (!this.buildersStack.isEmpty()) From 77f631a2dcd5ec6d854f59fb3f9a3b1659bf5c20 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 14 Mar 2014 14:36:07 +0100 Subject: [PATCH 125/173] [Project] fixed urls of projects --- README.md | 2 +- pom.xml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index ba63eaf11..e5eeeb487 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ 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/eitch/ch.eitchnet.privilege + 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 diff --git a/pom.xml b/pom.xml index 110c03a32..0f2ef0408 100644 --- a/pom.xml +++ b/pom.xml @@ -13,18 +13,18 @@ jar 0.2.0-SNAPSHOT ch.eitchnet.privilege - https://github.com/eitch/ch.eitchnet.privilege + https://github.com/eitchnet/ch.eitchnet.privilege 2011 Github Issues - https://github.com/eitch/ch.eitchnet.privilege/issues + https://github.com/eitchnet/ch.eitchnet.privilege/issues - scm:git:https://github.com/eitch/ch.eitchnet.privilege.git - scm:git:git@github.com:eitch/ch.eitchnet.privilege.git - https://github.com/eitch/ch.eitchnet.privilege + 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 From 2e1412de9364138b38dee1f7d00fad20b48ba077 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Tue, 15 Apr 2014 19:18:11 +0200 Subject: [PATCH 126/173] [Major] Removed the use of a ThreadLocal for the PrivilegeContext ThreadLocals are bad idea when ClassLoaders come into play, so removing the need makes Privilege better usable in different contexts. --- config/PrivilegeModel.xml | 17 ++++ .../handler/DefaultPrivilegeHandler.java | 4 +- .../privilege/model/PrivilegeContext.java | 44 +-------- .../privilege/policy/DefaultPrivilege.java | 6 +- .../privilege/policy/PrivilegePolicy.java | 6 +- .../privilege/test/PrivilegeTest.java | 91 +++++++++++++------ .../ch/eitchnet/privilege/test/XmlTest.java | 4 +- .../test/model/TestSystemUserAction.java | 2 - 8 files changed, 92 insertions(+), 82 deletions(-) diff --git a/config/PrivilegeModel.xml b/config/PrivilegeModel.xml index 9f15dcc53..488ae379a 100644 --- a/config/PrivilegeModel.xml +++ b/config/PrivilegeModel.xml @@ -28,6 +28,17 @@ + + System User + Administrator + SYSTEM + en_GB + + system_admin_privileges + system_admin_privileges2 + + + @@ -49,6 +60,12 @@ + + + true + + + hello diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index fe9f2a175..f48a10964 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -41,7 +41,6 @@ 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.utils.helper.ClassHelper; /** *

      @@ -1095,7 +1094,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // instantiate the policy PrivilegePolicy policy; try { - policy = ClassHelper.instantiateClass(policyClazz); + + 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); diff --git a/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java b/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java index d7780b635..8cd33eef1 100644 --- a/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java +++ b/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java @@ -108,48 +108,6 @@ public class PrivilegeContext { } // delegate to the policy - policy.validateAction(privilege, restrictable); - } - - // - // ThreadLocal binding - // - - private static final ThreadLocal threadLocal = new ThreadLocal(); - - /** - * Returns the currently {@link ThreadLocal} bound {@link PrivilegeContext} or throws a {@link PrivilegeException} - * if none is set - * - * @return the currently {@link ThreadLocal} bound {@link PrivilegeContext} or throws a {@link PrivilegeException} - * if none is set - * - * @throws PrivilegeException - * if no {@link PrivilegeContext} is set - */ - public static PrivilegeContext get() throws PrivilegeException { - PrivilegeContext privilegeContext = PrivilegeContext.threadLocal.get(); - if (privilegeContext == null) { - throw new PrivilegeException("There is no PrivilegeContext currently bound to the ThreadLocal!"); //$NON-NLS-1$ - } - return privilegeContext; - } - - /** - * Bind a {@link PrivilegeContext} to the {@link ThreadLocal} or throws a {@link PrivilegeException} if one is - * already bound - * - * @param privilegeContext - * the {@link PrivilegeContext} to bind - * - * @throws PrivilegeException - * if e {@link PrivilegeContext} is already bound - */ - public static void set(PrivilegeContext privilegeContext) throws PrivilegeException { - PrivilegeContext currentContext = PrivilegeContext.threadLocal.get(); - if (privilegeContext != null && currentContext != null) { - throw new PrivilegeException("There already is a PrivilegeContext bound to the ThreadLocal!"); //$NON-NLS-1$ - } - PrivilegeContext.threadLocal.set(privilegeContext); + policy.validateAction(this, privilege, restrictable); } } diff --git a/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java b/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java index 2a10f3270..32375ee3b 100644 --- a/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java +++ b/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java @@ -40,7 +40,7 @@ public class DefaultPrivilege implements PrivilegePolicy { * @see ch.eitchnet.privilege.policy.PrivilegePolicy#validateAction(IPrivilege, Restrictable) */ @Override - public void validateAction(IPrivilege privilege, Restrictable restrictable) { + public void validateAction(PrivilegeContext ctx, IPrivilege privilege, Restrictable restrictable) { if (privilege == null) throw new PrivilegeException(PrivilegeMessages.getString("Privilege.privilegeNull")); //$NON-NLS-1$ @@ -82,7 +82,7 @@ public class DefaultPrivilege implements PrivilegePolicy { if (privilege.isDenied(privilegeValue)) { // then throw access denied String msg = MessageFormat.format(PrivilegeMessages.getString("Privilege.accessdenied.noprivilege"), //$NON-NLS-1$ - PrivilegeContext.get().getUsername(), privilegeName, restrictable.getClass().getName()); + ctx.getUsername(), privilegeName, restrictable.getClass().getName()); throw new AccessDeniedException(msg); } @@ -92,7 +92,7 @@ public class DefaultPrivilege implements PrivilegePolicy { // default is not allowed String msg = MessageFormat.format(PrivilegeMessages.getString("Privilege.accessdenied.noprivilege"), //$NON-NLS-1$ - PrivilegeContext.get().getUsername(), privilegeName, restrictable.getClass().getName()); + ctx.getUsername(), privilegeName, restrictable.getClass().getName()); throw new AccessDeniedException(msg); } } diff --git a/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicy.java b/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicy.java index a585ef589..ba698b174 100644 --- a/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicy.java +++ b/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicy.java @@ -17,6 +17,7 @@ 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; @@ -38,6 +39,8 @@ 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 @@ -46,5 +49,6 @@ public interface PrivilegePolicy { * @throws AccessDeniedException * if action not allowed */ - public void validateAction(IPrivilege privilege, Restrictable restrictable) throws AccessDeniedException; + public abstract void validateAction(PrivilegeContext context, IPrivilege privilege, Restrictable restrictable) + throws AccessDeniedException; } diff --git a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java index 6d3b1b3ea..3968c6e25 100644 --- a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -27,7 +27,9 @@ import java.util.Map; 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; @@ -63,6 +65,7 @@ public class PrivilegeTest { 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_TEMP = "temp"; @@ -73,7 +76,11 @@ public class PrivilegeTest { private static final Logger logger = LoggerFactory.getLogger(PrivilegeTest.class); + @Rule + public ExpectedException exception = ExpectedException.none(); + private static PrivilegeHandler privilegeHandler; + private PrivilegeContext ctx; /** * @throws Exception @@ -151,19 +158,20 @@ public class PrivilegeTest { Certificate certificate = privilegeHandler.authenticate(username, password); assertTrue("Certificate is null!", certificate != null); PrivilegeContext privilegeContext = privilegeHandler.getPrivilegeContext(certificate); - PrivilegeContext.set(privilegeContext); + this.ctx = privilegeContext; } private void logout() { - try { - PrivilegeContext privilegeContext = PrivilegeContext.get(); - 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; - } finally { - PrivilegeContext.set(null); + 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; + } } } @@ -176,8 +184,9 @@ public class PrivilegeTest { } } - @Test(expected = AccessDeniedException.class) public void testFailAuthenticationNOk() throws Exception { + exception.expect(AccessDeniedException.class); + exception.expectMessage("blabla"); try { login(ADMIN, ArraysHelper.copyOf(PASS_BAD)); } finally { @@ -185,8 +194,9 @@ public class PrivilegeTest { } } - @Test(expected = PrivilegeException.class) public void testFailAuthenticationPWNull() throws Exception { + exception.expect(PrivilegeException.class); + exception.expectMessage("blabla"); try { login(ADMIN, null); } finally { @@ -202,7 +212,7 @@ public class PrivilegeTest { Map privilegeMap = new HashMap(); RoleRep roleRep = new RoleRep(ROLE_TEMP, privilegeMap); - Certificate certificate = PrivilegeContext.get().getCertificate(); + Certificate certificate = this.ctx.getCertificate(); privilegeHandler.addOrReplaceRole(certificate, roleRep); privilegeHandler.persist(certificate); } finally { @@ -217,7 +227,7 @@ public class PrivilegeTest { // see if admin can perform restrictable Restrictable restrictable = new TestRestrictable(); - PrivilegeContext.get().validateAction(restrictable); + this.ctx.validateAction(restrictable); } finally { logout(); @@ -238,8 +248,11 @@ public class PrivilegeTest { /** * Checks that the system user can not perform a valid action, but illegal privilege */ - @Test(expected = PrivilegeException.class) + @Test public void testPerformSystemRestrictableFailPrivilege() throws Exception { + exception.expect(PrivilegeException.class); + exception + .expectMessage("User system_admin does not have Privilege ch.eitchnet.privilege.test.model.TestSystemUserActionDeny"); try { // create the action to be performed as a system user TestSystemUserActionDeny action = new TestSystemUserActionDeny(); @@ -251,11 +264,31 @@ public class PrivilegeTest { } } + /** + * Checks that the system user can not perform a valid action, but illegal privilege + */ + @Test + public void testPerformSystemRestrictableFailNoAdditionalPrivilege() throws Exception { + exception.expect(PrivilegeException.class); + exception.expectMessage("User system_admin2 does not have Privilege ch.eitchnet.privilege.test.model.TestRestrictable"); + 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(expected = AccessDeniedException.class) + @Test public void testLoginSystemUser() throws Exception { + exception.expect(AccessDeniedException.class); + exception.expectMessage("User system_admin is a system user and may not login!"); try { login(SYSTEM_USER_ADMIN, SYSTEM_USER_ADMIN.getBytes()); } finally { @@ -268,7 +301,7 @@ public class PrivilegeTest { try { login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); Restrictable restrictable = new TestRestrictable(); - PrivilegeContext.get().validateAction(restrictable); + this.ctx.validateAction(restrictable); } finally { logout(); } @@ -325,7 +358,7 @@ public class PrivilegeTest { login(BOB, ArraysHelper.copyOf(PASS_BOB)); // see if bob can perform restrictable Restrictable restrictable = new TestRestrictable(); - PrivilegeContext.get().validateAction(restrictable); + this.ctx.validateAction(restrictable); } finally { logout(); } @@ -335,7 +368,7 @@ public class PrivilegeTest { try { // testAddAppRoleToBob login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); - Certificate certificate = PrivilegeContext.get().getCertificate(); + Certificate certificate = this.ctx.getCertificate(); privilegeHandler.addRoleToUser(certificate, BOB, ROLE_APP_USER); logger.info("Added " + ROLE_APP_USER + " to " + BOB); privilegeHandler.persist(certificate); @@ -352,7 +385,7 @@ public class PrivilegeTest { login(BOB, ArraysHelper.copyOf(PASS_BOB)); // see if bob can perform restrictable Restrictable restrictable = new TestRestrictable(); - PrivilegeContext.get().validateAction(restrictable); + this.ctx.validateAction(restrictable); fail("Should fail as bob does not have role app"); } catch (AccessDeniedException e) { String msg = "User bob does not have Privilege ch.eitchnet.privilege.test.model.TestRestrictable needed for Restrictable ch.eitchnet.privilege.test.model.TestRestrictable"; @@ -375,7 +408,7 @@ public class PrivilegeTest { try { // testTedChangesOwnPwd login(TED, ArraysHelper.copyOf(PASS_DEF)); - Certificate certificate = PrivilegeContext.get().getCertificate(); + Certificate certificate = this.ctx.getCertificate(); privilegeHandler.setUserPassword(certificate, TED, ArraysHelper.copyOf(PASS_TED)); } finally { logout(); @@ -387,7 +420,7 @@ public class PrivilegeTest { // testSetTedPwdAsBob login(BOB, ArraysHelper.copyOf(PASS_BOB)); // set ted's password to default - Certificate certificate = PrivilegeContext.get().getCertificate(); + Certificate certificate = this.ctx.getCertificate(); privilegeHandler.setUserPassword(certificate, TED, ArraysHelper.copyOf(PASS_DEF)); privilegeHandler.persist(certificate); } finally { @@ -419,7 +452,7 @@ public class PrivilegeTest { roles.add(ROLE_USER); userRep = new UserRep("2", TED, "Ted", "Newman", UserState.ENABLED, roles, null, new HashMap()); - Certificate certificate = PrivilegeContext.get().getCertificate(); + Certificate certificate = this.ctx.getCertificate(); privilegeHandler.addOrReplaceUser(certificate, userRep, null); logger.info("Added user " + TED); privilegeHandler.persist(certificate); @@ -432,7 +465,7 @@ public class PrivilegeTest { try { // testAddAdminRoleToBob login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); - Certificate certificate = PrivilegeContext.get().getCertificate(); + Certificate certificate = this.ctx.getCertificate(); privilegeHandler.addRoleToUser(certificate, BOB, PrivilegeHandler.PRIVILEGE_ADMIN_ROLE); logger.info("Added " + PrivilegeHandler.PRIVILEGE_ADMIN_ROLE + " to " + ADMIN); privilegeHandler.persist(certificate); @@ -452,7 +485,7 @@ public class PrivilegeTest { // let's add a new user Ted userRep = new UserRep("1", TED, "Ted", "And then Some", UserState.NEW, new HashSet(), null, new HashMap()); - certificate = PrivilegeContext.get().getCertificate(); + certificate = this.ctx.getCertificate(); privilegeHandler.addOrReplaceUser(certificate, userRep, null); fail("User bob may not add a user as bob does not have admin rights!"); } catch (PrivilegeException e) { @@ -476,7 +509,7 @@ public class PrivilegeTest { try { // testAddRoleUserToBob login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); - Certificate certificate = PrivilegeContext.get().getCertificate(); + Certificate certificate = this.ctx.getCertificate(); privilegeHandler.addRoleToUser(certificate, BOB, ROLE_USER); privilegeHandler.persist(certificate); logout(); @@ -491,7 +524,7 @@ public class PrivilegeTest { login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); Map privilegeMap = new HashMap(); RoleRep roleRep = new RoleRep(ROLE_USER, privilegeMap); - Certificate certificate = PrivilegeContext.get().getCertificate(); + Certificate certificate = this.ctx.getCertificate(); privilegeHandler.addOrReplaceRole(certificate, roleRep); privilegeHandler.persist(certificate); } finally { @@ -517,7 +550,7 @@ public class PrivilegeTest { try { // testEnableUserBob login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); - Certificate certificate = PrivilegeContext.get().getCertificate(); + Certificate certificate = this.ctx.getCertificate(); privilegeHandler.setUserState(certificate, BOB, UserState.ENABLED); privilegeHandler.persist(certificate); } finally { @@ -546,7 +579,7 @@ public class PrivilegeTest { // let's add a new user bob UserRep userRep = new UserRep("1", BOB, "Bob", "Newman", UserState.NEW, new HashSet(), null, new HashMap()); - Certificate certificate = PrivilegeContext.get().getCertificate(); + Certificate certificate = this.ctx.getCertificate(); privilegeHandler.addOrReplaceUser(certificate, userRep, null); logger.info("Added user " + BOB); diff --git a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java index dbdc7bc6f..a120c4d6b 100644 --- a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java @@ -168,8 +168,8 @@ public class XmlTest { List roles = xmlHandler.getRoles(); assertNotNull(roles); - assertEquals(2, users.size()); - assertEquals(4, roles.size()); + assertEquals(3, users.size()); + assertEquals(5, roles.size()); // assert model diff --git a/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java b/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java index 86c1613e2..5b64a5421 100644 --- a/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java +++ b/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java @@ -27,8 +27,6 @@ public class TestSystemUserAction implements SystemUserAction { @Override public void execute(PrivilegeContext context) { TestSystemRestrictable restrictable = new TestSystemRestrictable(); - PrivilegeContext.set(context); context.validateAction(restrictable); - PrivilegeContext.set(null); } } From 32c2c43fbdd86fb7618d8c0271dd6f20619103de Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Mon, 4 Aug 2014 00:44:09 +0200 Subject: [PATCH 127/173] [New] added Certificate.sessionDataMap for mutable session information --- .../ch/eitchnet/privilege/model/Certificate.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/main/java/ch/eitchnet/privilege/model/Certificate.java b/src/main/java/ch/eitchnet/privilege/model/Certificate.java index ba2f74bc1..b7e38ac5b 100644 --- a/src/main/java/ch/eitchnet/privilege/model/Certificate.java +++ b/src/main/java/ch/eitchnet/privilege/model/Certificate.java @@ -17,11 +17,13 @@ package ch.eitchnet.privilege.model; import java.io.Serializable; import java.util.Collections; +import java.util.HashMap; import java.util.Locale; import java.util.Map; 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; /** @@ -43,6 +45,7 @@ public final class Certificate implements Serializable { private Locale locale; private Map propertyMap; + private Map sessionDataMap; /** * Default constructor initializing with all information needed for this certificate @@ -93,15 +96,28 @@ public final class Certificate implements Serializable { this.propertyMap = Collections.emptyMap(); else this.propertyMap = Collections.unmodifiableMap(propertyMap); + + this.sessionDataMap = new HashMap<>(); } /** + * Returns the {@link User User's} property map. The map is immutable + * * @return the propertyMap */ public Map getPropertyMap() { return this.propertyMap; } + /** + * Returns a mutable {@link Map} for storing session relevant data + * + * @return the sessionDataMap + */ + public Map getSessionDataMap() { + return this.sessionDataMap; + } + /** * @return the locale */ From 828d725a692092ee33a84a51d4cd832425f7fb0b Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 14 Aug 2014 16:22:15 +0200 Subject: [PATCH 128/173] [Project] using parent version 1.1.0-SNAPSHOT --- pom.xml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 0f2ef0408..e48ed96f9 100644 --- a/pom.xml +++ b/pom.xml @@ -5,13 +5,13 @@ ch.eitchnet ch.eitchnet.parent - 0.1.0-SNAPSHOT + 1.1.0-SNAPSHOT ../ch.eitchnet.parent/pom.xml ch.eitchnet.privilege - jar 0.2.0-SNAPSHOT + jar ch.eitchnet.privilege https://github.com/eitchnet/ch.eitchnet.privilege 2011 @@ -25,6 +25,7 @@ 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 From b47d3e3dcd3c445316264a8925cb3948ea9a6392 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 22 Aug 2014 18:54:10 +0200 Subject: [PATCH 129/173] [Minor] firstname and lastname are not required for SYSTEM users --- .../ch/eitchnet/privilege/model/UserRep.java | 14 ++++++++------ .../privilege/model/internal/User.java | 18 ++++++++++-------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/main/java/ch/eitchnet/privilege/model/UserRep.java b/src/main/java/ch/eitchnet/privilege/model/UserRep.java index 26bdc141e..5f50594e2 100644 --- a/src/main/java/ch/eitchnet/privilege/model/UserRep.java +++ b/src/main/java/ch/eitchnet/privilege/model/UserRep.java @@ -90,15 +90,17 @@ public class UserRep implements Serializable { if (StringHelper.isEmpty(this.username)) throw new PrivilegeException("username is null or empty"); //$NON-NLS-1$ - if (StringHelper.isEmpty(this.firstname)) - throw new PrivilegeException("firstname is null or empty"); //$NON-NLS-1$ - - if (StringHelper.isEmpty(this.surname)) - throw new PrivilegeException("surname is null or empty"); //$NON-NLS-1$ - if (this.userState == null) throw new PrivilegeException("userState is null"); //$NON-NLS-1$ + if (this.userState != UserState.SYSTEM) { + if (StringHelper.isEmpty(this.firstname)) + throw new PrivilegeException("firstname is null or empty"); //$NON-NLS-1$ + + if (StringHelper.isEmpty(this.surname)) + throw new PrivilegeException("surname is null or empty"); //$NON-NLS-1$ + } + if (this.roles == null) throw new PrivilegeException("roles is null"); //$NON-NLS-1$ } diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/User.java b/src/main/java/ch/eitchnet/privilege/model/internal/User.java index 0e260ccaf..8e710e235 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/User.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/User.java @@ -84,17 +84,19 @@ public final class User { 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 (StringHelper.isEmpty(firstname)) { - throw new PrivilegeException("No firstname defined!"); //$NON-NLS-1$ - } - if (StringHelper.isEmpty(surname)) { - throw new PrivilegeException("No surname defined!"); //$NON-NLS-1$ - } - if (userState == null) { - throw new PrivilegeException("No userState defined!"); //$NON-NLS-1$ + if (userState != UserState.SYSTEM) { + if (StringHelper.isEmpty(surname)) { + throw new PrivilegeException("No surname 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 From d2faed1d2eabaaba0720e00f8c457ce9b90d44ed Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 22 Aug 2014 20:36:18 +0200 Subject: [PATCH 130/173] [Major] added firstname and lastname to certificate Also renamed model attribute from surname to lastname --- config/PrivilegeModel.xml | 6 +-- .../handler/DefaultPrivilegeHandler.java | 40 +++++++++---------- .../privilege/handler/PrivilegeHandler.java | 8 ++-- .../privilege/helper/XmlConstants.java | 4 +- .../eitchnet/privilege/model/Certificate.java | 37 ++++++++++++++++- .../ch/eitchnet/privilege/model/UserRep.java | 32 +++++++-------- .../privilege/model/internal/User.java | 30 +++++++------- .../xml/PrivilegeModelDomWriter.java | 8 ++-- .../xml/PrivilegeModelSaxReader.java | 10 ++--- src/main/resources/PrivilegeModel.xsd | 2 +- .../ch/eitchnet/privilege/test/XmlTest.java | 6 +-- 11 files changed, 108 insertions(+), 75 deletions(-) diff --git a/config/PrivilegeModel.xml b/config/PrivilegeModel.xml index 488ae379a..1ab9708d6 100644 --- a/config/PrivilegeModel.xml +++ b/config/PrivilegeModel.xml @@ -5,7 +5,7 @@ Application - Administrator + Administrator ENABLED en_GB @@ -20,7 +20,7 @@ System User - Administrator + Administrator SYSTEM en_GB @@ -30,7 +30,7 @@ System User - Administrator + Administrator SYSTEM en_GB diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index f48a10964..e3ec04f6f 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -129,7 +129,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { String selUserId = selectorRep.getUserId(); String selUsername = selectorRep.getUsername(); String selFirstname = selectorRep.getFirstname(); - String selSurname = selectorRep.getSurname(); + String selLastname = selectorRep.getLastname(); UserState selUserState = selectorRep.getUserState(); Locale selLocale = selectorRep.getLocale(); Set selRoles = selectorRep.getRoles(); @@ -143,7 +143,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { boolean userIdSelected; boolean usernameSelected; boolean firstnameSelected; - boolean surnameSelected; + boolean lastnameSelected; boolean userStateSelected; boolean localeSelected; boolean roleSelected; @@ -173,13 +173,13 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { else firstnameSelected = false; - // surname - if (selSurname == null) - surnameSelected = true; - else if (selSurname.equals(user.getSurname())) - surnameSelected = true; + // lastname + if (selLastname == null) + lastnameSelected = true; + else if (selLastname.equals(user.getLastname())) + lastnameSelected = true; else - surnameSelected = false; + lastnameSelected = false; // user state if (selUserState == null) @@ -203,7 +203,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // properties propertySelected = isSelectedByProperty(selPropertyMap, user.getProperties()); - boolean selected = userIdSelected && usernameSelected && firstnameSelected && surnameSelected + boolean selected = userIdSelected && usernameSelected && firstnameSelected && lastnameSelected && userStateSelected && localeSelected && roleSelected && propertySelected; if (selected) @@ -303,7 +303,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // create new user User user = new User(userRep.getUserId(), userRep.getUsername(), passwordHash, userRep.getFirstname(), - userRep.getSurname(), userRep.getUserState(), userRep.getRoles(), userRep.getLocale(), + userRep.getLastname(), userRep.getUserState(), userRep.getRoles(), userRep.getLocale(), userRep.getProperties()); // delegate to persistence handler @@ -387,7 +387,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { newRoles.add(roleName); User newUser = new User(user.getUserId(), user.getUsername(), user.getPassword(), user.getFirstname(), - user.getSurname(), user.getUserState(), newRoles, user.getLocale(), user.getProperties()); + user.getLastname(), user.getUserState(), newRoles, user.getLocale(), user.getProperties()); // delegate user replacement to persistence handler this.persistenceHandler.addOrReplaceUser(newUser); @@ -467,7 +467,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { Set newRoles = new HashSet(currentRoles); newRoles.remove(roleName); User newUser = new User(user.getUserId(), user.getUsername(), user.getPassword(), user.getFirstname(), - user.getSurname(), user.getUserState(), newRoles, user.getLocale(), user.getProperties()); + user.getLastname(), user.getUserState(), newRoles, user.getLocale(), user.getProperties()); // delegate user replacement to persistence handler this.persistenceHandler.addOrReplaceUser(newUser); @@ -505,14 +505,14 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // create new user User newUser = new User(user.getUserId(), user.getUsername(), user.getPassword(), user.getFirstname(), - user.getSurname(), user.getUserState(), user.getRoles(), locale, user.getProperties()); + user.getLastname(), user.getUserState(), user.getRoles(), locale, user.getProperties()); // delegate user replacement to persistence handler this.persistenceHandler.addOrReplaceUser(newUser); } @Override - public void setUserName(Certificate certificate, String username, String firstname, String surname) { + public void setUserName(Certificate certificate, String username, String firstname, String lastname) { // validate who is doing this assertIsPrivilegeAdmin(certificate); @@ -524,7 +524,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } // create new user - User newUser = new User(user.getUserId(), user.getUsername(), user.getPassword(), firstname, surname, + User newUser = new User(user.getUserId(), user.getUsername(), user.getPassword(), firstname, lastname, user.getUserState(), user.getRoles(), user.getLocale(), user.getProperties()); // delegate user replacement to persistence handler @@ -569,7 +569,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // create new user User newUser = new User(user.getUserId(), user.getUsername(), passwordHash, user.getFirstname(), - user.getSurname(), user.getUserState(), user.getRoles(), user.getLocale(), user.getProperties()); + user.getLastname(), user.getUserState(), user.getRoles(), user.getLocale(), user.getProperties()); // delegate user replacement to persistence handler this.persistenceHandler.addOrReplaceUser(newUser); @@ -598,7 +598,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // create new user User newUser = new User(user.getUserId(), user.getUsername(), user.getPassword(), user.getFirstname(), - user.getSurname(), state, user.getRoles(), user.getLocale(), user.getProperties()); + user.getLastname(), state, user.getRoles(), user.getLocale(), user.getProperties()); // delegate user replacement to persistence handler this.persistenceHandler.addOrReplaceUser(newUser); @@ -673,8 +673,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { String sessionId = nextSessionId(); // create a new certificate, with details of the user - certificate = new Certificate(sessionId, System.currentTimeMillis(), username, authToken, user.getLocale(), - new HashMap(user.getProperties())); + certificate = new Certificate(sessionId, System.currentTimeMillis(), username, user.getFirstname(), + user.getLastname(), authToken, user.getLocale(), new HashMap(user.getProperties())); PrivilegeContext privilegeContext = buildPrivilegeContext(certificate, user); this.privilegeContextMap.put(sessionId, privilegeContext); @@ -1055,7 +1055,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // create a new certificate, with details of the user Certificate systemUserCertificate = new Certificate(sessionId, System.currentTimeMillis(), systemUsername, - authToken, user.getLocale(), new HashMap(user.getProperties())); + null, null, authToken, user.getLocale(), new HashMap(user.getProperties())); // create and save a new privilege context PrivilegeContext privilegeContext = buildPrivilegeContext(systemUserCertificate, user); diff --git a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java index fafd54bdc..d527fd171 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java @@ -256,7 +256,7 @@ public interface PrivilegeHandler { throws AccessDeniedException, PrivilegeException; /** - * Changes the name of the user. This changes the first name and the surname. If either value is null, then that + * Changes the name of the user. This changes the first name and the lastname. If either value is null, then that * value is not changed * * @param certificate @@ -265,15 +265,15 @@ public interface PrivilegeHandler { * the username of the {@link User} for which the name is to be changed * @param firstname * the new first name - * @param surname - * the new surname + * @param lastname + * the new lastname * * @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 setUserName(Certificate certificate, String username, String firstname, String surname) + public void setUserName(Certificate certificate, String username, String firstname, String lastname) throws AccessDeniedException, PrivilegeException; /** diff --git a/src/main/java/ch/eitchnet/privilege/helper/XmlConstants.java b/src/main/java/ch/eitchnet/privilege/helper/XmlConstants.java index 079fe433d..d4a0c1e9d 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/XmlConstants.java +++ b/src/main/java/ch/eitchnet/privilege/helper/XmlConstants.java @@ -134,9 +134,9 @@ public class XmlConstants { public static final String XML_FIRSTNAME = "Firstname"; /** - * XML_SURNAME = "Surname" : + * XML_LASTNAME = "Lastname" : */ - public static final String XML_SURNAME = "Surname"; + public static final String XML_LASTNAME = "Lastname"; /** * XML_STATE = "State" : diff --git a/src/main/java/ch/eitchnet/privilege/model/Certificate.java b/src/main/java/ch/eitchnet/privilege/model/Certificate.java index b7e38ac5b..9cb3d9ada 100644 --- a/src/main/java/ch/eitchnet/privilege/model/Certificate.java +++ b/src/main/java/ch/eitchnet/privilege/model/Certificate.java @@ -40,6 +40,8 @@ public final class Certificate implements Serializable { private final String sessionId; private final long loginTime; private final String username; + private final String firstname; + private final String lastname; private final String authToken; private Locale locale; @@ -59,6 +61,10 @@ public final class Certificate implements Serializable { * 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 @@ -67,8 +73,8 @@ public final class Certificate implements Serializable { * 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, long loginTime, String username, String authToken, Locale locale, - Map propertyMap) { + public Certificate(String sessionId, long loginTime, String username, String firstname, String lastname, + String authToken, Locale locale, Map propertyMap) { // validate arguments are not null if (StringHelper.isEmpty(sessionId)) { @@ -84,6 +90,8 @@ public final class Certificate implements Serializable { this.sessionId = sessionId; this.loginTime = loginTime; this.username = username; + this.firstname = firstname; + this.lastname = lastname; this.authToken = authToken; // if no locale is given, set default @@ -147,6 +155,20 @@ public final class Certificate implements Serializable { return this.username; } + /** + * @return the firstname + */ + public String getFirstname() { + return this.firstname; + } + + /** + * @return the lastname + */ + public String getLastname() { + return this.lastname; + } + /** * @return the loginTime */ @@ -176,6 +198,17 @@ public final class Certificate implements Serializable { 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("]"); diff --git a/src/main/java/ch/eitchnet/privilege/model/UserRep.java b/src/main/java/ch/eitchnet/privilege/model/UserRep.java index 5f50594e2..3ffa0738d 100644 --- a/src/main/java/ch/eitchnet/privilege/model/UserRep.java +++ b/src/main/java/ch/eitchnet/privilege/model/UserRep.java @@ -39,7 +39,7 @@ public class UserRep implements Serializable { private final String userId; private String username; private String firstname; - private String surname; + private String lastname; private UserState userState; private Set roles; private Locale locale; @@ -54,8 +54,8 @@ public class UserRep implements Serializable { * the user's login name * @param firstname * the user's first name - * @param surname - * the user's surname + * @param lastname + * the user's last name * @param userState * the user's {@link UserState} * @param roles @@ -65,12 +65,12 @@ public class UserRep implements Serializable { * @param propertyMap * a {@link Map} containing string value pairs of properties for this user */ - public UserRep(String userId, String username, String firstname, String surname, UserState userState, + 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.surname = surname; + this.lastname = lastname; this.userState = userState; this.roles = roles; this.locale = locale; @@ -97,8 +97,8 @@ public class UserRep implements Serializable { if (StringHelper.isEmpty(this.firstname)) throw new PrivilegeException("firstname is null or empty"); //$NON-NLS-1$ - if (StringHelper.isEmpty(this.surname)) - throw new PrivilegeException("surname 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) @@ -143,18 +143,18 @@ public class UserRep implements Serializable { } /** - * @return the surname + * @return the lastname */ - public String getSurname() { - return this.surname; + public String getLastname() { + return this.lastname; } /** - * @param surname - * the surname to set + * @param lastname + * the lastname to set */ - public void setSurname(String surname) { - this.surname = surname; + public void setLastname(String lastname) { + this.lastname = lastname; } /** @@ -259,8 +259,8 @@ public class UserRep implements Serializable { builder.append(this.username); builder.append(", firstname="); builder.append(this.firstname); - builder.append(", surname="); - builder.append(this.surname); + builder.append(", lastname="); + builder.append(this.lastname); builder.append(", userState="); builder.append(this.userState); builder.append(", locale="); diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/User.java b/src/main/java/ch/eitchnet/privilege/model/internal/User.java index 8e710e235..f54ccb630 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/User.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/User.java @@ -29,7 +29,7 @@ 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 surname + * 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 @@ -46,7 +46,7 @@ public final class User { private final String password; private final String firstname; - private final String surname; + private final String lastname; private final UserState userState; @@ -67,8 +67,8 @@ public final class User { * the user's password (hashed) * @param firstname * the user's first name - * @param surname - * the user's surname + * @param lastname + * the user's lastname * @param userState * the user's {@link UserState} * @param roles @@ -78,7 +78,7 @@ public final class User { * @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 surname, UserState userState, + public User(String userId, String username, String password, String firstname, String lastname, UserState userState, Set roles, Locale locale, Map propertyMap) { if (StringHelper.isEmpty(userId)) { @@ -91,8 +91,8 @@ public final class User { throw new PrivilegeException("No username defined!"); //$NON-NLS-1$ } if (userState != UserState.SYSTEM) { - if (StringHelper.isEmpty(surname)) { - throw new PrivilegeException("No surname defined!"); //$NON-NLS-1$ + 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$ @@ -111,7 +111,7 @@ public final class User { this.userState = userState; this.firstname = firstname; - this.surname = surname; + this.lastname = lastname; if (roles == null) this.roles = Collections.emptySet(); @@ -153,17 +153,17 @@ public final class User { } /** - * @return the firstname + * @return the first name */ public String getFirstname() { return this.firstname; } /** - * @return the surname + * @return the last name */ - public String getSurname() { - return this.surname; + public String getLastname() { + return this.lastname; } /** @@ -233,7 +233,7 @@ public final class User { * @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.surname, this.userState, + return new UserRep(this.userId, this.username, this.firstname, this.lastname, this.userState, new HashSet(this.roles), this.locale, new HashMap(this.propertyMap)); } @@ -252,8 +252,8 @@ public final class User { builder.append(this.username); builder.append(", firstname="); builder.append(this.firstname); - builder.append(", surname="); - builder.append(this.surname); + builder.append(", lastname="); + builder.append(this.lastname); builder.append(", locale="); builder.append(this.locale); builder.append(", userState="); diff --git a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelDomWriter.java b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelDomWriter.java index 6e419a542..480904019 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelDomWriter.java +++ b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelDomWriter.java @@ -72,10 +72,10 @@ public class PrivilegeModelDomWriter { firstnameElement.setTextContent(user.getFirstname()); userElement.appendChild(firstnameElement); - // add surname element - Element surnameElement = doc.createElement(XmlConstants.XML_SURNAME); - surnameElement.setTextContent(user.getSurname()); - userElement.appendChild(surnameElement); + // add lastname element + Element lastnameElement = doc.createElement(XmlConstants.XML_LASTNAME); + lastnameElement.setTextContent(user.getLastname()); + userElement.appendChild(lastnameElement); // add state element Element stateElement = doc.createElement(XmlConstants.XML_STATE); diff --git a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java index 1ac89649e..03f43bf91 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java +++ b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java @@ -206,7 +206,7 @@ public class PrivilegeModelSaxReader extends DefaultHandler { // // Application -// Administrator +// Administrator // ENABLED // en_GB // @@ -227,7 +227,7 @@ public class PrivilegeModelSaxReader extends DefaultHandler { String username; String password; String firstName; - String surname; + String lastname; UserState userState; Locale locale; Set userRoles; @@ -259,8 +259,8 @@ public class PrivilegeModelSaxReader extends DefaultHandler { if (qName.equals(XmlConstants.XML_FIRSTNAME)) { this.firstName = this.text.toString().trim(); - } else if (qName.equals(XmlConstants.XML_SURNAME)) { - this.surname = 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)) { @@ -269,7 +269,7 @@ public class PrivilegeModelSaxReader extends DefaultHandler { this.userRoles.add(this.text.toString().trim()); } else if (qName.equals(XmlConstants.XML_USER)) { - User user = new User(this.userId, this.username, this.password, this.firstName, this.surname, + 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); diff --git a/src/main/resources/PrivilegeModel.xsd b/src/main/resources/PrivilegeModel.xsd index 7d0ea3d40..d2f82d897 100644 --- a/src/main/resources/PrivilegeModel.xsd +++ b/src/main/resources/PrivilegeModel.xsd @@ -45,7 +45,7 @@ Copyright 2013 Robert von Burg <eitch@eitchnet.ch> - + diff --git a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java index a120c4d6b..b1db9740e 100644 --- a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java @@ -183,7 +183,7 @@ public class XmlTest { assertEquals("admin", admin.getUsername()); assertEquals("8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918", admin.getPassword()); assertEquals("Application", admin.getFirstname()); - assertEquals("Administrator", admin.getSurname()); + assertEquals("Administrator", admin.getLastname()); assertEquals(UserState.ENABLED, admin.getUserState()); assertEquals("en_gb", admin.getLocale().toString()); assertThat(admin.getRoles(), containsInAnyOrder("PrivilegeAdmin", "AppUser")); @@ -198,7 +198,7 @@ public class XmlTest { assertEquals("system_admin", systemAdmin.getUsername()); assertEquals(null, systemAdmin.getPassword()); assertEquals("System User", systemAdmin.getFirstname()); - assertEquals("Administrator", systemAdmin.getSurname()); + assertEquals("Administrator", systemAdmin.getLastname()); assertEquals(UserState.SYSTEM, systemAdmin.getUserState()); assertEquals("en_gb", systemAdmin.getLocale().toString()); assertThat(systemAdmin.getRoles(), containsInAnyOrder("system_admin_privileges")); @@ -338,6 +338,6 @@ public class XmlTest { configSaxWriter.write(); String fileHash = StringHelper.getHexString(FileHelper.hashFileSha256(modelFile)); - assertEquals("a2127d20a61e00bcdbb61569cd2b200c4f0f111c972bac3b1e54df3b2fcdc8be", fileHash); + assertEquals("c9732a05bf0ed53d89b3d12e7c8d7216150b6a91412d1bf47fbe3e6f3be750ff", fileHash); } } From bb949f38af6e48831b924bbe193c4c2c2e1da849 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 22 Aug 2014 22:28:33 +0200 Subject: [PATCH 131/173] [Minor] Added check for user's referencing inexistant oles --- .../privilege/handler/XmlPersistenceHandler.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java index d94c6b246..dc21d5b91 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java @@ -189,6 +189,19 @@ public class XmlPersistenceHandler implements PersistenceHandler { 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()); //$NON-NLS-1$ + throw new PrivilegeException(msg); + } + } + } + // validate we have a user with PrivilegeAdmin access boolean privilegeAdminExists = false; for (String username : this.userMap.keySet()) { From 7e4adffb4c1b274a9e0af28840342cacb8740fab Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 24 Aug 2014 17:21:29 +0200 Subject: [PATCH 132/173] [Project] set version to 1.0.0-SNAPSHOT --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index e48ed96f9..7fda9e906 100644 --- a/pom.xml +++ b/pom.xml @@ -5,12 +5,12 @@ ch.eitchnet ch.eitchnet.parent - 1.1.0-SNAPSHOT + 1.0.0 ../ch.eitchnet.parent/pom.xml ch.eitchnet.privilege - 0.2.0-SNAPSHOT + 1.0.0-SNAPSHOT jar ch.eitchnet.privilege https://github.com/eitchnet/ch.eitchnet.privilege From dd9824f1303c4c8b5181cb675bfe1cb83d4b62fb Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 24 Aug 2014 18:17:35 +0200 Subject: [PATCH 133/173] [Project] set version to 1.0.0-SNAPSHOT --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 7fda9e906..ad8d85972 100644 --- a/pom.xml +++ b/pom.xml @@ -32,7 +32,7 @@ ch.eitchnet ch.eitchnet.utils - 0.2.0-SNAPSHOT + 1.0.0-SNAPSHOT From 901c2c86f2262ccc7d70c535a91f6648037b20ef Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Mon, 8 Sep 2014 13:35:02 +0200 Subject: [PATCH 134/173] [New] added PrivilegeContext.getFlatAllowList() This allows to query all the allows which can be used to define which UIs and buttons to show on a client --- .../eitchnet/privilege/model/PrivilegeContext.java | 14 ++++++++++++++ .../privilege/model/internal/PrivilegeImpl.java | 7 ++++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java b/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java index 8cd33eef1..09a282567 100644 --- a/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java +++ b/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java @@ -16,8 +16,10 @@ package ch.eitchnet.privilege.model; import java.text.MessageFormat; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Set; @@ -71,6 +73,18 @@ public class PrivilegeContext { return this.privileges.keySet(); } + public IPrivilege getPrivilege(String privilegeName) { + return this.privileges.get(privilegeName); + } + + public List getFlatAllowList() { + List allowList = new ArrayList<>(); + for (IPrivilege privilege : this.privileges.values()) { + allowList.addAll(privilege.getAllowList()); + } + return allowList; + } + // // business logic // diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeImpl.java b/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeImpl.java index e9d2b937c..3efe3e36a 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeImpl.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeImpl.java @@ -15,6 +15,7 @@ */ package ch.eitchnet.privilege.model.internal; +import java.text.MessageFormat; import java.util.Collections; import java.util.HashSet; import java.util.Set; @@ -76,13 +77,13 @@ public final class PrivilegeImpl implements IPrivilege { throw new PrivilegeException("No name defined!"); //$NON-NLS-1$ } if (StringHelper.isEmpty(policy)) { - throw new PrivilegeException("Policy may not be empty!"); //$NON-NLS-1$ + throw new PrivilegeException(MessageFormat.format("Policy may not be empty for Privilege {0}!", name)); //$NON-NLS-1$ } if (denyList == null) { - throw new PrivilegeException("denyList is null!"); //$NON-NLS-1$ + throw new PrivilegeException(MessageFormat.format("denyList is null for Privilege {0}!", name)); //$NON-NLS-1$ } if (allowList == null) { - throw new PrivilegeException("allowList is null!"); //$NON-NLS-1$ + throw new PrivilegeException(MessageFormat.format("allowList is null for Privilege {0}!", name)); //$NON-NLS-1$ } this.name = name; From e3dab98b3ff012054242eebbea523a1091d3b722 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 14 Sep 2014 12:19:34 +0200 Subject: [PATCH 135/173] [Minor] code cleanup --- .../privilege/handler/EncryptionHandler.java | 2 +- .../handler/XmlPersistenceHandler.java | 2 +- .../eitchnet/privilege/model/UserState.java | 2 +- .../privilege/model/internal/User.java | 4 ++-- .../privilege/test/PrivilegeTest.java | 21 ++++++++++--------- 5 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/main/java/ch/eitchnet/privilege/handler/EncryptionHandler.java b/src/main/java/ch/eitchnet/privilege/handler/EncryptionHandler.java index 186789f4d..59eddcfbe 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/EncryptionHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/EncryptionHandler.java @@ -40,7 +40,7 @@ public interface EncryptionHandler { * @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 * diff --git a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java index dc21d5b91..fe9745bc5 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java @@ -196,7 +196,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { // 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()); //$NON-NLS-1$ + msg = MessageFormat.format(msg, roleName, user.getUsername()); throw new PrivilegeException(msg); } } diff --git a/src/main/java/ch/eitchnet/privilege/model/UserState.java b/src/main/java/ch/eitchnet/privilege/model/UserState.java index 4922ff67e..2272002d3 100644 --- a/src/main/java/ch/eitchnet/privilege/model/UserState.java +++ b/src/main/java/ch/eitchnet/privilege/model/UserState.java @@ -48,7 +48,7 @@ public enum UserState { * 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 */ diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/User.java b/src/main/java/ch/eitchnet/privilege/model/internal/User.java index f54ccb630..c81ad1ee8 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/User.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/User.java @@ -78,8 +78,8 @@ public final class User { * @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) { + 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$ diff --git a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java index 3968c6e25..25804e155 100644 --- a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -185,8 +185,8 @@ public class PrivilegeTest { } public void testFailAuthenticationNOk() throws Exception { - exception.expect(AccessDeniedException.class); - exception.expectMessage("blabla"); + this.exception.expect(AccessDeniedException.class); + this.exception.expectMessage("blabla"); try { login(ADMIN, ArraysHelper.copyOf(PASS_BAD)); } finally { @@ -195,8 +195,8 @@ public class PrivilegeTest { } public void testFailAuthenticationPWNull() throws Exception { - exception.expect(PrivilegeException.class); - exception.expectMessage("blabla"); + this.exception.expect(PrivilegeException.class); + this.exception.expectMessage("blabla"); try { login(ADMIN, null); } finally { @@ -250,8 +250,8 @@ public class PrivilegeTest { */ @Test public void testPerformSystemRestrictableFailPrivilege() throws Exception { - exception.expect(PrivilegeException.class); - exception + this.exception.expect(PrivilegeException.class); + this.exception .expectMessage("User system_admin does not have Privilege ch.eitchnet.privilege.test.model.TestSystemUserActionDeny"); try { // create the action to be performed as a system user @@ -269,8 +269,9 @@ public class PrivilegeTest { */ @Test public void testPerformSystemRestrictableFailNoAdditionalPrivilege() throws Exception { - exception.expect(PrivilegeException.class); - exception.expectMessage("User system_admin2 does not have Privilege ch.eitchnet.privilege.test.model.TestRestrictable"); + this.exception.expect(PrivilegeException.class); + this.exception + .expectMessage("User system_admin2 does not have Privilege ch.eitchnet.privilege.test.model.TestRestrictable"); try { // create the action to be performed as a system user TestSystemUserActionDeny action = new TestSystemUserActionDeny(); @@ -287,8 +288,8 @@ public class PrivilegeTest { */ @Test public void testLoginSystemUser() throws Exception { - exception.expect(AccessDeniedException.class); - exception.expectMessage("User system_admin is a system user and may not login!"); + 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 { From a70b4309860cd3da8f22b20b4589973c115bb238 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 19 Sep 2014 21:12:03 +0200 Subject: [PATCH 136/173] [Minor] set parent version to 1.0.0-SNAPSHOT --- pom.xml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index ad8d85972..09a997b1a 100644 --- a/pom.xml +++ b/pom.xml @@ -5,12 +5,11 @@ ch.eitchnet ch.eitchnet.parent - 1.0.0 + 1.0.0-SNAPSHOT ../ch.eitchnet.parent/pom.xml ch.eitchnet.privilege - 1.0.0-SNAPSHOT jar ch.eitchnet.privilege https://github.com/eitchnet/ch.eitchnet.privilege From c8816472fcd89225178b66fad782ad49f83f181d Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 20 Sep 2014 00:35:00 +0200 Subject: [PATCH 137/173] [Project] clean up --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 09a997b1a..1da7a2503 100644 --- a/pom.xml +++ b/pom.xml @@ -1,5 +1,5 @@ - + + 4.0.0 From aa16887d674876a160bb8002a8d7899c85b2f7b1 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 26 Sep 2014 18:23:23 +0200 Subject: [PATCH 138/173] [New] Added new Certificate.getProperty()-method --- .../ch/eitchnet/privilege/model/Certificate.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/main/java/ch/eitchnet/privilege/model/Certificate.java b/src/main/java/ch/eitchnet/privilege/model/Certificate.java index 9cb3d9ada..51f264583 100644 --- a/src/main/java/ch/eitchnet/privilege/model/Certificate.java +++ b/src/main/java/ch/eitchnet/privilege/model/Certificate.java @@ -117,6 +117,18 @@ public final class Certificate implements Serializable { 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); + } + /** * Returns a mutable {@link Map} for storing session relevant data * From 67271d611ee66d67477843ca7fc388e5fb50750c Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 27 Sep 2014 12:23:38 +0200 Subject: [PATCH 139/173] [New] Added Certificate.getLastAccess() and PrivilegeHandler.checkPassword() --- .../handler/DefaultPrivilegeHandler.java | 102 +++++++++++------- .../privilege/handler/PrivilegeHandler.java | 14 +++ .../eitchnet/privilege/model/Certificate.java | 17 +++ 3 files changed, 97 insertions(+), 36 deletions(-) diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index e3ec04f6f..1a74f217e 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -622,42 +622,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { throw new PrivilegeException(msg); } - // 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 AccessDeniedException(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 AccessDeniedException(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 AccessDeniedException(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); - } + // check the password + User user = checkCredentialsAndUserState(username, password); // validate user has at least one role Set userRoles = user.getRoles(); @@ -696,6 +662,60 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { return certificate; } + /** + * 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 + */ + private User checkCredentialsAndUserState(String username, byte[] password) throws 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 AccessDeniedException(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 AccessDeniedException(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 AccessDeniedException(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} * @@ -801,6 +821,16 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // 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 { diff --git a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java index d527fd171..cf472d41e 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java @@ -351,6 +351,20 @@ public interface PrivilegeHandler { */ 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 diff --git a/src/main/java/ch/eitchnet/privilege/model/Certificate.java b/src/main/java/ch/eitchnet/privilege/model/Certificate.java index 51f264583..6e48ae4ec 100644 --- a/src/main/java/ch/eitchnet/privilege/model/Certificate.java +++ b/src/main/java/ch/eitchnet/privilege/model/Certificate.java @@ -45,6 +45,7 @@ public final class Certificate implements Serializable { private final String authToken; private Locale locale; + private long lastAccess; private Map propertyMap; private Map sessionDataMap; @@ -197,6 +198,21 @@ public final class Certificate implements Serializable { return this.authToken; } + /** + * @return the lastAccess + */ + public long getLastAccess() { + return this.lastAccess; + } + + /** + * @param lastAccess + * the lastAccess to set + */ + public void setLastAccess(long lastAccess) { + this.lastAccess = lastAccess; + } + /** * Returns a string representation of this object displaying its concrete type and its values * @@ -223,6 +239,7 @@ public final class Certificate implements Serializable { builder.append(", locale="); builder.append(this.locale); + builder.append("]"); return builder.toString(); } From 87239afa2a5c39ea24f2bb235280a56c6f14f6ac Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Wed, 31 Dec 2014 16:38:00 +0100 Subject: [PATCH 140/173] [New] added certificate.getUserRoles() --- .../handler/DefaultPrivilegeHandler.java | 10 +++++----- .../ch/eitchnet/privilege/model/Certificate.java | 16 +++++++++++++++- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 1a74f217e..185c79adb 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -135,7 +135,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { Set selRoles = selectorRep.getRoles(); Map selPropertyMap = selectorRep.getProperties(); - List result = new ArrayList(); + List result = new ArrayList<>(); List allUsers = this.persistenceHandler.getAllUsers(); for (User user : allUsers) { @@ -342,7 +342,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { IPrivilege newPrivilege = new PrivilegeImpl(privilegeRep); // copy existing privileges Set existingPrivilegeNames = role.getPrivilegeNames(); - Map privilegeMap = new HashMap(existingPrivilegeNames.size() + 1); + Map privilegeMap = new HashMap<>(existingPrivilegeNames.size() + 1); for (String name : existingPrivilegeNames) { IPrivilege privilege = role.getPrivilege(name); privilegeMap.put(name, privilege); @@ -383,7 +383,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } // create new user - Set newRoles = new HashSet(currentRoles); + Set newRoles = new HashSet<>(currentRoles); newRoles.add(roleName); User newUser = new User(user.getUserId(), user.getUsername(), user.getPassword(), user.getFirstname(), @@ -640,7 +640,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // create a new certificate, with details of the user certificate = new Certificate(sessionId, System.currentTimeMillis(), username, user.getFirstname(), - user.getLastname(), authToken, user.getLocale(), new HashMap(user.getProperties())); + user.getLastname(), authToken, user.getLocale(), userRoles, new HashMap<>(user.getProperties())); PrivilegeContext privilegeContext = buildPrivilegeContext(certificate, user); this.privilegeContextMap.put(sessionId, privilegeContext); @@ -1085,7 +1085,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // create a new certificate, with details of the user Certificate systemUserCertificate = new Certificate(sessionId, System.currentTimeMillis(), systemUsername, - null, null, authToken, user.getLocale(), new HashMap(user.getProperties())); + null, null, authToken, user.getLocale(), user.getRoles(), new HashMap<>(user.getProperties())); // create and save a new privilege context PrivilegeContext privilegeContext = buildPrivilegeContext(systemUserCertificate, user); diff --git a/src/main/java/ch/eitchnet/privilege/model/Certificate.java b/src/main/java/ch/eitchnet/privilege/model/Certificate.java index 6e48ae4ec..a479f2bd4 100644 --- a/src/main/java/ch/eitchnet/privilege/model/Certificate.java +++ b/src/main/java/ch/eitchnet/privilege/model/Certificate.java @@ -20,6 +20,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.Locale; import java.util.Map; +import java.util.Set; import ch.eitchnet.privilege.base.PrivilegeException; import ch.eitchnet.privilege.handler.PrivilegeHandler; @@ -47,6 +48,7 @@ public final class Certificate implements Serializable { private Locale locale; private long lastAccess; + private Set userRoles; private Map propertyMap; private Map sessionDataMap; @@ -70,12 +72,14 @@ public final class Certificate implements Serializable { * 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, long loginTime, String username, String firstname, String lastname, - String authToken, Locale locale, Map propertyMap) { + String authToken, Locale locale, Set userRoles, Map propertyMap) { // validate arguments are not null if (StringHelper.isEmpty(sessionId)) { @@ -106,9 +110,19 @@ public final class Certificate implements Serializable { else this.propertyMap = Collections.unmodifiableMap(propertyMap); + this.userRoles = Collections.unmodifiableSet(userRoles); this.sessionDataMap = new HashMap<>(); } + /** + * Returns the set or roles this user has + * + * @return the user's roles + */ + public Set getUserRoles() { + return userRoles; + } + /** * Returns the {@link User User's} property map. The map is immutable * From 811423eee69e7e0d3a2e7d44d67b13ab329ba828 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Mon, 9 Feb 2015 00:35:01 +0100 Subject: [PATCH 141/173] [Bugfix] fixed not adding firstname and last name to sys user cert --- .../ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 185c79adb..b8cd2e411 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -1085,7 +1085,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // create a new certificate, with details of the user Certificate systemUserCertificate = new Certificate(sessionId, System.currentTimeMillis(), systemUsername, - null, null, authToken, user.getLocale(), user.getRoles(), new HashMap<>(user.getProperties())); + user.getFirstname(), user.getLastname(), authToken, user.getLocale(), user.getRoles(), new HashMap<>( + user.getProperties())); // create and save a new privilege context PrivilegeContext privilegeContext = buildPrivilegeContext(systemUserCertificate, user); From a28dec47fdafd4c732361318f1eb9e5f99d43e75 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Mon, 2 Mar 2015 13:43:06 +0100 Subject: [PATCH 142/173] [Project] Bumped version to 1.1.0-SNAPSHOT --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1da7a2503..62f7a2fc6 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ ch.eitchnet ch.eitchnet.parent - 1.0.0-SNAPSHOT + 1.1.0-SNAPSHOT ../ch.eitchnet.parent/pom.xml From 5b462c6140c63b3931a003a70cfe1b594efaaacf Mon Sep 17 00:00:00 2001 From: Reto Breitenmoser Date: Mon, 2 Mar 2015 19:34:55 +0100 Subject: [PATCH 143/173] [Minor] updated version to 1.0.0 from utils --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 62f7a2fc6..5e53ce875 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ ch.eitchnet ch.eitchnet.parent - 1.1.0-SNAPSHOT + 1.0.0 ../ch.eitchnet.parent/pom.xml @@ -31,7 +31,7 @@ ch.eitchnet ch.eitchnet.utils - 1.0.0-SNAPSHOT + 1.0.0 From b5fac57dd8dfb14b1836c563c70ef87a21ef50e7 Mon Sep 17 00:00:00 2001 From: Reto Breitenmoser Date: Mon, 2 Mar 2015 19:46:13 +0100 Subject: [PATCH 144/173] [Minor] updated version to 1.1.0-SNAPSHOT --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 5e53ce875..98994412d 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ ch.eitchnet ch.eitchnet.parent - 1.0.0 + 1.1.0-SNAPSHOT ../ch.eitchnet.parent/pom.xml @@ -31,7 +31,7 @@ ch.eitchnet ch.eitchnet.utils - 1.0.0 + 1.1.0-SNAPSHOT From 83740b59e21e356ac3f4e3439cd038b7f4b9a073 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 5 Mar 2015 22:42:17 +0100 Subject: [PATCH 145/173] [Minor] ch.eitchnet.utils version is a propery for easier versioning --- pom.xml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 98994412d..51d2ba92f 100644 --- a/pom.xml +++ b/pom.xml @@ -15,6 +15,10 @@ https://github.com/eitchnet/ch.eitchnet.privilege 2011 + + 1.1.0-SNAPSHOT + + Github Issues https://github.com/eitchnet/ch.eitchnet.privilege/issues @@ -31,7 +35,7 @@ ch.eitchnet ch.eitchnet.utils - 1.1.0-SNAPSHOT + ${eitchnet.utils.version} From 5940a345d4f374aede973fb6d218a4199c8bfb4d Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 8 Mar 2015 13:38:15 +0100 Subject: [PATCH 146/173] [Major] refactoring Privilege - All reps are now JAXB enabled - replaced addOrReplace with add and repalace methods - added some more tests --- config/PrivilegeModel.xml | 2 + .../handler/DefaultPrivilegeHandler.java | 279 +++++++++++++++--- .../privilege/handler/PersistenceHandler.java | 24 +- .../privilege/handler/PrivilegeHandler.java | 145 +++++++-- .../handler/XmlPersistenceHandler.java | 26 +- .../eitchnet/privilege/model/Certificate.java | 22 +- .../privilege/model/PrivilegeRep.java | 25 +- .../ch/eitchnet/privilege/model/RoleRep.java | 62 +++- .../ch/eitchnet/privilege/model/UserRep.java | 70 ++++- .../privilege/test/PrivilegeTest.java | 145 +++++++-- 10 files changed, 700 insertions(+), 100 deletions(-) diff --git a/config/PrivilegeModel.xml b/config/PrivilegeModel.xml index 1ab9708d6..7ae0fd879 100644 --- a/config/PrivilegeModel.xml +++ b/config/PrivilegeModel.xml @@ -50,6 +50,8 @@ true + + diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index b8cd2e411..6a2fa51cb 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -17,13 +17,17 @@ package ch.eitchnet.privilege.handler; 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 java.util.Map; +import java.util.Map.Entry; import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -41,6 +45,7 @@ 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.utils.helper.StringHelper; /** *

      @@ -108,7 +113,11 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { private boolean autoPersistOnPasswordChange; @Override - public RoleRep getRole(String roleName) { + public RoleRep getRole(Certificate certificate, String roleName) { + + // validate who is doing this + assertIsPrivilegeAdmin(certificate); + Role role = this.persistenceHandler.getRole(roleName); if (role == null) return null; @@ -116,7 +125,11 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } @Override - public UserRep getUser(String username) { + public UserRep getUser(Certificate certificate, String username) { + + // validate who is doing this + assertIsPrivilegeAdmin(certificate); + User user = this.persistenceHandler.getUser(username); if (user == null) return null; @@ -124,7 +137,45 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } @Override - public List queryUsers(UserRep selectorRep) { + public Map getPolicyDefs(Certificate certificate) { + + // validate who is doing this + assertIsPrivilegeAdmin(certificate); + + 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 getUsers(Certificate certificate) { + + // validate who is doing this + assertIsPrivilegeAdmin(certificate); + + Stream usersStream = this.persistenceHandler.getAllUsers().stream(); + List users = usersStream.map(u -> u.asUserRep()).collect(Collectors.toList()); + return users; + } + + @Override + public List getRoles(Certificate certificate) { + + // validate who is doing this + assertIsPrivilegeAdmin(certificate); + + Stream rolesStream = this.persistenceHandler.getAllRoles().stream(); + List roles = rolesStream.map(r -> r.asRoleRep()).collect(Collectors.toList()); + return roles; + } + + @Override + public List queryUsers(Certificate certificate, UserRep selectorRep) { + + // validate who is doing this + assertIsPrivilegeAdmin(certificate); String selUserId = selectorRep.getUserId(); String selUsername = selectorRep.getUsername(); @@ -264,33 +315,28 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { return roles.containsAll(selectionRoles); } - @Override - public void addOrReplaceRole(Certificate certificate, RoleRep roleRep) { - - // validate who is doing this - assertIsPrivilegeAdmin(certificate); - - // create new role from RoleRep - Role role = new Role(roleRep); - - // validate policy if not null - validatePolicies(role); - - // delegate to persistence handler - this.persistenceHandler.addOrReplaceRole(role); - } - /** * @see ch.eitchnet.privilege.handler.PrivilegeHandler#addOrReplaceUser(ch.eitchnet.privilege.model.Certificate, * ch.eitchnet.privilege.model.UserRep, byte[]) */ @Override - public void addOrReplaceUser(Certificate certificate, UserRep userRep, byte[] password) { + public void addUser(Certificate certificate, UserRep userRep, byte[] password) { try { // validate who is doing this assertIsPrivilegeAdmin(certificate); + // 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) { @@ -302,18 +348,173 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } // create new user - User user = new User(userRep.getUserId(), userRep.getUsername(), passwordHash, userRep.getFirstname(), - userRep.getLastname(), userRep.getUserState(), userRep.getRoles(), userRep.getLocale(), - userRep.getProperties()); + User user = createUser(userRep, passwordHash); // delegate to persistence handler - this.persistenceHandler.addOrReplaceUser(user); + this.persistenceHandler.addUser(user); } finally { clearPassword(password); } } + /** + * @see ch.eitchnet.privilege.handler.PrivilegeHandler#addOrReplaceUser(ch.eitchnet.privilege.model.Certificate, + * ch.eitchnet.privilege.model.UserRep, byte[]) + */ + @Override + public void replaceUser(Certificate certificate, UserRep userRep, byte[] password) { + try { + + // validate who is doing this + assertIsPrivilegeAdmin(certificate); + + // first validate user + userRep.validate(); + + validateRolesExist(userRep); + + // validate user exists + if (this.persistenceHandler.getUser(userRep.getUsername()) == null) { + String msg = "User {0} can not be replaced as it does not exist!"; + 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 user = createUser(userRep, passwordHash); + + // delegate to persistence handler + this.persistenceHandler.replaceUser(user); + + } 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.getProperties()); + return user; + } + + @Override + public void updateUser(Certificate certificate, UserRep userRep) throws AccessDeniedException, PrivilegeException { + + // validate who is doing this + assertIsPrivilegeAdmin(certificate); + + // get existing user + User user = this.persistenceHandler.getUser(userRep.getUsername()); + if (user == 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}", userRep.getUsername())); //$NON-NLS-1$ + } + + String userId = user.getUserId(); + String username = user.getUsername(); + String password = user.getPassword(); + String firstname = user.getFirstname(); + String lastname = user.getLastname(); + UserState userState = user.getUserState(); + Set roles = user.getRoles(); + Locale locale = user.getLocale(); + Map propertyMap = user.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.getProperties(); + + // create new user + user = new User(userId, username, password, firstname, lastname, userState, roles, locale, propertyMap); + + // delegate to persistence handler + this.persistenceHandler.replaceUser(user); + } + + @Override + public void addRole(Certificate certificate, RoleRep roleRep) { + + // validate who is doing this + assertIsPrivilegeAdmin(certificate); + + // 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 role = new Role(roleRep); + + // validate policy if not null + validatePolicies(role); + + // delegate to persistence handler + this.persistenceHandler.addRole(role); + } + + @Override + public void replaceRole(Certificate certificate, RoleRep roleRep) { + + // validate who is doing this + assertIsPrivilegeAdmin(certificate); + + // first validate role + roleRep.validate(); + + // validate role does exist + if (this.persistenceHandler.getRole(roleRep.getName()) == 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 role = new Role(roleRep); + + // validate policy if not null + validatePolicies(role); + + // delegate to persistence handler + this.persistenceHandler.replaceRole(role); + } + @Override public void addOrReplacePrivilegeOnRole(Certificate certificate, String roleName, PrivilegeRep privilegeRep) { @@ -353,7 +554,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { Role newRole = new Role(role.getName(), privilegeMap); // delegate role replacement to persistence handler - this.persistenceHandler.addOrReplaceRole(newRole); + this.persistenceHandler.replaceRole(newRole); } @Override @@ -377,7 +578,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } // validate that role exists - if (getRole(roleName) == null) { + if (this.persistenceHandler.getRole(roleName) == null) { String msg = MessageFormat.format("Role {0} does not exist!", roleName); //$NON-NLS-1$ throw new PrivilegeException(msg); } @@ -390,7 +591,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { user.getLastname(), user.getUserState(), newRoles, user.getLocale(), user.getProperties()); // delegate user replacement to persistence handler - this.persistenceHandler.addOrReplaceUser(newUser); + this.persistenceHandler.replaceUser(newUser); } @Override @@ -424,7 +625,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { Role newRole = new Role(role.getName(), newPrivileges); // delegate user replacement to persistence handler - this.persistenceHandler.addOrReplaceRole(newRole); + this.persistenceHandler.replaceRole(newRole); } @Override @@ -433,6 +634,17 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // validate who is doing this assertIsPrivilegeAdmin(certificate); + // 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); + } + // delegate role removal to persistence handler Role removedRole = this.persistenceHandler.removeRole(roleName); @@ -470,7 +682,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { user.getLastname(), user.getUserState(), newRoles, user.getLocale(), user.getProperties()); // delegate user replacement to persistence handler - this.persistenceHandler.addOrReplaceUser(newUser); + this.persistenceHandler.replaceUser(newUser); } @Override @@ -488,7 +700,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // return user rep if it was removed return removedUser.asUserRep(); - } @Override @@ -508,7 +719,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { user.getLastname(), user.getUserState(), user.getRoles(), locale, user.getProperties()); // delegate user replacement to persistence handler - this.persistenceHandler.addOrReplaceUser(newUser); + this.persistenceHandler.replaceUser(newUser); } @Override @@ -528,7 +739,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { user.getUserState(), user.getRoles(), user.getLocale(), user.getProperties()); // delegate user replacement to persistence handler - this.persistenceHandler.addOrReplaceUser(newUser); + this.persistenceHandler.replaceUser(newUser); } /** @@ -572,7 +783,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { user.getLastname(), user.getUserState(), user.getRoles(), user.getLocale(), user.getProperties()); // delegate user replacement to persistence handler - this.persistenceHandler.addOrReplaceUser(newUser); + this.persistenceHandler.replaceUser(newUser); // perform automatic persisting, if enabled if (this.autoPersistOnPasswordChange) { @@ -601,7 +812,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { user.getLastname(), state, user.getRoles(), user.getLocale(), user.getProperties()); // delegate user replacement to persistence handler - this.persistenceHandler.addOrReplaceUser(newUser); + this.persistenceHandler.replaceUser(newUser); } /** diff --git a/src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java b/src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java index 85b009a33..c3fd0fdc6 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java @@ -95,20 +95,36 @@ public interface PersistenceHandler { public Role removeRole(String roleName); /** - * Adds a {@link User} object to the underlying database. If the {@link User} already exists, it is replaced + * Adds a {@link User} object to the underlying database * * @param user * the {@link User} object to add */ - public void addOrReplaceUser(User user); + public void addUser(User user); /** - * Adds a {@link Role} object to the underlying database. If the {@link Role} already exists, it is replaced + * 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 addOrReplaceRole(Role role); + 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 diff --git a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java index cf472d41e..15d16d686 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java @@ -17,6 +17,7 @@ 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.PrivilegeException; @@ -29,6 +30,7 @@ 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 @@ -36,7 +38,6 @@ import ch.eitchnet.privilege.model.internal.User; * an action * * @author Robert von Burg - * */ public interface PrivilegeHandler { @@ -48,33 +49,69 @@ public interface PrivilegeHandler { /** * 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(String username); + 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(String roleName); + 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 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 not relevant. + * 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(UserRep selectorRep); + public List queryUsers(Certificate certificate, UserRep selectorRep); /** * Removes the user with the given username @@ -125,7 +162,7 @@ public interface PrivilegeHandler { * @throws AccessDeniedException * if the user for this certificate may not perform the action * @throws PrivilegeException - * if there is anything wrong with this certificate + * 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; @@ -150,7 +187,7 @@ public interface PrivilegeHandler { /** *

      - * Adds a new user, or replaces the user with the information from this {@link UserRep} if the user already exists + * Adds a new user with the information from this {@link UserRep} *

      * *

      @@ -170,13 +207,71 @@ public interface PrivilegeHandler { * @throws AccessDeniedException * if the user for this certificate may not perform the action * @throws PrivilegeException - * if there is anything wrong with this certificate + * if there is anything wrong with this certificate or the user already exists */ - public void addOrReplaceUser(Certificate certificate, UserRep userRep, byte[] password) - throws AccessDeniedException, PrivilegeException; + public void addUser(Certificate certificate, UserRep userRep, byte[] password) throws AccessDeniedException, + PrivilegeException; /** - * Adds a new role, or replaces the role with the information from this {@link RoleRep} if the role already exists + *

      + * 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 void 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 void 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 @@ -186,10 +281,24 @@ public interface PrivilegeHandler { * @throws AccessDeniedException * if the user for this certificate may not perform the action * @throws PrivilegeException - * if there is anything wrong with this certificate + * if there is anything wrong with this certificate or if the role already exists */ - public void addOrReplaceRole(Certificate certificate, RoleRep roleRep) throws AccessDeniedException, - PrivilegeException; + public void 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 void replaceRole(Certificate certificate, RoleRep roleRep) throws AccessDeniedException, PrivilegeException; /** * Adds the role with the given roleName to the {@link User} with the given username @@ -204,25 +313,25 @@ public interface PrivilegeHandler { * @throws AccessDeniedException * if the user for this certificate may not perform the action * @throws PrivilegeException - * if there is anything wrong with this certificate + * if there is anything wrong with this certificate or if the role does not exist */ public void addRoleToUser(Certificate certificate, String username, String roleName) throws AccessDeniedException, PrivilegeException; /** - * Adds the {@link PrivilegeRep} to the {@link Role} with the given roleName + * 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 to the {@link Role} + * 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 + * if there is anything wrong with this certificate or the role does not exist */ public void addOrReplacePrivilegeOnRole(Certificate certificate, String roleName, PrivilegeRep privilegeRep) throws AccessDeniedException, PrivilegeException; diff --git a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java index fe9745bc5..420edcf3c 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java @@ -94,13 +94,35 @@ public class XmlPersistenceHandler implements PersistenceHandler { } @Override - public void addOrReplaceUser(User user) { + 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 addOrReplaceRole(Role role) { + 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.userMap.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.userMap.containsKey(role)) + throw new IllegalStateException(MessageFormat.format( + "The role {0} can not be replaced as it does not exiset!", role.getName())); this.roleMap.put(role.getName(), role); this.roleMapDirty = true; } diff --git a/src/main/java/ch/eitchnet/privilege/model/Certificate.java b/src/main/java/ch/eitchnet/privilege/model/Certificate.java index a479f2bd4..cf9538c3c 100644 --- a/src/main/java/ch/eitchnet/privilege/model/Certificate.java +++ b/src/main/java/ch/eitchnet/privilege/model/Certificate.java @@ -45,13 +45,13 @@ public final class Certificate implements Serializable { private final String lastname; private final String authToken; + private final Set userRoles; + private final Map propertyMap; + private final Map sessionDataMap; + private Locale locale; private long lastAccess; - private Set userRoles; - private Map propertyMap; - private Map sessionDataMap; - /** * Default constructor initializing with all information needed for this certificate * @@ -120,7 +120,19 @@ public final class Certificate implements Serializable { * @return the user's roles */ public Set getUserRoles() { - return userRoles; + 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); } /** diff --git a/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java b/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java index e2c1a3a39..d0edd8119 100644 --- a/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java +++ b/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java @@ -18,6 +18,12 @@ package ch.eitchnet.privilege.model; import java.io.Serializable; 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; @@ -31,14 +37,25 @@ import ch.eitchnet.utils.helper.StringHelper; * * @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; /** @@ -62,8 +79,14 @@ public class PrivilegeRep implements Serializable { this.allAllowed = allAllowed; this.denyList = denyList; this.allowList = allowList; + } - validate(); + /** + * + */ + @SuppressWarnings("unused") + private PrivilegeRep() { + // no-arg constructor for JAXB } /** diff --git a/src/main/java/ch/eitchnet/privilege/model/RoleRep.java b/src/main/java/ch/eitchnet/privilege/model/RoleRep.java index c32a226bc..296a7eccb 100644 --- a/src/main/java/ch/eitchnet/privilege/model/RoleRep.java +++ b/src/main/java/ch/eitchnet/privilege/model/RoleRep.java @@ -16,8 +16,18 @@ package ch.eitchnet.privilege.model; import java.io.Serializable; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; import java.util.Map; +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; @@ -29,11 +39,15 @@ import ch.eitchnet.utils.helper.StringHelper; * * @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; + private Map privilegeMap; /** @@ -45,11 +59,16 @@ public class RoleRep implements Serializable { * the map of privileges granted to this role */ public RoleRep(String name, Map privilegeMap) { - this.name = name; this.privilegeMap = privilegeMap; + } - validate(); + /** + * + */ + @SuppressWarnings("unused") + private RoleRep() { + // no-arg constructor for JAXB } /** @@ -58,6 +77,18 @@ public class RoleRep implements Serializable { public void validate() { if (StringHelper.isEmpty(this.name)) throw new PrivilegeException("name is null"); //$NON-NLS-1$ + + if (this.privilegeMap != null && !this.privilegeMap.isEmpty()) { + for (PrivilegeRep privilege : this.privilegeMap.values()) { + 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); + } + } + } } /** @@ -82,6 +113,33 @@ public class RoleRep implements Serializable { return this.privilegeMap; } + /** + * Returns the privileges assigned to this Role as a list + * + * @return the privileges assigned to this Role as a list + */ + @XmlElement(name = "privileges") + public List getPrivileges() { + return new ArrayList<>(this.privilegeMap.values()); + } + + /** + * Sets the privileges on this from a list + * + * @param privileges + * the list of privileges to assign to this role + */ + public void setPrivileges(List privileges) { + if (this.privilegeMap == null) + this.privilegeMap = new HashMap<>(privileges.size()); + else + this.privilegeMap.clear(); + + for (PrivilegeRep privilegeRep : privileges) { + this.privilegeMap.put(privilegeRep.getName(), privilegeRep); + } + } + /** * Returns a string representation of this object displaying its concrete type and its values * diff --git a/src/main/java/ch/eitchnet/privilege/model/UserRep.java b/src/main/java/ch/eitchnet/privilege/model/UserRep.java index 3ffa0738d..8ccf97b1e 100644 --- a/src/main/java/ch/eitchnet/privilege/model/UserRep.java +++ b/src/main/java/ch/eitchnet/privilege/model/UserRep.java @@ -16,14 +16,22 @@ package ch.eitchnet.privilege.model; import java.io.Serializable; +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 @@ -32,17 +40,33 @@ import ch.eitchnet.utils.helper.StringHelper; * * @author Robert von Burg */ +@XmlRootElement(name = "User") +@XmlAccessorType(XmlAccessType.NONE) public class UserRep implements Serializable { private static final long serialVersionUID = 1L; - private final String userId; + @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; - private Set roles; + + @XmlAttribute(name = "locale") private Locale locale; + + @XmlElement(name = "roles") + private Set roles; + private Map propertyMap; /** @@ -75,8 +99,14 @@ public class UserRep implements Serializable { this.roles = roles; this.locale = locale; this.propertyMap = propertyMap; + } - validate(); + /** + * + */ + @SuppressWarnings("unused") + private UserRep() { + // No arg constructor for JAXB } /** @@ -93,16 +123,14 @@ public class UserRep implements Serializable { if (this.userState == null) throw new PrivilegeException("userState is null"); //$NON-NLS-1$ - if (this.userState != UserState.SYSTEM) { - if (StringHelper.isEmpty(this.firstname)) - throw new PrivilegeException("firstname is null or empty"); //$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 (StringHelper.isEmpty(this.lastname)) + throw new PrivilegeException("lastname is null or empty"); //$NON-NLS-1$ - if (this.roles == null) - throw new PrivilegeException("roles is null"); //$NON-NLS-1$ + if (this.roles == null || this.roles.isEmpty()) + throw new PrivilegeException("roles is null or empty"); //$NON-NLS-1$ } /** @@ -244,6 +272,26 @@ public class UserRep implements Serializable { return this.propertyMap; } + /** + * 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 getPropertiesAsKeyValue() { + return XmlKeyValue.valueOf(this.propertyMap); + } + + /** + * 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 setPropertiesAsKeyValue(List values) { + this.propertyMap = XmlKeyValue.toMap(values); + } + /** * Returns a string representation of this object displaying its concrete type and its values * diff --git a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java index 25804e155..6b0122b32 100644 --- a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -16,12 +16,15 @@ package ch.eitchnet.privilege.test; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.File; +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import org.junit.AfterClass; @@ -68,6 +71,7 @@ public class PrivilegeTest { 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_TEMP = "temp"; private static final String ROLE_USER = "user"; private static final byte[] PASS_DEF = "def".getBytes(); @@ -213,7 +217,7 @@ public class PrivilegeTest { RoleRep roleRep = new RoleRep(ROLE_TEMP, privilegeMap); Certificate certificate = this.ctx.getCertificate(); - privilegeHandler.addOrReplaceRole(certificate, roleRep); + privilegeHandler.addRole(certificate, roleRep); privilegeHandler.persist(certificate); } finally { logout(); @@ -308,6 +312,117 @@ public class PrivilegeTest { } } + @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(); + } + } + /** * This test performs multiple tests which are dependent on each other as the following is done: *
        @@ -328,7 +443,6 @@ public class PrivilegeTest { *
      • add app role to bob
      • *
      • perform restrictable as bob
      • *
      - * */ @Test public void testUserStory() throws Exception { @@ -336,7 +450,6 @@ public class PrivilegeTest { addBobAsAdmin(); failAuthAsBobNotEnabled(); enableBob(); - failAuthAsBobNoRole(); addRoleUser(); addRoleUserToBob(); authAsBob(); @@ -454,7 +567,7 @@ public class PrivilegeTest { userRep = new UserRep("2", TED, "Ted", "Newman", UserState.ENABLED, roles, null, new HashMap()); Certificate certificate = this.ctx.getCertificate(); - privilegeHandler.addOrReplaceUser(certificate, userRep, null); + privilegeHandler.addUser(certificate, userRep, null); logger.info("Added user " + TED); privilegeHandler.persist(certificate); } finally { @@ -487,7 +600,7 @@ public class PrivilegeTest { userRep = new UserRep("1", TED, "Ted", "And then Some", UserState.NEW, new HashSet(), null, new HashMap()); certificate = this.ctx.getCertificate(); - privilegeHandler.addOrReplaceUser(certificate, userRep, null); + 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 = "User does not have PrivilegeAdmin role! Certificate: " + certificate; @@ -526,27 +639,13 @@ public class PrivilegeTest { Map privilegeMap = new HashMap(); RoleRep roleRep = new RoleRep(ROLE_USER, privilegeMap); Certificate certificate = this.ctx.getCertificate(); - privilegeHandler.addOrReplaceRole(certificate, roleRep); + privilegeHandler.addRole(certificate, roleRep); privilegeHandler.persist(certificate); } finally { logout(); } } - private void failAuthAsBobNoRole() { - try { - // testFailAuthUserBob - // Will fail as user bob has no role - privilegeHandler.authenticate(BOB, ArraysHelper.copyOf(PASS_BOB)); - fail("User Bob may not authenticate because the user has no role"); - } catch (PrivilegeException e) { - String msg = "User bob does not have any roles defined!"; - assertEquals(msg, e.getMessage()); - } finally { - logout(); - } - } - private void enableBob() { try { // testEnableUserBob @@ -578,10 +677,10 @@ public class PrivilegeTest { login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); // let's add a new user bob - UserRep userRep = new UserRep("1", BOB, "Bob", "Newman", UserState.NEW, new HashSet(), null, - new HashMap()); + UserRep userRep = new UserRep("1", BOB, "Bob", "Newman", UserState.NEW, new HashSet( + Arrays.asList(ROLE_MY)), null, new HashMap()); Certificate certificate = this.ctx.getCertificate(); - privilegeHandler.addOrReplaceUser(certificate, userRep, null); + privilegeHandler.addUser(certificate, userRep, null); logger.info("Added user " + BOB); // set bob's password From eeb3356372500e6413af613079c7ec71bb1f6e4c Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 8 Mar 2015 13:39:27 +0100 Subject: [PATCH 147/173] [Minor] fixed broken test --- src/test/java/ch/eitchnet/privilege/test/XmlTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java index b1db9740e..6d46fc25d 100644 --- a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java @@ -169,7 +169,7 @@ public class XmlTest { assertNotNull(roles); assertEquals(3, users.size()); - assertEquals(5, roles.size()); + assertEquals(6, roles.size()); // assert model From 5ef43eaebe670b0cf3f8ea1ccbc251b2c48f3dca Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 8 Mar 2015 20:51:10 +0100 Subject: [PATCH 148/173] [Major] All methods in PrivilegeHandler now return a value after op - also fixed JAXB (un)marshalling of list values on UserRep and RoleRep --- .../handler/DefaultPrivilegeHandler.java | 121 +++++++++++------- .../privilege/handler/PrivilegeHandler.java | 30 +++-- .../handler/XmlPersistenceHandler.java | 6 +- .../privilege/model/PrivilegeRep.java | 5 +- .../ch/eitchnet/privilege/model/RoleRep.java | 38 ++---- .../ch/eitchnet/privilege/model/UserRep.java | 64 +++++++-- .../model/internal/PrivilegeImpl.java | 2 +- .../privilege/model/internal/Role.java | 23 ++-- .../privilege/test/PrivilegeTest.java | 12 +- 9 files changed, 178 insertions(+), 123 deletions(-) diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 6a2fa51cb..ba3f28c5f 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -184,7 +184,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { UserState selUserState = selectorRep.getUserState(); Locale selLocale = selectorRep.getLocale(); Set selRoles = selectorRep.getRoles(); - Map selPropertyMap = selectorRep.getProperties(); + Map selPropertyMap = selectorRep.getPropertyMap(); List result = new ArrayList<>(); List allUsers = this.persistenceHandler.getAllUsers(); @@ -315,17 +315,22 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { return roles.containsAll(selectionRoles); } - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#addOrReplaceUser(ch.eitchnet.privilege.model.Certificate, - * ch.eitchnet.privilege.model.UserRep, byte[]) - */ @Override - public void addUser(Certificate certificate, UserRep userRep, byte[] password) { + public UserRep addUser(Certificate certificate, UserRep userRep, byte[] password) { try { // validate who is doing this assertIsPrivilegeAdmin(certificate); + // 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(); @@ -353,17 +358,15 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // delegate to persistence handler this.persistenceHandler.addUser(user); + return user.asUserRep(); + } finally { clearPassword(password); } } - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#addOrReplaceUser(ch.eitchnet.privilege.model.Certificate, - * ch.eitchnet.privilege.model.UserRep, byte[]) - */ @Override - public void replaceUser(Certificate certificate, UserRep userRep, byte[] password) { + public UserRep replaceUser(Certificate certificate, UserRep userRep, byte[] password) { try { // validate who is doing this @@ -375,11 +378,19 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { validateRolesExist(userRep); // validate user exists - if (this.persistenceHandler.getUser(userRep.getUsername()) == null) { + User user = this.persistenceHandler.getUser(userRep.getUsername()); + if (user == 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 (!user.getUserId().equals(userRep.getUserId())) { + String msg = "UserId of existing user {0} does not match userRep {1}"; + msg = MessageFormat.format(msg, user.getUserId(), userRep.getUserId()); + throw new PrivilegeException(MessageFormat.format(msg, userRep.getUsername())); + } + String passwordHash = null; if (password != null) { @@ -390,11 +401,13 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { passwordHash = this.encryptionHandler.convertToHash(password); } - User user = createUser(userRep, passwordHash); + user = createUser(userRep, passwordHash); // delegate to persistence handler this.persistenceHandler.replaceUser(user); + return user.asUserRep(); + } finally { clearPassword(password); } @@ -414,12 +427,13 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { 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.getProperties()); + userRep.getPropertyMap()); return user; } @Override - public void updateUser(Certificate certificate, UserRep userRep) throws AccessDeniedException, PrivilegeException { + public UserRep updateUser(Certificate certificate, UserRep userRep) throws AccessDeniedException, + PrivilegeException { // validate who is doing this assertIsPrivilegeAdmin(certificate); @@ -456,17 +470,19 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { if (userRep.getLocale() != null) locale = userRep.getLocale(); if (userRep.getProperties() != null && !userRep.getProperties().isEmpty()) - propertyMap = userRep.getProperties(); + propertyMap = userRep.getPropertyMap(); // create new user user = new User(userId, username, password, firstname, lastname, userState, roles, locale, propertyMap); // delegate to persistence handler this.persistenceHandler.replaceUser(user); + + return user.asUserRep(); } @Override - public void addRole(Certificate certificate, RoleRep roleRep) { + public RoleRep addRole(Certificate certificate, RoleRep roleRep) { // validate who is doing this assertIsPrivilegeAdmin(certificate); @@ -488,10 +504,12 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // delegate to persistence handler this.persistenceHandler.addRole(role); + + return role.asRoleRep(); } @Override - public void replaceRole(Certificate certificate, RoleRep roleRep) { + public RoleRep replaceRole(Certificate certificate, RoleRep roleRep) { // validate who is doing this assertIsPrivilegeAdmin(certificate); @@ -513,10 +531,12 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // delegate to persistence handler this.persistenceHandler.replaceRole(role); + + return role.asRoleRep(); } @Override - public void addOrReplacePrivilegeOnRole(Certificate certificate, String roleName, PrivilegeRep privilegeRep) { + public RoleRep addOrReplacePrivilegeOnRole(Certificate certificate, String roleName, PrivilegeRep privilegeRep) { // validate who is doing this assertIsPrivilegeAdmin(certificate); @@ -541,6 +561,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // create new role with the additional privilege IPrivilege newPrivilege = new PrivilegeImpl(privilegeRep); + // copy existing privileges Set existingPrivilegeNames = role.getPrivilegeNames(); Map privilegeMap = new HashMap<>(existingPrivilegeNames.size() + 1); @@ -548,6 +569,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { IPrivilege privilege = role.getPrivilege(name); privilegeMap.put(name, privilege); } + // add new one privilegeMap.put(newPrivilege.getName(), newPrivilege); @@ -555,10 +577,12 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // delegate role replacement to persistence handler this.persistenceHandler.replaceRole(newRole); + + return newRole.asRoleRep(); } @Override - public void addRoleToUser(Certificate certificate, String username, String roleName) { + public UserRep addRoleToUser(Certificate certificate, String username, String roleName) { // validate who is doing this assertIsPrivilegeAdmin(certificate); @@ -569,12 +593,11 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { throw new PrivilegeException(MessageFormat.format("User {0} does not exist!", username)); //$NON-NLS-1$ } - // ignore if user already has role + // check that user not already has role Set currentRoles = user.getRoles(); if (currentRoles.contains(roleName)) { String msg = MessageFormat.format("User {0} already has role {1}", username, roleName); //$NON-NLS-1$ - DefaultPrivilegeHandler.logger.error(msg); - return; + throw new PrivilegeException(msg); } // validate that role exists @@ -592,10 +615,12 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // delegate user replacement to persistence handler this.persistenceHandler.replaceUser(newUser); + + return newUser.asUserRep(); } @Override - public void removePrivilegeFromRole(Certificate certificate, String roleName, String privilegeName) { + public RoleRep removePrivilegeFromRole(Certificate certificate, String roleName, String privilegeName) { // validate who is doing this assertIsPrivilegeAdmin(certificate); @@ -626,6 +651,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // delegate user replacement to persistence handler this.persistenceHandler.replaceRole(newRole); + + return newRole.asRoleRep(); } @Override @@ -647,16 +674,17 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // delegate role removal to persistence handler Role removedRole = this.persistenceHandler.removeRole(roleName); - - if (removedRole == null) - return null; + if (removedRole == null) { + String msg = "Can not remove Role {0} because role does not exist!"; + throw new PrivilegeException(MessageFormat.format(msg, roleName)); + } // return role rep if it was removed return removedRole.asRoleRep(); } @Override - public void removeRoleFromUser(Certificate certificate, String username, String roleName) { + public UserRep removeRoleFromUser(Certificate certificate, String username, String roleName) { // validate who is doing this assertIsPrivilegeAdmin(certificate); @@ -670,9 +698,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // ignore if user does not have role Set currentRoles = user.getRoles(); if (!currentRoles.contains(roleName)) { - String msg = MessageFormat.format("User {0} does not have role {1}", user, roleName); //$NON-NLS-1$ - logger.error(msg); - return; + String msg = MessageFormat.format("User {0} does not have role {1}", user.getUsername(), roleName); //$NON-NLS-1$ + throw new PrivilegeException(msg); } // create new user @@ -683,6 +710,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // delegate user replacement to persistence handler this.persistenceHandler.replaceUser(newUser); + + return newUser.asUserRep(); } @Override @@ -693,17 +722,17 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // delegate user removal to persistence handler User removedUser = this.persistenceHandler.removeUser(username); - - // return user rep if it was removed - if (removedUser == null) - return null; + if (removedUser == null) { + String msg = "Can not remove User {0} because user does not exist!"; + throw new PrivilegeException(MessageFormat.format(msg, username)); + } // return user rep if it was removed return removedUser.asUserRep(); } @Override - public void setUserLocale(Certificate certificate, String username, Locale locale) { + public UserRep setUserLocale(Certificate certificate, String username, Locale locale) { // validate who is doing this assertIsPrivilegeAdmin(certificate); @@ -720,10 +749,12 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // delegate user replacement to persistence handler this.persistenceHandler.replaceUser(newUser); + + return newUser.asUserRep(); } @Override - public void setUserName(Certificate certificate, String username, String firstname, String lastname) { + public UserRep setUserName(Certificate certificate, String username, String firstname, String lastname) { // validate who is doing this assertIsPrivilegeAdmin(certificate); @@ -740,12 +771,10 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // delegate user replacement to persistence handler this.persistenceHandler.replaceUser(newUser); + + return newUser.asUserRep(); } - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#setUserPassword(ch.eitchnet.privilege.model.Certificate, - * java.lang.String, byte[]) - */ @Override public void setUserPassword(Certificate certificate, String username, byte[] password) { try { @@ -796,7 +825,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } @Override - public void setUserState(Certificate certificate, String username, UserState state) { + public UserRep setUserState(Certificate certificate, String username, UserState state) { // validate who is doing this assertIsPrivilegeAdmin(certificate); @@ -813,14 +842,10 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // delegate user replacement to persistence handler this.persistenceHandler.replaceUser(newUser); + + return newUser.asUserRep(); } - /** - * @see ch.eitchnet.privilege.handler.PrivilegeHandler#authenticate(java.lang.String, byte[]) - * - * @throws AccessDeniedException - * if the user credentials are not valid - */ @Override public Certificate authenticate(String username, byte[] password) { diff --git a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java index 15d16d686..46c0e0e55 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java @@ -146,7 +146,7 @@ public interface PrivilegeHandler { * @throws PrivilegeException * if there is anything wrong with this certificate */ - public void removeRoleFromUser(Certificate certificate, String username, String roleName) + public UserRep removeRoleFromUser(Certificate certificate, String username, String roleName) throws AccessDeniedException, PrivilegeException; /** @@ -182,7 +182,7 @@ public interface PrivilegeHandler { * @throws PrivilegeException * if there is anything wrong with this certificate */ - public void removePrivilegeFromRole(Certificate certificate, String roleName, String privilegeName) + public RoleRep removePrivilegeFromRole(Certificate certificate, String roleName, String privilegeName) throws AccessDeniedException, PrivilegeException; /** @@ -209,7 +209,7 @@ public interface PrivilegeHandler { * @throws PrivilegeException * if there is anything wrong with this certificate or the user already exists */ - public void addUser(Certificate certificate, UserRep userRep, byte[] password) throws AccessDeniedException, + public UserRep addUser(Certificate certificate, UserRep userRep, byte[] password) throws AccessDeniedException, PrivilegeException; /** @@ -241,7 +241,8 @@ public interface PrivilegeHandler { * @throws PrivilegeException * if there is anything wrong with this certificate or if the user does not exist */ - public void updateUser(Certificate certificate, UserRep userRep) throws AccessDeniedException, PrivilegeException; + public UserRep updateUser(Certificate certificate, UserRep userRep) throws AccessDeniedException, + PrivilegeException; /** *

      @@ -267,7 +268,7 @@ public interface PrivilegeHandler { * @throws PrivilegeException * if there is anything wrong with this certificate or if the user does not exist */ - public void replaceUser(Certificate certificate, UserRep userRep, byte[] password) throws AccessDeniedException, + public UserRep replaceUser(Certificate certificate, UserRep userRep, byte[] password) throws AccessDeniedException, PrivilegeException; /** @@ -283,7 +284,7 @@ public interface PrivilegeHandler { * @throws PrivilegeException * if there is anything wrong with this certificate or if the role already exists */ - public void addRole(Certificate certificate, RoleRep roleRep) throws AccessDeniedException, PrivilegeException; + public RoleRep addRole(Certificate certificate, RoleRep roleRep) throws AccessDeniedException, PrivilegeException; /** * Replaces the existing role with the information from this {@link RoleRep} @@ -298,7 +299,8 @@ public interface PrivilegeHandler { * @throws PrivilegeException * if there is anything wrong with this certificate or if the role does not exist */ - public void replaceRole(Certificate certificate, RoleRep roleRep) throws AccessDeniedException, PrivilegeException; + 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 @@ -315,8 +317,8 @@ public interface PrivilegeHandler { * @throws PrivilegeException * if there is anything wrong with this certificate or if the role does not exist */ - public void addRoleToUser(Certificate certificate, String username, String roleName) throws AccessDeniedException, - PrivilegeException; + 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 @@ -333,7 +335,7 @@ public interface PrivilegeHandler { * @throws PrivilegeException * if there is anything wrong with this certificate or the role does not exist */ - public void addOrReplacePrivilegeOnRole(Certificate certificate, String roleName, PrivilegeRep privilegeRep) + public RoleRep addOrReplacePrivilegeOnRole(Certificate certificate, String roleName, PrivilegeRep privilegeRep) throws AccessDeniedException, PrivilegeException; /** @@ -382,7 +384,7 @@ public interface PrivilegeHandler { * @throws PrivilegeException * if there is anything wrong with this certificate */ - public void setUserName(Certificate certificate, String username, String firstname, String lastname) + public UserRep setUserName(Certificate certificate, String username, String firstname, String lastname) throws AccessDeniedException, PrivilegeException; /** @@ -400,8 +402,8 @@ public interface PrivilegeHandler { * @throws PrivilegeException * if there is anything wrong with this certificate */ - public void setUserState(Certificate certificate, String username, UserState state) throws AccessDeniedException, - PrivilegeException; + public UserRep setUserState(Certificate certificate, String username, UserState state) + throws AccessDeniedException, PrivilegeException; /** * Changes the {@link Locale} of the user @@ -418,7 +420,7 @@ public interface PrivilegeHandler { * @throws PrivilegeException * if there is anything wrong with this certificate */ - public void setUserLocale(Certificate certificate, String username, Locale locale) throws AccessDeniedException, + public UserRep setUserLocale(Certificate certificate, String username, Locale locale) throws AccessDeniedException, PrivilegeException; /** diff --git a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java index 420edcf3c..292743a7e 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java @@ -112,7 +112,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { @Override public void addRole(Role role) { - if (this.userMap.containsKey(role.getName())) + 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; @@ -120,9 +120,9 @@ public class XmlPersistenceHandler implements PersistenceHandler { @Override public void replaceRole(Role role) { - if (!this.userMap.containsKey(role)) + if (!this.roleMap.containsKey(role.getName())) throw new IllegalStateException(MessageFormat.format( - "The role {0} can not be replaced as it does not exiset!", role.getName())); + "The role {0} can not be replaced as it does not exist!", role.getName())); this.roleMap.put(role.getName(), role); this.roleMapDirty = true; } diff --git a/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java b/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java index d0edd8119..66dff555b 100644 --- a/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java +++ b/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java @@ -16,6 +16,7 @@ package ch.eitchnet.privilege.model; import java.io.Serializable; +import java.util.HashSet; import java.util.Set; import javax.xml.bind.annotation.XmlAccessType; @@ -159,7 +160,7 @@ public class PrivilegeRep implements Serializable { * @return the denyList */ public Set getDenyList() { - return this.denyList; + return this.denyList == null ? new HashSet<>() : this.denyList; } /** @@ -174,7 +175,7 @@ public class PrivilegeRep implements Serializable { * @return the allowList */ public Set getAllowList() { - return this.allowList; + return this.allowList == null ? new HashSet<>() : this.allowList; } /** diff --git a/src/main/java/ch/eitchnet/privilege/model/RoleRep.java b/src/main/java/ch/eitchnet/privilege/model/RoleRep.java index 296a7eccb..6e449ff97 100644 --- a/src/main/java/ch/eitchnet/privilege/model/RoleRep.java +++ b/src/main/java/ch/eitchnet/privilege/model/RoleRep.java @@ -18,9 +18,7 @@ package ch.eitchnet.privilege.model; import java.io.Serializable; import java.text.MessageFormat; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; @@ -48,19 +46,20 @@ public class RoleRep implements Serializable { @XmlAttribute(name = "name") private String name; - private Map privilegeMap; + @XmlElement(name = "privileges") + private List privileges; /** * Default constructor * * @param name * the name of this role - * @param privilegeMap - * the map of privileges granted to this role + * @param privileges + * the list of privileges granted to this role */ - public RoleRep(String name, Map privilegeMap) { + public RoleRep(String name, List privileges) { this.name = name; - this.privilegeMap = privilegeMap; + this.privileges = privileges; } /** @@ -78,8 +77,8 @@ public class RoleRep implements Serializable { if (StringHelper.isEmpty(this.name)) throw new PrivilegeException("name is null"); //$NON-NLS-1$ - if (this.privilegeMap != null && !this.privilegeMap.isEmpty()) { - for (PrivilegeRep privilege : this.privilegeMap.values()) { + if (this.privileges != null && !this.privileges.isEmpty()) { + for (PrivilegeRep privilege : this.privileges) { try { privilege.validate(); } catch (Exception e) { @@ -106,21 +105,13 @@ public class RoleRep implements Serializable { this.name = name; } - /** - * @return the privilegeMap - */ - public Map getPrivilegeMap() { - return this.privilegeMap; - } - /** * Returns the privileges assigned to this Role as a list * * @return the privileges assigned to this Role as a list */ - @XmlElement(name = "privileges") public List getPrivileges() { - return new ArrayList<>(this.privilegeMap.values()); + return this.privileges == null ? new ArrayList<>() : this.privileges; } /** @@ -130,14 +121,7 @@ public class RoleRep implements Serializable { * the list of privileges to assign to this role */ public void setPrivileges(List privileges) { - if (this.privilegeMap == null) - this.privilegeMap = new HashMap<>(privileges.size()); - else - this.privilegeMap.clear(); - - for (PrivilegeRep privilegeRep : privileges) { - this.privilegeMap.put(privilegeRep.getName(), privilegeRep); - } + this.privileges = privileges; } /** @@ -152,7 +136,7 @@ public class RoleRep implements Serializable { builder.append("RoleRep [name="); builder.append(this.name); builder.append(", privilegeMap="); - builder.append((this.privilegeMap == null ? "null" : this.privilegeMap)); + builder.append((this.privileges == null ? "null" : this.privileges)); builder.append("]"); return builder.toString(); } diff --git a/src/main/java/ch/eitchnet/privilege/model/UserRep.java b/src/main/java/ch/eitchnet/privilege/model/UserRep.java index 8ccf97b1e..6300fe96c 100644 --- a/src/main/java/ch/eitchnet/privilege/model/UserRep.java +++ b/src/main/java/ch/eitchnet/privilege/model/UserRep.java @@ -16,6 +16,9 @@ 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; @@ -67,7 +70,8 @@ public class UserRep implements Serializable { @XmlElement(name = "roles") private Set roles; - private Map propertyMap; + @XmlElement(name = "properties") + private List properties; /** * Default constructor @@ -98,7 +102,7 @@ public class UserRep implements Serializable { this.userState = userState; this.roles = roles; this.locale = locale; - this.propertyMap = propertyMap; + this.properties = propertyMap == null ? new ArrayList<>() : XmlKeyValue.valueOf(propertyMap); } /** @@ -140,6 +144,16 @@ public class UserRep implements Serializable { return this.userId; } + /** + * Set the userId + * + * @param userId + * to set + */ + public void setUserId(String userId) { + this.userId = userId; + } + /** * @return the username */ @@ -239,7 +253,13 @@ public class UserRep implements Serializable { * @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); + if (this.properties == null) + return null; + for (XmlKeyValue keyValue : this.properties) { + if (keyValue.getKey().equals(key)) + return keyValue.getValue(); + } + return null; } /** @@ -251,7 +271,21 @@ public class UserRep implements Serializable { * the value of the property to set */ public void setProperty(String key, String value) { - this.propertyMap.put(key, 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)); + } } /** @@ -260,7 +294,13 @@ public class UserRep implements Serializable { * @return the {@link Set} of keys of all properties */ public Set getPropertyKeySet() { - return this.propertyMap.keySet(); + 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; } /** @@ -268,8 +308,10 @@ public class UserRep implements Serializable { * * @return the map of properties */ - public Map getProperties() { - return this.propertyMap; + public Map getPropertyMap() { + if (this.properties == null) + return new HashMap<>(); + return XmlKeyValue.toMap(this.properties); } /** @@ -278,8 +320,8 @@ public class UserRep implements Serializable { * @return the string map properties of this user as a list of {@link XmlKeyValue} elements */ @XmlElement(name = "properties") - public List getPropertiesAsKeyValue() { - return XmlKeyValue.valueOf(this.propertyMap); + public List getProperties() { + return this.properties == null ? new ArrayList<>() : this.properties; } /** @@ -288,8 +330,8 @@ public class UserRep implements Serializable { * @param values * the list of {@link XmlKeyValue} from which to set the properties */ - public void setPropertiesAsKeyValue(List values) { - this.propertyMap = XmlKeyValue.toMap(values); + public void setProperties(List values) { + this.properties = values; } /** diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeImpl.java b/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeImpl.java index 3efe3e36a..9cfe3bca5 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeImpl.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeImpl.java @@ -101,7 +101,7 @@ public final class PrivilegeImpl implements IPrivilege { */ public PrivilegeImpl(PrivilegeRep privilegeRep) { this(privilegeRep.getName(), privilegeRep.getPolicy(), privilegeRep.isAllAllowed(), privilegeRep.getDenyList(), - privilegeRep.getDenyList()); + privilegeRep.getAllowList()); } /** diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/Role.java b/src/main/java/ch/eitchnet/privilege/model/internal/Role.java index b962095b7..6048f672f 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/Role.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/Role.java @@ -15,9 +15,12 @@ */ 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; @@ -78,14 +81,14 @@ public final class Role { throw new PrivilegeException("No name defined!"); //$NON-NLS-1$ } - if (roleRep.getPrivilegeMap() == null) { - throw new PrivilegeException("No privileges defined!"); //$NON-NLS-1$ + if (roleRep.getPrivileges() == null) { + throw new PrivilegeException("Privileges may not be null!"); //$NON-NLS-1$ } - // build privileges from reps - Map privilegeMap = new HashMap(roleRep.getPrivilegeMap().size()); - for (String privilegeName : roleRep.getPrivilegeMap().keySet()) { - privilegeMap.put(privilegeName, new PrivilegeImpl(roleRep.getPrivilegeMap().get(privilegeName))); + // 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; @@ -133,11 +136,11 @@ public final class Role { * @return a {@link RoleRep} which is a representation of this object used to serialize and view on clients */ public RoleRep asRoleRep() { - Map privilegeMap = new HashMap(); - for (String privilegeName : this.privilegeMap.keySet()) { - privilegeMap.put(privilegeName, this.privilegeMap.get(privilegeName).asPrivilegeRep()); + List privileges = new ArrayList(); + for (Entry entry : this.privilegeMap.entrySet()) { + privileges.add(entry.getValue().asPrivilegeRep()); } - return new RoleRep(this.name, privilegeMap); + return new RoleRep(this.name, privileges); } /** diff --git a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java index 6b0122b32..0671c4591 100644 --- a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -21,11 +21,11 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.File; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; -import java.util.Map; import org.junit.AfterClass; import org.junit.Before; @@ -213,8 +213,7 @@ public class PrivilegeTest { try { login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); - Map privilegeMap = new HashMap(); - RoleRep roleRep = new RoleRep(ROLE_TEMP, privilegeMap); + RoleRep roleRep = new RoleRep(ROLE_TEMP, new ArrayList<>()); Certificate certificate = this.ctx.getCertificate(); privilegeHandler.addRole(certificate, roleRep); @@ -564,7 +563,7 @@ public class PrivilegeTest { // let's add a new user ted HashSet roles = new HashSet(); roles.add(ROLE_USER); - userRep = new UserRep("2", TED, "Ted", "Newman", UserState.ENABLED, roles, null, + userRep = new UserRep(null, TED, "Ted", "Newman", UserState.ENABLED, roles, null, new HashMap()); Certificate certificate = this.ctx.getCertificate(); privilegeHandler.addUser(certificate, userRep, null); @@ -636,8 +635,7 @@ public class PrivilegeTest { try { // add role user login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); - Map privilegeMap = new HashMap(); - RoleRep roleRep = new RoleRep(ROLE_USER, privilegeMap); + RoleRep roleRep = new RoleRep(ROLE_USER, new ArrayList()); Certificate certificate = this.ctx.getCertificate(); privilegeHandler.addRole(certificate, roleRep); privilegeHandler.persist(certificate); @@ -677,7 +675,7 @@ public class PrivilegeTest { login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); // let's add a new user bob - UserRep userRep = new UserRep("1", BOB, "Bob", "Newman", UserState.NEW, new HashSet( + 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); From a3d76d4cd88ffd1fbeb6e9c646db0e55211f6d09 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 8 Mar 2015 21:44:21 +0100 Subject: [PATCH 149/173] [Major] allow user to change their own locale incl. auto persist --- config/Privilege.xml | 2 +- .../handler/DefaultPrivilegeHandler.java | 54 ++++++++----------- .../privilege/handler/PrivilegeHandler.java | 21 -------- 3 files changed, 24 insertions(+), 53 deletions(-) diff --git a/config/Privilege.xml b/config/Privilege.xml index 2c523ead3..1f7f06837 100644 --- a/config/Privilege.xml +++ b/config/Privilege.xml @@ -5,7 +5,7 @@ - + diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index ba3f28c5f..b75969bf0 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -70,7 +70,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { /** * configuration parameter to define automatic persisting on password change */ - private static final String PARAM_AUTO_PERSIST_ON_PASSWORD_CHANGE = "autoPersistOnPasswordChange"; //$NON-NLS-1$ + private static final String PARAM_AUTO_PERSIST_ON_USER_CHANGES_DATA = "autoPersistOnUserChangesData"; //$NON-NLS-1$ /** * slf4j logger @@ -108,9 +108,9 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { private boolean initialized; /** - * flag to define if a persist should be performed after a user changes their password + * flag to define if a persist should be performed after a user changes their own data */ - private boolean autoPersistOnPasswordChange; + private boolean autoPersistOnUserChangesData; @Override public RoleRep getRole(Certificate certificate, String roleName) { @@ -734,8 +734,17 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { @Override public UserRep setUserLocale(Certificate certificate, String username, Locale locale) { - // validate who is doing this - assertIsPrivilegeAdmin(certificate); + // check if certificate is for same user, in which case user is changing their own locale + if (certificate.getUsername().equals(username)) { + + // validate the certificate + isCertificateValid(certificate); + + } else { + + // otherwise validate the the certificate is for a privilege admin + assertIsPrivilegeAdmin(certificate); + } // get User User user = this.persistenceHandler.getUser(username); @@ -750,28 +759,11 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // delegate user replacement to persistence handler this.persistenceHandler.replaceUser(newUser); - return newUser.asUserRep(); - } - - @Override - public UserRep setUserName(Certificate certificate, String username, String firstname, String lastname) { - - // validate who is doing this - assertIsPrivilegeAdmin(certificate); - - // get User - User user = this.persistenceHandler.getUser(username); - if (user == null) { - throw new PrivilegeException(MessageFormat.format("User {0} does not exist!", username)); //$NON-NLS-1$ + // perform automatic persisting, if enabled + if (this.autoPersistOnUserChangesData) { + this.persistenceHandler.persist(); } - // create new user - User newUser = new User(user.getUserId(), user.getUsername(), user.getPassword(), firstname, lastname, - user.getUserState(), user.getRoles(), user.getLocale(), user.getProperties()); - - // delegate user replacement to persistence handler - this.persistenceHandler.replaceUser(newUser); - return newUser.asUserRep(); } @@ -815,7 +807,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { this.persistenceHandler.replaceUser(newUser); // perform automatic persisting, if enabled - if (this.autoPersistOnPasswordChange) { + if (this.autoPersistOnUserChangesData) { this.persistenceHandler.persist(); } @@ -1151,15 +1143,15 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { this.encryptionHandler = encryptionHandler; this.persistenceHandler = persistenceHandler; - String autoPersistS = parameterMap.get(PARAM_AUTO_PERSIST_ON_PASSWORD_CHANGE); + String autoPersistS = parameterMap.get(PARAM_AUTO_PERSIST_ON_USER_CHANGES_DATA); if (autoPersistS == null || autoPersistS.equals(Boolean.FALSE.toString())) { - this.autoPersistOnPasswordChange = false; + this.autoPersistOnUserChangesData = false; } else if (autoPersistS.equals(Boolean.TRUE.toString())) { - this.autoPersistOnPasswordChange = true; - logger.info("Enabling automatic persistence on password change."); //$NON-NLS-1$ + 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_PASSWORD_CHANGE, autoPersistS, Boolean.FALSE); + msg = MessageFormat.format(msg, PARAM_AUTO_PERSIST_ON_USER_CHANGES_DATA, autoPersistS, Boolean.FALSE); logger.error(msg); } diff --git a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java index 46c0e0e55..adaccd05e 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java @@ -366,27 +366,6 @@ public interface PrivilegeHandler { public void setUserPassword(Certificate certificate, String username, byte[] password) throws AccessDeniedException, PrivilegeException; - /** - * Changes the name of the user. This changes the first name and the lastname. If either value is null, then that - * value is not changed - * - * @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 name is to be changed - * @param firstname - * the new first name - * @param lastname - * the new lastname - * - * @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 setUserName(Certificate certificate, String username, String firstname, String lastname) - throws AccessDeniedException, PrivilegeException; - /** * Changes the {@link UserState} of the user * From fa40671b8cc8c1b4f0cefc877d2786edbb77cc88 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 12 Mar 2015 17:32:06 +0100 Subject: [PATCH 150/173] [Major] removed the need for a role PrivilegeAdmin - now use privileges - this solves the situation where a user might be allowed to add a user with a specific role, but not change a role and other such use cases Now there are privileges for every use case with two new PrivilegePolicies: - RoleAccessPrivilege - UserAccessPrivilege both of these policies expect a ch.eitchnet.utils.collections.Tuple as privilege value. The Tuple is a simple wrapper for two values: first and second. Each privilege has its own requirement on the actual values Special privilege actions: - PrivilegeAction -> privilege vlaue: String - Persist (required Allow) - Reload (required Allow) - GetPolicies (required Allow) Role specific privileges: - PrivilegeGetRole -> privilege value: Tuple(null, newRole) - PrivilegeAddRole -> privilege value: Tuple(null, newRole) - PrivilegeRemoveRole -> privilege value: Tuple(null, newRole) - PrivilegeModifyRole -> privilege value: Tuple(oldRole, newRole) Use specific privileges: - PrivilegeGetUser -> privilege value: Tuple(null, newUser) - PrivilegeAddUser -> privilege value: Tuple(null, newUser) - PrivilegeRemoveUser -> privilege value: Tuple(null, newUser) - PrivilegeModifyUser -> privilege value: Tuple(oldUser, newUser) - NOTE: without modifying roles, only fields and properties! - PrivilegeAddRoleToUser -> privilege value: Tuple(oldUser, roleName) - PrivilegeRemoveRoleFromUser -> privilege value: Tuple(oldUser, roleName) --- config/{Privilege.xml => PrivilegeConfig.xml} | 2 + config/PrivilegeModel.xml | 42 +- .../handler/DefaultPrivilegeHandler.java | 671 ++++++++++-------- .../privilege/handler/PrivilegeHandler.java | 118 ++- .../handler/XmlPersistenceHandler.java | 16 - .../privilege/model/PrivilegeContext.java | 28 +- .../privilege/model/SimpleRestrictable.java | 45 ++ .../privilege/policy/DefaultPrivilege.java | 47 +- .../policy/PrivilegePolicyHelper.java | 80 +++ .../privilege/policy/RoleAccessPrivilege.java | 110 +++ .../privilege/policy/UserAccessPrivilege.java | 139 ++++ .../xml/PrivilegeModelSaxReader.java | 17 + .../resources/PrivilegeMessages.properties | 8 +- .../privilege/test/PrivilegeTest.java | 12 +- 14 files changed, 951 insertions(+), 384 deletions(-) rename config/{Privilege.xml => PrivilegeConfig.xml} (79%) create mode 100644 src/main/java/ch/eitchnet/privilege/model/SimpleRestrictable.java create mode 100644 src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicyHelper.java create mode 100644 src/main/java/ch/eitchnet/privilege/policy/RoleAccessPrivilege.java create mode 100644 src/main/java/ch/eitchnet/privilege/policy/UserAccessPrivilege.java diff --git a/config/Privilege.xml b/config/PrivilegeConfig.xml similarity index 79% rename from config/Privilege.xml rename to config/PrivilegeConfig.xml index 1f7f06837..17fb4ad27 100644 --- a/config/Privilege.xml +++ b/config/PrivilegeConfig.xml @@ -25,6 +25,8 @@ + + \ No newline at end of file diff --git a/config/PrivilegeModel.xml b/config/PrivilegeModel.xml index 7ae0fd879..104b28130 100644 --- a/config/PrivilegeModel.xml +++ b/config/PrivilegeModel.xml @@ -43,14 +43,52 @@ - + + + Persist + Reload + GetPolicies + + + + true + + + true + + + true + + + true + + + + true + + + true + + + true + + + true + + + true + + + true + + true - + diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index b75969bf0..89fc92884 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -39,12 +39,14 @@ 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.utils.collections.Tuple; import ch.eitchnet.utils.helper.StringHelper; /** @@ -67,10 +69,7 @@ import ch.eitchnet.utils.helper.StringHelper; */ public class DefaultPrivilegeHandler implements PrivilegeHandler { - /** - * configuration parameter to define automatic persisting on password change - */ - private static final String PARAM_AUTO_PERSIST_ON_USER_CHANGES_DATA = "autoPersistOnUserChangesData"; //$NON-NLS-1$ + /// /** * slf4j logger @@ -115,32 +114,40 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { @Override public RoleRep getRole(Certificate certificate, String roleName) { - // validate who is doing this - assertIsPrivilegeAdmin(certificate); + // 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 who is doing this - assertIsPrivilegeAdmin(certificate); + // 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 who is doing this - assertIsPrivilegeAdmin(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()) { @@ -149,33 +156,60 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { return policyDef; } - @Override - public List getUsers(Certificate certificate) { - - // validate who is doing this - assertIsPrivilegeAdmin(certificate); - - Stream usersStream = this.persistenceHandler.getAllUsers().stream(); - List users = usersStream.map(u -> u.asUserRep()).collect(Collectors.toList()); - return users; - } - @Override public List getRoles(Certificate certificate) { - // validate who is doing this - assertIsPrivilegeAdmin(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.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.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 who is doing this - assertIsPrivilegeAdmin(certificate); + // 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(); @@ -190,6 +224,13 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { 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; @@ -319,8 +360,9 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { public UserRep addUser(Certificate certificate, UserRep userRep, byte[] password) { try { - // validate who is doing this - assertIsPrivilegeAdmin(certificate); + // 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())) { @@ -353,12 +395,15 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } // create new user - User user = createUser(userRep, passwordHash); + User newUser = createUser(userRep, passwordHash); + + // 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(user); + this.persistenceHandler.addUser(newUser); - return user.asUserRep(); + return newUser.asUserRep(); } finally { clearPassword(password); @@ -369,8 +414,9 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { public UserRep replaceUser(Certificate certificate, UserRep userRep, byte[] password) { try { - // validate who is doing this - assertIsPrivilegeAdmin(certificate); + // validate user actually has this type of privilege + PrivilegeContext prvCtx = getPrivilegeContext(certificate); + prvCtx.assertHasPrivilege(PRIVILEGE_MODIFY_USER); // first validate user userRep.validate(); @@ -378,16 +424,16 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { validateRolesExist(userRep); // validate user exists - User user = this.persistenceHandler.getUser(userRep.getUsername()); - if (user == null) { + 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 (!user.getUserId().equals(userRep.getUserId())) { + if (!existingUser.getUserId().equals(userRep.getUserId())) { String msg = "UserId of existing user {0} does not match userRep {1}"; - msg = MessageFormat.format(msg, user.getUserId(), userRep.getUserId()); + msg = MessageFormat.format(msg, existingUser.getUserId(), userRep.getUserId()); throw new PrivilegeException(MessageFormat.format(msg, userRep.getUsername())); } @@ -401,12 +447,15 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { passwordHash = this.encryptionHandler.convertToHash(password); } - user = createUser(userRep, passwordHash); + User newUser = createUser(userRep, passwordHash); + + // 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(user); + this.persistenceHandler.replaceUser(newUser); - return user.asUserRep(); + return newUser.asUserRep(); } finally { clearPassword(password); @@ -435,12 +484,13 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { public UserRep updateUser(Certificate certificate, UserRep userRep) throws AccessDeniedException, PrivilegeException { - // validate who is doing this - assertIsPrivilegeAdmin(certificate); + // validate user actually has this type of privilege + PrivilegeContext prvCtx = getPrivilegeContext(certificate); + prvCtx.assertHasPrivilege(PRIVILEGE_MODIFY_USER); // get existing user - User user = this.persistenceHandler.getUser(userRep.getUsername()); - if (user == null) { + 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$ } @@ -452,15 +502,15 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { "All updateable fields are empty for update of user {0}", userRep.getUsername())); //$NON-NLS-1$ } - String userId = user.getUserId(); - String username = user.getUsername(); - String password = user.getPassword(); - String firstname = user.getFirstname(); - String lastname = user.getLastname(); - UserState userState = user.getUserState(); - Set roles = user.getRoles(); - Locale locale = user.getLocale(); - Map propertyMap = user.getProperties(); + 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())) @@ -473,134 +523,64 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { propertyMap = userRep.getPropertyMap(); // create new user - user = new User(userId, username, password, firstname, lastname, userState, roles, locale, propertyMap); + User newUser = new User(userId, username, password, firstname, lastname, userState, roles, locale, propertyMap); + + // 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(user); + this.persistenceHandler.replaceUser(newUser); - return user.asUserRep(); + return newUser.asUserRep(); } @Override - public RoleRep addRole(Certificate certificate, RoleRep roleRep) { + public UserRep removeUser(Certificate certificate, String username) { - // validate who is doing this - assertIsPrivilegeAdmin(certificate); + // validate user actually has this type of privilege + PrivilegeContext prvCtx = getPrivilegeContext(certificate); + prvCtx.assertHasPrivilege(PRIVILEGE_REMOVE_USER); - // 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); + // 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)); } - // create new role from RoleRep - Role role = new Role(roleRep); + // validate this user may remove this user + prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_REMOVE_USER, new Tuple(null, existingUser))); - // validate policy if not null - validatePolicies(role); + // delegate user removal to persistence handler + this.persistenceHandler.removeUser(username); - // delegate to persistence handler - this.persistenceHandler.addRole(role); - - return role.asRoleRep(); - } - - @Override - public RoleRep replaceRole(Certificate certificate, RoleRep roleRep) { - - // validate who is doing this - assertIsPrivilegeAdmin(certificate); - - // first validate role - roleRep.validate(); - - // validate role does exist - if (this.persistenceHandler.getRole(roleRep.getName()) == 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 role = new Role(roleRep); - - // validate policy if not null - validatePolicies(role); - - // delegate to persistence handler - this.persistenceHandler.replaceRole(role); - - return role.asRoleRep(); - } - - @Override - public RoleRep addOrReplacePrivilegeOnRole(Certificate certificate, String roleName, PrivilegeRep privilegeRep) { - - // validate who is doing this - assertIsPrivilegeAdmin(certificate); - - // validate PrivilegeRep - privilegeRep.validate(); - - // get role - Role role = this.persistenceHandler.getRole(roleName); - if (role == 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 = role.getPrivilegeNames(); - Map privilegeMap = new HashMap<>(existingPrivilegeNames.size() + 1); - for (String name : existingPrivilegeNames) { - IPrivilege privilege = role.getPrivilege(name); - privilegeMap.put(name, privilege); - } - - // add new one - privilegeMap.put(newPrivilege.getName(), newPrivilege); - - Role newRole = new Role(role.getName(), privilegeMap); - - // delegate role replacement to persistence handler - this.persistenceHandler.replaceRole(newRole); - - return newRole.asRoleRep(); + return existingUser.asUserRep(); } @Override public UserRep addRoleToUser(Certificate certificate, String username, String roleName) { - // validate who is doing this - assertIsPrivilegeAdmin(certificate); + // validate user actually has this type of privilege + PrivilegeContext prvCtx = getPrivilegeContext(certificate); + prvCtx.assertHasPrivilege(PRIVILEGE_ADD_ROLE_TO_USER); // get user - User user = this.persistenceHandler.getUser(username); - if (user == null) { + 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 = user.getRoles(); + 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 role exists + // 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); @@ -610,8 +590,9 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { Set newRoles = new HashSet<>(currentRoles); newRoles.add(roleName); - User newUser = new User(user.getUserId(), user.getUsername(), user.getPassword(), user.getFirstname(), - user.getLastname(), user.getUserState(), newRoles, user.getLocale(), user.getProperties()); + 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); @@ -619,94 +600,35 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { return newUser.asUserRep(); } - @Override - public RoleRep removePrivilegeFromRole(Certificate certificate, String roleName, String privilegeName) { - - // validate who is doing this - assertIsPrivilegeAdmin(certificate); - - // get role - Role role = this.persistenceHandler.getRole(roleName); - if (role == null) { - throw new PrivilegeException(MessageFormat.format("Role {0} does not exist!", roleName)); //$NON-NLS-1$ - } - - // ignore if role does not have privilege - if (!role.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 = role.getPrivilegeNames(); - Map newPrivileges = new HashMap(privilegeNames.size() - 1); - for (String name : privilegeNames) { - IPrivilege privilege = role.getPrivilege(name); - if (!privilege.getName().equals(privilegeName)) - newPrivileges.put(privilege.getName(), privilege); - } - - // create new role - Role newRole = new Role(role.getName(), newPrivileges); - - // delegate user replacement to persistence handler - this.persistenceHandler.replaceRole(newRole); - - return newRole.asRoleRep(); - } - - @Override - public RoleRep removeRole(Certificate certificate, String roleName) { - - // validate who is doing this - assertIsPrivilegeAdmin(certificate); - - // 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); - } - - // delegate role removal to persistence handler - Role removedRole = this.persistenceHandler.removeRole(roleName); - if (removedRole == null) { - String msg = "Can not remove Role {0} because role does not exist!"; - throw new PrivilegeException(MessageFormat.format(msg, roleName)); - } - - // return role rep if it was removed - return removedRole.asRoleRep(); - } - @Override public UserRep removeRoleFromUser(Certificate certificate, String username, String roleName) { - // validate who is doing this - assertIsPrivilegeAdmin(certificate); + // validate user actually has this type of privilege + PrivilegeContext prvCtx = getPrivilegeContext(certificate); + prvCtx.assertHasPrivilege(PRIVILEGE_REMOVE_ROLE_FROM_USER); // get User - User user = this.persistenceHandler.getUser(username); - if (user == null) { + 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 = user.getRoles(); + Set currentRoles = existingUser.getRoles(); if (!currentRoles.contains(roleName)) { - String msg = MessageFormat.format("User {0} does not have role {1}", user.getUsername(), roleName); //$NON-NLS-1$ + 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(user.getUserId(), user.getUsername(), user.getPassword(), user.getFirstname(), - user.getLastname(), user.getUserState(), newRoles, user.getLocale(), user.getProperties()); + 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); @@ -714,23 +636,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { return newUser.asUserRep(); } - @Override - public UserRep removeUser(Certificate certificate, String username) { - - // validate who is doing this - assertIsPrivilegeAdmin(certificate); - - // delegate user removal to persistence handler - User removedUser = this.persistenceHandler.removeUser(username); - if (removedUser == null) { - String msg = "Can not remove User {0} because user does not exist!"; - throw new PrivilegeException(MessageFormat.format(msg, username)); - } - - // return user rep if it was removed - return removedUser.asUserRep(); - } - @Override public UserRep setUserLocale(Certificate certificate, String username, Locale locale) { @@ -742,19 +647,27 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } else { - // otherwise validate the the certificate is for a privilege admin - assertIsPrivilegeAdmin(certificate); + // validate user actually has this type of privilege + PrivilegeContext prvCtx = getPrivilegeContext(certificate); + prvCtx.assertHasPrivilege(PRIVILEGE_MODIFY_USER); } // get User - User user = this.persistenceHandler.getUser(username); - if (user == null) { + 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(user.getUserId(), user.getUsername(), user.getPassword(), user.getFirstname(), - user.getLastname(), user.getUserState(), user.getRoles(), locale, user.getProperties()); + 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)) { + PrivilegeContext prvCtx = getPrivilegeContext(certificate); + prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_MODIFY_USER, new Tuple(existingUser, newUser))); + } // delegate user replacement to persistence handler this.persistenceHandler.replaceUser(newUser); @@ -779,13 +692,14 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } else { - // otherwise validate the the certificate is for a privilege admin - assertIsPrivilegeAdmin(certificate); + // validate user actually has this type of privilege + PrivilegeContext prvCtx = getPrivilegeContext(certificate); + prvCtx.assertHasPrivilege(PRIVILEGE_MODIFY_USER); } // get User - User user = this.persistenceHandler.getUser(username); - if (user == null) { + User existingUser = this.persistenceHandler.getUser(username); + if (existingUser == null) { throw new PrivilegeException(MessageFormat.format("User {0} does not exist!", username)); //$NON-NLS-1$ } @@ -800,8 +714,15 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } // create new user - User newUser = new User(user.getUserId(), user.getUsername(), passwordHash, user.getFirstname(), - user.getLastname(), user.getUserState(), user.getRoles(), user.getLocale(), user.getProperties()); + 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)) { + PrivilegeContext prvCtx = getPrivilegeContext(certificate); + prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_MODIFY_USER, new Tuple(existingUser, newUser))); + } // delegate user replacement to persistence handler this.persistenceHandler.replaceUser(newUser); @@ -819,18 +740,23 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { @Override public UserRep setUserState(Certificate certificate, String username, UserState state) { - // validate who is doing this - assertIsPrivilegeAdmin(certificate); + // validate user actually has this type of privilege + PrivilegeContext prvCtx = getPrivilegeContext(certificate); + prvCtx.assertHasPrivilege(PRIVILEGE_MODIFY_USER); // get User - User user = this.persistenceHandler.getUser(username); - if (user == null) { + 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(user.getUserId(), user.getUsername(), user.getPassword(), user.getFirstname(), - user.getLastname(), state, user.getRoles(), user.getLocale(), user.getProperties()); + 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_MODIFY_USER, new Tuple(existingUser, newUser))); // delegate user replacement to persistence handler this.persistenceHandler.replaceUser(newUser); @@ -838,6 +764,194 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { 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); + + // 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); + + // 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) { @@ -1068,28 +1182,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { return this.privilegeContextMap.get(certificate.getSessionId()); } - @Override - public void assertIsPrivilegeAdmin(Certificate certificate) throws PrivilegeException { - - // validate certificate - isCertificateValid(certificate); - - // get user object - User user = this.persistenceHandler.getUser(certificate.getUsername()); - if (user == null) { - String msg = "Oh boy, how did this happen: No User in user map although the certificate is valid! Certificate: {0}"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, certificate); - throw new PrivilegeException(msg); - } - - // validate user has PrivilegeAdmin role - if (!user.hasRole(PrivilegeHandler.PRIVILEGE_ADMIN_ROLE)) { - String msg = "User does not have {0} role! Certificate: {1}"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, PrivilegeHandler.PRIVILEGE_ADMIN_ROLE, certificate); - throw new AccessDeniedException(msg); - } - } - /** * This simple implementation validates that the password is not null, and that the password string is not empty * @@ -1111,11 +1203,22 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { public boolean persist(Certificate certificate) { // validate who is doing this - assertIsPrivilegeAdmin(certificate); + PrivilegeContext prvCtx = getPrivilegeContext(certificate); + prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_ACTION, PRIVILEGE_ACTION_PERSIST)); return this.persistenceHandler.persist(); } + @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 diff --git a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java index adaccd05e..9aea7c272 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java @@ -41,10 +41,85 @@ import ch.eitchnet.privilege.policy.PrivilegePolicy; */ public interface PrivilegeHandler { + /// + /** - * PRIVILEGE_ADMIN_ROLE = PrivilegeAdmin: This is the role users must have, if they are allowed to modify objects + * 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_ADMIN_ROLE = "PrivilegeAdmin"; //$NON-NLS-1$ + 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 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"; + + /// + + /** + * 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"; + + /// + + /** + * configuration parameter to define automatic persisting on password change + */ + public static final String PARAM_AUTO_PERSIST_ON_USER_CHANGES_DATA = "autoPersistOnUserChangesData"; //$NON-NLS-1$ /** * Returns a {@link UserRep} for the given username @@ -469,28 +544,6 @@ public interface PrivilegeHandler { */ public PrivilegeContext getPrivilegeContext(Certificate certificate) throws PrivilegeException; - /** - *

      - * Validates if this {@link Certificate} is for a {@link ch.eitchnet.privilege.model.internal.User} with - * {@link Role} with name {@link PrivilegeHandler#PRIVILEGE_ADMIN_ROLE} - *

      - * - *

      - * In other words, this method checks if the given certificate is for a user who has the rights to change objects - *

      - * - *

      - * If the user is not the administrator, then a {@link ch.eitchnet.privilege.base.PrivilegeException} is thrown - *

      - * - * @param certificate - * the {@link Certificate} for which the role should be validated against - * - * @throws AccessDeniedException - * if the user does not not have admin privileges - */ - public void assertIsPrivilegeAdmin(Certificate certificate) throws AccessDeniedException; - /** * Validate that the given password meets certain requirements. What these requirements are is a decision made by * the concrete implementation @@ -503,6 +556,23 @@ public interface PrivilegeHandler { */ 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 diff --git a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java index 292743a7e..0d31c1048 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java @@ -224,22 +224,6 @@ public class XmlPersistenceHandler implements PersistenceHandler { } } - // validate we have a user with PrivilegeAdmin access - boolean privilegeAdminExists = false; - for (String username : this.userMap.keySet()) { - User user = this.userMap.get(username); - if (user.hasRole(PrivilegeHandler.PRIVILEGE_ADMIN_ROLE)) { - privilegeAdminExists = true; - break; - } - } - - if (!privilegeAdminExists) { - String msg = "No User with role ''{0}'' exists. Privilege modifications will not be possible!"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, PrivilegeHandler.PRIVILEGE_ADMIN_ROLE); - logger.warn(msg); - } - return true; } diff --git a/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java b/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java index 09a282567..32eef9e6a 100644 --- a/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java +++ b/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java @@ -16,10 +16,8 @@ package ch.eitchnet.privilege.model; import java.text.MessageFormat; -import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.Set; @@ -73,16 +71,26 @@ public class PrivilegeContext { 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 List getFlatAllowList() { - List allowList = new ArrayList<>(); - for (IPrivilege privilege : this.privileges.values()) { - allowList.addAll(privilege.getAllowList()); + 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 allowList; + return policy; } // @@ -115,11 +123,7 @@ public class PrivilegeContext { // get the policy referenced by the restrictable String policyName = privilege.getPolicy(); - 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)); - } + PrivilegePolicy policy = getPolicy(policyName); // delegate to the policy policy.validateAction(this, privilege, restrictable); diff --git a/src/main/java/ch/eitchnet/privilege/model/SimpleRestrictable.java b/src/main/java/ch/eitchnet/privilege/model/SimpleRestrictable.java new file mode 100644 index 000000000..341bbb37c --- /dev/null +++ b/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/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java b/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java index 32375ee3b..05494e5c5 100644 --- a/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java +++ b/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java @@ -17,14 +17,12 @@ 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.privilege.model.internal.Role; -import ch.eitchnet.utils.helper.StringHelper; /** * This is a simple implementation of {@link PrivilegePolicy} which uses the {@link Restrictable#getPrivilegeName()} to @@ -41,29 +39,7 @@ public class DefaultPrivilege implements PrivilegePolicy { */ @Override public void validateAction(PrivilegeContext ctx, 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)); - } - - // if everything is allowed, then no need to carry on - if (privilege.isAllAllowed()) - return; + PrivilegePolicyHelper.preValidate(privilege, restrictable); // get the value on which the action is to be performed Object object = restrictable.getPrivilegeValue(); @@ -76,23 +52,12 @@ public class DefaultPrivilege implements PrivilegePolicy { throw new PrivilegeException(msg); } - String privilegeValue = (String) object; - - // 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(), privilegeName, restrictable.getClass().getName()); - throw new AccessDeniedException(msg); - } - - // now check values allowed - if (privilege.isAllowed(privilegeValue)) + // if everything is allowed, then no need to carry on + if (privilege.isAllAllowed()) return; - // default is not allowed - String msg = MessageFormat.format(PrivilegeMessages.getString("Privilege.accessdenied.noprivilege"), //$NON-NLS-1$ - ctx.getUsername(), privilegeName, restrictable.getClass().getName()); - throw new AccessDeniedException(msg); + String privilegeValue = (String) object; + + PrivilegePolicyHelper.checkByAllowDenyValues(ctx, privilege, restrictable, privilegeValue); } } diff --git a/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicyHelper.java b/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicyHelper.java new file mode 100644 index 000000000..b06456453 --- /dev/null +++ b/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/src/main/java/ch/eitchnet/privilege/policy/RoleAccessPrivilege.java b/src/main/java/ch/eitchnet/privilege/policy/RoleAccessPrivilege.java new file mode 100644 index 000000000..a2bf2fa1d --- /dev/null +++ b/src/main/java/ch/eitchnet/privilege/policy/RoleAccessPrivilege.java @@ -0,0 +1,110 @@ +/* + * 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; + +/** + * TODO + * + * @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(); + + // 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/src/main/java/ch/eitchnet/privilege/policy/UserAccessPrivilege.java b/src/main/java/ch/eitchnet/privilege/policy/UserAccessPrivilege.java new file mode 100644 index 000000000..ad35d4115 --- /dev/null +++ b/src/main/java/ch/eitchnet/privilege/policy/UserAccessPrivilege.java @@ -0,0 +1,139 @@ +/* + * 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; + +/** + * TODO + * + * @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(); + + // 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; + } + + default: + String msg = Restrictable.class.getName() + + PrivilegeMessages.getString("Privilege.userAccessPrivilege.unknownPrivilege"); //$NON-NLS-1$ + msg = MessageFormat.format(msg, privilegeName); + throw new PrivilegeException(msg); + } + } +} diff --git a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java index 03f43bf91..03d5779c4 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java +++ b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java @@ -169,6 +169,11 @@ public class PrivilegeModelSaxReader extends DefaultHandler { } 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); } } @@ -267,12 +272,20 @@ public class PrivilegeModelSaxReader extends DefaultHandler { 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); } } @@ -296,6 +309,10 @@ public class PrivilegeModelSaxReader extends DefaultHandler { 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); } } diff --git a/src/main/resources/PrivilegeMessages.properties b/src/main/resources/PrivilegeMessages.properties index 62f19745c..89127fb00 100644 --- a/src/main/resources/PrivilegeMessages.properties +++ b/src/main/resources/PrivilegeMessages.properties @@ -1,6 +1,12 @@ Privilege.accessdenied.noprivilege=User {0} does not have Privilege {1} needed for Restrictable {2} -Privilege.illegalArgument.nonstring=\ {1} has returned a non-string privilege value\! +Privilege.illegalArgument.nonstring=\ {0} has returned a non-string privilege value\! +Privilege.illegalArgument.nonrole=\ {0} did not return a Role 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.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 a Privilege {0} +Privilege.roleAccessPrivilege.unknownPrivilege=Unhandled RoleAccessPrivilege {0} +Privilege.userAccessPrivilege.unknownPrivilege=Unhandled UserAccessPrivilege {0} diff --git a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java index 0671c4591..310c88ad4 100644 --- a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -21,6 +21,7 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.File; +import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -40,6 +41,7 @@ import ch.eitchnet.privilege.base.AccessDeniedException; import ch.eitchnet.privilege.base.PrivilegeException; import ch.eitchnet.privilege.handler.PrivilegeHandler; import ch.eitchnet.privilege.helper.PrivilegeInitializationHelper; +import ch.eitchnet.privilege.i18n.PrivilegeMessages; import ch.eitchnet.privilege.model.Certificate; import ch.eitchnet.privilege.model.PrivilegeContext; import ch.eitchnet.privilege.model.PrivilegeRep; @@ -63,6 +65,7 @@ import ch.eitchnet.utils.helper.FileHelper; @SuppressWarnings("nls") public class PrivilegeTest { + private static final String ROLE_PRIVILEGE_ADMIN = "PrivilegeAdmin"; private static final String ADMIN = "admin"; private static final byte[] PASS_ADMIN = "admin".getBytes(); private static final String BOB = "bob"; @@ -146,7 +149,7 @@ public class PrivilegeTest { String pwd = System.getProperty("user.dir"); - File privilegeConfigFile = new File(pwd + "/config/Privilege.xml"); + File privilegeConfigFile = new File(pwd + "/config/PrivilegeConfig.xml"); // initialize privilege privilegeHandler = PrivilegeInitializationHelper.initializeFromXml(privilegeConfigFile); @@ -579,8 +582,8 @@ public class PrivilegeTest { // testAddAdminRoleToBob login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); Certificate certificate = this.ctx.getCertificate(); - privilegeHandler.addRoleToUser(certificate, BOB, PrivilegeHandler.PRIVILEGE_ADMIN_ROLE); - logger.info("Added " + PrivilegeHandler.PRIVILEGE_ADMIN_ROLE + " to " + ADMIN); + privilegeHandler.addRoleToUser(certificate, BOB, ROLE_PRIVILEGE_ADMIN); + logger.info("Added " + ROLE_PRIVILEGE_ADMIN + " to " + ADMIN); privilegeHandler.persist(certificate); } finally { logout(); @@ -602,7 +605,8 @@ public class PrivilegeTest { 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 = "User does not have PrivilegeAdmin role! Certificate: " + certificate; + String msg = MessageFormat.format(PrivilegeMessages.getString("Privilege.noprivilege.user"), //$NON-NLS-1$ + BOB, PrivilegeHandler.PRIVILEGE_ADD_USER); assertEquals(msg, e.getMessage()); } finally { logout(); From 7ff8ba67793b35480b01134003aa5607244382a9 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 12 Mar 2015 18:30:38 +0100 Subject: [PATCH 151/173] [Bugfix] Fixed bad parsing of Allow on multiple privileges per Role --- .../xml/PrivilegeModelSaxReader.java | 6 +++++ .../ch/eitchnet/privilege/test/XmlTest.java | 23 ++++++++++++++++--- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java index 03d5779c4..be05f0b62 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java +++ b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java @@ -198,6 +198,12 @@ public class PrivilegeModelSaxReader extends DefaultHandler { 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); diff --git a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java index 6d46fc25d..6526e913a 100644 --- a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java @@ -40,6 +40,7 @@ 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; @@ -107,7 +108,7 @@ public class XmlTest { PrivilegeContainerModel containerModel = new PrivilegeContainerModel(); PrivilegeConfigSaxReader saxReader = new PrivilegeConfigSaxReader(containerModel); - File xmlFile = new File("config/Privilege.xml"); + File xmlFile = new File("config/PrivilegeConfig.xml"); XmlHelper.parseDocument(xmlFile, saxReader); logger.info(containerModel.toString()); @@ -120,7 +121,7 @@ public class XmlTest { assertNotNull(containerModel.getPersistenceHandlerParameterMap()); assertEquals(1, containerModel.getParameterMap().size()); - assertEquals(1, containerModel.getPolicies().size()); + assertEquals(3, containerModel.getPolicies().size()); assertEquals(1, containerModel.getEncryptionHandlerParameterMap().size()); assertEquals(2, containerModel.getPersistenceHandlerParameterMap().size()); @@ -211,7 +212,23 @@ public class XmlTest { // PrivilegeAdmin Role privilegeAdmin = findRole("PrivilegeAdmin", roles); assertEquals("PrivilegeAdmin", privilegeAdmin.getName()); - assertTrue(privilegeAdmin.getPrivilegeNames().isEmpty()); + assertEquals(11, 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); From 9870513beb0a656a7d9153ea30aa7a0000e17417 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 13 Mar 2015 22:55:10 +0100 Subject: [PATCH 152/173] [New] Added new param "privilegeConflictResolution" - privilegeConflictResolution is used to configure how conflicts of privileges on multiple roles are handled. - Implemented is STRICT where if a privilege with the same name exists on a role used by the same user occurs, then an exception is thrown. - Next is MERGE where if a conflict occurs, then the privileges are merged: allAllowed overrides, allow and deny list are merged --- config/PrivilegeConfig.xml | 1 + config/PrivilegeConfigMerge.xml | 33 ++++ config/PrivilegeModel.xml | 12 +- config/PrivilegeModelMerge.xml | 54 ++++++ .../base/PrivilegeConflictResolution.java | 52 ++++++ .../handler/DefaultPrivilegeHandler.java | 147 +++++++++++++++- .../privilege/handler/PrivilegeHandler.java | 6 + .../test/PrivilegeConflictMergeTest.java | 164 ++++++++++++++++++ .../privilege/test/PrivilegeTest.java | 35 +++- .../ch/eitchnet/privilege/test/XmlTest.java | 4 +- 10 files changed, 497 insertions(+), 11 deletions(-) create mode 100644 config/PrivilegeConfigMerge.xml create mode 100644 config/PrivilegeModelMerge.xml create mode 100644 src/main/java/ch/eitchnet/privilege/base/PrivilegeConflictResolution.java create mode 100644 src/test/java/ch/eitchnet/privilege/test/PrivilegeConflictMergeTest.java diff --git a/config/PrivilegeConfig.xml b/config/PrivilegeConfig.xml index 17fb4ad27..9dd3e2b11 100644 --- a/config/PrivilegeConfig.xml +++ b/config/PrivilegeConfig.xml @@ -6,6 +6,7 @@ + diff --git a/config/PrivilegeConfigMerge.xml b/config/PrivilegeConfigMerge.xml new file mode 100644 index 000000000..923189b53 --- /dev/null +++ b/config/PrivilegeConfigMerge.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/config/PrivilegeModel.xml b/config/PrivilegeModel.xml index 104b28130..070e087d7 100644 --- a/config/PrivilegeModel.xml +++ b/config/PrivilegeModel.xml @@ -89,7 +89,17 @@
      - + + + true + + + + + + true + + diff --git a/config/PrivilegeModelMerge.xml b/config/PrivilegeModelMerge.xml new file mode 100644 index 000000000..97fb7065d --- /dev/null +++ b/config/PrivilegeModelMerge.xml @@ -0,0 +1,54 @@ + + + + + + System User + Administrator + ENABLED + en_GB + + RoleA1 + RoleA2 + + + + System User + Administrator + ENABLED + en_GB + + RoleB1 + RoleB2 + + + + + + + + + allow1 + + + + + true + + + + + + allow1 + deny1 + + + + + allow2 + deny2 + + + + + \ No newline at end of file diff --git a/src/main/java/ch/eitchnet/privilege/base/PrivilegeConflictResolution.java b/src/main/java/ch/eitchnet/privilege/base/PrivilegeConflictResolution.java new file mode 100644 index 000000000..0d60fb28d --- /dev/null +++ b/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/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 89fc92884..26eb14d20 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -33,6 +33,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; 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; @@ -111,6 +112,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { */ private boolean autoPersistOnUserChangesData; + private PrivilegeConflictResolution privilegeConflictResolution; + @Override public RoleRep getRole(Certificate certificate, String roleName) { @@ -397,6 +400,9 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // 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))); @@ -449,6 +455,9 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { 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))); @@ -525,6 +534,9 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // 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))); @@ -594,6 +606,9 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { 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); @@ -815,6 +830,9 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // 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))); @@ -903,6 +921,9 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // 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))); @@ -1078,16 +1099,40 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { Set privilegeNames = role.getPrivilegeNames(); for (String privilegeName : privilegeNames) { - // cache the privilege - if (privileges.containsKey(privilegeName)) - continue; - 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 @@ -1258,16 +1303,110 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { logger.error(msg); } + 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$ + // validate policies on privileges of Roles for (Role role : persistenceHandler.getAllRoles()) { validatePolicies(role); } + // validate privilege conflicts + validatePrivilegeConflicts(); + this.lastSessionId = 0l; this.privilegeContextMap = Collections.synchronizedMap(new HashMap()); this.initialized = true; } + 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 has conflicts for privilege {0} on roles {1} and {2}"; + msg = MessageFormat.format(msg, privilegeName, roleOrigin, roleName); + conflicts.add(msg); + } + } + } + + return conflicts; + } + /** * Validates that the policies which are not null on the privileges of the role exist * diff --git a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java index 9aea7c272..5b6d81ad1 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java @@ -20,6 +20,7 @@ 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; @@ -121,6 +122,11 @@ public interface PrivilegeHandler { */ public static final String PARAM_AUTO_PERSIST_ON_USER_CHANGES_DATA = "autoPersistOnUserChangesData"; //$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 * diff --git a/src/test/java/ch/eitchnet/privilege/test/PrivilegeConflictMergeTest.java b/src/test/java/ch/eitchnet/privilege/test/PrivilegeConflictMergeTest.java new file mode 100644 index 000000000..ab9a352e1 --- /dev/null +++ b/src/test/java/ch/eitchnet/privilege/test/PrivilegeConflictMergeTest.java @@ -0,0 +1,164 @@ +/* + * 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 java.io.File; + +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +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.IPrivilege; +import ch.eitchnet.privilege.model.PrivilegeContext; +import ch.eitchnet.utils.helper.FileHelper; + +/** + * @author Robert von Burg + */ +public class PrivilegeConflictMergeTest { + + private static final Logger logger = LoggerFactory.getLogger(PrivilegeConflictMergeTest.class); + + @BeforeClass + public static void init() throws Exception { + try { + destroy(); + + // copy configuration to tmp + String pwd = System.getProperty("user.dir"); + + File origPrivilegeModelFile = new File(pwd + "/config/PrivilegeModelMerge.xml"); + File tmpPrivilegeModelFile = new File(pwd + "/target/testPrivilege/PrivilegeModelMerge.xml"); + if (tmpPrivilegeModelFile.exists() && !tmpPrivilegeModelFile.delete()) { + throw new RuntimeException("Tmp configuration still exists and can not be deleted at " + + tmpPrivilegeModelFile.getAbsolutePath()); + } + + File parentFile = tmpPrivilegeModelFile.getParentFile(); + if (!parentFile.exists()) { + if (!parentFile.mkdirs()) + throw new RuntimeException("Could not create parent for tmp " + tmpPrivilegeModelFile); + } + + if (!FileHelper.copy(origPrivilegeModelFile, tmpPrivilegeModelFile, true)) + throw new RuntimeException("Failed to copy " + origPrivilegeModelFile + " to " + tmpPrivilegeModelFile); + + } catch (Exception e) { + logger.error(e.getMessage(), e); + + throw new RuntimeException("Initialization failed: " + e.getLocalizedMessage(), e); + } + } + + @AfterClass + public static void destroy() throws Exception { + + // delete temporary file + String pwd = System.getProperty("user.dir"); + + File tmpPrivilegeModelFile = new File(pwd + "/target/testPrivilege/PrivilegeModelMerge.xml"); + if (tmpPrivilegeModelFile.exists() && !tmpPrivilegeModelFile.delete()) { + throw new RuntimeException("Tmp configuration still exists and can not be deleted at " + + tmpPrivilegeModelFile.getAbsolutePath()); + } + + // and temporary parent + File parentFile = tmpPrivilegeModelFile.getParentFile(); + if (parentFile.exists() && !parentFile.delete()) { + throw new RuntimeException("Could not remove temporary parent for tmp " + tmpPrivilegeModelFile); + } + } + + private PrivilegeHandler privilegeHandler; + private PrivilegeContext ctx; + + @Before + public void setup() throws Exception { + try { + + String pwd = System.getProperty("user.dir"); + + File privilegeConfigFile = new File(pwd + "/config/PrivilegeConfigMerge.xml"); + + // initialize privilege + privilegeHandler = PrivilegeInitializationHelper.initializeFromXml(privilegeConfigFile); + + } catch (Exception e) { + logger.error(e.getMessage(), e); + + throw new RuntimeException("Setup failed: " + e.getLocalizedMessage(), e); + } + } + + private 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; + } + + private 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; + } + } + } + + @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/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java index 310c88ad4..f23293162 100644 --- a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -24,6 +24,7 @@ import java.io.File; 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; @@ -75,6 +76,7 @@ public class PrivilegeTest { 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_TEMP = "temp"; private static final String ROLE_USER = "user"; private static final byte[] PASS_DEF = "def".getBytes(); @@ -89,10 +91,6 @@ public class PrivilegeTest { private static PrivilegeHandler privilegeHandler; private PrivilegeContext ctx; - /** - * @throws Exception - * if something goes wrong - */ @BeforeClass public static void init() throws Exception { try { @@ -425,6 +423,35 @@ public class PrivilegeTest { } } + @Test + public void shouldDetectPrivilegeConflict1() { + exception.expect(PrivilegeException.class); + exception.expectMessage("User 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 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: *
        diff --git a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java index 6526e913a..ba39eb0e2 100644 --- a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java @@ -120,7 +120,7 @@ public class XmlTest { assertNotNull(containerModel.getPersistenceHandlerClassName()); assertNotNull(containerModel.getPersistenceHandlerParameterMap()); - assertEquals(1, containerModel.getParameterMap().size()); + assertEquals(2, containerModel.getParameterMap().size()); assertEquals(3, containerModel.getPolicies().size()); assertEquals(1, containerModel.getEncryptionHandlerParameterMap().size()); assertEquals(2, containerModel.getPersistenceHandlerParameterMap().size()); @@ -170,7 +170,7 @@ public class XmlTest { assertNotNull(roles); assertEquals(3, users.size()); - assertEquals(6, roles.size()); + assertEquals(7, roles.size()); // assert model From c2f4d7468b124f2579ac21d080d47c179f007afd Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sat, 14 Mar 2015 21:22:20 +0100 Subject: [PATCH 153/173] [Major] changed Certificate to use Date and not long --- .../privilege/handler/DefaultPrivilegeHandler.java | 10 +++++----- .../ch/eitchnet/privilege/model/Certificate.java | 13 +++++++------ 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 26eb14d20..4a76bf936 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -19,6 +19,7 @@ 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; @@ -1002,8 +1003,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { String sessionId = nextSessionId(); // create a new certificate, with details of the user - certificate = new Certificate(sessionId, System.currentTimeMillis(), username, user.getFirstname(), - user.getLastname(), authToken, user.getLocale(), userRoles, new HashMap<>(user.getProperties())); + certificate = new Certificate(sessionId, new Date(), username, user.getFirstname(), user.getLastname(), + authToken, user.getLocale(), userRoles, new HashMap<>(user.getProperties())); PrivilegeContext privilegeContext = buildPrivilegeContext(certificate, user); this.privilegeContextMap.put(sessionId, privilegeContext); @@ -1554,9 +1555,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { String sessionId = nextSessionId(); // create a new certificate, with details of the user - Certificate systemUserCertificate = new Certificate(sessionId, System.currentTimeMillis(), systemUsername, - user.getFirstname(), user.getLastname(), authToken, user.getLocale(), user.getRoles(), new HashMap<>( - user.getProperties())); + Certificate systemUserCertificate = new Certificate(sessionId, new Date(), systemUsername, user.getFirstname(), + user.getLastname(), authToken, user.getLocale(), user.getRoles(), new HashMap<>(user.getProperties())); // create and save a new privilege context PrivilegeContext privilegeContext = buildPrivilegeContext(systemUserCertificate, user); diff --git a/src/main/java/ch/eitchnet/privilege/model/Certificate.java b/src/main/java/ch/eitchnet/privilege/model/Certificate.java index cf9538c3c..2255300ca 100644 --- a/src/main/java/ch/eitchnet/privilege/model/Certificate.java +++ b/src/main/java/ch/eitchnet/privilege/model/Certificate.java @@ -17,6 +17,7 @@ package ch.eitchnet.privilege.model; import java.io.Serializable; import java.util.Collections; +import java.util.Date; import java.util.HashMap; import java.util.Locale; import java.util.Map; @@ -39,7 +40,7 @@ public final class Certificate implements Serializable { private static final long serialVersionUID = 1L; private final String sessionId; - private final long loginTime; + private final Date loginTime; private final String username; private final String firstname; private final String lastname; @@ -50,7 +51,7 @@ public final class Certificate implements Serializable { private final Map sessionDataMap; private Locale locale; - private long lastAccess; + private Date lastAccess; /** * Default constructor initializing with all information needed for this certificate @@ -78,7 +79,7 @@ public final class Certificate implements Serializable { * 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, long loginTime, String username, String firstname, String lastname, + public Certificate(String sessionId, Date loginTime, String username, String firstname, String lastname, String authToken, Locale locale, Set userRoles, Map propertyMap) { // validate arguments are not null @@ -211,7 +212,7 @@ public final class Certificate implements Serializable { /** * @return the loginTime */ - public long getLoginTime() { + public Date getLoginTime() { return this.loginTime; } @@ -227,7 +228,7 @@ public final class Certificate implements Serializable { /** * @return the lastAccess */ - public long getLastAccess() { + public Date getLastAccess() { return this.lastAccess; } @@ -235,7 +236,7 @@ public final class Certificate implements Serializable { * @param lastAccess * the lastAccess to set */ - public void setLastAccess(long lastAccess) { + public void setLastAccess(Date lastAccess) { this.lastAccess = lastAccess; } From 6ccb4425cc683e96ec152c59da2621c5b99773eb Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 15 Mar 2015 11:03:23 +0100 Subject: [PATCH 154/173] [New] changed session ID to be a UUID --- .../handler/DefaultPrivilegeHandler.java | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 4a76bf936..3e6179ef0 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -27,6 +27,7 @@ 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; @@ -78,11 +79,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { */ protected static final Logger logger = LoggerFactory.getLogger(DefaultPrivilegeHandler.class); - /** - * last assigned id for the {@link Certificate}s - */ - private long lastSessionId; - /** * Map keeping a reference to all active sessions */ @@ -1000,7 +996,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { String authToken = this.encryptionHandler.convertToHash(this.encryptionHandler.nextToken()); // get next session id - String sessionId = nextSessionId(); + String sessionId = UUID.randomUUID().toString(); // create a new certificate, with details of the user certificate = new Certificate(sessionId, new Date(), username, user.getFirstname(), user.getLastname(), @@ -1329,7 +1325,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // validate privilege conflicts validatePrivilegeConflicts(); - this.lastSessionId = 0l; this.privilegeContextMap = Collections.synchronizedMap(new HashMap()); this.initialized = true; } @@ -1426,13 +1421,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } } - /** - * @return a new session id - */ - private synchronized String nextSessionId() { - return Long.toString(++this.lastSessionId % Long.MAX_VALUE); - } - /** * 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 @@ -1552,7 +1540,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { String authToken = this.encryptionHandler.nextToken(); // get next session id - String sessionId = nextSessionId(); + String sessionId = UUID.randomUUID().toString(); // create a new certificate, with details of the user Certificate systemUserCertificate = new Certificate(sessionId, new Date(), systemUsername, user.getFirstname(), From e076ced839e0ed1490a445b91c028272682b743e Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 15 Mar 2015 11:03:37 +0100 Subject: [PATCH 155/173] [New] Added UsernameFromCertificatePrivilege policy --- .../UsernameFromCertificatePrivilege.java | 57 +++++++++++++++++++ .../resources/PrivilegeMessages.properties | 5 +- 2 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 src/main/java/ch/eitchnet/privilege/policy/UsernameFromCertificatePrivilege.java diff --git a/src/main/java/ch/eitchnet/privilege/policy/UsernameFromCertificatePrivilege.java b/src/main/java/ch/eitchnet/privilege/policy/UsernameFromCertificatePrivilege.java new file mode 100644 index 000000000..258f731e1 --- /dev/null +++ b/src/main/java/ch/eitchnet/privilege/policy/UsernameFromCertificatePrivilege.java @@ -0,0 +1,57 @@ +/* + * 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; + +/** + * TODO + * + * @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/src/main/resources/PrivilegeMessages.properties b/src/main/resources/PrivilegeMessages.properties index 89127fb00..5c291a359 100644 --- a/src/main/resources/PrivilegeMessages.properties +++ b/src/main/resources/PrivilegeMessages.properties @@ -1,12 +1,13 @@ -Privilege.accessdenied.noprivilege=User {0} does not have Privilege {1} needed for Restrictable {2} +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.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 a Privilege {0} +Privilege.noprivilege.user=User {0} does not have the privilege {1} Privilege.roleAccessPrivilege.unknownPrivilege=Unhandled RoleAccessPrivilege {0} Privilege.userAccessPrivilege.unknownPrivilege=Unhandled UserAccessPrivilege {0} From 2076675ca5f244181912c0ba67a609366bacb849 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Tue, 17 Mar 2015 21:47:22 +0100 Subject: [PATCH 156/173] [Minor] fixed broken tests --- src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java index f23293162..3b7a35411 100644 --- a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -275,7 +275,7 @@ public class PrivilegeTest { public void testPerformSystemRestrictableFailNoAdditionalPrivilege() throws Exception { this.exception.expect(PrivilegeException.class); this.exception - .expectMessage("User system_admin2 does not have Privilege ch.eitchnet.privilege.test.model.TestRestrictable"); + .expectMessage("User system_admin2 does not have the privilege ch.eitchnet.privilege.test.model.TestRestrictable"); try { // create the action to be performed as a system user TestSystemUserActionDeny action = new TestSystemUserActionDeny(); @@ -531,7 +531,7 @@ public class PrivilegeTest { this.ctx.validateAction(restrictable); fail("Should fail as bob does not have role app"); } catch (AccessDeniedException e) { - String msg = "User bob does not have Privilege ch.eitchnet.privilege.test.model.TestRestrictable needed for Restrictable ch.eitchnet.privilege.test.model.TestRestrictable"; + 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(); From 3f7636428d460b37f1a5cb02144b41c0376be433 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Wed, 18 Mar 2015 20:11:45 +0100 Subject: [PATCH 157/173] [Minor] added JavaDoc to new policies --- .../eitchnet/privilege/policy/RoleAccessPrivilege.java | 5 ++++- .../eitchnet/privilege/policy/UserAccessPrivilege.java | 4 +++- .../policy/UsernameFromCertificatePrivilege.java | 10 +++++++++- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/main/java/ch/eitchnet/privilege/policy/RoleAccessPrivilege.java b/src/main/java/ch/eitchnet/privilege/policy/RoleAccessPrivilege.java index a2bf2fa1d..d5b94f859 100644 --- a/src/main/java/ch/eitchnet/privilege/policy/RoleAccessPrivilege.java +++ b/src/main/java/ch/eitchnet/privilege/policy/RoleAccessPrivilege.java @@ -28,7 +28,10 @@ import ch.eitchnet.utils.collections.Tuple; import ch.eitchnet.utils.dbc.DBC; /** - * TODO + * 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 */ diff --git a/src/main/java/ch/eitchnet/privilege/policy/UserAccessPrivilege.java b/src/main/java/ch/eitchnet/privilege/policy/UserAccessPrivilege.java index ad35d4115..1b92f19cf 100644 --- a/src/main/java/ch/eitchnet/privilege/policy/UserAccessPrivilege.java +++ b/src/main/java/ch/eitchnet/privilege/policy/UserAccessPrivilege.java @@ -28,7 +28,9 @@ import ch.eitchnet.utils.collections.Tuple; import ch.eitchnet.utils.dbc.DBC; /** - * TODO + * 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 */ diff --git a/src/main/java/ch/eitchnet/privilege/policy/UsernameFromCertificatePrivilege.java b/src/main/java/ch/eitchnet/privilege/policy/UsernameFromCertificatePrivilege.java index 258f731e1..425c4183d 100644 --- a/src/main/java/ch/eitchnet/privilege/policy/UsernameFromCertificatePrivilege.java +++ b/src/main/java/ch/eitchnet/privilege/policy/UsernameFromCertificatePrivilege.java @@ -25,7 +25,15 @@ import ch.eitchnet.privilege.model.PrivilegeContext; import ch.eitchnet.privilege.model.Restrictable; /** - * TODO + *

        + * 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 */ From 2607bbef3fc7df863548820665d8ed133e524c39 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Wed, 18 Mar 2015 20:40:10 +0100 Subject: [PATCH 158/173] [Bugfix] fixed bug where stream was not configured correctly --- .../eitchnet/privilege/handler/DefaultPrivilegeHandler.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 3e6179ef0..a91a018cd 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -167,7 +167,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // validate access to each role // TODO throwing and catching exception ain't cool - rolesStream.filter(role -> { + rolesStream = rolesStream.filter(role -> { try { prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_GET_ROLE, new Tuple(null, role))); return true; @@ -191,7 +191,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // validate access to each user // TODO throwing and catching exception ain't cool - usersStream.filter(user -> { + usersStream = usersStream.filter(user -> { try { prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_GET_USER, new Tuple(null, user))); return true; From 4c6434f475dc40e73b54890540eaf943f21e1084 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 5 Apr 2015 00:13:16 +0200 Subject: [PATCH 159/173] [Major] Setting user password, locale and state are now separate privs --- config/PrivilegeModel.xml | 11 ++ .../handler/DefaultPrivilegeHandler.java | 41 ++---- .../privilege/handler/PrivilegeHandler.java | 14 +++ .../privilege/policy/RoleAccessPrivilege.java | 4 + .../privilege/policy/UserAccessPrivilege.java | 55 +++++++- .../resources/PrivilegeMessages.properties | 5 +- .../privilege/test/PrivilegeTest.java | 118 +++++++++++++++++- .../ch/eitchnet/privilege/test/XmlTest.java | 2 +- 8 files changed, 211 insertions(+), 39 deletions(-) diff --git a/config/PrivilegeModel.xml b/config/PrivilegeModel.xml index 070e087d7..68ef7222f 100644 --- a/config/PrivilegeModel.xml +++ b/config/PrivilegeModel.xml @@ -81,6 +81,17 @@ true + + true + + + ENABLED + DISABLED + SYSTEM + + + true + diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index a91a018cd..1737181c4 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -651,18 +651,9 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { @Override public UserRep setUserLocale(Certificate certificate, String username, Locale locale) { - // check if certificate is for same user, in which case user is changing their own locale - if (certificate.getUsername().equals(username)) { - - // validate the certificate - isCertificateValid(certificate); - - } else { - - // validate user actually has this type of privilege - PrivilegeContext prvCtx = getPrivilegeContext(certificate); - prvCtx.assertHasPrivilege(PRIVILEGE_MODIFY_USER); - } + // 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); @@ -677,8 +668,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // 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)) { - PrivilegeContext prvCtx = getPrivilegeContext(certificate); - prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_MODIFY_USER, new Tuple(existingUser, newUser))); + prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_SET_USER_LOCALE, new Tuple(existingUser, newUser))); } // delegate user replacement to persistence handler @@ -696,18 +686,9 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { public void setUserPassword(Certificate certificate, String username, byte[] password) { try { - // check if certificate is for same user, in which case user is changing their own password - if (certificate.getUsername().equals(username)) { - - // validate the certificate - isCertificateValid(certificate); - - } else { - - // validate user actually has this type of privilege - PrivilegeContext prvCtx = getPrivilegeContext(certificate); - prvCtx.assertHasPrivilege(PRIVILEGE_MODIFY_USER); - } + // 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); @@ -732,8 +713,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // 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)) { - PrivilegeContext prvCtx = getPrivilegeContext(certificate); - prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_MODIFY_USER, new Tuple(existingUser, newUser))); + prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_SET_USER_PASSWORD, new Tuple(existingUser, + newUser))); } // delegate user replacement to persistence handler @@ -754,7 +735,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // validate user actually has this type of privilege PrivilegeContext prvCtx = getPrivilegeContext(certificate); - prvCtx.assertHasPrivilege(PRIVILEGE_MODIFY_USER); + prvCtx.assertHasPrivilege(PRIVILEGE_SET_USER_STATE); // get User User existingUser = this.persistenceHandler.getUser(username); @@ -768,7 +749,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { existingUser.getLocale(), existingUser.getProperties()); // validate that this user may modify this user's state - prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_MODIFY_USER, new Tuple(existingUser, newUser))); + prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_SET_USER_STATE, new Tuple(existingUser, newUser))); // delegate user replacement to persistence handler this.persistenceHandler.replaceUser(newUser); diff --git a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java index 5b6d81ad1..4e6b848d9 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java @@ -114,6 +114,20 @@ public interface PrivilegeHandler { * 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"; /// diff --git a/src/main/java/ch/eitchnet/privilege/policy/RoleAccessPrivilege.java b/src/main/java/ch/eitchnet/privilege/policy/RoleAccessPrivilege.java index d5b94f859..d82f3576d 100644 --- a/src/main/java/ch/eitchnet/privilege/policy/RoleAccessPrivilege.java +++ b/src/main/java/ch/eitchnet/privilege/policy/RoleAccessPrivilege.java @@ -44,6 +44,10 @@ public class RoleAccessPrivilege implements PrivilegePolicy { // 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() diff --git a/src/main/java/ch/eitchnet/privilege/policy/UserAccessPrivilege.java b/src/main/java/ch/eitchnet/privilege/policy/UserAccessPrivilege.java index 1b92f19cf..cafbd37c5 100644 --- a/src/main/java/ch/eitchnet/privilege/policy/UserAccessPrivilege.java +++ b/src/main/java/ch/eitchnet/privilege/policy/UserAccessPrivilege.java @@ -43,6 +43,10 @@ public class UserAccessPrivilege implements PrivilegePolicy { // 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() @@ -130,11 +134,56 @@ public class UserAccessPrivilege implements PrivilegePolicy { 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 = Restrictable.class.getName() - + PrivilegeMessages.getString("Privilege.userAccessPrivilege.unknownPrivilege"); //$NON-NLS-1$ - msg = MessageFormat.format(msg, privilegeName); + 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/src/main/resources/PrivilegeMessages.properties b/src/main/resources/PrivilegeMessages.properties index 5c291a359..a92f0f135 100644 --- a/src/main/resources/PrivilegeMessages.properties +++ b/src/main/resources/PrivilegeMessages.properties @@ -4,10 +4,11 @@ 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 RoleAccessPrivilege {0} -Privilege.userAccessPrivilege.unknownPrivilege=Unhandled UserAccessPrivilege {0} +Privilege.roleAccessPrivilege.unknownPrivilege=Unhandled privilege {0} for policy {1} +Privilege.userAccessPrivilege.unknownPrivilege=Unhandled privilege {0} for policy {1} diff --git a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java index 3b7a35411..37d159165 100644 --- a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -28,6 +28,7 @@ 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; @@ -67,6 +68,7 @@ import ch.eitchnet.utils.helper.FileHelper; public class PrivilegeTest { 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"; @@ -77,6 +79,7 @@ public class PrivilegeTest { 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(); @@ -487,13 +490,121 @@ public class PrivilegeTest { addTedAsBob(); failAuthAsTedNoPass(); setPassForTedAsBob(); - tedChangesOwnPass(); + 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 @@ -547,12 +658,13 @@ public class PrivilegeTest { } } - private void tedChangesOwnPass() { + 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(); } @@ -666,7 +778,7 @@ public class PrivilegeTest { try { // add role user login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); - RoleRep roleRep = new RoleRep(ROLE_USER, new ArrayList()); + RoleRep roleRep = new RoleRep(ROLE_USER, new ArrayList<>()); Certificate certificate = this.ctx.getCertificate(); privilegeHandler.addRole(certificate, roleRep); privilegeHandler.persist(certificate); diff --git a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java index ba39eb0e2..637c43901 100644 --- a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java @@ -212,7 +212,7 @@ public class XmlTest { // PrivilegeAdmin Role privilegeAdmin = findRole("PrivilegeAdmin", roles); assertEquals("PrivilegeAdmin", privilegeAdmin.getName()); - assertEquals(11, privilegeAdmin.getPrivilegeNames().size()); + assertEquals(14, privilegeAdmin.getPrivilegeNames().size()); IPrivilege privilegeAction = privilegeAdmin.getPrivilege(PrivilegeHandler.PRIVILEGE_ACTION); assertFalse(privilegeAction.isAllAllowed()); assertEquals(3, privilegeAction.getAllowList().size()); From 0c7315b713edb81442208c2b347c6432a3b6bc70 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Wed, 3 Jun 2015 23:25:30 +0200 Subject: [PATCH 160/173] [Major] SystemUserAction is now a normal privilege which is added as follows: ch.eitchnet.privilege.test.model.TestSystemUserAction ch.eitchnet.privilege.test.model.TestSystemUserActionDeny --- config/PrivilegeModel.xml | 14 ++----- .../handler/DefaultPrivilegeHandler.java | 42 ++++--------------- .../privilege/handler/SystemUserAction.java | 15 ++++++- .../privilege/test/PrivilegeTest.java | 8 ++-- .../ch/eitchnet/privilege/test/XmlTest.java | 20 ++++----- .../test/model/TestSystemUserAction.java | 2 +- .../test/model/TestSystemUserActionDeny.java | 2 +- 7 files changed, 40 insertions(+), 63 deletions(-) diff --git a/config/PrivilegeModel.xml b/config/PrivilegeModel.xml index 68ef7222f..83131e8bc 100644 --- a/config/PrivilegeModel.xml +++ b/config/PrivilegeModel.xml @@ -35,7 +35,6 @@ en_GB system_admin_privileges - system_admin_privileges2 @@ -113,22 +112,17 @@ - - true + + ch.eitchnet.privilege.test.model.TestSystemUserAction + ch.eitchnet.privilege.test.model.TestSystemUserActionDeny true - - - true - - - - + hello goodbye diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 1737181c4..6560c16e1 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -1374,8 +1374,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { privilegeNames.put(privilegeName, roleName); } else { String roleOrigin = privilegeNames.get(privilegeName); - String msg = "User has conflicts for privilege {0} on roles {1} and {2}"; - msg = MessageFormat.format(msg, privilegeName, roleOrigin, roleName); + 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); } } @@ -1434,12 +1434,12 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { if (systemUser.getUserState() != UserState.SYSTEM) throw new PrivilegeException(MessageFormat.format("User {0} is not a System user!", systemUsername)); //$NON-NLS-1$ - // validate this system user may perform the given action - String actionClassname = action.getClass().getName(); - checkPrivilege(actionClassname, systemUser); - - // get certificate for this system user + // 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 { @@ -1450,34 +1450,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } } - /** - * Checks if the given user has the given privilege - * - * @param privilegeName - * the name of the privilege to check on the user - * @param user - * the user to check for the given privilege - * - * @throws PrivilegeException - * if the user does not have the privilege - */ - private void checkPrivilege(String privilegeName, User user) throws PrivilegeException { - - // check each role if it has the privilege - for (String roleName : user.getRoles()) { - - Role role = this.persistenceHandler.getRole(roleName); - - // on the first occurrence of our privilege, stop - if (role.hasPrivilege(privilegeName)) - return; - } - - // default throw exception, as the user does not have the privilege - String msg = MessageFormat.format("User {0} does not have Privilege {1}", user.getUsername(), privilegeName); //$NON-NLS-1$ - throw new PrivilegeException(msg); - } - /** * Returns the {@link Certificate} for the given system username. If it does not yet exist, then it is created by * authenticating the system user diff --git a/src/main/java/ch/eitchnet/privilege/handler/SystemUserAction.java b/src/main/java/ch/eitchnet/privilege/handler/SystemUserAction.java index caf484f8f..082bc2ca3 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/SystemUserAction.java +++ b/src/main/java/ch/eitchnet/privilege/handler/SystemUserAction.java @@ -17,6 +17,7 @@ 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 @@ -25,7 +26,17 @@ import ch.eitchnet.privilege.model.PrivilegeContext; * * @author Robert von Burg */ -public interface SystemUserAction { +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 @@ -37,5 +48,5 @@ public interface SystemUserAction { * @param privilegeContext * the {@link PrivilegeContext} which was generated for a valid system user */ - public void execute(PrivilegeContext privilegeContext); + public abstract void execute(PrivilegeContext privilegeContext); } diff --git a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java index 37d159165..3d29e60d7 100644 --- a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -259,7 +259,7 @@ public class PrivilegeTest { public void testPerformSystemRestrictableFailPrivilege() throws Exception { this.exception.expect(PrivilegeException.class); this.exception - .expectMessage("User system_admin does not have Privilege ch.eitchnet.privilege.test.model.TestSystemUserActionDeny"); + .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(); @@ -278,7 +278,7 @@ public class PrivilegeTest { public void testPerformSystemRestrictableFailNoAdditionalPrivilege() throws Exception { this.exception.expect(PrivilegeException.class); this.exception - .expectMessage("User system_admin2 does not have the privilege ch.eitchnet.privilege.test.model.TestRestrictable"); + .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(); @@ -429,7 +429,7 @@ public class PrivilegeTest { @Test public void shouldDetectPrivilegeConflict1() { exception.expect(PrivilegeException.class); - exception.expectMessage("User has conflicts for privilege "); + exception.expectMessage("User admin has conflicts for privilege "); try { login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); Certificate certificate = this.ctx.getCertificate(); @@ -444,7 +444,7 @@ public class PrivilegeTest { @Test public void shouldDetectPrivilegeConflict2() { exception.expect(PrivilegeException.class); - exception.expectMessage("User has conflicts for privilege "); + exception.expectMessage("User admin has conflicts for privilege "); try { login(ADMIN, ArraysHelper.copyOf(PASS_ADMIN)); Certificate certificate = this.ctx.getCertificate(); diff --git a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java index 637c43901..4789374ca 100644 --- a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java @@ -170,7 +170,7 @@ public class XmlTest { assertNotNull(roles); assertEquals(3, users.size()); - assertEquals(7, roles.size()); + assertEquals(6, roles.size()); // assert model @@ -249,16 +249,16 @@ public class XmlTest { assertEquals(2, systemAdminPrivileges.getPrivilegeNames().size()); assertThat( systemAdminPrivileges.getPrivilegeNames(), - containsInAnyOrder("ch.eitchnet.privilege.test.model.TestSystemUserAction", + containsInAnyOrder("ch.eitchnet.privilege.handler.SystemUserAction", "ch.eitchnet.privilege.test.model.TestSystemRestrictable")); IPrivilege testSystemUserAction = systemAdminPrivileges - .getPrivilege("ch.eitchnet.privilege.test.model.TestSystemUserAction"); - assertEquals("ch.eitchnet.privilege.test.model.TestSystemUserAction", testSystemUserAction.getName()); + .getPrivilege("ch.eitchnet.privilege.handler.SystemUserAction"); + assertEquals("ch.eitchnet.privilege.handler.SystemUserAction", testSystemUserAction.getName()); assertEquals("DefaultPrivilege", testSystemUserAction.getPolicy()); - assertTrue(testSystemUserAction.isAllAllowed()); - assertEquals(0, testSystemUserAction.getAllowList().size()); - assertEquals(0, testSystemUserAction.getDenyList().size()); + assertFalse(testSystemUserAction.isAllAllowed()); + assertEquals(1, testSystemUserAction.getAllowList().size()); + assertEquals(1, testSystemUserAction.getDenyList().size()); IPrivilege testSystemRestrictable = systemAdminPrivileges .getPrivilege("ch.eitchnet.privilege.test.model.TestSystemRestrictable"); @@ -273,11 +273,11 @@ public class XmlTest { assertEquals("restrictedRole", restrictedRole.getName()); assertEquals(1, restrictedRole.getPrivilegeNames().size()); assertThat(restrictedRole.getPrivilegeNames(), - containsInAnyOrder("ch.eitchnet.privilege.test.model.TestSystemUserAction")); + containsInAnyOrder("ch.eitchnet.privilege.handler.SystemUserAction")); IPrivilege testSystemUserAction2 = restrictedRole - .getPrivilege("ch.eitchnet.privilege.test.model.TestSystemUserAction"); - assertEquals("ch.eitchnet.privilege.test.model.TestSystemUserAction", testSystemUserAction2.getName()); + .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()); diff --git a/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java b/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java index 5b64a5421..b3942b7cc 100644 --- a/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java +++ b/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java @@ -22,7 +22,7 @@ import ch.eitchnet.privilege.model.PrivilegeContext; * @author Robert von Burg * */ -public class TestSystemUserAction implements SystemUserAction { +public class TestSystemUserAction extends SystemUserAction { @Override public void execute(PrivilegeContext context) { diff --git a/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserActionDeny.java b/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserActionDeny.java index 5cd8092f9..51c1574cc 100644 --- a/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserActionDeny.java +++ b/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserActionDeny.java @@ -22,7 +22,7 @@ import ch.eitchnet.privilege.model.PrivilegeContext; * @author Robert von Burg * */ -public class TestSystemUserActionDeny implements SystemUserAction { +public class TestSystemUserActionDeny extends SystemUserAction { @Override public void execute(PrivilegeContext privilegeContext) { From d9dc5261e7ed349d43a967ef316698c4d2ed3ff9 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 9 Jul 2015 19:29:45 +0200 Subject: [PATCH 161/173] [Minor] fixed broken test - the test has changed because default encoding is now UTF-8 instead of utf-8 --- src/test/java/ch/eitchnet/privilege/test/XmlTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java index 4789374ca..d72c9264a 100644 --- a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java @@ -154,7 +154,7 @@ public class XmlTest { configSaxWriter.write(); String fileHash = StringHelper.getHexString(FileHelper.hashFileSha256(configFile)); - assertEquals("2abd3442eec8bcec5bee365aab6db2fd4e1789325425cb1e017e900582525685", fileHash); + assertEquals("22d4ba39605d49c758184d9bd63beae5ccf8786f3dabbab45cd9f59c2afbcbd0", fileHash); } @Test From 21d640e081b3a32f7d668d39862014d4b629c1b9 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 9 Jul 2015 19:31:46 +0200 Subject: [PATCH 162/173] [Minor] fixed broken test - the test has changed because default encoding is now UTF-8 instead of utf-8 --- src/test/java/ch/eitchnet/privilege/test/XmlTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java index d72c9264a..91bd4a007 100644 --- a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java @@ -355,6 +355,6 @@ public class XmlTest { configSaxWriter.write(); String fileHash = StringHelper.getHexString(FileHelper.hashFileSha256(modelFile)); - assertEquals("c9732a05bf0ed53d89b3d12e7c8d7216150b6a91412d1bf47fbe3e6f3be750ff", fileHash); + assertEquals("fd5f4554312b18ca9fd61f0a6a4c87603e7c191ee206afca6328e5bffb87f86c", fileHash); } } From 65992ce0ebaf90ea302f5c1ee63b60903664135c Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Tue, 11 Aug 2015 13:44:56 +0200 Subject: [PATCH 163/173] [Minor] changed logger for logged in system user --- .../eitchnet/privilege/handler/DefaultPrivilegeHandler.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 6560c16e1..db9d7ce96 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -1503,8 +1503,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { 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); + 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; From bf15669ef25701e5f75174bf96eb99bed32a88fc Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Tue, 1 Sep 2015 19:20:22 +0200 Subject: [PATCH 164/173] [New] Added new Privileges for enforcing same organisation access --- ...erAccessWithSameOrganisationPrivilege.java | 115 ++++++++++++++++++ ...tificateWithSameOrganisationPrivilege.java | 79 ++++++++++++ 2 files changed, 194 insertions(+) create mode 100644 src/main/java/ch/eitchnet/privilege/policy/UserAccessWithSameOrganisationPrivilege.java create mode 100644 src/main/java/ch/eitchnet/privilege/policy/UsernameFromCertificateWithSameOrganisationPrivilege.java diff --git a/src/main/java/ch/eitchnet/privilege/policy/UserAccessWithSameOrganisationPrivilege.java b/src/main/java/ch/eitchnet/privilege/policy/UserAccessWithSameOrganisationPrivilege.java new file mode 100644 index 000000000..599c4a7c3 --- /dev/null +++ b/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/src/main/java/ch/eitchnet/privilege/policy/UsernameFromCertificateWithSameOrganisationPrivilege.java b/src/main/java/ch/eitchnet/privilege/policy/UsernameFromCertificateWithSameOrganisationPrivilege.java new file mode 100644 index 000000000..124addf28 --- /dev/null +++ b/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); + } +} From 471cc1f37fa954883fac5c61a46050083b671bed Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 8 Oct 2015 12:26:31 +0200 Subject: [PATCH 165/173] [New] added getter for EncryptionHandler and return SystemUserAction --- .../handler/DefaultPrivilegeHandler.java | 34 ++++++++------- .../privilege/handler/PrivilegeHandler.java | 42 ++++++++++++------- 2 files changed, 47 insertions(+), 29 deletions(-) diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index db9d7ce96..dcd77042e 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -72,8 +72,6 @@ import ch.eitchnet.utils.helper.StringHelper; */ public class DefaultPrivilegeHandler implements PrivilegeHandler { - /// - /** * slf4j logger */ @@ -111,6 +109,11 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { private PrivilegeConflictResolution privilegeConflictResolution; + @Override + public EncryptionHandler getEncryptionHandler() throws PrivilegeException { + return this.encryptionHandler; + } + @Override public RoleRep getRole(Certificate certificate, String roleName) { @@ -487,8 +490,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } @Override - public UserRep updateUser(Certificate certificate, UserRep userRep) throws AccessDeniedException, - PrivilegeException { + public UserRep updateUser(Certificate certificate, UserRep userRep) + throws AccessDeniedException, PrivilegeException { // validate user actually has this type of privilege PrivilegeContext prvCtx = getPrivilegeContext(certificate); @@ -504,8 +507,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { 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}", userRep.getUsername())); //$NON-NLS-1$ + throw new PrivilegeException(MessageFormat.format("All updateable fields are empty for update of user {0}", //$NON-NLS-1$ + userRep.getUsername())); } String userId = existingUser.getUserId(); @@ -626,7 +629,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } // validate that this user may remove this role from this user - prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_REMOVE_ROLE_FROM_USER, new Tuple(existingUser, roleName))); + prvCtx.validateAction( + new SimpleRestrictable(PRIVILEGE_REMOVE_ROLE_FROM_USER, new Tuple(existingUser, roleName))); // ignore if user does not have role Set currentRoles = existingUser.getRoles(); @@ -713,8 +717,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // 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))); + prvCtx.validateAction( + new SimpleRestrictable(PRIVILEGE_SET_USER_PASSWORD, new Tuple(existingUser, newUser))); } // delegate user replacement to persistence handler @@ -987,8 +991,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { this.privilegeContextMap.put(sessionId, privilegeContext); // log - DefaultPrivilegeHandler.logger.info(MessageFormat.format( - "User {0} authenticated: {1}", username, certificate)); //$NON-NLS-1$ + DefaultPrivilegeHandler.logger + .info(MessageFormat.format("User {0} authenticated: {1}", username, certificate)); //$NON-NLS-1$ } catch (RuntimeException e) { String msg = "User {0} Failed to authenticate: {1}"; //$NON-NLS-1$ @@ -1042,8 +1046,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // 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$ + throw new AccessDeniedException( + MessageFormat.format("User {0} has no password and may not login!", username)); //$NON-NLS-1$ if (!pwHash.equals(passwordHash)) throw new AccessDeniedException(MessageFormat.format("Password is incorrect for {0}", username)); //$NON-NLS-1$ @@ -1418,7 +1422,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } @Override - public void runAsSystem(String systemUsername, SystemUserAction action) throws PrivilegeException { + public T runAsSystem(String systemUsername, T action) throws PrivilegeException { if (systemUsername == null) throw new PrivilegeException("systemUsername may not be null!"); //$NON-NLS-1$ @@ -1448,6 +1452,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } finally { this.privilegeContextMap.remove(sessionId); } + + return action; } /** diff --git a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java index 4e6b848d9..b6e0f8856 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java @@ -223,8 +223,8 @@ public interface PrivilegeHandler { * @throws PrivilegeException * if there is anything wrong with this certificate */ - public UserRep removeUser(Certificate certificate, String username) throws AccessDeniedException, - PrivilegeException; + public UserRep removeUser(Certificate certificate, String username) + throws AccessDeniedException, PrivilegeException; /** * Removes the role with the given roleName from the user with the given username @@ -259,8 +259,8 @@ public interface PrivilegeHandler { * @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; + public RoleRep removeRole(Certificate certificate, String roleName) + throws AccessDeniedException, PrivilegeException; /** * Removes the privilege with the given privilegeName from the role with the given roleName @@ -304,8 +304,8 @@ public interface PrivilegeHandler { * @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; + public UserRep addUser(Certificate certificate, UserRep userRep, byte[] password) + throws AccessDeniedException, PrivilegeException; /** *

        @@ -336,8 +336,8 @@ public interface PrivilegeHandler { * @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; + public UserRep updateUser(Certificate certificate, UserRep userRep) + throws AccessDeniedException, PrivilegeException; /** *

        @@ -363,8 +363,8 @@ public interface PrivilegeHandler { * @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; + public UserRep replaceUser(Certificate certificate, UserRep userRep, byte[] password) + throws AccessDeniedException, PrivilegeException; /** * Adds a new role with the information from this {@link RoleRep} @@ -394,8 +394,8 @@ public interface PrivilegeHandler { * @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; + 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 @@ -494,8 +494,8 @@ public interface PrivilegeHandler { * @throws PrivilegeException * if there is anything wrong with this certificate */ - public UserRep setUserLocale(Certificate certificate, String username, Locale locale) throws AccessDeniedException, - PrivilegeException; + 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 @@ -612,10 +612,22 @@ public interface PrivilegeHandler { * 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 void runAsSystem(String systemUsername, SystemUserAction 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; } From 6a62864331d93d180d4382706e9b30b8ed6cab6a Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 8 Oct 2015 22:08:39 +0200 Subject: [PATCH 166/173] [Minor] better writing of privilege XML on persist --- .../xml/PrivilegeModelDomWriter.java | 54 +++++++++++-------- 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelDomWriter.java b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelDomWriter.java index 480904019..a79efe2d8 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelDomWriter.java +++ b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelDomWriter.java @@ -26,6 +26,7 @@ import ch.eitchnet.privilege.helper.XmlConstants; import ch.eitchnet.privilege.model.IPrivilege; import ch.eitchnet.privilege.model.internal.Role; import ch.eitchnet.privilege.model.internal.User; +import ch.eitchnet.utils.helper.StringHelper; import ch.eitchnet.utils.helper.XmlHelper; /** @@ -57,7 +58,7 @@ public class PrivilegeModelDomWriter { Element usersElement = doc.createElement(XmlConstants.XML_USERS); rootElement.appendChild(usersElement); - for (User user : this.users) { + this.users.stream().sorted((u1, u2) -> u1.getUserId().compareTo(u2.getUserId())).forEach(user -> { // create the user element Element userElement = doc.createElement(XmlConstants.XML_USER); @@ -65,17 +66,22 @@ public class PrivilegeModelDomWriter { userElement.setAttribute(XmlConstants.XML_ATTR_USER_ID, user.getUserId()); userElement.setAttribute(XmlConstants.XML_ATTR_USERNAME, user.getUsername()); - userElement.setAttribute(XmlConstants.XML_ATTR_PASSWORD, user.getPassword()); + if (StringHelper.isNotEmpty(user.getPassword())) + userElement.setAttribute(XmlConstants.XML_ATTR_PASSWORD, user.getPassword()); // add first name element - Element firstnameElement = doc.createElement(XmlConstants.XML_FIRSTNAME); - firstnameElement.setTextContent(user.getFirstname()); - userElement.appendChild(firstnameElement); + if (StringHelper.isNotEmpty(user.getFirstname())) { + Element firstnameElement = doc.createElement(XmlConstants.XML_FIRSTNAME); + firstnameElement.setTextContent(user.getFirstname()); + userElement.appendChild(firstnameElement); + } - // add lastname element - Element lastnameElement = doc.createElement(XmlConstants.XML_LASTNAME); - lastnameElement.setTextContent(user.getLastname()); - userElement.appendChild(lastnameElement); + // 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); @@ -97,20 +103,22 @@ public class PrivilegeModelDomWriter { } // add the parameters - Element parametersElement = doc.createElement(XmlConstants.XML_PARAMETERS); - userElement.appendChild(parametersElement); - for (Entry entry : user.getProperties().entrySet()) { - Element paramElement = doc.createElement(XmlConstants.XML_PARAMETER); - paramElement.setAttribute(XmlConstants.XML_ATTR_NAME, entry.getKey()); - paramElement.setAttribute(XmlConstants.XML_ATTR_VALUE, entry.getValue()); - parametersElement.appendChild(paramElement); + if (!user.getProperties().isEmpty()) { + Element parametersElement = doc.createElement(XmlConstants.XML_PARAMETERS); + userElement.appendChild(parametersElement); + for (Entry entry : user.getProperties().entrySet()) { + Element paramElement = doc.createElement(XmlConstants.XML_PARAMETER); + paramElement.setAttribute(XmlConstants.XML_ATTR_NAME, entry.getKey()); + paramElement.setAttribute(XmlConstants.XML_ATTR_VALUE, entry.getValue()); + parametersElement.appendChild(paramElement); + } } - } + }); Element rolesElement = doc.createElement(XmlConstants.XML_ROLES); rootElement.appendChild(rolesElement); - for (Role role : this.roles) { + this.roles.stream().sorted((r1, r2) -> r1.getName().compareTo(r2.getName())).forEach(role -> { // create the role element Element roleElement = doc.createElement(XmlConstants.XML_ROLE); @@ -129,9 +137,11 @@ public class PrivilegeModelDomWriter { privilegeElement.setAttribute(XmlConstants.XML_ATTR_POLICY, privilege.getPolicy()); // add the all allowed element - Element allAllowedElement = doc.createElement(XmlConstants.XML_ALL_ALLOWED); - allAllowedElement.setTextContent(Boolean.toString(privilege.isAllAllowed())); - privilegeElement.appendChild(allAllowedElement); + 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()) { @@ -147,7 +157,7 @@ public class PrivilegeModelDomWriter { privilegeElement.appendChild(allowValueElement); } } - } + }); // write the container file to disk XmlHelper.writeDocument(doc, this.modelFile); From c6f531c08eab8e834b17df9d15cb7b766cf7c821 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Thu, 8 Oct 2015 22:15:35 +0200 Subject: [PATCH 167/173] [Minor] fixed broken tests --- src/test/java/ch/eitchnet/privilege/test/XmlTest.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java index 91bd4a007..5186dba16 100644 --- a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java @@ -247,8 +247,7 @@ public class XmlTest { Role systemAdminPrivileges = findRole("system_admin_privileges", roles); assertEquals("system_admin_privileges", systemAdminPrivileges.getName()); assertEquals(2, systemAdminPrivileges.getPrivilegeNames().size()); - assertThat( - systemAdminPrivileges.getPrivilegeNames(), + assertThat(systemAdminPrivileges.getPrivilegeNames(), containsInAnyOrder("ch.eitchnet.privilege.handler.SystemUserAction", "ch.eitchnet.privilege.test.model.TestSystemRestrictable")); @@ -355,6 +354,6 @@ public class XmlTest { configSaxWriter.write(); String fileHash = StringHelper.getHexString(FileHelper.hashFileSha256(modelFile)); - assertEquals("fd5f4554312b18ca9fd61f0a6a4c87603e7c191ee206afca6328e5bffb87f86c", fileHash); + assertEquals("9fac0049862b2b54b41b08b0e12a9cb105894d57a1500d6603c473661f4c7313", fileHash); } } From 5dc94514e13d142de8e2532b3bec18b28c7855dd Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 16 Oct 2015 13:16:27 +0200 Subject: [PATCH 168/173] [Major] Added persisting of sessions --- config/PrivilegeConfig.xml | 6 +- config/PrivilegeConfigMerge.xml | 2 +- .../handler/DefaultPrivilegeHandler.java | 239 ++++++++++++++++-- .../privilege/handler/PrivilegeHandler.java | 56 ++++ .../handler/XmlPersistenceHandler.java | 8 +- .../privilege/helper/XmlConstants.java | 40 +++ .../eitchnet/privilege/model/Certificate.java | 32 +-- .../eitchnet/privilege/model/UserState.java | 4 + .../xml/CertificateStubsDomWriter.java | 79 ++++++ .../xml/CertificateStubsSaxReader.java | 114 +++++++++ .../xml/PrivilegeConfigSaxReader.java | 11 +- .../privilege/test/AbstractPrivilegeTest.java | 112 ++++++++ .../privilege/test/PersistSessionsTest.java | 47 ++++ .../test/PrivilegeConflictMergeTest.java | 99 +------- .../privilege/test/PrivilegeTest.java | 111 ++------ .../ch/eitchnet/privilege/test/XmlTest.java | 2 +- 16 files changed, 729 insertions(+), 233 deletions(-) create mode 100644 src/main/java/ch/eitchnet/privilege/xml/CertificateStubsDomWriter.java create mode 100644 src/main/java/ch/eitchnet/privilege/xml/CertificateStubsSaxReader.java create mode 100644 src/test/java/ch/eitchnet/privilege/test/AbstractPrivilegeTest.java create mode 100644 src/test/java/ch/eitchnet/privilege/test/PersistSessionsTest.java diff --git a/config/PrivilegeConfig.xml b/config/PrivilegeConfig.xml index 9dd3e2b11..4d293aca2 100644 --- a/config/PrivilegeConfig.xml +++ b/config/PrivilegeConfig.xml @@ -5,6 +5,10 @@ + + + + @@ -17,7 +21,7 @@ - + diff --git a/config/PrivilegeConfigMerge.xml b/config/PrivilegeConfigMerge.xml index 923189b53..8838bc468 100644 --- a/config/PrivilegeConfigMerge.xml +++ b/config/PrivilegeConfigMerge.xml @@ -17,7 +17,7 @@ - + diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index dcd77042e..818e3fb17 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -15,6 +15,11 @@ */ 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; @@ -31,6 +36,8 @@ 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; @@ -49,7 +56,11 @@ 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; /** @@ -107,6 +118,21 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { */ 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 @@ -159,6 +185,16 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { 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) { @@ -958,8 +994,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { @Override public Certificate authenticate(String username, byte[] password) { - // create certificate - Certificate certificate; try { // username must be at least 2 characters in length if (username == null || username.length() < 2) { @@ -984,16 +1018,23 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { String sessionId = UUID.randomUUID().toString(); // create a new certificate, with details of the user - certificate = new Certificate(sessionId, new Date(), username, user.getFirstname(), user.getLastname(), - authToken, user.getLocale(), userRoles, new HashMap<>(user.getProperties())); + 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()); @@ -1002,9 +1043,88 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } finally { clearPassword(password); } + } - // return the certificate - return certificate; + 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 (OutputStream outputStream = AesCryptoHelper.wrapEncrypt(this.secretKey, + new FileOutputStream(this.persistSessionsPath))) { + + 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 (InputStream inputStream = AesCryptoHelper.wrapDecrypt(this.secretKey, + new FileInputStream(this.persistSessionsPath))) { + + CertificateStubsSaxReader reader = new CertificateStubsSaxReader(inputStream); + certificateStubs = reader.read(); + + } catch (Exception e) { + throw new PrivilegeException("Failed to load sessions!", e); + } + + 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; } /** @@ -1146,6 +1266,9 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // 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) @@ -1236,6 +1359,16 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { 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) { @@ -1273,8 +1406,29 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { 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 (autoPersistS == null || autoPersistS.equals(Boolean.FALSE.toString())) { + if (StringHelper.isEmpty(autoPersistS) || autoPersistS.equals(Boolean.FALSE.toString())) { this.autoPersistOnUserChangesData = false; } else if (autoPersistS.equals(Boolean.TRUE.toString())) { this.autoPersistOnUserChangesData = true; @@ -1283,8 +1437,48 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { 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("Enabling persistence of sessions."); //$NON-NLS-1$ + } 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; @@ -1301,17 +1495,28 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } } logger.info("Privilege conflict resolution set to " + this.privilegeConflictResolution); //$NON-NLS-1$ + } - // validate policies on privileges of Roles - for (Role role : persistenceHandler.getAllRoles()) { - validatePolicies(role); + 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); } - // validate privilege conflicts - validatePrivilegeConflicts(); + 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.privilegeContextMap = Collections.synchronizedMap(new HashMap()); - this.initialized = true; + this.secretKey = AesCryptoHelper.buildSecret(secretKeyS.toCharArray(), secretSaltS.getBytes()); } private void validatePrivilegeConflicts() { @@ -1502,8 +1707,10 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { String sessionId = UUID.randomUUID().toString(); // create a new certificate, with details of the user - Certificate systemUserCertificate = new Certificate(sessionId, new Date(), systemUsername, user.getFirstname(), - user.getLastname(), authToken, user.getLocale(), user.getRoles(), new HashMap<>(user.getProperties())); + 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); diff --git a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java index b6e0f8856..cb612278d 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java @@ -55,6 +55,11 @@ public interface PrivilegeHandler { * 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 @@ -65,6 +70,14 @@ public interface PrivilegeHandler { * 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"; /// @@ -131,11 +144,31 @@ public interface PrivilegeHandler { /// + /** + * 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} */ @@ -175,6 +208,16 @@ public interface PrivilegeHandler { */ 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} * @@ -607,6 +650,19 @@ public interface PrivilegeHandler { */ 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 diff --git a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java index 0d31c1048..40e4b1dc4 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java @@ -104,8 +104,8 @@ public class XmlPersistenceHandler implements PersistenceHandler { @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())); + 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; } @@ -121,8 +121,8 @@ public class XmlPersistenceHandler implements PersistenceHandler { @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())); + 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; } diff --git a/src/main/java/ch/eitchnet/privilege/helper/XmlConstants.java b/src/main/java/ch/eitchnet/privilege/helper/XmlConstants.java index d4a0c1e9d..83ff72744 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/XmlConstants.java +++ b/src/main/java/ch/eitchnet/privilege/helper/XmlConstants.java @@ -48,6 +48,11 @@ public class XmlConstants { */ 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" : */ @@ -78,6 +83,16 @@ public class XmlConstants { */ 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" */ @@ -153,6 +168,16 @@ public class XmlConstants { */ 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" : */ @@ -173,11 +198,26 @@ public class XmlConstants { */ 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" : */ diff --git a/src/main/java/ch/eitchnet/privilege/model/Certificate.java b/src/main/java/ch/eitchnet/privilege/model/Certificate.java index 2255300ca..d170ab45b 100644 --- a/src/main/java/ch/eitchnet/privilege/model/Certificate.java +++ b/src/main/java/ch/eitchnet/privilege/model/Certificate.java @@ -18,7 +18,6 @@ package ch.eitchnet.privilege.model; import java.io.Serializable; import java.util.Collections; import java.util.Date; -import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.Set; @@ -40,15 +39,15 @@ public final class Certificate implements Serializable { private static final long serialVersionUID = 1L; private final String sessionId; - private final Date loginTime; 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 final Map sessionDataMap; private Locale locale; private Date lastAccess; @@ -79,8 +78,8 @@ public final class Certificate implements Serializable { * 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, Date loginTime, String username, String firstname, String lastname, - String authToken, Locale locale, Set userRoles, Map propertyMap) { + 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)) { @@ -92,13 +91,17 @@ public final class Certificate implements Serializable { 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.loginTime = loginTime; 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) @@ -112,7 +115,6 @@ public final class Certificate implements Serializable { this.propertyMap = Collections.unmodifiableMap(propertyMap); this.userRoles = Collections.unmodifiableSet(userRoles); - this.sessionDataMap = new HashMap<>(); } /** @@ -157,15 +159,6 @@ public final class Certificate implements Serializable { return this.propertyMap.get(key); } - /** - * Returns a mutable {@link Map} for storing session relevant data - * - * @return the sessionDataMap - */ - public Map getSessionDataMap() { - return this.sessionDataMap; - } - /** * @return the locale */ @@ -209,6 +202,13 @@ public final class Certificate implements Serializable { return this.lastname; } + /** + * @return the userState + */ + public UserState getUserState() { + return userState; + } + /** * @return the loginTime */ diff --git a/src/main/java/ch/eitchnet/privilege/model/UserState.java b/src/main/java/ch/eitchnet/privilege/model/UserState.java index 2272002d3..b78e492ff 100644 --- a/src/main/java/ch/eitchnet/privilege/model/UserState.java +++ b/src/main/java/ch/eitchnet/privilege/model/UserState.java @@ -53,4 +53,8 @@ public enum UserState { * 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/src/main/java/ch/eitchnet/privilege/xml/CertificateStubsDomWriter.java b/src/main/java/ch/eitchnet/privilege/xml/CertificateStubsDomWriter.java new file mode 100644 index 000000000..7dab6d30e --- /dev/null +++ b/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/src/main/java/ch/eitchnet/privilege/xml/CertificateStubsSaxReader.java b/src/main/java/ch/eitchnet/privilege/xml/CertificateStubsSaxReader.java new file mode 100644 index 000000000..0cbfdb3c2 --- /dev/null +++ b/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/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java index c1b31c5a2..660a4544d 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java +++ b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java @@ -32,8 +32,6 @@ import ch.eitchnet.privilege.model.internal.PrivilegeContainerModel; */ public class PrivilegeConfigSaxReader extends DefaultHandler { - // private static final Logger logger = LoggerFactory.getLogger(PrivilegeConfigSaxReader.class); - private Deque buildersStack = new ArrayDeque(); private PrivilegeContainerModel containerModel; @@ -109,7 +107,8 @@ public class PrivilegeConfigSaxReader extends DefaultHandler { private String currentElement; @Override - public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + 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)) { @@ -147,7 +146,8 @@ public class PrivilegeConfigSaxReader extends DefaultHandler { private Map parameterMap = new HashMap(); @Override - public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + 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); @@ -168,7 +168,8 @@ public class PrivilegeConfigSaxReader extends DefaultHandler { // @Override - public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + 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); diff --git a/src/test/java/ch/eitchnet/privilege/test/AbstractPrivilegeTest.java b/src/test/java/ch/eitchnet/privilege/test/AbstractPrivilegeTest.java new file mode 100644 index 000000000..b7b2e1c57 --- /dev/null +++ b/src/test/java/ch/eitchnet/privilege/test/AbstractPrivilegeTest.java @@ -0,0 +1,112 @@ +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 modelFilename) { + try { + String pwd = System.getProperty("user.dir"); + + File configPath = new File(pwd, "config"); + + File privilegeConfigFile = new File(configPath, configFilename); + File privilegeModelFile = new File(configPath, modelFilename); + + 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 dstModel = new File(targetPath, modelFilename); + + // 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(privilegeModelFile.toPath(), dstModel.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/src/test/java/ch/eitchnet/privilege/test/PersistSessionsTest.java b/src/test/java/ch/eitchnet/privilege/test/PersistSessionsTest.java new file mode 100644 index 000000000..a032c980a --- /dev/null +++ b/src/test/java/ch/eitchnet/privilege/test/PersistSessionsTest.java @@ -0,0 +1,47 @@ +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", "PrivilegeModel.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/src/test/java/ch/eitchnet/privilege/test/PrivilegeConflictMergeTest.java b/src/test/java/ch/eitchnet/privilege/test/PrivilegeConflictMergeTest.java index ab9a352e1..f78edbb23 100644 --- a/src/test/java/ch/eitchnet/privilege/test/PrivilegeConflictMergeTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/PrivilegeConflictMergeTest.java @@ -19,120 +19,33 @@ import static org.junit.Assert.assertEquals; 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; -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.IPrivilege; -import ch.eitchnet.privilege.model.PrivilegeContext; -import ch.eitchnet.utils.helper.FileHelper; /** * @author Robert von Burg */ -public class PrivilegeConflictMergeTest { - - private static final Logger logger = LoggerFactory.getLogger(PrivilegeConflictMergeTest.class); +public class PrivilegeConflictMergeTest extends AbstractPrivilegeTest { @BeforeClass public static void init() throws Exception { - try { - destroy(); - - // copy configuration to tmp - String pwd = System.getProperty("user.dir"); - - File origPrivilegeModelFile = new File(pwd + "/config/PrivilegeModelMerge.xml"); - File tmpPrivilegeModelFile = new File(pwd + "/target/testPrivilege/PrivilegeModelMerge.xml"); - if (tmpPrivilegeModelFile.exists() && !tmpPrivilegeModelFile.delete()) { - throw new RuntimeException("Tmp configuration still exists and can not be deleted at " - + tmpPrivilegeModelFile.getAbsolutePath()); - } - - File parentFile = tmpPrivilegeModelFile.getParentFile(); - if (!parentFile.exists()) { - if (!parentFile.mkdirs()) - throw new RuntimeException("Could not create parent for tmp " + tmpPrivilegeModelFile); - } - - if (!FileHelper.copy(origPrivilegeModelFile, tmpPrivilegeModelFile, true)) - throw new RuntimeException("Failed to copy " + origPrivilegeModelFile + " to " + tmpPrivilegeModelFile); - - } catch (Exception e) { - logger.error(e.getMessage(), e); - - throw new RuntimeException("Initialization failed: " + e.getLocalizedMessage(), e); - } + removeConfigs(PrivilegeConflictMergeTest.class.getSimpleName()); + prepareConfigs(PrivilegeConflictMergeTest.class.getSimpleName(), "PrivilegeConfigMerge.xml", + "PrivilegeModelMerge.xml"); } @AfterClass public static void destroy() throws Exception { - - // delete temporary file - String pwd = System.getProperty("user.dir"); - - File tmpPrivilegeModelFile = new File(pwd + "/target/testPrivilege/PrivilegeModelMerge.xml"); - if (tmpPrivilegeModelFile.exists() && !tmpPrivilegeModelFile.delete()) { - throw new RuntimeException("Tmp configuration still exists and can not be deleted at " - + tmpPrivilegeModelFile.getAbsolutePath()); - } - - // and temporary parent - File parentFile = tmpPrivilegeModelFile.getParentFile(); - if (parentFile.exists() && !parentFile.delete()) { - throw new RuntimeException("Could not remove temporary parent for tmp " + tmpPrivilegeModelFile); - } + removeConfigs(PrivilegeConflictMergeTest.class.getSimpleName()); } - private PrivilegeHandler privilegeHandler; - private PrivilegeContext ctx; - @Before public void setup() throws Exception { - try { - - String pwd = System.getProperty("user.dir"); - - File privilegeConfigFile = new File(pwd + "/config/PrivilegeConfigMerge.xml"); - - // initialize privilege - privilegeHandler = PrivilegeInitializationHelper.initializeFromXml(privilegeConfigFile); - - } catch (Exception e) { - logger.error(e.getMessage(), e); - - throw new RuntimeException("Setup failed: " + e.getLocalizedMessage(), e); - } - } - - private 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; - } - - private 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; - } - } + initialize(PrivilegeConflictMergeTest.class.getSimpleName(), "PrivilegeConfigMerge.xml"); } @Test diff --git a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java index 3d29e60d7..0cb60c782 100644 --- a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -17,10 +17,8 @@ package ch.eitchnet.privilege.test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import java.io.File; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; @@ -42,10 +40,8 @@ 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.helper.PrivilegeInitializationHelper; import ch.eitchnet.privilege.i18n.PrivilegeMessages; import ch.eitchnet.privilege.model.Certificate; -import ch.eitchnet.privilege.model.PrivilegeContext; import ch.eitchnet.privilege.model.PrivilegeRep; import ch.eitchnet.privilege.model.Restrictable; import ch.eitchnet.privilege.model.RoleRep; @@ -55,7 +51,6 @@ 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; -import ch.eitchnet.utils.helper.FileHelper; /** * JUnit for performing Privilege tests. This JUnit is by no means complete, but checks the bare minimum.br /> @@ -65,7 +60,7 @@ import ch.eitchnet.utils.helper.FileHelper; * @author Robert von Burg */ @SuppressWarnings("nls") -public class PrivilegeTest { +public class PrivilegeTest extends AbstractPrivilegeTest { private static final String ROLE_PRIVILEGE_ADMIN = "PrivilegeAdmin"; private static final String PRIVILEGE_USER_ACCESS = "UserAccessPrivilege"; @@ -91,96 +86,20 @@ public class PrivilegeTest { @Rule public ExpectedException exception = ExpectedException.none(); - private static PrivilegeHandler privilegeHandler; - private PrivilegeContext ctx; - @BeforeClass public static void init() throws Exception { - try { - destroy(); - - // copy configuration to tmp - String pwd = System.getProperty("user.dir"); - - File origPrivilegeModelFile = new File(pwd + "/config/PrivilegeModel.xml"); - File tmpPrivilegeModelFile = new File(pwd + "/target/testPrivilege/PrivilegeModel.xml"); - if (tmpPrivilegeModelFile.exists() && !tmpPrivilegeModelFile.delete()) { - throw new RuntimeException("Tmp configuration still exists and can not be deleted at " - + tmpPrivilegeModelFile.getAbsolutePath()); - } - - File parentFile = tmpPrivilegeModelFile.getParentFile(); - if (!parentFile.exists()) { - if (!parentFile.mkdirs()) - throw new RuntimeException("Could not create parent for tmp " + tmpPrivilegeModelFile); - } - - if (!FileHelper.copy(origPrivilegeModelFile, tmpPrivilegeModelFile, true)) - throw new RuntimeException("Failed to copy " + origPrivilegeModelFile + " to " + tmpPrivilegeModelFile); - - } catch (Exception e) { - logger.error(e.getMessage(), e); - - throw new RuntimeException("Initialization failed: " + e.getLocalizedMessage(), e); - } + removeConfigs(PrivilegeTest.class.getSimpleName()); + prepareConfigs(PrivilegeTest.class.getSimpleName(), "PrivilegeConfig.xml", "PrivilegeModel.xml"); } @AfterClass public static void destroy() throws Exception { - - // delete temporary file - String pwd = System.getProperty("user.dir"); - - File tmpPrivilegeModelFile = new File(pwd + "/target/testPrivilege/PrivilegeModel.xml"); - if (tmpPrivilegeModelFile.exists() && !tmpPrivilegeModelFile.delete()) { - throw new RuntimeException("Tmp configuration still exists and can not be deleted at " - + tmpPrivilegeModelFile.getAbsolutePath()); - } - - // and temporary parent - File parentFile = tmpPrivilegeModelFile.getParentFile(); - if (parentFile.exists() && !parentFile.delete()) { - throw new RuntimeException("Could not remove temporary parent for tmp " + tmpPrivilegeModelFile); - } + removeConfigs(PrivilegeTest.class.getSimpleName()); } @Before public void setup() throws Exception { - try { - - String pwd = System.getProperty("user.dir"); - - File privilegeConfigFile = new File(pwd + "/config/PrivilegeConfig.xml"); - - // initialize privilege - privilegeHandler = PrivilegeInitializationHelper.initializeFromXml(privilegeConfigFile); - - } catch (Exception e) { - logger.error(e.getMessage(), e); - - throw new RuntimeException("Setup failed: " + e.getLocalizedMessage(), e); - } - } - - private 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; - } - - private 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; - } - } + initialize(PrivilegeTest.class.getSimpleName(), "PrivilegeConfig.xml"); } @Test @@ -258,8 +177,8 @@ public class PrivilegeTest { @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"); + 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(); @@ -277,8 +196,8 @@ public class PrivilegeTest { @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"); + 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(); @@ -398,8 +317,8 @@ public class PrivilegeTest { Certificate certificate = this.ctx.getCertificate(); - UserRep selectorRep = new UserRep(null, null, null, null, null, new HashSet<>( - Arrays.asList("PrivilegeAdmin")), null, null); + 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()); @@ -590,8 +509,8 @@ public class PrivilegeTest { 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()); + 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)); @@ -818,8 +737,8 @@ public class PrivilegeTest { 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()); + 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); diff --git a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java index 5186dba16..4dfc029b3 100644 --- a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java @@ -120,7 +120,7 @@ public class XmlTest { assertNotNull(containerModel.getPersistenceHandlerClassName()); assertNotNull(containerModel.getPersistenceHandlerParameterMap()); - assertEquals(2, containerModel.getParameterMap().size()); + assertEquals(6, containerModel.getParameterMap().size()); assertEquals(3, containerModel.getPolicies().size()); assertEquals(1, containerModel.getEncryptionHandlerParameterMap().size()); assertEquals(2, containerModel.getPersistenceHandlerParameterMap().size()); From 19331b964812a1d9225f651375aa43e1ac2a0a8f Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 16 Oct 2015 18:20:25 +0200 Subject: [PATCH 169/173] [Fix] Simply delete the sessions file if not readable on load --- .../eitchnet/privilege/handler/DefaultPrivilegeHandler.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 818e3fb17..2f2253ee3 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -1089,7 +1089,9 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { certificateStubs = reader.read(); } catch (Exception e) { - throw new PrivilegeException("Failed to load sessions!", e); + logger.error("Failed to load sessions!", e); + this.persistSessionsPath.delete(); + return false; } if (certificateStubs.isEmpty()) { From d5491e4f0d0106866eea4f59c32826aa20a9d139 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 23 Oct 2015 18:21:02 +0200 Subject: [PATCH 170/173] [New] Use InvalidCredentialsException --- .../base/InvalidCredentialsException.java | 19 +++++++++++++++++++ .../handler/DefaultPrivilegeHandler.java | 13 +++++++++---- 2 files changed, 28 insertions(+), 4 deletions(-) create mode 100644 src/main/java/ch/eitchnet/privilege/base/InvalidCredentialsException.java diff --git a/src/main/java/ch/eitchnet/privilege/base/InvalidCredentialsException.java b/src/main/java/ch/eitchnet/privilege/base/InvalidCredentialsException.java new file mode 100644 index 000000000..013e9dce8 --- /dev/null +++ b/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/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 2f2253ee3..649d2abea 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -42,6 +42,7 @@ 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; @@ -1141,8 +1142,11 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { * * @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 AccessDeniedException { + private User checkCredentialsAndUserState(String username, byte[] password) + throws InvalidCredentialsException, AccessDeniedException { // and validate the password validatePassword(password); @@ -1155,14 +1159,14 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // 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 AccessDeniedException(msg); + 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 AccessDeniedException(msg); + throw new InvalidCredentialsException(msg); } // validate password @@ -1171,7 +1175,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { throw new AccessDeniedException( MessageFormat.format("User {0} has no password and may not login!", username)); //$NON-NLS-1$ if (!pwHash.equals(passwordHash)) - throw new AccessDeniedException(MessageFormat.format("Password is incorrect for {0}", username)); //$NON-NLS-1$ + 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 @@ -1180,6 +1184,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { msg = MessageFormat.format(msg, username, UserState.ENABLED); throw new AccessDeniedException(msg); } + return user; } From 461eef4b23498a5ab7324e5c00101d85ab30d191 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Wed, 10 Feb 2016 20:33:27 +0100 Subject: [PATCH 171/173] [Minor] Code cleanup --- .gitignore | 2 +- .../handler/DefaultPrivilegeHandler.java | 16 +++++----- .../handler/XmlPersistenceHandler.java | 6 ++-- .../helper/BootstrapConfigurationHelper.java | 6 ++-- .../privilege/helper/PasswordCreaterUI.java | 2 +- .../helper/PrivilegeInitializationHelper.java | 4 +-- .../privilege/model/PrivilegeContext.java | 4 +-- .../internal/PrivilegeContainerModel.java | 2 +- .../model/internal/PrivilegeImpl.java | 4 +-- .../privilege/model/internal/Role.java | 4 +-- .../privilege/model/internal/User.java | 6 ++-- .../xml/PrivilegeConfigSaxReader.java | 4 +-- .../xml/PrivilegeModelSaxReader.java | 20 ++++++------- .../privilege/test/PrivilegeTest.java | 4 +-- .../ch/eitchnet/privilege/test/XmlTest.java | 30 +++++++++---------- 15 files changed, 57 insertions(+), 57 deletions(-) diff --git a/.gitignore b/.gitignore index 2945707a2..18d2ca6cd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ target/ .classpath .project -.settings/ \ No newline at end of file +.settings/ diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 649d2abea..9a83aaa90 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -677,7 +677,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } // create new user - Set newRoles = new HashSet(currentRoles); + 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, @@ -973,7 +973,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { // create new set of privileges with out the to removed privilege Set privilegeNames = existingRole.getPrivilegeNames(); - Map newPrivileges = new HashMap(privilegeNames.size() - 1); + Map newPrivileges = new HashMap<>(privilegeNames.size() - 1); for (String name : privilegeNames) { IPrivilege privilege = existingRole.getPrivilege(name); if (!privilege.getName().equals(privilegeName)) @@ -1053,8 +1053,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { List sessions = this.privilegeContextMap.values().stream().map(p -> p.getCertificate()) .filter(c -> !c.getUserState().isSystem()).collect(Collectors.toList()); - try (OutputStream outputStream = AesCryptoHelper.wrapEncrypt(this.secretKey, - new FileOutputStream(this.persistSessionsPath))) { + try (FileOutputStream fout = new FileOutputStream(this.persistSessionsPath); + OutputStream outputStream = AesCryptoHelper.wrapEncrypt(this.secretKey, fout)) { CertificateStubsDomWriter writer = new CertificateStubsDomWriter(sessions, outputStream); writer.write(); @@ -1083,8 +1083,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { "Sessions data file is not a file but exists at " + this.persistSessionsPath.getAbsolutePath()); List certificateStubs; - try (InputStream inputStream = AesCryptoHelper.wrapDecrypt(this.secretKey, - new FileInputStream(this.persistSessionsPath))) { + try (FileInputStream fin = new FileInputStream(this.persistSessionsPath); + InputStream inputStream = AesCryptoHelper.wrapDecrypt(this.secretKey, fin)) { CertificateStubsSaxReader reader = new CertificateStubsSaxReader(inputStream); certificateStubs = reader.read(); @@ -1199,8 +1199,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { private PrivilegeContext buildPrivilegeContext(Certificate certificate, User user) { Set userRoles = user.getRoles(); - Map privileges = new HashMap(); - Map policies = new HashMap(); + Map privileges = new HashMap<>(); + Map policies = new HashMap<>(); // get a cache of the privileges and policies for this user for (String roleName : userRoles) { diff --git a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java index 40e4b1dc4..c161b5beb 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java +++ b/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java @@ -58,14 +58,14 @@ public class XmlPersistenceHandler implements PersistenceHandler { @Override public List getAllUsers() { synchronized (this.userMap) { - return new LinkedList(this.userMap.values()); + return new LinkedList<>(this.userMap.values()); } } @Override public List getAllRoles() { synchronized (this.roleMap) { - return new LinkedList(this.roleMap.values()); + return new LinkedList<>(this.roleMap.values()); } } @@ -138,7 +138,7 @@ public class XmlPersistenceHandler implements PersistenceHandler { public void initialize(Map paramsMap) { // copy parameter map - this.parameterMap = Collections.unmodifiableMap(new HashMap(paramsMap)); + this.parameterMap = Collections.unmodifiableMap(new HashMap<>(paramsMap)); // get and validate base bath String basePath = this.parameterMap.get(XmlConstants.XML_PARAM_BASE_PATH); diff --git a/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java b/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java index f9d3b158b..6504e12d4 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java +++ b/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java @@ -77,9 +77,9 @@ public class BootstrapConfigurationHelper { throw new RuntimeException("Could not create path " + pathF.getAbsolutePath()); } - Map parameterMap = new HashMap(); - Map encryptionHandlerParameterMap = new HashMap(); - Map persistenceHandlerParameterMap = new HashMap(); + Map parameterMap = new HashMap<>(); + Map encryptionHandlerParameterMap = new HashMap<>(); + Map persistenceHandlerParameterMap = new HashMap<>(); // TODO ask other questions... parameterMap.put("autoPersistOnPasswordChange", "true"); diff --git a/src/main/java/ch/eitchnet/privilege/helper/PasswordCreaterUI.java b/src/main/java/ch/eitchnet/privilege/helper/PasswordCreaterUI.java index d409a196e..62eab7fa8 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/PasswordCreaterUI.java +++ b/src/main/java/ch/eitchnet/privilege/helper/PasswordCreaterUI.java @@ -61,7 +61,7 @@ public class PasswordCreaterUI { 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); + final JComboBox digestCombo = new JComboBox<>(digests); digestCombo.setSelectedIndex(3); final JPasswordField passwordField = new JPasswordField(); final JTextField hashField = new JTextField(150); diff --git a/src/main/java/ch/eitchnet/privilege/helper/PrivilegeInitializationHelper.java b/src/main/java/ch/eitchnet/privilege/helper/PrivilegeInitializationHelper.java index 03bc8d21e..5cdf1b556 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/PrivilegeInitializationHelper.java +++ b/src/main/java/ch/eitchnet/privilege/helper/PrivilegeInitializationHelper.java @@ -59,8 +59,8 @@ public class PrivilegeInitializationHelper { } // delegate using input stream - try { - return initializeFromXml(new FileInputStream(privilegeXmlFile)); + 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()); diff --git a/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java b/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java index 32eef9e6a..95a7a5efd 100644 --- a/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java +++ b/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java @@ -51,8 +51,8 @@ public class PrivilegeContext { Map policies) { this.userRep = userRep; this.certificate = certificate; - this.privileges = Collections.unmodifiableMap(new HashMap(privileges)); - this.policies = Collections.unmodifiableMap(new HashMap(policies)); + this.privileges = Collections.unmodifiableMap(new HashMap<>(privileges)); + this.policies = Collections.unmodifiableMap(new HashMap<>(policies)); } public UserRep getUserRep() { diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java b/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java index efc2d7954..cec81e18d 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java @@ -46,7 +46,7 @@ public class PrivilegeContainerModel { * Default constructor */ public PrivilegeContainerModel() { - this.policies = new HashMap>(); + this.policies = new HashMap<>(); } /** diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeImpl.java b/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeImpl.java index 9cfe3bca5..89b67366a 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeImpl.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeImpl.java @@ -109,8 +109,8 @@ public final class PrivilegeImpl implements IPrivilege { */ @Override public PrivilegeRep asPrivilegeRep() { - return new PrivilegeRep(this.name, this.policy, this.allAllowed, new HashSet(this.denyList), - new HashSet(this.allowList)); + return new PrivilegeRep(this.name, this.policy, this.allAllowed, new HashSet<>(this.denyList), + new HashSet<>(this.allowList)); } /** diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/Role.java b/src/main/java/ch/eitchnet/privilege/model/internal/Role.java index 6048f672f..bd8acdf8f 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/Role.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/Role.java @@ -86,7 +86,7 @@ public final class Role { } // build privileges from rep - Map privilegeMap = new HashMap(roleRep.getPrivileges().size()); + Map privilegeMap = new HashMap<>(roleRep.getPrivileges().size()); for (PrivilegeRep privilege : roleRep.getPrivileges()) { privilegeMap.put(privilege.getName(), new PrivilegeImpl(privilege)); } @@ -136,7 +136,7 @@ public final class Role { * @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(); + List privileges = new ArrayList<>(); for (Entry entry : this.privilegeMap.entrySet()) { privileges.add(entry.getValue().asPrivilegeRep()); } diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/User.java b/src/main/java/ch/eitchnet/privilege/model/internal/User.java index c81ad1ee8..5afe247aa 100644 --- a/src/main/java/ch/eitchnet/privilege/model/internal/User.java +++ b/src/main/java/ch/eitchnet/privilege/model/internal/User.java @@ -116,7 +116,7 @@ public final class User { if (roles == null) this.roles = Collections.emptySet(); else - this.roles = Collections.unmodifiableSet(new HashSet(roles)); + this.roles = Collections.unmodifiableSet(new HashSet<>(roles)); if (locale == null) this.locale = Locale.getDefault(); @@ -126,7 +126,7 @@ public final class User { if (propertyMap == null) this.propertyMap = Collections.emptyMap(); else - this.propertyMap = Collections.unmodifiableMap(new HashMap(propertyMap)); + this.propertyMap = Collections.unmodifiableMap(new HashMap<>(propertyMap)); } /** @@ -234,7 +234,7 @@ public final class User { */ 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)); + new HashSet<>(this.roles), this.locale, new HashMap<>(this.propertyMap)); } /** diff --git a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java index 660a4544d..421aff46f 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java +++ b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java @@ -32,7 +32,7 @@ import ch.eitchnet.privilege.model.internal.PrivilegeContainerModel; */ public class PrivilegeConfigSaxReader extends DefaultHandler { - private Deque buildersStack = new ArrayDeque(); + private Deque buildersStack = new ArrayDeque<>(); private PrivilegeContainerModel containerModel; @@ -143,7 +143,7 @@ public class PrivilegeConfigSaxReader extends DefaultHandler { // - private Map parameterMap = new HashMap(); + private Map parameterMap = new HashMap<>(); @Override public void startElement(String uri, String localName, String qName, Attributes attributes) diff --git a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java index be05f0b62..438b91d7a 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java +++ b/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java @@ -47,7 +47,7 @@ public class PrivilegeModelSaxReader extends DefaultHandler { protected static final Logger logger = LoggerFactory.getLogger(PrivilegeModelSaxReader.class); - private Deque buildersStack = new ArrayDeque(); + private Deque buildersStack = new ArrayDeque<>(); private List users; private List roles; @@ -55,8 +55,8 @@ public class PrivilegeModelSaxReader extends DefaultHandler { private boolean insideUser; public PrivilegeModelSaxReader() { - this.users = new ArrayList(); - this.roles = new ArrayList(); + this.users = new ArrayList<>(); + this.roles = new ArrayList<>(); } /** @@ -147,7 +147,7 @@ public class PrivilegeModelSaxReader extends DefaultHandler { } private void init() { - this.privileges = new HashMap(); + this.privileges = new HashMap<>(); this.text = null; @@ -155,8 +155,8 @@ public class PrivilegeModelSaxReader extends DefaultHandler { this.privilegeName = null; this.privilegePolicy = null; this.allAllowed = false; - this.denyList = new HashSet(); - this.allowList = new HashSet(); + this.denyList = new HashSet<>(); + this.allowList = new HashSet<>(); } @Override @@ -201,8 +201,8 @@ public class PrivilegeModelSaxReader extends DefaultHandler { this.privilegeName = null; this.privilegePolicy = null; this.allAllowed = false; - this.denyList = new HashSet(); - this.allowList = new HashSet(); + this.denyList = new HashSet<>(); + this.allowList = new HashSet<>(); } else if (qName.equals(XmlConstants.XML_ROLE)) { @@ -245,7 +245,7 @@ public class PrivilegeModelSaxReader extends DefaultHandler { Map parameters; public UserParser() { - this.userRoles = new HashSet(); + this.userRoles = new HashSet<>(); } @Override @@ -307,7 +307,7 @@ public class PrivilegeModelSaxReader extends DefaultHandler { // - public Map parameterMap = new HashMap(); + public Map parameterMap = new HashMap<>(); @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { diff --git a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java index 0cb60c782..674ccaebb 100644 --- a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -622,7 +622,7 @@ public class PrivilegeTest extends AbstractPrivilegeTest { // testAddUserTedAsBob login(BOB, ArraysHelper.copyOf(PASS_BOB)); // let's add a new user ted - HashSet roles = new HashSet(); + HashSet roles = new HashSet<>(); roles.add(ROLE_USER); userRep = new UserRep(null, TED, "Ted", "Newman", UserState.ENABLED, roles, null, new HashMap()); @@ -738,7 +738,7 @@ public class PrivilegeTest extends AbstractPrivilegeTest { // 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()); + new HashSet<>(Arrays.asList(ROLE_MY)), null, new HashMap()); Certificate certificate = this.ctx.getCertificate(); privilegeHandler.addUser(certificate, userRep, null); logger.info("Added user " + BOB); diff --git a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java index 4dfc029b3..e1ddd7e72 100644 --- a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java +++ b/src/test/java/ch/eitchnet/privilege/test/XmlTest.java @@ -131,9 +131,9 @@ public class XmlTest { @Test public void canWriteConfig() { - Map parameterMap = new HashMap(); - Map encryptionHandlerParameterMap = new HashMap(); - Map persistenceHandlerParameterMap = new HashMap(); + Map parameterMap = new HashMap<>(); + Map encryptionHandlerParameterMap = new HashMap<>(); + Map persistenceHandlerParameterMap = new HashMap<>(); parameterMap.put("autoPersistOnPasswordChange", "true"); encryptionHandlerParameterMap.put("hashAlgorithm", "SHA-256"); @@ -189,7 +189,7 @@ public class XmlTest { 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(new HashSet<>(Arrays.asList("organization", "organizationalUnit")), properties.keySet()); assertEquals("eitchnet.ch", properties.get("organization")); assertEquals("Development", properties.get("organizationalUnit")); @@ -233,7 +233,7 @@ public class XmlTest { // AppUser Role appUser = findRole("AppUser", roles); assertEquals("AppUser", appUser.getName()); - assertEquals(new HashSet(Arrays.asList("ch.eitchnet.privilege.test.model.TestRestrictable")), + assertEquals(new HashSet<>(Arrays.asList("ch.eitchnet.privilege.test.model.TestRestrictable")), appUser.getPrivilegeNames()); IPrivilege testRestrictable = appUser.getPrivilege("ch.eitchnet.privilege.test.model.TestRestrictable"); @@ -320,31 +320,31 @@ public class XmlTest { Set userRoles; Map privilegeMap; - List users = new ArrayList(); - propertyMap = new HashMap(); + List users = new ArrayList<>(); + propertyMap = new HashMap<>(); propertyMap.put("prop1", "value1"); - userRoles = new HashSet(); + userRoles = new HashSet<>(); userRoles.add("role1"); users.add(new User("1", "user1", "blabla", "Bob", "White", UserState.DISABLED, userRoles, Locale.ENGLISH, propertyMap)); - propertyMap = new HashMap(); + propertyMap = new HashMap<>(); propertyMap.put("prop2", "value2"); - userRoles = new HashSet(); + userRoles = new HashSet<>(); userRoles.add("role2"); users.add(new User("2", "user2", "haha", "Leonard", "Sheldon", UserState.ENABLED, userRoles, Locale.ENGLISH, propertyMap)); - List roles = new ArrayList(); + List roles = new ArrayList<>(); Set list = Collections.emptySet(); - privilegeMap = new HashMap(); + privilegeMap = new HashMap<>(); privilegeMap.put("priv1", new PrivilegeImpl("priv1", "DefaultPrivilege", true, list, list)); roles.add(new Role("role1", privilegeMap)); - privilegeMap = new HashMap(); - Set denyList = new HashSet(); + privilegeMap = new HashMap<>(); + Set denyList = new HashSet<>(); denyList.add("myself"); - Set allowList = new HashSet(); + Set allowList = new HashSet<>(); allowList.add("other"); privilegeMap.put("priv2", new PrivilegeImpl("priv2", "DefaultPrivilege", false, denyList, allowList)); roles.add(new Role("role2", privilegeMap)); From b3b274f143d6a658759467813cf5beb7f3c1068a Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Sun, 28 Feb 2016 19:53:53 +0100 Subject: [PATCH 172/173] [Project] Moved everything to a sub directory --- LICENSE => ch.eitchnet.privilege/LICENSE | 0 README.md => ch.eitchnet.privilege/README.md | 0 .../config}/PrivilegeConfig.xml | 3 +- .../config}/PrivilegeConfigMerge.xml | 3 +- .../config/PrivilegeRoles.xml | 90 ++++++++ .../config/PrivilegeRolesMerge.xml | 28 +++ .../config/PrivilegeUsers.xml | 39 ++++ .../config/PrivilegeUsersMerge.xml | 26 +++ .../docs}/PrivilegeAuthentication.dia | Bin .../docs}/PrivilegeHandlers.dia | Bin .../docs}/PrivilegeModelPrivilege.dia | Bin .../docs}/PrivilegeModelUser.dia | Bin {docs => ch.eitchnet.privilege/docs}/TODO | 0 pom.xml => ch.eitchnet.privilege/pom.xml | 0 .../privilege.jardesc | 0 .../privilege/base/AccessDeniedException.java | 0 .../base/InvalidCredentialsException.java | 0 .../base/PrivilegeConflictResolution.java | 0 .../privilege/base/PrivilegeException.java | 0 .../handler/DefaultEncryptionHandler.java | 0 .../handler/DefaultPrivilegeHandler.java | 3 +- .../privilege/handler/EncryptionHandler.java | 0 .../privilege/handler/PersistenceHandler.java | 0 .../privilege/handler/PrivilegeHandler.java | 0 .../privilege/handler/SystemUserAction.java | 0 .../handler/XmlPersistenceHandler.java | 125 ++++++++--- .../helper/BootstrapConfigurationHelper.java | 0 .../privilege/helper/PasswordCreaterUI.java | 0 .../privilege/helper/PasswordCreator.java | 0 .../helper/PrivilegeInitializationHelper.java | 0 .../privilege/helper/XmlConstants.java | 9 +- .../privilege/i18n/PrivilegeMessages.java | 0 .../eitchnet/privilege/model/Certificate.java | 0 .../eitchnet/privilege/model/IPrivilege.java | 0 .../privilege/model/PrivilegeContext.java | 0 .../privilege/model/PrivilegeRep.java | 0 .../privilege/model/Restrictable.java | 0 .../ch/eitchnet/privilege/model/RoleRep.java | 0 .../privilege/model/SimpleRestrictable.java | 0 .../ch/eitchnet/privilege/model/UserRep.java | 0 .../eitchnet/privilege/model/UserState.java | 0 .../internal/PrivilegeContainerModel.java | 0 .../model/internal/PrivilegeImpl.java | 0 .../privilege/model/internal/Role.java | 0 .../privilege/model/internal/User.java | 0 .../privilege/policy/DefaultPrivilege.java | 0 .../privilege/policy/PrivilegePolicy.java | 0 .../policy/PrivilegePolicyHelper.java | 0 .../privilege/policy/RoleAccessPrivilege.java | 0 .../privilege/policy/UserAccessPrivilege.java | 0 ...erAccessWithSameOrganisationPrivilege.java | 0 .../UsernameFromCertificatePrivilege.java | 0 ...tificateWithSameOrganisationPrivilege.java | 0 .../xml/CertificateStubsDomWriter.java | 0 .../xml/CertificateStubsSaxReader.java | 0 .../eitchnet/privilege/xml/ElementParser.java | 0 .../privilege/xml/ElementParserAdapter.java | 0 .../xml/PrivilegeConfigDomWriter.java | 0 .../xml/PrivilegeConfigSaxReader.java | 0 .../xml/PrivilegeRolesDomWriter.java | 98 ++++++++ .../xml/PrivilegeRolesSaxReader.java | 129 +---------- .../xml/PrivilegeUsersDomWriter.java | 67 +----- .../xml/PrivilegeUsersSaxReader.java | 209 ++++++++++++++++++ .../src}/main/resources/Privilege.xsd | 0 .../resources/PrivilegeMessages.properties | 0 .../src}/main/resources/PrivilegeModel.xsd | 0 .../privilege/test/AbstractPrivilegeTest.java | 12 +- .../privilege/test/PersistSessionsTest.java | 3 +- .../test/PrivilegeConflictMergeTest.java | 2 +- .../privilege/test/PrivilegeTest.java | 3 +- .../ch/eitchnet/privilege/test/XmlTest.java | 143 ++++++++++-- .../test/model/TestRestrictable.java | 0 .../test/model/TestSystemRestrictable.java | 0 .../test/model/TestSystemUserAction.java | 0 .../test/model/TestSystemUserActionDeny.java | 0 .../src}/test/resources/log4j.xml | 0 config/PrivilegeModel.xml | 132 ----------- config/PrivilegeModelMerge.xml | 54 ----- 78 files changed, 742 insertions(+), 436 deletions(-) rename LICENSE => ch.eitchnet.privilege/LICENSE (100%) rename README.md => ch.eitchnet.privilege/README.md (100%) rename {config => ch.eitchnet.privilege/config}/PrivilegeConfig.xml (90%) rename {config => ch.eitchnet.privilege/config}/PrivilegeConfigMerge.xml (87%) create mode 100644 ch.eitchnet.privilege/config/PrivilegeRoles.xml create mode 100644 ch.eitchnet.privilege/config/PrivilegeRolesMerge.xml create mode 100644 ch.eitchnet.privilege/config/PrivilegeUsers.xml create mode 100644 ch.eitchnet.privilege/config/PrivilegeUsersMerge.xml rename {docs => ch.eitchnet.privilege/docs}/PrivilegeAuthentication.dia (100%) rename {docs => ch.eitchnet.privilege/docs}/PrivilegeHandlers.dia (100%) rename {docs => ch.eitchnet.privilege/docs}/PrivilegeModelPrivilege.dia (100%) rename {docs => ch.eitchnet.privilege/docs}/PrivilegeModelUser.dia (100%) rename {docs => ch.eitchnet.privilege/docs}/TODO (100%) rename pom.xml => ch.eitchnet.privilege/pom.xml (100%) rename privilege.jardesc => ch.eitchnet.privilege/privilege.jardesc (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/base/AccessDeniedException.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/base/InvalidCredentialsException.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/base/PrivilegeConflictResolution.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/base/PrivilegeException.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java (99%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/handler/EncryptionHandler.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/handler/SystemUserAction.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java (62%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/helper/PasswordCreaterUI.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/helper/PasswordCreator.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/helper/PrivilegeInitializationHelper.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/helper/XmlConstants.java (95%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/i18n/PrivilegeMessages.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/model/Certificate.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/model/IPrivilege.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/model/Restrictable.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/model/RoleRep.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/model/SimpleRestrictable.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/model/UserRep.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/model/UserState.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/model/internal/PrivilegeImpl.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/model/internal/Role.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/model/internal/User.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/policy/PrivilegePolicy.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/policy/PrivilegePolicyHelper.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/policy/RoleAccessPrivilege.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/policy/UserAccessPrivilege.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/policy/UserAccessWithSameOrganisationPrivilege.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/policy/UsernameFromCertificatePrivilege.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/policy/UsernameFromCertificateWithSameOrganisationPrivilege.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/xml/CertificateStubsDomWriter.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/xml/CertificateStubsSaxReader.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/xml/ElementParser.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/xml/ElementParserAdapter.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigDomWriter.java (100%) rename {src => ch.eitchnet.privilege/src}/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java (100%) create mode 100644 ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeRolesDomWriter.java rename src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java => ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeRolesSaxReader.java (63%) rename src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelDomWriter.java => ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeUsersDomWriter.java (61%) create mode 100644 ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeUsersSaxReader.java rename {src => ch.eitchnet.privilege/src}/main/resources/Privilege.xsd (100%) rename {src => ch.eitchnet.privilege/src}/main/resources/PrivilegeMessages.properties (100%) rename {src => ch.eitchnet.privilege/src}/main/resources/PrivilegeModel.xsd (100%) rename {src => ch.eitchnet.privilege/src}/test/java/ch/eitchnet/privilege/test/AbstractPrivilegeTest.java (89%) rename {src => ch.eitchnet.privilege/src}/test/java/ch/eitchnet/privilege/test/PersistSessionsTest.java (95%) rename {src => ch.eitchnet.privilege/src}/test/java/ch/eitchnet/privilege/test/PrivilegeConflictMergeTest.java (97%) rename {src => ch.eitchnet.privilege/src}/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java (99%) rename {src => ch.eitchnet.privilege/src}/test/java/ch/eitchnet/privilege/test/XmlTest.java (71%) rename {src => ch.eitchnet.privilege/src}/test/java/ch/eitchnet/privilege/test/model/TestRestrictable.java (100%) rename {src => ch.eitchnet.privilege/src}/test/java/ch/eitchnet/privilege/test/model/TestSystemRestrictable.java (100%) rename {src => ch.eitchnet.privilege/src}/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java (100%) rename {src => ch.eitchnet.privilege/src}/test/java/ch/eitchnet/privilege/test/model/TestSystemUserActionDeny.java (100%) rename {src => ch.eitchnet.privilege/src}/test/resources/log4j.xml (100%) delete mode 100644 config/PrivilegeModel.xml delete mode 100644 config/PrivilegeModelMerge.xml diff --git a/LICENSE b/ch.eitchnet.privilege/LICENSE similarity index 100% rename from LICENSE rename to ch.eitchnet.privilege/LICENSE diff --git a/README.md b/ch.eitchnet.privilege/README.md similarity index 100% rename from README.md rename to ch.eitchnet.privilege/README.md diff --git a/config/PrivilegeConfig.xml b/ch.eitchnet.privilege/config/PrivilegeConfig.xml similarity index 90% rename from config/PrivilegeConfig.xml rename to ch.eitchnet.privilege/config/PrivilegeConfig.xml index 4d293aca2..744933dcf 100644 --- a/config/PrivilegeConfig.xml +++ b/ch.eitchnet.privilege/config/PrivilegeConfig.xml @@ -22,7 +22,8 @@ - + + diff --git a/config/PrivilegeConfigMerge.xml b/ch.eitchnet.privilege/config/PrivilegeConfigMerge.xml similarity index 87% rename from config/PrivilegeConfigMerge.xml rename to ch.eitchnet.privilege/config/PrivilegeConfigMerge.xml index 8838bc468..403652054 100644 --- a/config/PrivilegeConfigMerge.xml +++ b/ch.eitchnet.privilege/config/PrivilegeConfigMerge.xml @@ -18,7 +18,8 @@ - + + 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/docs/PrivilegeAuthentication.dia b/ch.eitchnet.privilege/docs/PrivilegeAuthentication.dia similarity index 100% rename from docs/PrivilegeAuthentication.dia rename to ch.eitchnet.privilege/docs/PrivilegeAuthentication.dia diff --git a/docs/PrivilegeHandlers.dia b/ch.eitchnet.privilege/docs/PrivilegeHandlers.dia similarity index 100% rename from docs/PrivilegeHandlers.dia rename to ch.eitchnet.privilege/docs/PrivilegeHandlers.dia diff --git a/docs/PrivilegeModelPrivilege.dia b/ch.eitchnet.privilege/docs/PrivilegeModelPrivilege.dia similarity index 100% rename from docs/PrivilegeModelPrivilege.dia rename to ch.eitchnet.privilege/docs/PrivilegeModelPrivilege.dia diff --git a/docs/PrivilegeModelUser.dia b/ch.eitchnet.privilege/docs/PrivilegeModelUser.dia similarity index 100% rename from docs/PrivilegeModelUser.dia rename to ch.eitchnet.privilege/docs/PrivilegeModelUser.dia diff --git a/docs/TODO b/ch.eitchnet.privilege/docs/TODO similarity index 100% rename from docs/TODO rename to ch.eitchnet.privilege/docs/TODO diff --git a/pom.xml b/ch.eitchnet.privilege/pom.xml similarity index 100% rename from pom.xml rename to ch.eitchnet.privilege/pom.xml diff --git a/privilege.jardesc b/ch.eitchnet.privilege/privilege.jardesc similarity index 100% rename from privilege.jardesc rename to ch.eitchnet.privilege/privilege.jardesc diff --git a/src/main/java/ch/eitchnet/privilege/base/AccessDeniedException.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/base/AccessDeniedException.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/base/AccessDeniedException.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/base/AccessDeniedException.java diff --git a/src/main/java/ch/eitchnet/privilege/base/InvalidCredentialsException.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/base/InvalidCredentialsException.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/base/InvalidCredentialsException.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/base/InvalidCredentialsException.java diff --git a/src/main/java/ch/eitchnet/privilege/base/PrivilegeConflictResolution.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/base/PrivilegeConflictResolution.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/base/PrivilegeConflictResolution.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/base/PrivilegeConflictResolution.java diff --git a/src/main/java/ch/eitchnet/privilege/base/PrivilegeException.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/base/PrivilegeException.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/base/PrivilegeException.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/base/PrivilegeException.java diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/handler/DefaultEncryptionHandler.java diff --git a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java similarity index 99% rename from src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java index 9a83aaa90..91e800a2e 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java +++ b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/handler/DefaultPrivilegeHandler.java @@ -1476,7 +1476,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { } this.persistSessionsPath = persistSessionsPath; - logger.info("Enabling persistence of sessions."); //$NON-NLS-1$ + 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); diff --git a/src/main/java/ch/eitchnet/privilege/handler/EncryptionHandler.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/handler/EncryptionHandler.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/handler/EncryptionHandler.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/handler/EncryptionHandler.java diff --git a/src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/handler/PersistenceHandler.java diff --git a/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/handler/PrivilegeHandler.java diff --git a/src/main/java/ch/eitchnet/privilege/handler/SystemUserAction.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/handler/SystemUserAction.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/handler/SystemUserAction.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/handler/SystemUserAction.java diff --git a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java similarity index 62% rename from src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java index c161b5beb..2ad3462b4 100644 --- a/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java +++ b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/handler/XmlPersistenceHandler.java @@ -30,8 +30,11 @@ 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.PrivilegeModelDomWriter; -import ch.eitchnet.privilege.xml.PrivilegeModelSaxReader; +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; /** @@ -47,13 +50,15 @@ public class XmlPersistenceHandler implements PersistenceHandler { private Map userMap; private Map roleMap; - private long modelsFileDate; private boolean userMapDirty; private boolean roleMapDirty; private Map parameterMap; - private File modelPath; + private long usersFileDate; + private long rolesFileDate; + private File usersPath; + private File rolesPath; @Override public List getAllUsers() { @@ -150,26 +155,45 @@ public class XmlPersistenceHandler implements PersistenceHandler { throw new PrivilegeException(msg); } - // get model file name - String modelFileName = this.parameterMap.get(XmlConstants.XML_PARAM_MODEL_FILE); - if (modelFileName == null || modelFileName.isEmpty()) { + // 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_MODEL_FILE); + msg = MessageFormat.format(msg, PersistenceHandler.class.getName(), XmlConstants.XML_PARAM_USERS_FILE); throw new PrivilegeException(msg); } - // validate file exists - String modelPathS = basePath + "/" + modelFileName; //$NON-NLS-1$ - File modelPath = new File(modelPathS); - if (!modelPath.exists()) { - String msg = "[{0}] Defined parameter {1} is invalid as model file does not exist at path {2}"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, PersistenceHandler.class.getName(), XmlConstants.XML_PARAM_MODEL_FILE, - modelPath.getAbsolutePath()); + // 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.modelPath = modelPath; + this.usersPath = usersPath; + this.rolesPath = rolesPath; if (reload()) logger.info("Privilege Data loaded."); //$NON-NLS-1$ @@ -188,19 +212,23 @@ public class XmlPersistenceHandler implements PersistenceHandler { this.userMap = Collections.synchronizedMap(new HashMap()); // parse models xml file to XML document - PrivilegeModelSaxReader xmlHandler = new PrivilegeModelSaxReader(); - XmlHelper.parseDocument(this.modelPath, xmlHandler); + PrivilegeUsersSaxReader usersXmlHandler = new PrivilegeUsersSaxReader(); + XmlHelper.parseDocument(this.usersPath, usersXmlHandler); - this.modelsFileDate = this.modelPath.lastModified(); + PrivilegeRolesSaxReader rolesXmlHandler = new PrivilegeRolesSaxReader(); + XmlHelper.parseDocument(this.rolesPath, rolesXmlHandler); + + this.usersFileDate = this.usersPath.lastModified(); + this.rolesFileDate = this.rolesPath.lastModified(); // ROLES - List roles = xmlHandler.getRoles(); + List roles = rolesXmlHandler.getRoles(); for (Role role : roles) { this.roleMap.put(role.getName(), role); } // USERS - List users = xmlHandler.getUsers(); + List users = usersXmlHandler.getUsers(); for (User user : users) { this.userMap.put(user.getUsername(), user); } @@ -233,29 +261,54 @@ public class XmlPersistenceHandler implements PersistenceHandler { @Override public boolean persist() { - // get models file name - String modelFileName = this.parameterMap.get(XmlConstants.XML_PARAM_MODEL_FILE); - if (modelFileName == null || modelFileName.isEmpty()) { + // 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_MODEL_FILE); + msg = MessageFormat.format(msg, PersistenceHandler.class.getName(), XmlConstants.XML_PARAM_USERS_FILE); throw new PrivilegeException(msg); } - // get model file - boolean modelFileUnchanged = this.modelPath.exists() && this.modelPath.lastModified() == this.modelsFileDate; - if (modelFileUnchanged && !this.roleMapDirty && !this.userMapDirty) { - logger.warn("Not persisting as current file is unchanged and model data is not dirty"); //$NON-NLS-1$ - return false; + // 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); } - // delegate writing - PrivilegeModelDomWriter modelWriter = new PrivilegeModelDomWriter(getAllUsers(), getAllRoles(), this.modelPath); - modelWriter.write(); + 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 - this.userMapDirty = false; - this.roleMapDirty = false; - return true; + return saved; } } diff --git a/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/helper/BootstrapConfigurationHelper.java diff --git a/src/main/java/ch/eitchnet/privilege/helper/PasswordCreaterUI.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/helper/PasswordCreaterUI.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/helper/PasswordCreaterUI.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/helper/PasswordCreaterUI.java diff --git a/src/main/java/ch/eitchnet/privilege/helper/PasswordCreator.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/helper/PasswordCreator.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/helper/PasswordCreator.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/helper/PasswordCreator.java diff --git a/src/main/java/ch/eitchnet/privilege/helper/PrivilegeInitializationHelper.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/helper/PrivilegeInitializationHelper.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/helper/PrivilegeInitializationHelper.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/helper/PrivilegeInitializationHelper.java diff --git a/src/main/java/ch/eitchnet/privilege/helper/XmlConstants.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/helper/XmlConstants.java similarity index 95% rename from src/main/java/ch/eitchnet/privilege/helper/XmlConstants.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/helper/XmlConstants.java index 83ff72744..041ce2f5f 100644 --- a/src/main/java/ch/eitchnet/privilege/helper/XmlConstants.java +++ b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/helper/XmlConstants.java @@ -229,9 +229,14 @@ public class XmlConstants { public static final String XML_PARAM_HASH_ALGORITHM = "hashAlgorithm"; /** - * XML_PARAM_MODEL_FILE = "modelXmlFile" : + * XML_PARAM_USERS_FILE = "usersXmlFile" : */ - public static final String XML_PARAM_MODEL_FILE = "modelXmlFile"; + 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" : diff --git a/src/main/java/ch/eitchnet/privilege/i18n/PrivilegeMessages.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/i18n/PrivilegeMessages.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/i18n/PrivilegeMessages.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/i18n/PrivilegeMessages.java diff --git a/src/main/java/ch/eitchnet/privilege/model/Certificate.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/Certificate.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/model/Certificate.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/Certificate.java diff --git a/src/main/java/ch/eitchnet/privilege/model/IPrivilege.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/IPrivilege.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/model/IPrivilege.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/IPrivilege.java diff --git a/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/PrivilegeContext.java diff --git a/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/PrivilegeRep.java diff --git a/src/main/java/ch/eitchnet/privilege/model/Restrictable.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/Restrictable.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/model/Restrictable.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/Restrictable.java diff --git a/src/main/java/ch/eitchnet/privilege/model/RoleRep.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/RoleRep.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/model/RoleRep.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/RoleRep.java diff --git a/src/main/java/ch/eitchnet/privilege/model/SimpleRestrictable.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/SimpleRestrictable.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/model/SimpleRestrictable.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/SimpleRestrictable.java diff --git a/src/main/java/ch/eitchnet/privilege/model/UserRep.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/UserRep.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/model/UserRep.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/UserRep.java diff --git a/src/main/java/ch/eitchnet/privilege/model/UserState.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/UserState.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/model/UserState.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/UserState.java diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeContainerModel.java diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeImpl.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeImpl.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeImpl.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/internal/PrivilegeImpl.java diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/Role.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/internal/Role.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/model/internal/Role.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/internal/Role.java diff --git a/src/main/java/ch/eitchnet/privilege/model/internal/User.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/internal/User.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/model/internal/User.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/model/internal/User.java diff --git a/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/DefaultPrivilege.java diff --git a/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicy.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicy.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicy.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicy.java diff --git a/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicyHelper.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicyHelper.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicyHelper.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/PrivilegePolicyHelper.java diff --git a/src/main/java/ch/eitchnet/privilege/policy/RoleAccessPrivilege.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/RoleAccessPrivilege.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/policy/RoleAccessPrivilege.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/RoleAccessPrivilege.java diff --git a/src/main/java/ch/eitchnet/privilege/policy/UserAccessPrivilege.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/UserAccessPrivilege.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/policy/UserAccessPrivilege.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/UserAccessPrivilege.java diff --git a/src/main/java/ch/eitchnet/privilege/policy/UserAccessWithSameOrganisationPrivilege.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/UserAccessWithSameOrganisationPrivilege.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/policy/UserAccessWithSameOrganisationPrivilege.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/UserAccessWithSameOrganisationPrivilege.java diff --git a/src/main/java/ch/eitchnet/privilege/policy/UsernameFromCertificatePrivilege.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/UsernameFromCertificatePrivilege.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/policy/UsernameFromCertificatePrivilege.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/UsernameFromCertificatePrivilege.java diff --git a/src/main/java/ch/eitchnet/privilege/policy/UsernameFromCertificateWithSameOrganisationPrivilege.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/UsernameFromCertificateWithSameOrganisationPrivilege.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/policy/UsernameFromCertificateWithSameOrganisationPrivilege.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/policy/UsernameFromCertificateWithSameOrganisationPrivilege.java diff --git a/src/main/java/ch/eitchnet/privilege/xml/CertificateStubsDomWriter.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/CertificateStubsDomWriter.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/xml/CertificateStubsDomWriter.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/CertificateStubsDomWriter.java diff --git a/src/main/java/ch/eitchnet/privilege/xml/CertificateStubsSaxReader.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/CertificateStubsSaxReader.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/xml/CertificateStubsSaxReader.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/CertificateStubsSaxReader.java diff --git a/src/main/java/ch/eitchnet/privilege/xml/ElementParser.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/ElementParser.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/xml/ElementParser.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/ElementParser.java diff --git a/src/main/java/ch/eitchnet/privilege/xml/ElementParserAdapter.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/ElementParserAdapter.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/xml/ElementParserAdapter.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/ElementParserAdapter.java diff --git a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigDomWriter.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigDomWriter.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigDomWriter.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigDomWriter.java diff --git a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java similarity index 100% rename from src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeConfigSaxReader.java 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/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeRolesSaxReader.java similarity index 63% rename from src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeRolesSaxReader.java index 438b91d7a..5efa039b4 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelSaxReader.java +++ b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeRolesSaxReader.java @@ -22,7 +22,6 @@ 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; @@ -34,41 +33,25 @@ import org.xml.sax.helpers.DefaultHandler; import ch.eitchnet.privilege.helper.XmlConstants; import ch.eitchnet.privilege.model.IPrivilege; -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.utils.helper.StringHelper; /** * @author Robert von Burg */ -public class PrivilegeModelSaxReader extends DefaultHandler { +public class PrivilegeRolesSaxReader extends DefaultHandler { - protected static final Logger logger = LoggerFactory.getLogger(PrivilegeModelSaxReader.class); + protected static final Logger logger = LoggerFactory.getLogger(PrivilegeRolesSaxReader.class); private Deque buildersStack = new ArrayDeque<>(); - private List users; private List roles; - private boolean insideUser; - - public PrivilegeModelSaxReader() { - this.users = new ArrayList<>(); + public PrivilegeRolesSaxReader() { this.roles = new ArrayList<>(); } - /** - * @return the users - */ - public List getUsers() { - return this.users; - } - - /** - * @return the roles - */ public List getRoles() { return this.roles; } @@ -76,13 +59,10 @@ public class PrivilegeModelSaxReader extends DefaultHandler { @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()); - this.insideUser = true; + if (qName.equals(XmlConstants.XML_ROLE)) { + this.buildersStack.push(new RoleParser()); } else if (qName.equals(XmlConstants.XML_PROPERTIES)) { this.buildersStack.push(new PropertyParser()); - } else if (qName.equals(XmlConstants.XML_ROLE) && !this.insideUser) { - this.buildersStack.push(new RoleParser()); } if (!this.buildersStack.isEmpty()) @@ -102,13 +82,10 @@ public class PrivilegeModelSaxReader extends DefaultHandler { this.buildersStack.peek().endElement(uri, localName, qName); ElementParser elementParser = null; - if (qName.equals(XmlConstants.XML_USER)) { + if (qName.equals(XmlConstants.XML_ROLE)) { elementParser = this.buildersStack.pop(); - this.insideUser = false; } else if (qName.equals(XmlConstants.XML_PROPERTIES)) { elementParser = this.buildersStack.pop(); - } else if (qName.equals(XmlConstants.XML_ROLE) && !this.insideUser) { - elementParser = this.buildersStack.pop(); } if (!this.buildersStack.isEmpty() && elementParser != null) @@ -160,7 +137,8 @@ public class PrivilegeModelSaxReader extends DefaultHandler { } @Override - public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + public void startElement(String uri, String localName, String qName, Attributes attributes) + throws SAXException { this.text = new StringBuilder(); @@ -215,94 +193,6 @@ public class PrivilegeModelSaxReader extends DefaultHandler { } } -// -// 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 { // @@ -310,7 +200,8 @@ public class PrivilegeModelSaxReader extends DefaultHandler { public Map parameterMap = new HashMap<>(); @Override - public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + 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); diff --git a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelDomWriter.java b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeUsersDomWriter.java similarity index 61% rename from src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelDomWriter.java rename to ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeUsersDomWriter.java index a79efe2d8..77b7879f0 100644 --- a/src/main/java/ch/eitchnet/privilege/xml/PrivilegeModelDomWriter.java +++ b/ch.eitchnet.privilege/src/main/java/ch/eitchnet/privilege/xml/PrivilegeUsersDomWriter.java @@ -23,28 +23,24 @@ 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.privilege.model.internal.User; import ch.eitchnet.utils.helper.StringHelper; import ch.eitchnet.utils.helper.XmlHelper; /** * @author Robert von Burg - * */ -public class PrivilegeModelDomWriter { +public class PrivilegeUsersDomWriter { private List users; - private List roles; private File modelFile; /** - * + * @param users + * @param modelFile */ - public PrivilegeModelDomWriter(List users, List roles, File modelFile) { + public PrivilegeUsersDomWriter(List users, File modelFile) { this.users = users; - this.roles = roles; this.modelFile = modelFile; } @@ -52,17 +48,14 @@ public class PrivilegeModelDomWriter { // create document root Document doc = XmlHelper.createDocument(); - Element rootElement = doc.createElement(XmlConstants.XML_ROOT_PRIVILEGE_USERS_AND_ROLES); + Element rootElement = doc.createElement(XmlConstants.XML_USERS); doc.appendChild(rootElement); - Element usersElement = doc.createElement(XmlConstants.XML_USERS); - rootElement.appendChild(usersElement); - this.users.stream().sorted((u1, u2) -> u1.getUserId().compareTo(u2.getUserId())).forEach(user -> { // create the user element Element userElement = doc.createElement(XmlConstants.XML_USER); - usersElement.appendChild(userElement); + rootElement.appendChild(userElement); userElement.setAttribute(XmlConstants.XML_ATTR_USER_ID, user.getUserId()); userElement.setAttribute(XmlConstants.XML_ATTR_USERNAME, user.getUsername()); @@ -104,10 +97,10 @@ public class PrivilegeModelDomWriter { // add the parameters if (!user.getProperties().isEmpty()) { - Element parametersElement = doc.createElement(XmlConstants.XML_PARAMETERS); + Element parametersElement = doc.createElement(XmlConstants.XML_PROPERTIES); userElement.appendChild(parametersElement); for (Entry entry : user.getProperties().entrySet()) { - Element paramElement = doc.createElement(XmlConstants.XML_PARAMETER); + 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); @@ -115,50 +108,6 @@ public class PrivilegeModelDomWriter { } }); - Element rolesElement = doc.createElement(XmlConstants.XML_ROLES); - rootElement.appendChild(rolesElement); - - this.roles.stream().sorted((r1, r2) -> r1.getName().compareTo(r2.getName())).forEach(role -> { - - // create the role element - Element roleElement = doc.createElement(XmlConstants.XML_ROLE); - rolesElement.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/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/src/main/resources/Privilege.xsd b/ch.eitchnet.privilege/src/main/resources/Privilege.xsd similarity index 100% rename from src/main/resources/Privilege.xsd rename to ch.eitchnet.privilege/src/main/resources/Privilege.xsd diff --git a/src/main/resources/PrivilegeMessages.properties b/ch.eitchnet.privilege/src/main/resources/PrivilegeMessages.properties similarity index 100% rename from src/main/resources/PrivilegeMessages.properties rename to ch.eitchnet.privilege/src/main/resources/PrivilegeMessages.properties diff --git a/src/main/resources/PrivilegeModel.xsd b/ch.eitchnet.privilege/src/main/resources/PrivilegeModel.xsd similarity index 100% rename from src/main/resources/PrivilegeModel.xsd rename to ch.eitchnet.privilege/src/main/resources/PrivilegeModel.xsd diff --git a/src/test/java/ch/eitchnet/privilege/test/AbstractPrivilegeTest.java b/ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/AbstractPrivilegeTest.java similarity index 89% rename from src/test/java/ch/eitchnet/privilege/test/AbstractPrivilegeTest.java rename to ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/AbstractPrivilegeTest.java index b7b2e1c57..9f1c1a3ce 100644 --- a/src/test/java/ch/eitchnet/privilege/test/AbstractPrivilegeTest.java +++ b/ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/AbstractPrivilegeTest.java @@ -43,21 +43,24 @@ public class AbstractPrivilegeTest { } } - protected static void prepareConfigs(String dst, String configFilename, String modelFilename) { + 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 privilegeModelFile = new File(configPath, modelFilename); + 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 dstModel = new File(targetPath, modelFilename); + File dstUsers = new File(targetPath, usersFilename); + File dstRoles = new File(targetPath, rolesFilename); // write config String config = new String(Files.readAllBytes(privilegeConfigFile.toPath()), "UTF-8"); @@ -65,7 +68,8 @@ public class AbstractPrivilegeTest { Files.write(dstConfig.toPath(), config.getBytes("UTF-8")); // copy model - Files.copy(privilegeModelFile.toPath(), dstModel.toPath()); + Files.copy(privilegeUsersFile.toPath(), dstUsers.toPath()); + Files.copy(privilegeRolesFile.toPath(), dstRoles.toPath()); } catch (Exception e) { logger.error(e.getMessage(), e); diff --git a/src/test/java/ch/eitchnet/privilege/test/PersistSessionsTest.java b/ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/PersistSessionsTest.java similarity index 95% rename from src/test/java/ch/eitchnet/privilege/test/PersistSessionsTest.java rename to ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/PersistSessionsTest.java index a032c980a..57f6b9d03 100644 --- a/src/test/java/ch/eitchnet/privilege/test/PersistSessionsTest.java +++ b/ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/PersistSessionsTest.java @@ -15,7 +15,8 @@ public class PersistSessionsTest extends AbstractPrivilegeTest { @BeforeClass public static void init() throws Exception { removeConfigs(PersistSessionsTest.class.getSimpleName()); - prepareConfigs(PersistSessionsTest.class.getSimpleName(), "PrivilegeConfig.xml", "PrivilegeModel.xml"); + prepareConfigs(PersistSessionsTest.class.getSimpleName(), "PrivilegeConfig.xml", "PrivilegeUsers.xml", + "PrivilegeRoles.xml"); } @AfterClass diff --git a/src/test/java/ch/eitchnet/privilege/test/PrivilegeConflictMergeTest.java b/ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/PrivilegeConflictMergeTest.java similarity index 97% rename from src/test/java/ch/eitchnet/privilege/test/PrivilegeConflictMergeTest.java rename to ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/PrivilegeConflictMergeTest.java index f78edbb23..2fd50fad1 100644 --- a/src/test/java/ch/eitchnet/privilege/test/PrivilegeConflictMergeTest.java +++ b/ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/PrivilegeConflictMergeTest.java @@ -35,7 +35,7 @@ public class PrivilegeConflictMergeTest extends AbstractPrivilegeTest { public static void init() throws Exception { removeConfigs(PrivilegeConflictMergeTest.class.getSimpleName()); prepareConfigs(PrivilegeConflictMergeTest.class.getSimpleName(), "PrivilegeConfigMerge.xml", - "PrivilegeModelMerge.xml"); + "PrivilegeUsersMerge.xml", "PrivilegeRolesMerge.xml"); } @AfterClass diff --git a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java b/ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java similarity index 99% rename from src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java rename to ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java index 674ccaebb..242c265bb 100644 --- a/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java +++ b/ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/PrivilegeTest.java @@ -89,7 +89,8 @@ public class PrivilegeTest extends AbstractPrivilegeTest { @BeforeClass public static void init() throws Exception { removeConfigs(PrivilegeTest.class.getSimpleName()); - prepareConfigs(PrivilegeTest.class.getSimpleName(), "PrivilegeConfig.xml", "PrivilegeModel.xml"); + prepareConfigs(PrivilegeTest.class.getSimpleName(), "PrivilegeConfig.xml", "PrivilegeUsers.xml", + "PrivilegeRoles.xml"); } @AfterClass diff --git a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java b/ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/XmlTest.java similarity index 71% rename from src/test/java/ch/eitchnet/privilege/test/XmlTest.java rename to ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/XmlTest.java index e1ddd7e72..54a2e1b4b 100644 --- a/src/test/java/ch/eitchnet/privilege/test/XmlTest.java +++ b/ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/XmlTest.java @@ -50,8 +50,10 @@ 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.PrivilegeModelDomWriter; -import ch.eitchnet.privilege.xml.PrivilegeModelSaxReader; +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; @@ -92,7 +94,12 @@ public class XmlTest { throw new RuntimeException("Tmp still exists and can not be deleted at " + tmpFile.getAbsolutePath()); } - tmpFile = new File("target/test/PrivilegeModelTest.xml"); + 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()); } @@ -123,7 +130,7 @@ public class XmlTest { assertEquals(6, containerModel.getParameterMap().size()); assertEquals(3, containerModel.getPolicies().size()); assertEquals(1, containerModel.getEncryptionHandlerParameterMap().size()); - assertEquals(2, containerModel.getPersistenceHandlerParameterMap().size()); + assertEquals(3, containerModel.getPersistenceHandlerParameterMap().size()); // TODO extend assertions to actual model } @@ -158,21 +165,16 @@ public class XmlTest { } @Test - public void canReadModel() { + public void canReadUsers() { - PrivilegeModelSaxReader xmlHandler = new PrivilegeModelSaxReader(); - File xmlFile = new File("config/PrivilegeModel.xml"); + PrivilegeUsersSaxReader xmlHandler = new PrivilegeUsersSaxReader(); + File xmlFile = new File("config/PrivilegeUsers.xml"); XmlHelper.parseDocument(xmlFile, xmlHandler); List users = xmlHandler.getUsers(); assertNotNull(users); - List roles = xmlHandler.getRoles(); - assertNotNull(roles); assertEquals(3, users.size()); - assertEquals(6, roles.size()); - - // assert model // // users @@ -204,6 +206,21 @@ public class XmlTest { 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 @@ -314,32 +331,71 @@ public class XmlTest { } @Test - public void canWriteModel() { + public void canWriteUsers() { Map propertyMap; Set userRoles; - Map privilegeMap; List users = new ArrayList<>(); propertyMap = new HashMap<>(); propertyMap.put("prop1", "value1"); userRoles = new HashSet<>(); userRoles.add("role1"); - users.add(new User("1", "user1", "blabla", "Bob", "White", UserState.DISABLED, userRoles, Locale.ENGLISH, - propertyMap)); + 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"); - users.add(new User("2", "user2", "haha", "Leonard", "Sheldon", UserState.ENABLED, userRoles, Locale.ENGLISH, - propertyMap)); + 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)); - roles.add(new Role("role1", privilegeMap)); + Role role1 = new Role("role1", privilegeMap); + roles.add(role1); privilegeMap = new HashMap<>(); Set denyList = new HashSet<>(); @@ -347,13 +403,52 @@ public class XmlTest { Set allowList = new HashSet<>(); allowList.add("other"); privilegeMap.put("priv2", new PrivilegeImpl("priv2", "DefaultPrivilege", false, denyList, allowList)); - roles.add(new Role("role2", privilegeMap)); + Role role2 = new Role("role2", privilegeMap); + roles.add(role2); - File modelFile = new File("./target/test/PrivilegeModelTest.xml"); - PrivilegeModelDomWriter configSaxWriter = new PrivilegeModelDomWriter(users, roles, modelFile); + File modelFile = new File("./target/test/PrivilegeRolesTest.xml"); + PrivilegeRolesDomWriter configSaxWriter = new PrivilegeRolesDomWriter(roles, modelFile); configSaxWriter.write(); - String fileHash = StringHelper.getHexString(FileHelper.hashFileSha256(modelFile)); - assertEquals("9fac0049862b2b54b41b08b0e12a9cb105894d57a1500d6603c473661f4c7313", fileHash); + 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/src/test/java/ch/eitchnet/privilege/test/model/TestRestrictable.java b/ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/model/TestRestrictable.java similarity index 100% rename from src/test/java/ch/eitchnet/privilege/test/model/TestRestrictable.java rename to ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/model/TestRestrictable.java diff --git a/src/test/java/ch/eitchnet/privilege/test/model/TestSystemRestrictable.java b/ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/model/TestSystemRestrictable.java similarity index 100% rename from src/test/java/ch/eitchnet/privilege/test/model/TestSystemRestrictable.java rename to ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/model/TestSystemRestrictable.java diff --git a/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java b/ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java similarity index 100% rename from src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java rename to ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserAction.java diff --git a/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserActionDeny.java b/ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserActionDeny.java similarity index 100% rename from src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserActionDeny.java rename to ch.eitchnet.privilege/src/test/java/ch/eitchnet/privilege/test/model/TestSystemUserActionDeny.java diff --git a/src/test/resources/log4j.xml b/ch.eitchnet.privilege/src/test/resources/log4j.xml similarity index 100% rename from src/test/resources/log4j.xml rename to ch.eitchnet.privilege/src/test/resources/log4j.xml diff --git a/config/PrivilegeModel.xml b/config/PrivilegeModel.xml deleted file mode 100644 index 83131e8bc..000000000 --- a/config/PrivilegeModel.xml +++ /dev/null @@ -1,132 +0,0 @@ - - - - - - - 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 - - - - - - - - - - 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/config/PrivilegeModelMerge.xml b/config/PrivilegeModelMerge.xml deleted file mode 100644 index 97fb7065d..000000000 --- a/config/PrivilegeModelMerge.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - System User - Administrator - ENABLED - en_GB - - RoleA1 - RoleA2 - - - - System User - Administrator - ENABLED - en_GB - - RoleB1 - RoleB2 - - - - - - - - - allow1 - - - - - true - - - - - - allow1 - deny1 - - - - - allow2 - deny2 - - - - - \ No newline at end of file From 86fa9573f742d9d066a88bb8487bcb54b265089d Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Fri, 24 Jun 2016 17:39:21 +0200 Subject: [PATCH 173/173] [Project] removed .gitignore --- .gitignore | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 .gitignore diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 18d2ca6cd..000000000 --- a/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -target/ -.classpath -.project -.settings/

    ^<7W@(9yr9mUm2tK?C|&v@%Rm35X`o{%fo+w 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%0A?GZzYp7)bLPx}z&<;hZaX^DZ#RA|ACLXD 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%0Aok&dtGbv;D|BgjT@f(sv0`S4#(FudZ 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 diff --git a/lib/log4j-1.2.15.jar b/lib/log4j-1.2.15.jar deleted file mode 100644 index c930a6ab4d4b73c1a6feb9e929091205664bb340..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 391834 zcmb5V1C(S@l0RIwZQFL2ZQHiZE_T_jF59+kv&*)!?EZRoXZM?#|IF-}Jm+P;b28$_ zjd&5iK;Bf80R@8r`pc^o6r1zEZvOfP{pV9oOjU?pN?x2%@vmf1KrMgB>fNyjEB|~A z0Sp9$_P1nmLh@4LVk)W(a^iMz(+ELKNFg_JZwTZ@LPL^r22wKb0CC3O06o$CyF1U z$E&&;GHF8=e~t~3V-aqPE_8RnpJ<2xi$f}bQm&-PMq8EGwYM2JRpP?4V=?*~$j(k& zY;)3^8y30!6wL*u)OqQ26l~$Z;`T0xn7dizj!GL$#rz-TfPjR7|K0{*f0T1@w)kK5 z|6dB&zfu@E8kty{{SBeWKP-j(_k^|%7Hro4h8+E$k$c+N{--Rq=IsB51@E6p`3sA? zm6?Z`^WSj(qh(0{4^AsPBMUQ^zggfP?Lq&4EMQ{fYG&c!>}BTZXzSqocYFEAr6~Td zr5x0LB|1(jRX10!I&i~HT z|8$2YmPS_gE=C^zMjYlp6X$N^Y-RLsnEt8b+MBulr!3ZXp8tmBFR1gE!2E02{dY7u z{H??Oqmy9$3l0~fe<$Z(aar5_JA?mIuK&}K{;5v(|ITn2|4b)qQ)83=wH(xcLE4)b zk>#gc#}jUZ(={XvZmv(3~B#ciiB`0ava=!qgjf`Bk{#Jj`23kxU~h|*8xCEvbKc9ZY+JRa|*zGyldYQ5J8 zeTljhw?f?C(5UV4ah|uwdgEj?xPE?bFS0uxu{Yr8O7VFG!Q9^2{OrPrT5k6A>Fm(o zW^~Z70bD=H`|9qX?Oz`cZe2Z&w!XLtDd;h-K6^|O3hZ-|?Pj&NsW)Fg08O;N!q!et z%MG>WYC}$U+42zIDpK-&eL{RQ{uGsb1OZWUot9W85v2); zwXab>IuNoeNSk{b2aZQ`5lvVdou949HL$ebVYDX5tk_Ct(M#$}sD5=Y_UJau1u&zz zP)KO!KxCY#RyY_OXRbdH9lJji+H+WJUTX_{-MCAxD(vxbBMc{=jPf&f){FWBRjV&j z-QBrz&a15!cQRX}3h6z&;2`+f%0Q~YW%80;996P?E)TkW^vX3Z(ONH<-N#G@%@6Qd zxud_}4ly+th8Q%`4{zx90NgfCgGm-N7IWSQRScmaUu5A;k@nkoB%|$n^9v(y=TtXB zz&r(F2aZxIx|gpf@HukRG6Fhqx6TzFINvZfC2;!@+0sg#4m35?*)oBIwu4%ARp`-! zjU2}BXq~2#1xGe1Ep)u9&IaspwD^z$)?iE5N+*xoLe|H+(Oc&xVyN0=JOLKCSmCVvN5yn=YA#{OF0J8XAO9?qM4=exHcY39WaAIFcLqF_z zdT$cI@^lYPb-5?AknRXYN?-i_dVtv&r0(paURw(|o=AUO9*w$tQZf~j zr?hdH)roV+y8U1jOC(O-SfI^=F8GhLZn733MPE{Up4vjyDo4a<|8Rm4n$5_~(Du5W zGv~BGU@n{QI6xUz%fS>H#%RG-zBy3f7#tkFAjsR(UL0OVnn%gOlEfD>Yg25=Xmzp< zZjhZn-#*adK;@T{9vFw}U%MWeGP`diyJ~UNo0UgDRJ)tds4X#h#4D?;5#k}YELb)n z3cwGFlBzr4~lHYLxRt`qFkyJP~7;)w(b<2vC z5YadQ(8G>L8Itj)YaC55{sGdjBr=h}N;`cka^LMZ?SXjtJi)ew=EX9Ty!p{BNe2L^ z+Q?n9N7uis6Hw7b&DIGH{0jTh+0*x}?(W={^{4hT%=Ix=-=QY!B-!J?uK;0To`%NQ zQpG-Dth$NvkId*87-AfXYm6&emi8B$62Euu|58S)r`-YlqR0k9b_N#5k;zL#M@+sf`5Ua0ke0A9Wvtn`5EDCEwt%RSV;!E$o|~uQuE|8k^UUgy zQn*VsbIF8h;Gv3`V2`S1^~h2ja(qMj9Xo%P!X1q&&Dnbc`ALMTAa1PAf)u&mx&_2X zCgJ4V*FAGU;)92P*K`+?TESj$X!OZar5@Cb17;tZs*r>XviROl8HMNZ0t&FW+foed z^Kb_j;(~c!pBvYJo5^<&W=C?WimUls9OG$`U-<5~- zIA2nea3X}P2;ax}8xv`GEf62osSnj`76#gUV0;ejvaqi5@*QGBZZU9id?H*=zqug+ z=W=@1{0As)V*F+YGAHkt{z=hta&|NL12lfjLjKcHM8F@ zo1dK|!F$ZotRw;Zv%6l@IPD&V1h-`|>(0nPQQ$|Ylw88YCqS{*Ahg%MmDz{M1?D?v-BycvfgT?6aXZq1;2EcN2U{EUYc zZ?|+e%Y9kcN~?9eB*)yNg}(>$IA%c;d>gzQBbUTAYD5-{-G{=4|JWId+=__3_IS z%t<`gL~>XlqMg|r0=r1pEPPHlrQf80oBQB09tJb_ut`-RMqQ7@QtbEp2%_FMovm%g zo$V>2R|3ce&XVjMP->4Bg&aL>u%F|}sFR7kEK2#0$-5$2N^u!M^4aXPL5V}$4iqE7 zIK-fYA+d_SF?y`bC-lB6HE6a(g-Q!XyeSH2pNYxUg3*SHoI-NguCgui7@49eKI%UR zotbjMv~pG#fs^Ol2C(TeHdKKXj&OB;YHLB%q{+4WIyjCI$&incP{Wf}rv z{V7M0sV+1-#Mc))3K$LZI^W)}icr=NYDx)vlm&`~Fvy@)3>T27VTnCtYgmDv{5H6| zXn8Z0f*dt|9VzQ4o<&kDCpW#n7okp;J2R|G;??HHF)ai0b5mWe!qIe#ONN3b#)%Dk za!CHD3^kqW(8t>SLk{SA z-r7FIc~`agg#>Dm$R%PY{1-T)WPfBm`MR)zL}0)zwlIE%x2hQg0IX}CNZqdZ$fx*V zdvFK*12~Tf3F${&I^c8y$r(Ymy%Cp~t*ckj>3y!aAy=HXLfZhVwU z056Ec5|4zuxm^8|$2!0#1biz{p{^)NCjT}IqqbBDw~pmD76su{4>=3r=Tgp;g$M_D z@2jL0y3$2RT1)c_*azyRbGQ_e+7@10VDSvDP8AsFp_sM)0$P94ObD&LQ$ zwKunqR_|JGhKwJhUM`&7;9Vu|;crRhCG>bAD0Iskn5`y=GLt60t3X;hkE<>r{Za`B zH`!*q+1Wued|Y03e!RACf4iTa<*a-c{_u5qexjUn1{~ad-hE-ha6WfgPiURmSo!FpPGkt4p_B)KdCya7WbBitRe2W zMULzZY5UccTpBY2OgV4>(Y6vBzm=gp%8?5J{hY=2SPX?H5_ z*4)u63T-XW+ai1sgM)iklLD+Y?fO|I8h z<1PaxrM?XRLQc_B$#(h~(*16+HRnD~B)qNI+*w5NS*h2)d|5}WLMsyiWTMpxb^B6Q zUtN5CXh}xMHvPRVZ})dNXz-!bT9o*{TUkuT!>+mSb6hT<-A=l8uQPa+o>j}9Holvu ziR`1{%%lN@yf;&D8ut9GMUj@(2<4yP~;d)0TrvWER$Ydw{WbW03Ui zZ}+)D^=F3*hBCjaLybl8GAifKBcH6_!HEm)Kswif<$}?L*e-324Bw^~N(Q?YtWGNK%#oR5%?Qzq_@!@@c5Yp51^_lZj zI1>Y?fZ*c-_O;UI1-HB*&O_?<CBEa8C|75ifq&@LFAEuOw2V9{S5NAYLQQClFD$jF2p z=KK^t+JR$phtgRJdIvFlCYLT3=#iyk3>M#@Hgosu17mMqCXy%5_NdQfsLv`_j+6+3 z)g=D5rVCTiUfHqmR`0L5C0TiK58}Kdq=7G)+MFIpgkJ3( z-N1>aL!0zB6}%x_gkjUd=0(oz+dHCwU22LifrVSazHwWdVhWlNLrQPZdQgdr7E&+* zMNlflEdZ{+zjdN#C-1k_kKq8Izwgn#nf)E$p@4w+|BRCe{`Bbo&-mpp)5-rD#4wn; zns#e#xon6c1;l2#a$_i_16|hK?ODoI6HN;0A6(Q^#L3-qOFVt}QouUIxrPzZns}6C zsm1Z8ZFS5{Hh*Q!m80Gv3^lcRtHwlun9u) z=i~^+9CQFZU5w2seuNO{LzuN+Ltx3zqiY*Y4+!_HL835TKeQTX2-o0I`?pogtj zg!5pn1`RLng-?e8nVZ!v|G4|`?c74h2;)I>cDrWd?G@3Hw>V%8!tFCjy(^+>vH;Y=OzIn?b;lFTXmB&+qwyXhNIJE7p?!NhZvt}qlO79mAeC{huayqgXi1aXJ^}rCqkr8S2p-mpaXn*>{gVjtV_> zyds%G)2l+|yE-hJu22m|dyMrsG(uY7mRiUuV>iSyjHoOiB_4Y9JihzlUYN?GWq_jx zx0gWSBtCN4Kv#)=30p{}U6w^oqIZ`unm83_QB626Rc{ja zD@rU@fW0Z#9qFE&Rs}ss{5S*jpf)YVY8}>SP|KXn5T->}BNt1inaZyb57+?f5xJJ5 z+uW-9BC*{0hs6PmLZ0wog#4*yH=o z@haAPN5q!Hsy4Q5E523E!dIxCp6n0qp1_>ymzy=zq)7G(e*AAYz;_kR*5CZ5V!IRR z_I>b`e5`-^R9*hck)b~HA4ijUHg4Zc0zj@njxB&0jxK669C|X)w|!F~6h=E;Izh~b z`*dJ*bh?RDR6C?9Wu6~5SW_n#lTyWywoUAnOD&+kViQSFA033?g-{Eq_Z%fX42HD8 zVnZt?yy34kS05Xx-I?0yx%1Udv33+VxJWkYb-wn&`u)V^g|(xKixm26ep|5_M&~>1 zT3$n)`Az-3$Oy+Kns@l=OWn!}?{`$N7+vp>J_?Zom>1!v#oLPz9^BvWkpqf*t`%$>-NHGohszfd23j}&9y)g-ioiBv zpZu*16*P0j8AOkPLjo6emr2Pcb7siQdh5G2u$ePO6TX_ZEC~G83Gu7dIT5~++GI*OkS^eJjEKerHsAPe-jEIXy=FEfa zaiQc{QHx!kW46-n*Dy%K?x)3MTw-@Z@8{Z7i4b#9(E zRnM=F9Rp27`0<&e6S3C}B8=9uiP0i?S8Sn<_~a^G}`3zchX9VAMi zKqY<;q~?{6{s^kLw2XDT60jVd<|*}dKG#BTj@5Tz`+TQ9`kZm_R zS=sKv(@GJ5v+*Pf>Nn`;u>O%pUEJMiC)KOyn~aVNs$OawgP%)1%gCpe`=;DnY8-y_ z0|M9qrX*EN#?8(AEcFUzm+%1+1NY*3L5raY;kr=JrKe2fqppEaHdXx~&0x2@`OIH@ z`4hk2G@G;PiT+r|#KvEIfyvx+^}^={GG%V#Wh(TU^}6%=Zq@q@b1>?2_!gW4>Z8(Q zy;ItWlEe>?v-^p;{;aem=L+XRd*0NvIytvc!nvw(&AHt-oZ3UOH&I_}Vs3FxTpoL( ze5hLyf~f&|`-3?pPjQ%oI~{R5S?YyKcVmg(kj zUZ}SK&<=US?kf`4-71s8VdBU9MTV4cM_KfO_JwWmF<la)2{a&U8HFtIgqaf#8eb46Rm_P_R@Hf2p9OO(tTp4N68|53SQj4RAW z(GS59nD|{-+9^@i9g>sx;?kCE9izFapg=VWrg;TKJ2D6bRt9LeHdEWW+Rp9*fETbP z@U8S)>B)n{@AaOW%T?1x;ee^`=g+QLzZ<_DpOXO#i`l^#b>Bk?+y@w6_>J~<5#dcl z#E}e1M-#^+)9vidbQa}J9seTcy-~e2Vv~lT@XidsxM+vQLtcMhOkT6i$ss?M>omYO zf@|Zbo5>eoRK&40`U4>XezF52Y#&p;fu%y0r#FBIAH{*wbpV+O;j_Y+XX1OQ>7_+C zXvzz7PAUNPIu~$I>bVk^yGJyMFwRAHz>PgS`NA#2OSKmsVgN<8^{tYZQyiid{=xnX z8L1mYO;4P}tQ^`V2}i-k{`*?lW}Cm?Y?xibcF(qu@%WH4V(BOthG$$}deXV1EbM5^>Ep&A@5sU4 z#v2K;+k&JHn2El0BS~Ytr?Ac?^yt2I^5MKP_a1STnrZ7ca4QW@R!(WG=39uh)kUK1 z1S}5gl+!Xw8!;QEI!*ZxQgJqEPzJ~46KqW8#FBReyF?R}kq95* zm|2W!-0hkeM z^^5rsBkS`1bgRvjErs9T6F4NAmU&Rq(4TH_TGZ=sTB$T6+`%~u^zwQ>GEBM)VR)Px zgBVa@By{ji`_e?{*ak3Bx&d+_i;uLifq)2$mRZ(*Khpsw~*igL%C$WWxKO@ zmA$*Qt}QMQg%!mRDCb^d3k(FRqO@(NmJwy>g-yyyQuNF;b10cva4waC`TiXGEXxgI)W(E|{*fSJEUGP4 zU?**nR!)&JG<=p75ooLQ>$3D>lX)Px#Y1{;0Y>Dj=s(Fpf08EyY)*GWR&-6}SPqU3 z^GT%@PexMAbcvi$2^&-L`$o>`*?c- zOwB1H1TQupPcW1y7+gVo4>MVqcqQ^{5(Xe*KvXcb)mxBO(l)>O`pqdCO11AMw}bQF zM03@$hEkmEPfbGv1}|7p+NIWzWlq{FX?F#d51=`8k2kkp?YL+6rNkI{X{rCF3 zIe7Y!{$BZgQ;tB$@RUvUJ>9GhTZ3ns1m5dSDF^=4Ychf%BzH%}v`J%ca&AR`Qa=ji zWwHGDvb@w7wbpSm8#nnhxVwx!8DXFQeoKoN4xfc%Ox{lRR*P^tdb&roU{-5CZ_LFe zt*=`T1S7p&S-;MSPEP+FM|r!@#O|b>b1+{p!p7a(Ky)JNm_X{jcROM2K9Kn-<&7U-Oq}E!bk1<;L*=sq-cW#JIKrdR-l&jL%*n9u z+qg|p>&>#KkgkM6wLBiGjgv0i9(n$rS;7c(ebTm>&uye9O6d^mSoq+>*tb|KCmf3* zTrS$}er^D8w}Zfl&AtN{<5%qJaPQgh8}_#AxaVXukV9t>*9nZ#NHZR$?v>~4Of4CD zT>x>oPI;I&RG^UBJqM1TEO$#rWP<3n{Qy!Av{sjlTIi!MTVX`?3r1In3FGjA`-IrU zkZ(#}LZs2eqSXMOu)gk#s3RBcq&1<}62N?S(98T&0s zh}ob6kk{8KThyslb$q8zxvoahSCJ^mWT8@eml;o+SU-V3m9n?tRgtjVAl#pGHeIew z9PUnTW_O^Bv3+PDkYP=P#J~Vd7=xP1byXxG>ALEwr;=lfi%dCE2kmU?YF&LY=(V@n z3=T!F>O#u>nL!?K`Rt1v-Oq6SYXit48WY^eaGzR>7@bE0^^S$2uRR=gcjCZ>Fr>Q1YIcF(B#e}xLa-B5?_D=6ZDE5i@I+9sN6vwPZDiIn* z2mBr~Bzof!1r|K+$|WdB=wanLc-TuS*G{y0sOmHAHd!tGbEGgOt)$ZA_KF3S^_SSc zXWXJT!u)sJ4->mYVETo}&Z)KSNv1!EMe7I2T! zVA{ewDDg{@7Vaj6(c99#RPDjC;54iyj4=#e>e(mUS(DX!_)<6%3|hL-*82SLtf#s} z4J$^_sGIxK`4@c2Z+r5>b%)*Jh@8jWa146dzC%p=?-zZZ7VACd3;S(QeKJ9buB^9R#DeICQ&1 zn5z*5iAsawIITcbh@UEAWY%@5lo?wjm3>Vp#2@P5iI7WLo1!;f{BzG6F`cmX5o288 zOL<|eu15!e&3C|5+*b?1tZdT=#W^d_X6MnE_le+Vc{>wujChYJ^Lsdh*iRouar14b zb`Gke^gkR@!*=0!_#*uKSRnJ1S_S`e6f^YC#qqCVK~zCb3g!kPqMv0i}j4S`7eQ_Gu()M#LoG1U(;_N$9iE&4JM>A2VmNg0OnoQ9U$SHkhEzG^Is_)`> zBJothL(+v1({}=pWU%~ln+Jbs7q+poh~%Zj@8_ao9d;X8C>4&( z+$GZlQ#(HdMf*YfB8o6 zUM>J5uJi%__lA9oJd8{K12QsyK<3ZMq<=p0{71tw{}0UADJ&|Y`P*}@DA5c8NN_~s zBQ`wGkbH%Z0*RraBgf$qumqB??UP-v7Tm%EM$=!^Py`C z%OgyeuQ26ZV5e0kYHxQSNDnjVFe^lQY0=R~I;HFK2>yn>mMTcM)@W1yv+t$kBn`gN zTH9yAvFmQL$4S#Ey1DwqYnJ}kQHKLWj>1a4*hX_Od0?ok4+W;mm@Ta6y?z3UtnGS@ zuTt%$^zW4Rix6s`)HBIlm)oES{B7B*RW030#pI>c z1gjWq)gaR>0^|HJ?y(q8nMX7pL(UEDpSXF^s1ma64>$LUBXwUiElJ`7l90_6l$gyY zdZQ)hZdaRW%1Vx<(`ApQYHV9a0g3G(+Iu3<$V5%tHzN&gcMXZfp#tJo=^3L*JF!E`@y z>C>SzM}AlBN}W&>M2;HEq99X?Rx4ok=c42Thz)0M%Jfo zUQb<4u{O?5eZ75tQX04YUyMw`>nwUc$$|qMudjAAF9?D5$W;78R z5rKG}t7FZ#V1-LYh!zp?@XYJ=)1zS_o6OBN%!N!HM?!p}7f>-wXK2?~<2(;*pLN@) zX23g@VAt8%^snFJ@lj`gi0*r&;_0#FR<9eVD|5qiX7+Rck#8|r?HJadcyya)WS8dS z5UrO*SDt8t^Ax+MCG3FhBN_o`sMw14!w0uz@BJs;*3s;g(tNku;5~F@nb!vaF*Wb(REbMT)T_m?@f?siVY5LnJew z5!P9V9^&(p=&x=zL{vi6s$FW(cS*UMSi!J-*0HNCPWm=BupZd; zG9V;o{sb+3pH+%zBd!U{_P3QMZW~$D`@)=Ds<83j9Kpn;pK?#V@3u=9o8?T0j4>TQ z^&=-;2O(h|Qs1pIA&Hbw%Y(@b0$-F1%I)aI18_8?2#KvCx2GM&-hPu!iXzk{ha9rG<>jUgH|WKd{aDYz6|@2e7Q(nwdh_ zVxf6*qd&+tN6@U;JgY%k$0!}&$A+S_mvNZ=^zL!# z@tLtw`0e{1izJ>9myd5UZ5cd}=7fyO90DMUl=9Dw+#@E4@Wc~g9Vze}xLN$kiaJcU zGZmhW3Q!$l4SUJamc~|Z->>$Gt;vMbd)0GOP=nJM&mL@qo4$ZdZ7uW(8$klN!*R0D zFeSHU7Zjwb*-2_Qd$UxHLLAXmU0w{o%>42N2j@}@XBh9CTFm<^@w|hFIA&Z@F7c_P z;k*saM*T_b>OGeKxNRb_T1|HA4ue*_Jdd|=(0_9_DO0&_dR;|7@OwYgnjl(OY96)w z@H~}UE@29*uJQ2j*(7HI5=_?oYHJbB({1&z6t??w3c#p0ZQIav^fnp z{h}F}Qi208=<-3B z%_>`m`=F*b3AqmcxFkZd!&_-#98$PT(rHbT7-LIbg}RpJN}HVURk#EA`JFp)I?Jv| z)%O7RZp_*4p4%RE`>@#c_!>Py5tiIV{q}39*0zpT|6T8n4B9zZN&`EjXPlRtbSBv` zH^{;-qKcyid!_v;w}@Inp|tQc>!Nt9QUzt+ZhL4b*ZJM}E$}ayTdj&KVb{lkxF^o> zObUnCp~X?dvjkG8vnga@11J>7s1+z~;g`S-Q<+V1Jdb#=fJR8iv@Xwd1+*bV-=JIb zH(KFQOYLbg618yB44Q%;&7Rep3NO%7FQFl~8*y2DsOUpSD@4j4lS03Jut<*m3?hKA z2SXM^a>i$oN7x^8^Sth{>P2PHQDzN>;Tuq@n^chnx?F!Q!j7H^$j}lU)E0*H)f#6p zmc(3o@x={&{X<58+sf{``4c?X|4Bw*`=8)}|4ZUqn0!a71t!h}6hQLY_{ean$~~2>T4z5p9H`KvDt(x9@N#Q*X?pl#L|EVtiNMra|GqJ~~G{LjK=C3Rxjo56o!L_Jq&toYt zrw1Ntx(sM0CNFHP7S~o_(Pf-uXP%}n!mjCE&cmJxl-C!k z{+u95h?iVZP1z4KKJ(Rw?A9d%#VU$6vQ+OVTWb-Qw1FvDBN=rWNm`dY4pK`kL$y7Yy)wtw2!D(Q^6y2m4ny<$>flg_mPM4BO;8^J#CO0*8 zMmhmkb6KhVYhG>;qn^q*qW>nHdvqZNM8w0kYGt^<80`>eLW=#ZVkT>8tLcJ_Qu0(n zTbS{U%r!qN{{{t`&nY9H)qpw$!Td;@HOY&T(FnYfBuDa3i4?YNjxCdNWw(tKFzqPf zKyG>~D~xvlcZrW(*-T1Onv<%(v%ahEJJ44@N#g>_W zQOnADVWFrH&D%ulp5U-yH$9HO7OOsSi0y5Bq7Z5U|9ce|E~#E-%d!1E*;$L#a&Jfs zHaFN>LZr)*_M2!VKVSAktQgUpn0!Zo{V-4!eeB0QN{ZT^zfD{Ta zgv>_>jp7(1PDn2N5_W+m!zT{K$prSrgLpt;24J}<57&7C+n#=*^o*TvNezFgKpI6= zdqKo~4T19&>KmMCdOk~M&i2Glx<+q!m4SYE;xWs2^F|8Tt`bG3jiT_5BkTfqm9jc z9QP-1`2Lf?VgG+hsTMpm8L@B@BJ|p$qsZ@(-ytF?Z<1HBh78*c|TkH|#sjM6s;a@lttSdPh-~DeR(PPqj%CA&wf#K zTPcY;C2u}ioA=N*YPEQ`A~aGw;vBP_ExN3>n(}qnFrDUJm9J3Gg9TPgn1kq&s3xd%3%mUKaTbrz~%#k@~G*(O01%R-j~5^IHOqHWJIFbWx?E{YJ7 znuNC(5*dC5p_|9%qnXDczspj9b6UnFktXM25o;x}2*vGV95!MP+TA5GUAWS?Qdqzk zAn^*#mLhyNK<;y#EQcp5c9Na@G(9ZcMJSOhe6`ZhpvaQt{(|pzP(Yq7qm*o7A(NUY zvg8%|Q%>z*R^U5OsxaYSJv*`%i01pWLqyb(^M#lGV@5br4KKW*1OnRE{5PYvf9Qh$ z+pUXzp?y`CA8)yz<&V2Fyo|7+j5Hy^;Ix!5$O}*sfdc8$!O+m0Bq_!v$A-p3hOPA? zSNv*W+;)sF!gPoV(4RZp8>`nV)H*d=E8OfVzh>JVGC0S<2A|JnGdgozu7AyTqvpOp zHz!V?(D_&LfksMc;E+M_XX^BZoZZh?hHP&e+UjDe#S@Dp1mLk{FWJwdwoY0@Q?ic zgt|0~b$-I*kNlnag*?u87^vrXC4lY20jT8Qx9dk3WbcvQ&U`eGTpk11W9U+-1f~|4 znrD0MYbb-SE5qKZXdj|W2r`g}DJl?iv_Kdt1+y?FDYl~GsLP8FS}k_938sg$D|1+{ ziyN~#DzRSl;;^SalMp7Vm$5Ps&5)^Hh#PaLCTR&vCa5cO4DX^Fb1)|UDW;ck3B?`g z92Lw{&j2w{Ql80 z*ty|^4$ZPC7X7wxM4g%lnBFq9DE4|$Oc^(&;)8M7eil8t=P2uv?l7Yz=h9fE>bYU1 zjI1;4_o0jFak?+Bw^E;y2;C{ZZFYcVnaJ@PV+Tj_GUZ8>5 z=~}OY>DS>YiRt5cX{5(j5M{trB;i)CF$%xw8!bSvI*j`9Nfy92Y~ihqI(xdG2KTCU zEimf+)3IW>PuCxw?sUB8jK2E;H0< zH#GEA)mPNG>zVvQ2{C+(NRUfNe3F!~krH7+zOeiTN8HJ`J)aWoN~R&U-dPC6?Y4}5 z!D7pCPHRI;b3uDiZT~A0-Ma5G*0JL;C2E>@C$izK0jKe*mO)3awPF79^vEnGMdtim zgVeL~B9*SZ>IA%^zY|$}19pX;f!5*BAe0K5{JI-YglbL=4nq{ePwlZxmHJ8T}R0-?#x#n z_2sr9GO}W%Lapl;9`;IGQ%{2twaWrtT~U2af%E(X`N~>Zh4z4=j(Rr7d1*{ajJieB z_$GWsVK%N|8f4iRHjFz2v&nIiOl`hVnTrbkq zU7RG#=?n`Xho4|1vAbj%u>yg5KfsY&Xc)O z2pvNGuG!ADl5@!J^{$yD&cf^?R7IM~2O@MltsA;a9mko}4PP7UCTi2kSrbQrlQCU& z0w|GuuaG1e@w1srvW-GMv4At3NsYN8M|Wd49igvZxfTtHpN|Npua7-u(J9lhA;pE3 zG|O4<@p4y@a`82h66-4WF=-<8S`+MLav>tX#R7hIrCJEKnzOcwK0X|gq#S5_pdQ$s zx@S+2*DjDq92czy36y%Pg%OHsk;p(>`Sv^`?UL@)GTMy9A# zWJlXJ4!w0v61{Vx_YUBRg-AbaPm@J$u*c0 z-x(2tCTIzW*ZP6e0;hmaNtoqA(|BNbX0qEF-nw;-shX{IR5K?k7t8FErG!mGjNnL0 z60UvJME|8qi+v)yayHYJ!PHC@^1C7wMpCa{J2Yz5>LpPK#5Kznbe1p)0i|=3Y0>f~ zd4cE?aK^6-ii9h{c{^`L9#ahbv%@54hp3}j3iB{ydq*-I0x5Bw6ct?FaP+x;GA}n{ zRU0-fGiqZwwqF( z+EH6L=FQ6cD!k|DHy~5O7Un_FlggnTspqam!J2P*DB6WX(#bqXUG)sGwF9d7zi@6! zwyTIckz61pF5PV$D8QXCkW(F3py-mG3QoDAzJ$V=x?~XiX~_({N9ji4LY7 zbD80(utFZ0SXZE{YY*o1a_i@ixCueripZuV+c-EuJ&IR>?Cug5LoDiwfhxI`iAhMX zY5l=O?*PmAEokKkqqBG9*e0TpeR4JY>@6uCUA6|N03b-?azFX-Gf0Gdw$M_393zVk zLaHw4=1`QQFF~XUxh>OeH?bB?&4aColGERV#7q1enyi7MTnr*@Z~XmNki?RwX0WJA zMKc{@z#8h=r!@5+<}k=Y8py%;%x0=l9pjWNg$QGHzvOGH#Au*(6s+XFo@?cf01^nx z=y2Wx37@W-TIB;WLYqe?({0F>u{AgL#ipdlI(sB5w+uM=i1(y zzcf!>V~Nr|4JvW3B)HFN8Jo$yi}ZKbm;!LT1K^pPfMfr zvJc%1O+w7{<9{%(_6gG!Wb%s)+wH>f%^~$-1`r{#SfZ3i2;l6a11c!=S%-3ma^V3T zo+79YIQxwI^m1Kx;T}T<@BmIg1I3qpctE%Zv<}3X($vN5iP#oBz3oS#^??yDE<&R) z+mAT*A%S5ZQ(v1wxj%oxKE8dkYzvLkvJHR6UWN>NiH~c8uz?38ykK%eNaP|o{^CN2E0PH=M;lIOw zCputdHYcnN|Aqs=BUAOD?_i^l;V6fv$+e>YvfM>~K?R*1A;eXdjtitDF4huXN*Tvp zD;cpZModSH7*bVBKg$R=huaRz;u>}RY+ z1`t#5?+mqYR+7{4zosxND@@w4(&NB0@yx?nxPJuVWCBp9S7O9UZIX4AL94LiN zC78hRrBmbOCJ4XpTYd1KvxRK3&=g3LnQM*iOWI#WHz2tjX(}6HvmA19697{=RmCYQ zi-BWC)sfon;M|q~qoo74rO#=Ib{eZvgiO1HQS)I?+fbWHy_cP`rr~%3$+I4UaVI2J zf7u%(CBboPg+*)&SyR(UstC&?WiwaM4ali4kCu&8g?M4?5a7}oNKJT<{V?*Av|+D#n;Hi ztJRRj{m;Ex8Ds)V7K{dB)WQ)o(99rS9AEv!cx_TiVA&#U#X()XV)d-__%LxAEu221 z`zSz0=3Bwq6n%(W1+Q_8e}nq2A`@ii4$c1JO%D!<3v8h%Zfg)@zEv-wp*7|}{d$`a^R2;gi;S@WcOK-IedaMEW`lzp5a|zp=-5PsmPBR@%W6YrfCFQQH=kkxo&-NIotFBdeR};lT zSqL181*wm1D7ki_j5l$-!&4z6Wp$NNGdBW3=Pa5p0`(UK&i#G#wQ@%uCO-9aUJa^}AzI#BqWjl66NfXtyoZggvH# z3U2pct>N`5tvIhKOP^CJ*pQ-NJ>l}9RhE_94%oJMV#83oDhGE0~S86 zzAi2{YdET9tci>pCP2Ovr7<5RaiH*276q6>Plzl_o>+SI_i3U9I9Z`G!{fc8?WrazRg&(>%SgXR&g<|M$u zj8MO_*^k*EuTlCfnX!3Nq*bTut3t$)2Vx!|%HC$7Ss201-$=BHeMLMFLyU3~q3V*> zZOIP`T{bm2XcoveNkSt5;~Er!ycFksL#N`ZUcL-dexqc@m6=tQDsf%HKb^Zybd){6 z!T_%P8wva=3$#Br%4!J!sY_Yqgo&%TJ-E1?kTNcNIJAtFsF)Q_?Hqgi1o<4<2=mJ2 z#jd|6{>sKp@te!wLSd~LnLBByYGVTWa#$KPyBf4=Lr%hwcDHZjjzX}yza+HrhHgR} zNShK_TCR3g%znUY8LHwvP^~^gHNM<;y4;63N%$VUEj{#F)f`i862pHyH{88-)#rLL zGda0rD{y*!2a2>?G)7YvvHCu&6>G-|72aq3)*@e9J5HrK?SXMydvw|C8w;sobH)vb zo^xRV2L=hb;^RW2v1!idVRXighJN&5_f+aYCEcRyZL~8@8M&4M_o2v8XX&>Zu&K~D zgF>;&`&Z~OF<>O;AF>ZX|CdwPE@0%5m29dYi%AX zWZJav&5;+BQMiYbOsUs3vOT)0)>~dCsfGwF4FAx@W+g-xZs&_F1Yb`tu09fM851GH zSlp%U_{FTxh!Vfgd+X9C(O1oL70^!wQ6nuA&{|y{9X$L6abpLN@ukg)1NiuZW=Z0u zqN4aIdSu-GM)Q#kzc91!Rp|LwSlkVsuoBEx2 zYO{`_+walQCm0BaOUV0?btKPYt*{1F>P#E3A(oEnDZ?=m|5v<#xXnZzFYL#)CIX-f) zL3ZGy5UYtd+JW>8pHIHOsrO&9eLR2LRPSUrnm1k4b`rp|n~h@<+pKAy)X}!+4ky1A z9iGyD4WS6vM2mG$)@e#FmqB)Mf4cblrYxkE=XjS>YNP0%@RW~96=HH0NY{yZx@TY- zS4mT>_z5ajE7OD61V#P1#X0O#OFRV-`T_y~s8QItIPVeho?7~5dvaFg%Xs5e?a6%e zQSC{515ot^c!E*=)?J)&dEzVnqF4PkJBM?9;>-O4SN;B}w{m(4ScGeT8(PFWI@jg@ zoU8siTAZms;&hIJ?_7vU|d0P&#d8R*^AKJT9BVha! zJ3w#e+Pj|cAJ2>wZs6XaeS$eBvnOf00gn32nIR$bq53w%uWIFDT+nE#f9kS+y_;VK ze}MJsu0r{B=`Eu2z^Dsz)h#N*PhJP;7y76>WV4N6jh_eXfwT4X%c(@Sz=_O8qRzkR z5et;z&Ik1Iq6-^#_s7=2i4Z*sOuLN2ntSR?m6d>bZFH_aYzY#~Fu`vFwF!N#>Xo7m zBW&CE-964hA-N0)ZKmPfKlT@D2rJ6dN%6Lul{gM-pbO`u%%{^n+mDi3O>;%**ZM}SxJm58_Z$P%cAPh&wHr^O0Tyx)C4C zB~H$jD*s{X3U>caP1H!<$xk13e=e88Ky2`%0KlI`wr*O8TNO9%8(nG#cHrT;|Ixpn zHl6xo+blGoLEQ>7ivCnYBI4pib&_zNBk_eE=ej-k4qdI+C?3I z9Gj{=jB*9fGR=pmJprv+D0aT2Q-#3qb3iU0m7pvycRcznn*Z)^Fjt{jg(U z8|)h9^&lQ@iI3E{<$`Gfoq5#z?3w3*bl zg)851C1)BcXBlRwPRuzpqMu3K;tWDf&N+f$C1>`wW2Zf;nRAq4P>kwh!Phy-~hRV9H6Y1Wwzg>nBF3i$D=O;H7y#FV`0MFlDpkQ+Vr z)p3hLqH@ddL)?|~s!0X{hr89JlfgWuVY+>=Mggw(NvT-t%i~od#X7`7r2hV(3Ql9s zXmo&`Br5Q5-e`kyw(c2zFkgal*cIK6XjR(;i5p_TEq^xOLHrJbd@Qb6))c1! zunNp0(c@)@sek_aZ_Lze3A1#F$4k^aGfea!*+dn0J%$fOb7j=UKHZ-m>TKBB>BgQ4 z7Ih+!oK3;@mrDFM#F`bc-^&}hFt)8t<;|zO?0!#_2Ior<5;ZdPPZr)~WQXpU*Y<4- z{N7}X#`jtDTwAX3Cy>yOIRgT?8#UN7K1Ur{<29?*W#Sx#I%d~FrS~#H7#|u09hImp zu%$*IE3r{Hl4nDAcJS2!AZ~z4dcYR75YfXXYBBPwp)|3BD(om5yCZ{D6Lvsak`HW# z*fwOeU${uOoTN!!vZS!2VLinm>?r*b{s0nAunk^AkY=@BLx8Pw_fROcTlY{bc422| zYOpeYZ-bGbc4~=Jr9EQtM-d_ScrW{j&-_Ai5NOnW#A}`2aIax)SZw-|J^ZUCnfIOS zYopMOH1@%p%LOLjlhjc;UDP&!s#s?XQD?RoSP%2?C$5Rz_PuNK(AL#qW*@+M!4xo`tZkH236%%l6m0&PP1D^BDne~BG$i3G}7kZD~SmgU`3`wvDFc!IHzC$ z;topOcam850ba1jIy_K66rb^P;f8C!^>P43>o0P{;0TD=mFPsj9+BJw=uF4A2|2mb zcwy5KB{=l}?(_qkjgmpt6kJ<0Nm_F4BSzGP2>W6N-$&h)*l&*?W`{TGdq@n*l4?U! zWZPso*n0t@A_nIc4hodq3qkF?yG?^csst|Kl(_Y*@Oj~+5r=F>r7Vj9&z54~dI8#3 zgX=V4dQOBcTky7{^!$UDj=QUL1R!G!C#U&{o;jQ>p}u6U^!!*jzp!9Emc3U9R@NI( zq^@?}q!mP|u&R9OkWDv|Qm_L&A)paG^W38Pn!~^ojbj_cgMY>SK9nLpWO6=D5bEjAH_>K zr=ptiks^*o@jga4CLf9}c1>zJ)0os`rxC3@Q>31>zC0SGd;)AHhgO8*Th?MoRra31Hx7z(v_cu(ev=PKnZ6ovj&Cs8-Im+RUoQc_-6X-? zbj4l-+~9>!K?8$1UI?Pm0HC1;#06yjq*96l&4MDJC_%46KT(cN6({bisvs(W$!dy^ zV!KU92o%RFq>F=xt>4Ki+GQk}NXhGnmoBSo3C^E6&>*1;ZZAuzkS5?UC7{54-)`Qk zKdXz6UPlIPy*8`1;gggo>AUJ{=(D+zDr7Jqak!DEiS+=Uba*O)ObeF*f?WrDW&)a` zDDU8l@HeBwxEnxEBDN_RMF%l5z?KiXm7*c-u`@tu3>k@q(p#Y@?n*JB(87cDDjRTa ziaqcU;$0(y*u{|hXXT@N3{jJeSrb=ODaoas4W|t%GHsa!DNt>xi8D4yAcbqHm;-9w zVRNTQBAwPM#8R5|Jt#pc$m$GA@w*gDAgx}t{@HTtK8n4z!=49;7UW!MM`I5?bgkpM zYaJXTUsL7*O}XC!ZTj0^BuqRoaV`BUYJkgd36D+&8)R<4%V6zyvA>TWk{VDsNsd*# z7Zjdq9~*&IHaR-L_teC#_y<-b51yb%>Tf8u+yw$?a3nDiq(9U&VYp4IPQ5P6H(+wDA_$%4M+SQoRh; znRDd$23_90gMcsqqgz*!TwF>cV!2#$n*3I-%_DN9c~Lsdl}LykSHnDeLw)hoz?Ko_ z!c-zl#_7xXF|8Ynj;oVukTgFlC1o!ir9OI6Hj)quHR42BX+(R2@{yJ(fCL9kh{w3KbOC4_6Zd@?g)5aIm{xherZ`8^=7dHbq?{l#!^$%e%Z)v7>D;pwl}wlc z5a>i_0D}yb2h#Zc+M4A;R0_b1K)}Wye{IO{mgV&h&;`tS}j4|=K>?#E`qF?-4s|$hOF(lYFBFI*nh4P;HQ-l8^M|Zgf1V=@Qi}5??Y?hC> z=p!`qZ6ube+dpjI5{)GR+k$jYhPft|OnvdtB$srwXNawgKr;Ef*%NO07>yTvWR><7 z?eYM(h|##_pxt%Au&HmPOuxpxpPD4Yb5C4VhdsE4#bfVmzH*mIEAA_P{)7Hu8QpU%eA4Xv{-_2kc2qIQ_e{IF^xS)vTEnC6 zxs-Q3_RrivR1*=^gyQq7cqf6|@rU2>BSk2dfSZg}m&5O>6#~^uTg=rspT+l9%6r$Z z!UnxK0yj(G7aNziYoGLcI7EL5!v<5G&{`!Byp6@KQS-%4vjoDp{477MB)#MNtsEPI z*iU#_q%jI~!vDKV-lz{2yuulG$V^ksGf_{jNlG7r(%)4PW=Yo+uiol`T%)g=C|4*+ zcJf7=&pJsFUs497hr9R491wQ=k0T0Vtz7-gmcIG%Uh)qfdevGu_^QkCO$4ErLT&pv zusc%k6=nk3YLLyXc?9E3`7Zt4VO;VkP z{@=TTosxKY)Dghzk26X-Mg=}IGQRWjo+znxiRNil`sWs@jBn2%dS$^yoXKyMQo6?1 znP*^!+(aXz5Szft1isM>_zQH5Oy0`&(`>f*{&?iKYm0Akbvr3|^PYPJ)}SP>U9g>& zO#NI$K%KrEx4bI1&>%MnxMqcU6WW3~e}7}Zd~UEjs>2~K$Tb5pOcB^*3U9(b#M2gT zIFl^2@)kwc08pu6yhb>n6Hwt3Q|ZTR3HTI*e+)B{Cpy#X;(|ogBWN5-u#4luWE|er zN>>M99YWkXV)(rudAO-H_c=0x+KwXa;j{s4Ctcl!MGm;uBaKWX)C!T-Dt*DjrIhRm zxN{~?Fxjv^&-D7W(&J0u>y}IE{Z4uJ-bsQC~lF<#29UZ-N zb^_`qtL}H)Q+^2PM13c11c_e54;;!0S9gj`CapvW-y_y3ZC`dyV#BbMSLPKAM43cH zjkzE#GHwNuqV5ru)27mi?9wS|*TUrViz-XJU}pz={(Rxk4vetZ4Gzr72`2_a&6Pk> z;Y`d~CM&jLcPk%N;9>1KDwBeYR^pA2QGdI%-uoq*qonq~az9C38rR_4x{|o_j{IQ4Jvx8M9BI{qZ>C%mOJ4)hrInDr<7@IBw#r16 z_Tc5HoCZ@7=W2Z9kDpfeno>KV&02JekI%HwMnY_yG`*^L56auJeT0li@PG(kB7SYq z^-i3CKRm=z>E@C@5l$wWWHXHfmMa*`(a0ppH%+>3S*3>dD8-Kz3Ehj6VBqma%%}r& z549tUe}^=l3!0&Zj0h6stbl0&hC$@53Q)iTe1UAEZ1h8=_L-M(6Lm{NBf{cGQWd zWwZpp$#^tXby7BIke#F7g?HGi;!Lw^&+~Zk;d-j_SkW||iXX(Yson*4Xc4|Xcu^^e zBGin#r((Z)S=yR%`9SiKgbA}>qa4}fEBk; zw@mFYZ^B`z?aW4n)PAXm@41d5<4P?<*-_=Mz;U4rBG~mjeda!OO}zQ6ethx$$JU}L zuL+uhpN_<{pPJbJmZAS2zOWyf4^@39Lo*{=(|@gmRnV3^`037F7#<$Je3N(Ha(4X; zR+;O3DGrreD^cQ4xqf4BJ}@GlN;Ic@YXFv)zXf|KFuEBW7oalJi|1f7o$hkH_V(!Z z6~etx`%52bj;70fNUunIyEMQysqwNk1c%0l0l7gbe+E0C#KC=V!X4^P7*McSDrBR4 z4lSe11~On)ideT}ody1oku!*#2JRd%;%t*R)RmhcQa(_NZb1aJeHcHeC?H#K`6plco+dSkZ+HQ%tpHkmO`V4EKoRvwr z#=^a1GGOsS-fyVp!{oaIFhTQ*KF}J$f%l+xTNg?a{Q6vOs)ms(L}&xG0AXeS?cM;? zDK~*B+lm~VM=UIn`ZpxMiYq+4-KaE?D&Wg`o$|CKUYtn!(}Sem=sk+mN3L9f3`h$T z@%_*&E(YIw{R+N0uK#?UW>xv`PDu+6F*~%N3~Ua#Y)|biMnRfHpN1snqHdyxMhA0x zWcLqI3C<2-j|a>NtRNft;{__ib)-@I*F;!0Eeo%q%qP%)bThFiEdd75Pjlo?vkmS4 zsYg=kAGHf%8z*xocLifdTPv4;YsOYr{HNW9)>=GFzs@F?9H9kSbKf5-f)H7$(I0Xj z|6st{kwRZ!UR(j>E9@IkH=HP%m-8UTfx89Za(16Rq3vX|A?GG@Q@5wbM-*DUQ{Eo( zj2Ki&OH@RsUL3ld-d?az8YNt5JN~g58CLvS2GNzIt7M-y7$vgww7=A!0fmBF?pCg^ z)n634%COlo1P;SM#3PO%`+>S|HmyaQK~tR$+}95cJHGHW@DD z%_~Vlio3mXYP2e*1{2#d4ijCQIa}kv)C%EkCR{(BvDq^buL6A})+@`GUk0qJ>Il4ozS9-#$=4ov-3A@W-CnyNIyJyXrF535eM~Sj zaVeWn!IC#m`GbP4+%^z=1u1QK;F8~FAr(3#izTSVpe&OHGJTRTHMi+`u6T80Q*u%- zygQWvGR+(_cE_P{Vo!hpcRiAQvrjD>ZT;!w!O*4`0pA0>0wE?OT2yY5FmtULR!VO; zX^BE=8&JVWBJE@KPPmA1IBlbi^;Kc`=h*~X)O`qC%i|Nqlu-nZjfw-QV>O07t6tV7 z&^%EjqUCdfbwt45C|npG@3mRXm{+6uHTo_U{S;7qu@40?KA$n0t}(so+i`()+!W?5 zbI2)=gtYs_T}F*(zcfPUa|BJ{wxseS%?$LwE*+)4lc$%{Yw=D_7YTtL_qpYRp-wj( zzxZWM{7);S%8SBR$tIzd5OSu95tSRG2R--s;PHDE z3xSYk%U00UVG>SA2951KgCwsRg$-D)DeV!%ESsc;|5JzeNZ4}E^iOc%{^V1Z|5K1D z+c+2-+M3#!dl(xDTNzs$+c^CP;sa;dI-7oa_%2C;yu>^OG*%V#ahO_$08XKNg{7b= zcCwW?L(ymodA=$^zFfSZb0rk*-NJ<}&-t$B>-Wc35Vl+J0%#j1bizo>JuIK5>>@=K zU%NV1tvVR+2lMOB`~v}nJ<7KORd5jQ&qsWTMLat8OCg6J`oP z0YGhixP$Mm(02r9rl_nT9>2c-#3_{-VWo5XW0+q*2KxQq8R*}M+Wf0D&5rG6HJ-5ZKPd%oW4@OU~; zV=|4#88uS*tf@GM&z(*B7w1wzjvo*U-=~GBT2ri%m&MX=rFDE-p?? zT;vlW1;ze-M+Q{^9>5*|8wymP;Mb(WpGP+|U)kd(f(a#?q9CeipjHwxPC&3QEG`KM z!oEa`!%I&x5L7XjwKl6Jmpd~N6ema`Iwj3OQU6$4t~q{#0utFQl^LNDIC|?y;;ACF zOOFhHP=kEDSE*=hs()f|W@-$jY;e4Le6qW@x9g;Qbg;XBtkWojFKv2Mt{e`%{GX^k zOSWl_;C^yCy^dV^_7pYk&BU`v+2p(sfnA(@#~GX&-K-h)#bO&_PzezgzYFs+L+?_gY$Z>+9F+>Q-xNmKz&4 z8XDHSyN_F2cUoGudV5cLdXBrhk2*UK1_sXi`_H<%4mvycey~k{|LMTM>Fn&&%*^A$ z!gG4YP)_c6VbM%Z?pRUrYNyE@z2*z+0dnkPED#wTh!>IvvO3AyqE zB!*`h=r|aPn8-`kH-;zL8T}R(my#eStj`L|5;U{6lp|0jLqePiOEk5zkd>iE(4=FA ziUf#oRuChVrlQb5<%Qyem<~x4RU)Q@r#HFn?cE&?(Foy8LPr3S3<2UorkDE#$j>X7 zSSdH!ZyXQ;49$3mUP!BtgNDd%W=8)9fZ$g@pw=jVuBbmKJ8&=Zu9az?3_jx~6Mn%k zaaJEINMDRrw@KuZ*@EK_7VIY5T{Tp=p zM}KNGA&WQcC&c!CWXAs6to~n<*3TJTM{9j6D_!#+J)wWgR7!esdN0$z04oNPE}KBE zcY1n!zrWVkbB!bNb$Bk&4)ne22FqzR_7p0XCb&RgVPStp$kWr4W(x2&XO3gV(xYHb zQ*-iQ+5*i_aO$#74a~?sEZFe5upSe=*Uw~fAfcJMlk0vjY*5gQrL(btwQ*4K-880e zp1l6Gmk%sUtq1hg&`2)}+l?cib(?`ndfX$j$_VclTtc-$eb5`*XTwSMk) zA<$Q&3soW;b!zqW-MD7xog7`veliz^insGOP%Z536{F>K)JH>a1*0sVcoM;sj-t2k zW8eMg`5i-dN9C0ARw(FfdQ{0%J^$OX>1hU!=lgpmF=el%PQj~foPcKPWjg8{pKgfg zX;{_;cy*)V;o-s8gYA96AfhA1G3uvGWxL|FY2#rbC{lJN!@F{Dg)YK-PQRX4cwt1y zO*2czGwrE{gLPq_8St}-SE|LgXYuR^c_Trz;fsNhA=2LuD4b7SO$hyq>GThKE_Yw= zU5;Oz;IQF+R@Ou1x4^OvrG=bQm+{C46grZ=9w^vS=Ow zBhi^&NN%P!y6JEJp3=%)RGc*M6(0cM33F&b6{JkyDQ%YRQzK#=efgu^aVodVzxN6W z(i1_$xlrAX>;3Lcv>rj^kLvZKqC%P5$(|hyYDfJNCs5l=&4=FGh2?ijpZIha%H5D5 zI9P~qb%(a>dtQAsei=vbDPwysLFyx9RZ1!`?kFb~XQc#>HeHvGe8ZxNj+EUJTxw zh=>&bQGp1-gF?qq)}dyncH@1-kH9Yx-UoSYg&Y|KFW|vj1y(Dn#x{J=CfHRN{cP%u zntBlXMHPUo3xO0MKMka+ZyFR3fsKR%8SNm>>gCq^ozils7c&MuqWquhysQ>a*ILD4K_Vk;8Tibwyf1 z`2v;1G)J$cj_m_T2_zOuwo<~xFOwDr80rh7m$?*>HXl&Klr${FV2nqYbs~Wge(&$FPlN4RO-Eq_H+0!xoaiEL$iNb^4r^{?~ z3j!m1;2+EkP2h+LNp3;UKv#$?h>;u&qLrvD99U7`B)l4?4@X<(CL&0uwYJE-SXa7V z9}HRz-bQ(t9!n>3R~b%cj^3MM*!hVv_n-{3am&*7X)67nmvw};qCG9co^xw{o!xFHi{YL;j&qGVF*bmO1)8z zryMKeZmL?}6l|PQ9~09dvV{F`LH><>*kyY1+;-tQC|g zv`vAY#1w2f#;X^6Zd_5U=eun$seTYIPoA+BuE^OMTQ!L{uG2gjd}i_>W4=kgi1REr z&FDtCih2wZ=zoR9ub8Vcr7=bBZnCnc1YgZ5T#)~pLlnHJenD3}BXy_y=ObITE;#AYM7+-~|wb=Zm(gG^ob-K%Zg?gko)FG$`;;ULgcz}iWwNz$9 zG1wY$LC%6_Ke`xcGOBO39XD1kNHV{9DtOSdpEi9Fw;J6spJKZ$87+F3s`0v}e`QWW z3j`z}t6)T{!flgWK3~O^La|1kR3m)?OEqh#-9PI>=o3kTMAC%EbQ&un$z#PC#8W}= zTk!i>pv6l!DM(GbQbuz{ss|7my0Cptm5zfIiH((kKt3-4z%VL-hJo{Ul4+Gemy|iJ zY`RLB)Y)mA013H=^}4zf)Nw0`ak`eK|GRKS^yf=<_|3%&K=kPg^z`Wqa&)o?g6t|wgANjt z?RjgMZuv`6wC({LpNc60HXC_6o%|tF5Q&9;g=T0&2L7r#BtS_@PsT zbBm7wPYQy?i*u@)ZfT}r{< zx`vN?R2AzME*wL;4a=+c7Vhh$V(`=}B@j_J>qnys!&UsRpAGF&MH&pLl9M#Q#Xx$4 zrlB?s{0%xCE9Sz4vhQKt85A@UdY?HG1xs8Lht8ytis9Kl7|~H2vIMpt-mtn)DJ@5L zvjg)2*3Qkb-I*p=?C6;Z6SkKq_r46*7 zoq?AH899MIzYBTVRKMWkNEa0c@bJ^k35R&HH3vf!g-|>R*^JXmPBQ zOkC~Bnb2cc2;A&$g`l4mM9zv6Gx)v7k}WH;2a_=f_t5EKl8C6?;yp4n7pM*sy)g60 z>C1hwv&hVKAdSvtKt&!DXo;H)-=*G^IQ*%X-?pN+(8dZ zV>M(DPSruA)DdE!TViSn>R3kv6d%jdeMT!f>JyB9(bUZZDfhsZ7^X-Li|oNqfE#Yf zUHfx|p(mFYOr0@`ZOWNg+kKpr78xcl&sV#wd}RncyhQ#qtrAd*IWW~)Qx^=^6Q(Z7 zR?*RIDKm&?6XIk_J|AkP$Q$o8w;jamna&Lt@0t`pQ!#C;JR*p0rJG!)ce@~Mmng;= z51Xg=V!&4U>?wH64VCP7fLyFVN}MDZ37co>M-!J8NoUYtIiu( z1O_bo=4~mDogs%0-~VY^w1XH0aQ<0;2>rlb!T*P8@xLg*DmfS%OWPV5Tk#v|+d2KO zSBORO(vm<7@L7#b6;B0t&C0U*gi8F5<_ML_Ldb#2{6)`BTsDf$9V8i5Zw=fKxE}yt z2EpVY+@J*ww(-hz)aHh3APZ8OhBCY9pa$KLy?xr1ZH=T}Q;Z?e7&lYDW`2K0Tkd zQDh7=5aDHvRY8XjrT#B|E>Pe(%(6y z>xDMLX#9FYKOD2Ok8ygHE{TaYseKypXohBaE7b#1cx-v3_+=D`!Gugx&TecnyQQi} z=OorgJ!A!4J%BwFWDj(xm(5tOD6w|d}mgR@lw@~S95>7D*pf0tAhV3^eg&C+MJ`0GC7~GdvGRCd{YVPsLcMF}pU;k2)9R-K*UZyD{PVv>Zh^f1 z*%BD!I>@K<7wf2Hdd~K|S#xwbevhfyksB7Q6iw}$;-pjyvx91}Y*if4_AAFK_iCV- zJ;UG>b2DY5W%wn0QYRuCCilr3)TLSA+64xrM>0subfsgZDH z#5fZccRh4u#cR?K5nbS&oOpAVSy|BCtJ2^~IN}h0Xet4Rh?(FLmg6?$t-eyy7p~`r z^=*R^3OiXQZLaDrN#(n6KiV)@YRDpj(u8?Eq&7f_cSf>`?DnNg-Sc$wcr+S^LeAE; zlLcjokb2O*__;F7{=G7Hyul6R+bg-ve$#L(8@9tqI9+4cIMpVazl;{T93V^X4bH<* zqkIi8ZJ)HlK8SE!t7fM|g|2gdjbWmRC;t|CpYuw-d-Jk!10;H&s@u0$V5|kV5Jm3@ z1f?HNXIe`40sJ2U@I6Y~TkQ`OiazYGU!wmv#>Bs%;wse(6(m*UFHk_B18@d#6f1rz ziO`g?s^%i{Pyl4iSU?3L4X#*2bj~z!Q&O$Qs;Bw&h^`jCb<Rf zOI>7!;2@uIryALlYo@*jn{P-y4&;7FaFMYD`!^%NKj&aI`n zs7#F8DsnNB)Ux*u>cdtz)r)%;INS1eFgHI$bwcz*C61kw6Jiu*ezEr2eYl0{t-TR_ zXsBzmo{UnU)8w^!RA!>dDBCG}`C%m)lZ`9Iz&rXGoE$~itMxU}L_tZ!3J_g} zttXzGCNk!xGE3{e&6%br$9%;~yOQe^`xEI?YqJ`QO`l#t^3q)12J=mH_{wN74Aj}z zJFmkUuPVcnD$TiwL&5y>e$x+=cOkmyI$CXD(c|QmyvViuh<{+yCX^`#8@qWUP7CJ* zRb691h zEDT>}QU$-%wRG5-oU3SL++z<&APFVqVp`I2U}&VFsmDk}!IIqj+$h3R0BHeW)9@Sf zPmargidr)MBG}Z3W8}`*1sxB;x+h?l>ZjWnGY}C}-_mruTn|5{RitK)8UKBtk(w*m zzC=vTb^_jgl)}^FgP2Icbut;DA8=9Q+@ZiZoJ$#Ps2miWqIAwWq6D2EV$6f+G42vt z38pF78tEd~zcvmP7H3ob@(1EMFNb@l7?b#q&V>99e}c|5g>4d%9G70v50k=OUU-aM z#J#fpUuCbz4a)1?>cB6^gqi9tsj6D))Htc&@>o_ZwPz(Se_7>C)8L%F;BerQ#-}jn z!hJ_Cf&)nuxLXt=ud014FUcD5A)1PZR$%AB`amQ$=M@^IL086Z)jJ>`YBr=IRN_lY zXRAj1i%^^Ibt~q(4*$a9$9u5 z7544&z-k2nv}4qv&Xo@3epHuO5E)l`-TV~wRk8k7`l&I41g^(G^hAqSw;n5%M?tkd zZVCg{O_a3F>E4>W+!Ytzp^A7*X(wxoJ{^}fmh6&Y3rNgJuqj>Jzf4S5)dTBj5g;hpV~kwJRQ|-y!(UL<%)W33 z|5S2Q_piM5NAN~!$bst-XXldml^5#<0TuCLM=*0kJ6YB-{kFJeF%fqNBG-LFc#e}Q z{x+rGRa6T1Q?Ns|?+3p-5z;$SZJ$6ds1q|J&K#+B7K*ntlNc0mpO(j+x-b*32eNdt zi8N>-!+>&%uFPYJVO`$DieNsx;|{tDilQGLC9M6!nF>x6VDjc!1fx!g9paxvB%4Ap zT7kToA$&YYyo*JeMF5R7BY0X!oJqC_f5vN)Oebvv7S&-rZls&# zQnnV3&L-A^D1;vmi_r$5ZUcFoD{-U4XkSNM2M&~&Q_M4wafV+KS^9Y{S`ooS@`63K zkn&>kU^f_LNVHAX`8}6-YVTK5_<87xeQS5MkZ z690cEh5rc!|2I;I-}#4(bfjmag2#i$%vuQOK-%jT?ly*TIungyj_it*jqRZ;ZDxD&y5&Z+`E{9e2xkx2E0XWi1q+IzQ)XSuDI`QLc4H18vWXsEi$2KYa}UxqcV+C`#9(i5jB8s|L(7Vfl6 z#+qMu{I`f5WF;aLI+Oyr*I$p*C>PX~+d6aitjg)oi597_1+ z(&?eS#ItK)3`Ega^i9zTwD686Q;b^)A_+Iiq1eV=GXh$LOJD@#{CfEjAfjkFVnOt2{Cbwt?p6Xw5e^wl)>tqAojGJ^&@5U2A z_b+O%W)I4nh9uPLvmF6sB28m$jcY*OE6chkLj0kjKN3Sr*Mqk~AX{4mY4dol#># z%XNw_BKiVbCKU_iwauI;E+ttGl;s)N<5iAqld9lWi%^gUpxf<2*Ty^kLX>;dN@{8v zDbtNAYF8?X8QO7DEz@gSaaL)Xb7{3L>uKSn6Lp#kX;hciS7T1WNG#Vvg6(VBe1=*6 z@&jB!{p~{IHm9rgW7Cy&tRx(YS}8X97pMTc4P55z)?tAn6jh#I z@LKrCDvplY?aoPCiPOwfna+s6J89N0es?6zu~z_pTcCUxK=NR@)`9wH3cFYmE@|u; zS#=n|fT^a@nU^33lVD7Sl`IH;uhW!R&wf=M>O*3zS#VX%o0qg~Kq?n81qW&n6#Y$V z&1V*ED-`Zg<-%y^^EKlX*un~w%89S4wT|6f>V=+S?n$r=*xnLv-2;L^ft6D8ts^!b@40 z>EMy+(Y2}D^q~F6=HP=HK`5HE)u1H*NB`hN{xyoa4~i`4og`At*po9-Pd^r^56y(_ zc;AmmPM9_Oj1VLQQp(sZR;ST^OTHdeCccm9{S?c_@gVYk=F# z;QPxT8mChnr*UeKT7c!^(Zuw^q^WiFxqE$hytEgjuS&bG*(P$Sw?x^9E7S_2vW9^z zrtU{%Z^inuMpsYuI&-Em)*hEX(F0WnvRi8ER30pJCN5`@e(jP*xjrIi?v;dF{{CT= z@8!dZrtZ{cN%ac}*AFshOJ%w!BK6hO4G#B~ozZ%U=j*9-HCQZdLKDgz*Hs(~#5Y^r zJ3IVnT;b!S%^T|u-93?J=NlpA?$zy$F0tD6C27)p>X#Q>*q=J5oKnZGnjCU1SayE4 z1lB6e+3DKe>Hx#r$JA%nLj)9kZ8F-6e5sokg%|UW1;gVG1O9n#Tp91(KdT1&L<$t75AsUPfL0!wb$63oHu@zP*R*l>%Wlm2Gt4C^R2> z4%9yS%$|?9+W&*KZ;Gz0!M2W5QN^}x+qUhbV%w_Nwr$(CZQDu3`tyzMzPG>o*nf|) zU(Vwhd+xYMJ)Wn_ z7)O8f=3g!P{rUkE z49`_Cm^E$3^p0i0HpQPp0aa>GfZ`ys)Ii5W4ftnML5olMfY;oCV)4iGvob7E6v2Yp zQeX$+kLBTC&iFo^5x)f8P$3PX^;dML_U~>wOQP(`YUV;PhWES)aUwtnz4P68NrWQB zY#$ZL%AkdSMNmZs^89F!uA$3-Nw>(42gKObHVj_{FrcV{L#K1moY)W_(1O7RYJlo% z;-V@JyPUMtqm^4izLxeBEcp;=L@;@(wY7xz&t2Wwv7zAhOB0lX@ussS&0q%Dr&20* zzE4!$W;bd>_9PhZLeka82IfJkrq3=6J8S9dYP3Q=-3|HcUMAZQWCPgBT^kT{j#xm znx@KI-;2sh07i(s3R_Mkup+AZ8v2Hw2&l`22E*mYi2P`fwIPg5q?K!00%h1n*Tg`3 zcHPI;3MG1YclQr=`AXHeE$z664_{+@qIh4U$80Zu>OONnkcDA93=yWtp@#_z9wzYC zhAXRsNFV*0{3>_9z;Y*8gT0xPvx23(j9(&i(}mz+>&VTyicpcC6fuYP9LUV8lV6)H_p%K!$*XqwwOYQC==v+61xDhEWnPQiIoWLt_n;n*Lgr8rb8J&gmhuT` zGKuKdCFiaDf?h)L;6dwiPblkX)+x7&^b*XqZo-2N^sr9~8!26?gk)kLm9bvcoyGZ) zt!RQ5zIZxFRf~zqMm^)z+NnuMGn}~}<)OM~1|`2vxdBsaP42te2Hl>Y7@on!@Si&| zJ{QSu+enpsy0dBZ%j70>yuqzA3)cvFKS=zXJ2;hodI{t_A!L1w_Gc;Oh}Dd@itpJU zMkThI$RROW`Thy%voXgVLL9|(;$P2lqVGC~R}Aizs%e#!!NIzAagwdDy9$3qHE37e zu>2Tn0uG^*prWB(S#76|03=r+XiJ6--@B#o(E))8G@d<|unb$F*kIIt3qi9(bWC{y z+}+ng8$&YfY@K!2LOo)#I46lP&$pYja9jyFT5gd+MOqMqIY&`BI*!>e18zKrY)Nk; z;XtCh0^h}nUiRo}t^dhrRjl!Fp~JUuTtqsE@I;v>D7@611={In0B3V@JRvu!Xqz9y z0r*eVXHX77-;kb1tgwSV-lnYj;9WW1TPQds<%5YlooXt0)ed~#V%OdgbuC*Yh4#%W3-DqD++!u~-189_FM*uhw#MDdx&k>QV)%KLw&RNzulf${D z6HbCw0oOhLRj#xWgjPA*9X=5w$E}+Ys}5h+a&oJkwz+gOPE2 z!t?k!I=K5?0%A?lrq~QoyQEm8ZDbE2e4I*0|CZvqYiRF5Bkjn)By)BU_(NhRJ(@IfQ31knM>#Ag}Il9Km*JnaBbs`JvZytMX7nJzWF zB(q1hymHZNOLMmJtanXx#O9gQ=2Im{=HCa25zYi;OY_@G+lPtIPDW|WRhbuOCj2>0 z*V@$4&N5@gaGLG}@{Xtgq+9`u8{B}?nko_n-7(?@ION8x<%5@VD~wTPz4A|tP@D^f(#8q8BeptYXE2+X=C3J6Z&11Uc z^}=5nsY$dRGZfk_Yg@fVQxe1c2dQW>hEF#b0i zcek{$^vbgddxU#;1K?nlF5dx~`GNCu4 zua&D=?pSNcNmm>(cm~c0>PR*;QUtp$1*<47vBOZHK4||W(bkz%Q98yrt7>b4J3|uc zY5P;4T<2h*WMbZ^qG*qsPTmmwld>VQiLxQUnW7w1(PL)P0DoDa1A1AL2b?J#+fF%ef@nb$I+JKfc0|$ zrYN8ch5-aN2qlJ8?H-t9g%+hnu3mo{aL)t4UCFVgzv=B{(WPr4E4(~pT{sIObWK@q zC6PlgeRAvQRtqIsJMf3Kjb-p8&Y9oE;FzBk%ur@(w%FLnQz=P&i!1~RLpV{mQc<;3 zqe}-L`UtiXIQx&}&gV3vlF(OE?1y;~&-SJnxW{M#!GxyKX zYRFYZaH@zfIZP4zc-WBdWq?&g?VZ?wiIqT!(ENTYY=c9rboMLOM2S<6A5t&~n*}mb zNTE1#`>x$uMMa+s%swC2y2urpr6~3YG{wGV7!Miz+_V7e!c6yZsE(LeHbRThcw{EC z*uP~BOyC-mU03cT%*GcmE?wxG z8eDOK_NQ$*Lh0w9FqyXsw7nv-u@g(J^0slo4?8o-Zrail5^PAVMG+YAlHqy>lxt#U zkhP#OQS<(Cns5O{9hWV^?nVU2=xGJBl?OeB=QNTD3Njmz1dmpQ&i53Ge;C5$yjOOeQn? zZLWXQF!3S(KQe}#zN6_sGDe|_wj!b`nl}r?#jkdW5Q)Ma@DDjL-#@^W5;+0`VPG`^ z$_wVn2XH9KmTls2Z_JtICLHrJ*X!Xre}>X^7E)Oz7E)!dIp*I!eYgG;edRu~w@X69 zSu^!#L_c+0J7hiWcZ_F$eY|4!q4lIiq1x#Tf@0%mMCXC4!zgkT(dX~w1-Uquv=9ZW zvrm+|ss{Uwb1+wnrI*{MB=+GIZiW3FI~FH=vshz7 z()@l+ev-kGRq=?2bt5Kcb#kde+AunAK@L+^@(nylH1Gt*G+e`^$PEV>WgU{c8jWL! zl!as3*B2Pmzq=J%t{ex$>M4dSIqRuM>=?7R36$xU-W+Ek06OFv4x93#creKfn?UU+ z@;O$YDq5&j=xLWyvcmR7wR*Ry$k9j=VaN<8*M zBaP<1&|?;XJ~9IL`w!WDs*LQklx#$uVut70@>F=1D_4CDm3^XgUSycOwtU?rmg3fI z`Y2OnR=#wb$URS;#VD0*Y*?~M*PN+*g>pxv=R*@+s%s)GtHoGSZ7Jpp5_f_vziV;4 z*ZTCT1X-~{xjIFtfeybEc1Bc5Hn@^zWmkDOQJm6Fv7VULNr{O zuIMwW5RL+h0PXPR0)Qk{pEhRD%y8!JI-Xxa@T3Ct&1aqt)bT2{>uBjG?+k`b>REK5 zK>%}u&S~`K4T@rP&eYRzqmxiExcN-CFlq6zjIgQIgp0^hZF$fpEY8>c?>W1RJy(Vt zN61T0us1sYkBSPMh*%Y{VcVcN2o6^iMpw*a+o^mWA@}_T+#bA3aVA@VDuskvM%A;#cc#?F7w=5aDs)M2^N1yLM8@97KK2AZ| ziOo-5aYApznzcTBfk%a0VBR@*D6tdfKVt57Znt4`NcV$1Z%H;Ve)8sWk4b=BG2r3W z`t$sv_VyQiPH=mUXnbTZKgMyo+6U^=^V%>(=^g;;@(-+DZR@_>;Fc1Nx^I=SO^8wj z9(MB9nW_sa+&}KXB>e*a`|geq&t#^znpjV9Kczo@ z>_5P$TN-Zq`yHasQMy$e)egzEH3TL_;cmDjIui>Pa>F6^YVWdl2O`F_jSVgL0*f`&v-fY&se_s^$&`zc#Udrv!mHQ zy~fBUbR+_P<;A!VF2k1C#WBffvnOPcgdCpPG{le;8{YX7I^Bg7o*n5h$1<8|lrO$%C?`y3k9Op{18h3G2Hh#QCDI3sxh8Ei`4tzQ~Zz+UrFtJdS$65myde z5s@ny_d((x)y5dzA)P^_1~W}Xq&@KZQIN|q0x}fq?U7>7PTa!6F@n-7#tfSNIz{~Z zYt4QK&lLv$exLt8hXI^wb%1+w*&cNvV~^Hb^~S}Vcr2wncYPzDJQF! zpFp>zx9JI>O%gytg+YQx`yoG9tCs^2&d^z~ALoan#d2fjDT0%Yd<;9j?{N3r5k))l z98%%Ez{gpI_Dq1oc=Bjh>m_cfo21x9aHcBkl?UjuZ^6-Jz2odDILr#O;g>PtDOq46 zdRssy8+w;X)`dCvB;=_v%n0V9vvgY#b!}n#^CHi%HQ$-u*7I7RO*g<@@!;6Yh>>2PCU(JbWk=@JYZe>Z zsjG}@chH*aG1+qQyP2k-;?V7dL*WZ~JDY)YYeR$4I~BaffOw~Q1j=O(EpolN4!$k* zEAG#5E|HA;8z@0^psERX=ad2(yFf;n9Q?%F@RtJRnh@sG*B8TjaaI>u``%jAO9KXr zK<6!5ow{VmgWo|)i?&hxKUcRJqzN-OGn)Hj^Q?+3m$jLT-(UQ%S8;lI;kG9tsV-OH zu67nGGGRPONJ!=oGcgh6A9DH*abQ?4cf@(Lpy}H5`(f_WMQM11h-v8GyH?NiA)1VuOv> zNznq==X|pG=Y&wz8iPGqH5NM{ZW0}2XC1_c>66mCj@BBYrDb&|PLquR z-J77$?me3W0+Ey4Ls$NZut0CqR(f7k##@(nL6weC$VlV&_n6Fw@Ol2`AmCbjQeH$H z{XwH+1NLsTbkekx{lj-_M4Mfh$Lz_=L~+k)FHu2H!?^-}qPNS8!LhBi@TP+&f!B(| zZ`VK3Ljx6pHtMcUFGp)1>ISQ->j#?V@MiZ;XLY*U%Jjl+RvYeoi~tKzzl&8@{lr*N z7Atx)(}Sek#w8!Ju8$-aYpZ9Ey>w~>DPTbyDmBzXH<3L=gRgj#w8|pu>lwJW1IPXOl77fQr2kn_4heB8%aAVreZDLnqtaxBh{eD zjy&`Qk<1=>8_|Af4k6Il=O-#4<8{yB^^Az_wMGS`Zsr#GJ z^}_H|s7Y&oVsvQlhC9gZRD%)y$ItfsT;19oyzccVz<0-s9_|)Y3?8vcu;CZCp`SwR z1k^f_mM2)s%_^KsZj3jpu%=ce$Hn9~N(Sf}2B6IQ<0YMbKL$C%9$0fob^NywywG8c z(UxbPNjNknzGcqZE=jd-7m#T~?Mbtq*pZ=Y43D->1srGP`;Zz#78VxCc8cg8gLvU% zrr3&g2q!HOO=C6H3>V#HaG87e(A|!HZy|?2czxSLUqF5$>Y~;Xhn}|xI6@ZF``$nQ z;0cQE;|C*tya=Wrx!$^W1%llZhqeQSBRnE{v_CYvv&D)OyN|%w_2Xp3Vm^ITj{1oY z8NgC~?$nSSLUMJ{R>(Z3v8=IOMw@R~(fFx;QPszDgOy(_<_KwNnH$H*MWa$pOJ_ov-}@^ zb*0Lt0+J{icPEup3U(q{`7BwlZyVJZ5nw!80tgD-tTtVan!6#4WIg8sUH#&G_yJ9I zEa`}}b%n%|ghJvShfOT1xV`%?K7;pCjf&FC)6b&r`^@WVkFWQ)X5N_qO0{}=-&=32 zeq$(AOR1j5mt8_k+ZSlpAy&)_!Iv-4$Cpt9=e@wUpYM!oX^fR~#!* zOa{1=YLT*Cck1e`1Zz0vqNWYK`?B`^@nMBlC)sh4g0A@XN<0N5C5B!Bk`nX;vpwx6 zLEY&e8&sVzJ+;h!o`H&fhRVDNl9!iGrC09ffW*V)@|#dgT!-mSBopUFxKLEsQ|`>> z6(jBiEGQp*lETC8^=uD~9c)Prba#;8Ps+u^IQH!lM?n2HYtm=bvaFy*?fKrpV?o7n zwF^Mp#E9*Lqvk9>l18!$D%~~7(a)+X(Xb4OrEX=En>(mAs zODfOeZVeSgw&AuR0`~Oa2!BTH|L$q564cXQ>cye}v)bzO4-npPq%+Uy9%4nYW^ais z3rq_jrs8Y~uxW2J@!!=0B}7rd-uPC?H)}Wp0gJl=(^|HSpKI1l4McWzMLjYF>vT)! z%*41)clGF*RlII2m8p2I=%Iuceo0DePhxj9o6SeV{<3bW`oKmRmr5oTodYO3mYJzf zB%1@DC$bH|!6`bRP3602yEPo@S(K1)(Xn>F&YVlP)tzqEa3HSwHa#X_$Wf}k6~Z{L61B1-lt-T|>+Mhx`d zmykJYvN{0V!!vA1b7b1cfhW@gNP(ZTI3s%@`YQrlI8Lggz92Psz%vdD@NS@GA~T|m zHljbDr*)^PGx>3ck&#^Ip!aaTmn2(WpTw>;*Hdu&K7go*GKlEGK<8#p-He`^c{zby`gjm*BahX3fc8dc3yu?8``%~JxU z(%@JS5d;h(AQ^*Z(NqnXq6T6lZ2?1bz|ymTFw)H~P67A2uE#swxwkevuGr7OKjL7m z@oA1*JZBx=<dQR5k*lR#v-{h)!aS%%FL# zpKGS}=P^RH`l{g*l)X;jXCMD_7;&DNG!7MYZbyh~jg?CAl4bT{QL|WS zVzfA<1~>DVwN(<+ltf^}>AnZB8zEsbEf`5XNr+)U;Bl+{};V z{F`UI>k?!^&4~*?h9O*ks@V#UxTcZmJSnp;#L);2wkYSI9je?q#qcuYNQ0{0RJ{18 zpq~mUFK&Asaj?$GpM%Grr@DpB&hIiS!v)niVH!JPqlC14>h9{@UvmMzr4ePuj(DY; zM;8$hF+hK4l2|~Ed8V376xpxo2PcqOJJN4C6jyw(^4J2#aL@;Imjd=lJol@(9vM)D z!>c_xiczwZ0L)ye-vg%63azqKpr#)N;w|VMk|W~E+eOWBllXgfvV6QVaw23fy;|E zx;u5@rb}QngF3*;`;fq@dBZ)%dLtZ^rO*p9Er$v-kuV*8f?(FY`oK?y-G1PUGvN_@ zGeK_+?L3vs&0Q4GT)!2;wgxo8wp99gZ)yFseV(CmsvJJbKA@Be{9JCT{CxJc{W!n! zbS8?FraAm1%V$laj?czIKuTiu^^eTg0W~$X_f`puwQW{1R0BJmSvbZuV9v-U-MR54 zi5-4zR`Ul2V#`OE7+RE>9bN!fCIP*9EFlYexm1}&kLK*db4|i5cEEDDyAx76hF4Ht zbjDt`oQdkPnlD@RMrtVPKvxH}bz1_1k37uQF7%d2&yyn?-r9etRJ4gdh5gp9O zQO}!q3cXgxn_~v5nNS)XW6Fl9%ZT>-4W~=)S{PiU2F6esjgu0M(~i=5J|#+NBLi~N z(WH*jlqbMyHG4+k@LZ5+&jj<#OFD)EmM#`H7YbJO<*e%Y`)R{|nOW|o!x^1`Y*N{U zHPFm4R!2@SA|`WD5_7d8&Z9Ifm5q`Hc8Hb7#sdTbHZ-!c-K4EOb!hmEI9h6|p5gff z)!^lis=I}1N-XtJYX zt6T}u4~al@FBHR_FW27?t}u7{cD(Ow3oieyvneS+nNEmS857t^HFwUqcD}pQFm~=0 zF*Kx;7~FF%UZv#I)*W{?{ajceQ$|*G^OF8SBi~YZ?!%UJ$pMGH7;Q&vJkO=)vBopS zvpBkxa2dwyxdYxrjpG%WN#6nY_7K0_r4IF_V~_5s#B@hLFUjXG()aIQ z0pY*5OK>o_6mNnBe>VT>@_%+b(PWB+<*fM`gWG&H6oFB>P^^{Kxt4uUXVT^4{MkQk4qh*7I_3+|9N3<`Rzkge2nC&e*L|PvA+T37ThW%Cw1;y=&%if`dGbDxa zZ^B%dz*gw{0(gL;3Xz+j40G@S z&Gw9m)fgGaXx}e(RN9f$!rmJme=JU zs4W->h7@6v0AVU)xuLe6amH63Ala|~%8+cVAzAGh*I?R5i=I4d}8q6Ugz+@Bzk9%<) ze=*eCRzXgbJ-T&y8@JykX)|XXi*Pv}6-{0lUj;e5rbs~GZ8^^9+@83vJsvZTxsLlj zKd!HR@O(F)@x!dx=7TEPC?#3K^dp4m!yM`|l8q}ODNG1k@TSA%8+PNU_T{yPgpgF} z(0dojbOtT*ZrZTA;Rl(~2xJ=ZgFC1w!px8kCi5;P$Tla1Y`K9X1UY;xD9!haSi7uWXKbHt~5wrmjq?+k5^4=IVNJbLX+Wa4T$@@Z1@ed1nH&K z1;MD#s}Lrxk{{|Y6;Tn>v$OYOuxcqD4C&8MrWwNs!X9%la!Rg|F};U#+vnz5Ap48u z+$}O;`5cHbJ0^t7436o_C5^e68P}3EE!@YIxfUgIs4F+IaxnL=`wbKXC8_VNX{gd2 zbtJ^SEz~HIQ|c=BBtuuXlr;R%0g@00ICdqMXqvtzBnlT{(pc#LQxkA1SHeG{nU_IT zzIuf)`D{3iEcGWJY~}v+B_mq6Nl>i4m$G<@+e4GabnZpCzHAMeojpCRyGuHSNvj{D zN%!t`IfB-Fwk}%L(9=ImEZ$I8S>9V8RbKCHHk~ytgI_*4FcN`eQev(M)3~TLrohPi z)tV)rXc8aK6VTafHKQ=p5afG45t{~$Ov{GMS{x)*x@Tp9bbxc4vu`$Ww&GXp$Q61C zzrul)K;Wtv93B@{M};GSVPJ7rMi})w3bKsjNG?)OXBcQJY{v@wdPf^37^e-3z*Tnm zttEG#MCSAv09WKDHY)lyG)mTv!hhP)lZI6{>vD|qj4%jEoiRM0rxd_RFt1qs6`b7FRQBXrC z{7V-J>y;_VSbq^Fj7dYmSzAMv+=|^ev3k7M|Bgh>?^dXYMr*dl4isEyk`f(RFp3g* z`Cijmlq+7G%eG=*sl&))&S;73?brO2M@a zM%v$j;bT-t$!5&*7N+-F{WO{&{j5avRB0-sX@-!MnNP2e(;_GD`=F+~xWUja{Hbi7K{>aN=vC7Sud&DcJa6uAaqT_24Pxdvn^p%h?X#GYlvK zSBL0J{aKaV4Imr;URMtM9%!F3-RVc@<&$?wO7wb_XI1dTlGkpbHk_@~|N5y%r6%~M zZFvGaD8TH9wd!0B;lK$9B$yzBXbX19CP-7~$Qy<<^>!0AC(h}%+8+;K!+8Vrh+yx3 zOuI*1C-Ou3#-kSY4WcG+4csQ*$4~G=Q2$%nHk^d*@mjIb9r8+nV0vj9> z=qJn4%qs34iJYLS4;#t)mD)24i*|ftanNKfTqKvg-~}ID3z9l*o)#_qrLm)-|A8#S zT6sjj9+9F@ku8dFgyB&19h2Q9{_Tg9LP#@bN(muPC*tG^37TeDtfQPJn`!Q#cXj~4 zCC;Dy#5DFbsaR=jaqDS=u(fk?1M8-Z^kAH9;pDhsE~OEd#1hoB!&w<3r%z!Qcv;!_ z^DD4r{-nj)plbxqcLFIhv+qjp1GbQ{hFVNrg^_YBDE_L6z;Xjr@|8M2;FR5FhlJ>XM4T)PZ zrtw??K9bD8M2T8hZHB;Hex7F0)SS6juMf()SrA>zIc?L-FWcCk`L-8#Cz@ev)jB_P zDA+kZGu7j2IiuC(Xn5LYo8F(8i&=lq9m8y*&<>dO!B!IXo}{kDYVYS&+K7HH-&Rka zp9i#|xvtPEa*%>@ko3A$>lRP5*D4j(%I8+vCj5IyaMLlxR>Cd>uk{N9QIEK9n&pjz zAQ?Ie`kW@I-`cT+E0U`qhyb_Y$^;Cyn|`|f0)8sKA~IlgBkVbs=k=1807?0r!-o|s zl7>u=ymUAq^inzAPVRoY`~ZYq(594gV#qHoSqSuw{oO2RShoOu8dYvv&Zl9PmAJOD^b?=)vdThX^QDXt2TTvgf+Qwr z+n4E`_zT5V7!^^8AR+lD0#^!#WSN1i=5_(d;5Kkxz|NzSZ%RqSN-uD{(}^@j?TdIY%7!6O>Kn5Up6oF` z*(Lp6-MY(C1xDqtGiG{$I<)asm+Y^kPVw_xKR*gjruF;}pc*J*D&QHvkg2+#K})A= zCy62rjerwuV?`)+(p!F$pB(Q%R*}H-%%ztS8r>I*Wf~N)Mm`oXYDYNfo92es<9ZK& z)3kefnJo=)64{0ibux&8b)@67;5iWACgVU2R8G~t$+&;7ak@t6qQt(j4D(;YGQ&T& z{YvZB^L%jJ>AzFZ=9BO=Byu-^3dk1<3pV_H$&h{!$rt9?yYb7^No%$0Sah15Kz;R^ z;3eYS_JH5WhpbxVAP93YB{_FGa5+3qOu72JzdvG&Z}fO!vP>Dr^j1XPx5V@cYt`AI zvvX_Lj&_*uW89$}Q6Zttm7Nc_3J2Lsk3^jd>y(dg@;Qd3Ti4WHSif~l7p~tSUH@q9 z=N0mc0Z|I#6Kr^qSXUKDLSwMt%ssO|Yz}$$!+kx&?~i{UnQ%CVE`?l$-e=}Lh}=?% zA6M03#3Rx~q^4DSDyY81Pnk2L_w6kW!cR`@2{)7xGghK+C6-uNMd(bamXIm13R?BF znqPm^s;<`RniM=-dU!(aWZFX)PkV`m4d^6suN%yM)%aNGwL*2Q380_gWCI)Sa|#~o z)a!Ms2}wsu!rJ(r2caS`@W z7U~Syz5Ri$Q>OB;%FT(y--=Fo+Ncx=-kVJrni%2@?7c!C{1N3&B*+@@iC@>|bK-^d zDn5hO&$f!#KoM67-+qH!LH7k3Iz4m@GHG@Pn5D=i#pSk3L;`b+Bm;}yCWapL{th61 zzE3KJ>Kr>`h)UiUH6sSAXJ+J^U;Xq_j|93tG_%Emu`-8*5x)N8L3^AGyOo|RK5D^Gs?Te-9yL*6HG1aoRUpN zWeqBl{}LD`XEn(A6Y9!4noz{&g0qspCmrf-?AHx zBkR5}KG_`gc>cq0$52=Eg0ZJS9tC%*SLpLgqk&?sBSysV(h=#7O9ki3%O7;+77x1O z(7k>fOQo*F)Pae-)83Zlf>e3Pdt7~RP$h3IhXwJjh=du4+V|+#p-^hxi@%F#k(|kLh25Puk`?@Oh>GZh;_y;4koONQedyf8E6y0|fPxzyOIO7vpEQ z<;OHhW3#_B@030b^$FMsS@0k5!|jO{m}mq1iTAUS*u!LNs$-LNfAgC6d-8x%u149{ zX8`D?#h6_PRJAVx&uTX=u-Rt?#rSaQ zo3Jk~oiBzxx88}T0PXmzhlR8m6tc5lE?WF8i%%!|qos?c#?5`~YGygsVDBB6_s@f% zpG)L|B{>qy8&Io^LP_0ua6@M|C*Fg^Pau&p>x0o8+C-ao^b|&AIA7lnDrj*hAoSq@ zG1AJYSFq(mD(*|sT}cJb*tNpk9)y~(n{GTm1KKrJ z9PZo5-!I+EI*h9<5-k?}L+qHKNYqT0OtW=0mWH6^8_x@npcJD#!01sKr~wde8hW?c znl2w|h^T6K>vR;jg<4uUe~wgF97KTqD(4$3IDrvRXs-7EPyscj{6QJS2^2&>!vRfD z@^dO?!@FFb$!iQbiljG67OLeqS*&BrxP{AN&dwon>qlesv$apg+DCF@`P^kd4m-H0-Nzc zzgw0$Fx?X2gsz%G9Z)Vl1w=!l?irjh@%3;1Ie+2K+i5xz?!SmT=D*|aJBC3N{X6bX z{((CMjTnFX9e-GVY zszSdL08`TKz*f{J>Aof019W2;7*iP&)`|-uC4D3ZLj)MX)H%=fWDNK;as(!CH@+70 z!m151JBrMiyVMkIaQM$DUWfsh2BThbd^*s}m0gKQfu}59v52?ACfrIJo3Y1EIoYqh z4MyHSiw=I%jeo&z$pdILiCn36Ke)l8WxM#f^8!2~fispgAS=S_a=NFY${zqEgH>P5 zjxt7EAfjRJti;-0J8VwT8>uVbl{W0j46jD(JmOHO?M|$fDOavllcb>1ijeQ73FQlw zNbW==xK^~Y;FFb) zR2G8T>+uq&cU}k-mEK~*7%Dj`YRcC$rDgCepWd4RwZ$|*f8U^2YL;+LT+CY0K)x-W z;9{Y#6tbF281US?+_io{HAMQ?GP{x7Ql#-4!ym<^&d*EZ=SJmMJ_8 zO41P~6#c@rDmS~%aMrW57^WB(9*E65 z*??2Y4&VTAEz{-yq49-l()HFx7rq^f{09bIT|lm# zJBg|E4P&47RYA9pdQ=MIHGh_q5Y^TA`xsochp*fQC((~g6U!|HkxJ#zRg+{aD*GA< zXKMwftN4?9L}9FoQC0yrOgRC4+?Zc~*+spFR1_b0{E2qQ?QwsSkw4$$4ScGSvYfrZ z^P5h|XQ#Rh0if;KY<8oP(q|!C2Mq2+6kzjv!g<(6<8OToTzl5ooA{c@++#Z@pn+F_ zNghw5WYbll&pQw~|2JyzUjZx}jBNk(8%t*YB`mT23rqh>oCOdx5P%vy8jx1La6B8p zKXU(3ge?*vKXlm@jfz}ZH*6LC8L$yB=ilprE!m;P|0N9q4hP;5x6HVo+I+tiHQnX& z=}&-@t1XBQrJT{9w{5*t^IH>nqe`#U|1YZ-DUG#1;(F8!&TwP35y40#ziy0>NZw@o zL-Pph^r^66BDjlO_6O8{Iu`p`(dZzST;W)-5)~Wx)nS%>YrSf2zhX4K4kL_r?w|@2 zHBnu+ry`RAc0aiN6j%|a7l#TXfCr5+2Ex3a+`bwB}sfK)e#Wo(3M5!K!;#$zS0 zYioc8n@pY&(xKe-J+J zAm1nUfZm+GwVS#JE}i!fdG|X=fm8j8NW4a&Fk9S2wn5`q-XmlniQmM5b5E(WacdEsib**?`G#v?8Ov&ALWRM;7TAXmCTf5AUiqJ z≫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