diff --git a/ccm-core/src/main/java/com/arsdigita/kernel/security/DefaultSecurityHelper.java b/ccm-core/src/main/java/com/arsdigita/kernel/security/DefaultSecurityHelper.java new file mode 100644 index 000000000..e27c83cae --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/kernel/security/DefaultSecurityHelper.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2001-2004 Red Hat Inc. All Rights Reserved. + * + * This library 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 2.1 of + * the License, or (at your option) any later version. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +package com.arsdigita.kernel.security; + +import javax.servlet.http.HttpServletRequest; + +/** + * Default implementation of SecurityHelper interface. + * + * @author Sameer Ajmani + * @see SecurityHelper + */ +public class DefaultSecurityHelper implements SecurityHelper { + + /** + * Determines whether the request is secure by calling + * req.isSecure(). + * + * @param request The current {@link HttpServletRequest} + * + * @return req.isSecure(). + * + */ + @Override + public boolean isSecure(final HttpServletRequest request) { + return request.isSecure(); + } + + /** + * Determines whether the current request requires that the user be logged + * in. + * + * @param request The current {@link HttpServletRequest} + * + * @return true if the request is secure and the page is not on + * a list of allowed pages (such as the login page and the + * bad-password page), false otherwise. + * + */ + @Override + public boolean requiresLogin(final HttpServletRequest request) { + // XXX workaround, old is broken anyway, + // it doesn't take into account dispatcher prefix ( /ccm ) + return false; + } + + /** + * Returns the full URL of the login page stored in the page map. + * + * @param request The current {@link HttpServletRequest} + * + * @return the full URL of the login page. + * + */ + @Override + public String getLoginURL(final HttpServletRequest request) { + //ToDo: Add correct method call here. + + //return UI.getLoginPageURL(); + return ""; + } + +} diff --git a/ccm-core/src/main/java/com/arsdigita/kernel/security/SecurityConfig.java b/ccm-core/src/main/java/com/arsdigita/kernel/security/SecurityConfig.java new file mode 100644 index 000000000..20e79cdb1 --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/kernel/security/SecurityConfig.java @@ -0,0 +1,239 @@ +/* + * Copyright (C) 2003-2004 Red Hat Inc. All Rights Reserved. + * + * This library 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 2.1 of + * the License, or (at your option) any later version. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +package com.arsdigita.kernel.security; + +import com.arsdigita.runtime.AbstractConfig; +import com.arsdigita.util.parameter.BooleanParameter; +import com.arsdigita.util.parameter.IntegerParameter; +import com.arsdigita.util.parameter.Parameter; +import com.arsdigita.util.parameter.SpecificClassParameter; +import com.arsdigita.util.parameter.StringArrayParameter; +import com.arsdigita.util.parameter.StringParameter; + +import java.util.Arrays; +import java.util.List; + +/** + * A record containing server-session scoped security configuration properties. + * + * Accessors of this class may return null. Developers should take care to trap + * null return values in their code. + * + * + * @author Rafael H. Schloming <rhs@mit.edu> + * @author Jens Pelzetter + */ +public class SecurityConfig extends AbstractConfig { + + private static SecurityConfig s_config = null; + + private static String s_systemAdministratorEmailAddress = null; + + /** + * Size of secret key in bytes. * + */ + @SuppressWarnings("PublicField") + public static int SECRET_KEY_BYTES = 16; + + /** + * The class name of the SecurityHelper implementation. Must implement + * SecurityHelper interface + */ + private final Parameter m_securityHelperClass = new SpecificClassParameter( + "waf.security_helper_class", Parameter.REQUIRED, + com.arsdigita.kernel.security.DefaultSecurityHelper.class, + com.arsdigita.kernel.security.SecurityHelper.class); + + /** + * List of extensions excluded from authentication cookies. Authentication + * is checked for all requests, but requests with one of these extensions + * will never cause a new cookie to be set. Include a leading dot for each + * extension. + */ + private final Parameter m_excludedExtensions = new StringArrayParameter( + "waf.excluded_extensions", Parameter.REQUIRED, + new String[]{".jpg", ".gif", ".png", ".pdf"}); + + private final Parameter m_cookieDurationMinutes = new IntegerParameter( + "waf.pagemap.cookies_duration_minutes", Parameter.OPTIONAL, null); + + private final Parameter m_cookieDomain = new StringParameter( + "waf.cookie_domain", Parameter.OPTIONAL, null); + + private final Parameter m_loginConfig = new StringArrayParameter( + "waf.login_config", Parameter.REQUIRED, + new String[]{ + "Register:com.arsdigita.kernel.security.LocalLoginModule:requisite",}); + + private final Parameter m_adminEmail = new StringParameter( + "waf.admin.contact_email", Parameter.OPTIONAL, null); + + private final Parameter m_autoRegistrationOn = new BooleanParameter( + "waf.auto_registration_on", Parameter.REQUIRED, Boolean.TRUE); + + private final Parameter m_userBanOn = new BooleanParameter( + "waf.user_ban_on", + Parameter.REQUIRED, + Boolean.FALSE); + + private final Parameter m_enableQuestion = new BooleanParameter( + "waf.user_question.enable", Parameter.REQUIRED, Boolean.FALSE); + + /** + * The default hash algorithm used for new passwords. Default is SHA-512 + * which should sufficient for good security. + */ + private final Parameter m_hashAlgorithm = new StringParameter( + "waf.security.hash_algorithm", Parameter.REQUIRED, "SHA-512"); + + /** + * Default length of the salt for new passwords. + */ + private final Parameter m_saltLength = new IntegerParameter( + "waf.security.salt_length", Parameter.REQUIRED, 256); + + /** + * Constructs an empty SecurityConfig object + */ + public SecurityConfig() { + + register(m_securityHelperClass); + register(m_excludedExtensions); + + register(m_cookieDomain); + register(m_loginConfig); + register(m_cookieDurationMinutes); + register(m_adminEmail); + register(m_autoRegistrationOn); + register(m_userBanOn); + register(m_enableQuestion); + + register(m_hashAlgorithm); + register(m_saltLength); + + loadInfo(); + } + + /** + * Returns the singleton configuration record for the runtime environment. + * + * @return The RuntimeConfig record; it cannot be null + */ + public static final synchronized SecurityConfig getConfig() { + if (s_config == null) { + s_config = new SecurityConfig(); + s_config.load(); + } + + return s_config; + } + + /** + * + * @return + */ + public final Class getSecurityHelperClass() { + return (Class) get(m_securityHelperClass); + } + +// /** +// * Obsolete! +// * @return +// */ +// public final String getSessionTrackingMethod() { +// return (String) get(m_sessionTrackingMethod); +// } + /** + * + * @return + */ + public final List getExcludedExtensions() { + return Arrays.asList((String[]) get(m_excludedExtensions)); + } + + public String getCookieDomain() { + return (String) get(m_cookieDomain); + } + + String[] getLoginConfig() { + return (String[]) get(m_loginConfig); + } + + Integer getCookieDurationMinutes() { + return (Integer) get(m_cookieDurationMinutes); + } + + boolean isUserBanOn() { + return ((Boolean) get(m_userBanOn)).booleanValue(); + } + + public String getAdminContactEmail() { + String email = (String) get(m_adminEmail); + + // Return empty string instead of looking up into the database. If no + // email if configured for the admin we consider that as a configuration + // issue. + if (email == null || email.isEmpty()) { + return ""; + } else { + return email; + } +// if (email == null || email.trim().length() == 0) { +// email = getSystemAdministratorEmailAddress(); +// } +// return email; + } + + public Boolean getEnableQuestion() { + return (Boolean) get(m_enableQuestion); + } + +// private static synchronized String getSystemAdministratorEmailAddress() { +// if (s_systemAdministratorEmailAddress == null) { +// ObjectPermissionCollection perms = PermissionService. +// getGrantedUniversalPermissions(); +// perms.addEqualsFilter("granteeIsUser", Boolean.TRUE); +// perms.clearOrder(); +// perms.addOrder("granteeID"); +// if (perms.next()) { +// s_systemAdministratorEmailAddress = perms.getGranteeEmail(). +// toString(); +// perms.close(); +// } else { +// // Haven't found anything. We don't want to repeat this query +// // over and over again. +// s_systemAdministratorEmailAddress = ""; +// } +// } +// return s_systemAdministratorEmailAddress; +// } + + public final boolean isAutoRegistrationOn() { + return ((Boolean) get(m_autoRegistrationOn)).booleanValue(); + } + + public String getHashAlgorithm() { + return (String) get(m_hashAlgorithm); + } + + public Integer getSaltLength() { + return (Integer) get(m_saltLength); + } + +} diff --git a/ccm-core/src/main/java/com/arsdigita/kernel/security/SecurityHelper.java b/ccm-core/src/main/java/com/arsdigita/kernel/security/SecurityHelper.java new file mode 100755 index 000000000..169b746a7 --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/kernel/security/SecurityHelper.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2001-2004 Red Hat Inc. All Rights Reserved. + * + * This library 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 2.1 of + * the License, or (at your option) any later version. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +package com.arsdigita.kernel.security; + +import javax.servlet.http.HttpServletRequest; + +/** + * Provides methods for determining security properties for a request. + * + * @author Sameer Ajmani + * @version $Id: SecurityHelper.java 287 2005-02-22 00:29:02Z sskracic $ + */ +public interface SecurityHelper { + + /** + * Determines whether the given request is secure. Implementation may + * simply return req.isSecure(), but certain deployments + * may use other information (such as the requested port number) + * instead. + * + * @param req the request to check + * + * @return true if the given request uses a secure + * protocol, false otherwise. + **/ + public boolean isSecure(HttpServletRequest req); + + /** + * Determines whether the given request requires the user to be logged + * in. If this method returns true, the system will call + * getLoginURL to determine where to redirect the client to + * log in. + * + * @param req the request to check + * + * @return true if the given request requires the user to + * be logged in, false otherwise. + **/ + public boolean requiresLogin(HttpServletRequest req); + + /** + * Determines where to redirect the client to log in. The system calls + * this method if the user fails to log in and + * requiresLogin(req) is true. + * + * @return the URL to which the client should be redirected to log in, + * never null. + **/ + public String getLoginURL(HttpServletRequest req); +} diff --git a/ccm-core/src/main/resources/com/arsdigita/kernel/security/SecurityConfig_parameter.properties b/ccm-core/src/main/resources/com/arsdigita/kernel/security/SecurityConfig_parameter.properties new file mode 100755 index 000000000..cde439644 --- /dev/null +++ b/ccm-core/src/main/resources/com/arsdigita/kernel/security/SecurityConfig_parameter.properties @@ -0,0 +1,39 @@ +waf.login_config.title=Login Configuration +waf.login_config.purpose=Enter JAAS login configuration, using the syntax described in Javadoc for com.arsdigita.kernel.security.LoginConfig +waf.login_config.example=Request:com.arsdigita.kernel.security.AdminLoginModule:sufficient,Register:com.arsdigita.kernel.security.LocalLoginModule:requisite +waf.login_config.format=[string,string,...] + +waf.cookie_domain.title=Cookie Domain +waf.cookie_domain.purpose=Enter the domain to which the Aplaws authentication cookie is presented +waf.cookie_domain.example=.example.com +waf.cookie_domain.format=[string] + +waf.admin.contact_email.title=System administrator email address +waf.admin.contact_email.purpose=Email address that will be displayed on footer of login/admin pages, if empty then site-wide admin email will be substituted +waf.admin.contact_email.example=ccmadmin@example.com +waf.admin.contact_email.format=[string] + +waf.auto_registration_on.title=Auto Registration +waf.auto_registration_on.purpose=New users get automatically redirected to the create new user form +waf.auto_registration_on.example=true +waf.auto_registration_on.format=true|false + +waf.user_ban_on.title=User Ban +waf.user_ban_on.purpose=Check on each access if user has been banned from the site. +waf.user_ban_on.example=false +waf.user_ban_on.format=true|false + +waf.user_question_enable.title=Enable question +waf.user_question_enable.purpose=Enable question if a user has forgotten its password +waf.user_question_enable.example=false +waf.user_question_enable.format=true|false + +waf.security.hash_algorithm.title=Default Hash Algorithm +waf.security.hash_algorithm.purpose=Sets the Hash Algorithm to use for new passwords. The available algorithms depend on the Java Runtime. +waf.security.hash_algorithm.example=SHA-512 +waf.security.hash_algorithm.format=[string] + +waf.security.salt_length.title=Default Salt Length +waf.security.salt_length.purpose=Sets the length of the salt for new passwords +waf.security.salt_length.example=256 +waf.security.salt_length.format=[int] \ No newline at end of file diff --git a/ccm-core/src/test/java/com/arsdigita/kernel/KernelConfigTest.java b/ccm-core/src/test/java/com/arsdigita/kernel/KernelConfigTest.java index a5da49b37..0db685d37 100644 --- a/ccm-core/src/test/java/com/arsdigita/kernel/KernelConfigTest.java +++ b/ccm-core/src/test/java/com/arsdigita/kernel/KernelConfigTest.java @@ -37,7 +37,6 @@ import org.junit.Test; import org.junit.experimental.categories.Category; import org.junit.runner.RunWith; import org.libreccm.tests.categories.IntegrationTest; -import sun.util.locale.StringTokenIterator; import java.util.StringTokenizer; diff --git a/ccm-core/src/test/java/com/arsdigita/kernel/security/SecurityConfigTest.java b/ccm-core/src/test/java/com/arsdigita/kernel/security/SecurityConfigTest.java new file mode 100644 index 000000000..b6767aa75 --- /dev/null +++ b/ccm-core/src/test/java/com/arsdigita/kernel/security/SecurityConfigTest.java @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2015 LibreCCM Foundation. + * + * This library 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 2.1 of the License, or (at your option) any later version. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ +package com.arsdigita.kernel.security; + +import static org.hamcrest.Matchers.*; + +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.junit.Arquillian; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.EmptyAsset; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.jboss.shrinkwrap.resolver.api.maven.Maven; +import org.jboss.shrinkwrap.resolver.api.maven.PomEquippedResolveStage; +import org.junit.After; +import org.junit.AfterClass; + +import static org.junit.Assert.*; + +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.runner.RunWith; +import org.libreccm.tests.categories.IntegrationTest; + +import java.io.File; +import java.util.List; + +/** + * + * @author Jens Pelzetter + */ +@RunWith(Arquillian.class) +@Category(IntegrationTest.class) +public class SecurityConfigTest { + + public SecurityConfigTest() { + + } + + @BeforeClass + public static void setUpClass() { + } + + @AfterClass + public static void tearDownClass() { + } + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + + @Deployment + public static WebArchive createDeployment() { + final PomEquippedResolveStage pom = Maven + .resolver() + .loadPomFromFile("pom.xml"); + final PomEquippedResolveStage dependencies = pom + .importCompileAndRuntimeDependencies(); + final File[] libs = dependencies.resolve().withTransitivity().asFile(); + + for (File lib : libs) { + System.err.printf("Adding file '%s' to test archive...%n", + lib.getName()); + } + + return ShrinkWrap + .create(WebArchive.class, + "LibreCCM-com.arsdigita.kernel.security.SecurityConfigTest.war") + //.addPackage(CcmObject.class.getPackage()) + .addPackage(com.arsdigita.kernel.KernelConfig.class.getPackage()) + .addPackage(com.arsdigita.kernel.security.SecurityConfig.class + .getPackage()) + .addPackage(com.arsdigita.runtime.AbstractConfig.class.getPackage()) + .addPackage(com.arsdigita.util.parameter.AbstractParameter.class. + getPackage()) + .addPackage(com.arsdigita.util.JavaPropertyReader.class. + getPackage()) + .addPackage(com.arsdigita.web.CCMApplicationContextListener.class + .getPackage()) + .addPackage(com.arsdigita.xml.XML.class.getPackage()) + .addPackage(com.arsdigita.xml.formatters.DateFormatter.class + .getPackage()) + .addPackage(org.libreccm.tests.categories.IntegrationTest.class + .getPackage()) + .addAsLibraries(libs) + .addAsResource( + "configtests/com/arsdigita/kernel/security/SecurityConfigTest/ccm-core.config", + "ccm-core.config") + .addAsWebInfResource( + "configtests/com/arsdigita/kernel/security/SecurityConfigTest/registry.properties", + "conf/registry/registry.properties") + .addAsWebInfResource( + "configtests/com/arsdigita/kernel/security/SecurityConfigTest/kernel.properties", + "conf/registry/ccm-core/kernel.properties") + .addAsWebInfResource( + "configtests/com/arsdigita/kernel/security/SecurityConfigTest/security.properties", + "conf/registry/ccm-core/security.properties") + .addAsResource( + "com/arsdigita/kernel/KernelConfig_parameter.properties", + "com/arsdigita/kernel/KernelConfig_parameter.properties") + .addAsResource( + "com/arsdigita/kernel/security/SecurityConfig_parameter.properties", + "com/arsdigita/kernel/security/SecurityConfig_parameter.properties") + .addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml"); + } + + @Test + public void verifySecurityConfig() { + final SecurityConfig securityConfig = SecurityConfig.getConfig(); + + final String[] loginConfig = securityConfig.getLoginConfig(); + assertThat(loginConfig.length, is(1)); + assertThat(loginConfig[0], is(equalTo( + "Register:com.arsdigita.kernel.security.LocalLoginModule:requisite"))); + + final List excludedExtensions = securityConfig.getExcludedExtensions(); + assertThat(excludedExtensions.size(), is(4)); + assertThat(excludedExtensions.get(0), is(equalTo(".jpg"))); + assertThat(excludedExtensions.get(1), is(equalTo(".gif"))); + assertThat(excludedExtensions.get(2), is(equalTo(".png"))); + assertThat(excludedExtensions.get(3), is(equalTo(".pdf"))); + + assertThat(securityConfig.getCookieDurationMinutes(), is(nullValue())); + + assertThat(securityConfig.getCookieDomain(), + is(equalTo(".example.org"))); + + assertThat(securityConfig.getAdminContactEmail(), + is(equalTo("admin@example.org"))); + + assertThat(securityConfig.isAutoRegistrationOn(), is(false)); + + assertThat(securityConfig.isUserBanOn(), is(true)); + + assertThat(securityConfig.getEnableQuestion(), is(false)); + + assertThat(securityConfig.getHashAlgorithm(), is(equalTo("SHA-256"))); + + assertThat(securityConfig.getSaltLength(), is(128)); + } + +} diff --git a/ccm-core/src/test/resources/configtests/com/arsdigita/kernel/security/SecurityConfigTest/ccm-core.config b/ccm-core/src/test/resources/configtests/com/arsdigita/kernel/security/SecurityConfigTest/ccm-core.config new file mode 100644 index 000000000..dd5c4baf7 --- /dev/null +++ b/ccm-core/src/test/resources/configtests/com/arsdigita/kernel/security/SecurityConfigTest/ccm-core.config @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/ccm-core/src/test/resources/configtests/com/arsdigita/kernel/security/SecurityConfigTest/kernel.properties b/ccm-core/src/test/resources/configtests/com/arsdigita/kernel/security/SecurityConfigTest/kernel.properties new file mode 100644 index 000000000..f0ebc0b58 --- /dev/null +++ b/ccm-core/src/test/resources/configtests/com/arsdigita/kernel/security/SecurityConfigTest/kernel.properties @@ -0,0 +1 @@ +# this file is empty by purpose. \ No newline at end of file diff --git a/ccm-core/src/test/resources/configtests/com/arsdigita/kernel/security/SecurityConfigTest/registry.properties b/ccm-core/src/test/resources/configtests/com/arsdigita/kernel/security/SecurityConfigTest/registry.properties new file mode 100644 index 000000000..9bb7b6ea9 --- /dev/null +++ b/ccm-core/src/test/resources/configtests/com/arsdigita/kernel/security/SecurityConfigTest/registry.properties @@ -0,0 +1 @@ +waf.config.packages=ccm-core \ No newline at end of file diff --git a/ccm-core/src/test/resources/configtests/com/arsdigita/kernel/security/SecurityConfigTest/security.properties b/ccm-core/src/test/resources/configtests/com/arsdigita/kernel/security/SecurityConfigTest/security.properties new file mode 100644 index 000000000..727c136ce --- /dev/null +++ b/ccm-core/src/test/resources/configtests/com/arsdigita/kernel/security/SecurityConfigTest/security.properties @@ -0,0 +1,15 @@ +waf.login_config=Register:com.arsdigita.kernel.security.LocalLoginModule:requisite + +waf.cookie_domain=.example.org + +waf.admin.contact_email=admin@example.org + +waf.auto_registration_on=false + +waf.user_ban_on=true + +waf.user_question_enable=false + +waf.security.hash_algorithm=SHA-256 + +waf.security.salt_length=128 \ No newline at end of file