Author: [email protected]
Date: Tue Mar 27 12:22:42 2012
New Revision: 2158
Log:
[AMDATUAUTH-122] Implemented password restrictions, configurable using OSGi
config admin.
Added:
trunk/amdatu-auth/config/src/main/resources/org.amdatu.auth.useradmin.rest.cfg
trunk/amdatu-auth/useradmin-rest/src/main/java/org/amdatu/auth/useradmin/rest/bean/Label.java
trunk/amdatu-auth/useradmin-rest/src/main/java/org/amdatu/auth/useradmin/rest/bean/PasswordPolicy.java
trunk/amdatu-auth/useradmin-rest/src/main/java/org/amdatu/auth/useradmin/rest/util/
trunk/amdatu-auth/useradmin-rest/src/main/java/org/amdatu/auth/useradmin/rest/util/ConfigurationUtil.java
Modified:
trunk/amdatu-auth/test-integration/base/pom.xml
trunk/amdatu-auth/test-integration/base/src/main/java/org/amdatu/auth/test/integration/base/AuthFixture.java
trunk/amdatu-auth/useradmin-gadget/src/main/resources/static/js/useradmin.js
trunk/amdatu-auth/useradmin-gadget/src/main/resources/static/js/useradmin_rest.js
trunk/amdatu-auth/useradmin-gadget/src/main/resources/static/labels/useradmin_ALL_ALL.xml
trunk/amdatu-auth/useradmin-rest/pom.xml
trunk/amdatu-auth/useradmin-rest/src/main/java/org/amdatu/auth/useradmin/rest/osgi/Activator.java
trunk/amdatu-auth/useradmin-rest/src/main/java/org/amdatu/auth/useradmin/rest/service/ResourceBase.java
trunk/amdatu-auth/useradmin-rest/src/main/java/org/amdatu/auth/useradmin/rest/service/UsersResource.java
Added:
trunk/amdatu-auth/config/src/main/resources/org.amdatu.auth.useradmin.rest.cfg
==============================================================================
--- (empty file)
+++
trunk/amdatu-auth/config/src/main/resources/org.amdatu.auth.useradmin.rest.cfg
Tue Mar 27 12:22:42 2012
@@ -0,0 +1,26 @@
+# Copyright (c) 2010, 2011 The Amdatu Foundation
+#
+# 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.verning permissions and limitations
+# under the License.
+
+# Requires that the password consists of at least this amount of characters
+password.minimum_length=6
+
+# Requires that the password contains letters in both lowercase and uppercase
(true or false)
+password.require_mixed_case=false
+
+# Minimum amount of digits the password must have
+password.minimum_digits=0
+
+# Minimum amount of special characters (other then digits and letters) the
password must have
+password.minimum_special_characters=0
\ No newline at end of file
Modified: trunk/amdatu-auth/test-integration/base/pom.xml
==============================================================================
--- trunk/amdatu-auth/test-integration/base/pom.xml (original)
+++ trunk/amdatu-auth/test-integration/base/pom.xml Tue Mar 27 12:22:42 2012
@@ -41,6 +41,13 @@
<scope>provided</scope>
<type>bundle</type>
</dependency>
+ <dependency>
+ <groupId>org.amdatu.auth</groupId>
+ <artifactId>org.amdatu.auth.useradmin.rest</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ <type>bundle</type>
+ </dependency>
</dependencies>
<!--
Modified:
trunk/amdatu-auth/test-integration/base/src/main/java/org/amdatu/auth/test/integration/base/AuthFixture.java
==============================================================================
---
trunk/amdatu-auth/test-integration/base/src/main/java/org/amdatu/auth/test/integration/base/AuthFixture.java
(original)
+++
trunk/amdatu-auth/test-integration/base/src/main/java/org/amdatu/auth/test/integration/base/AuthFixture.java
Tue Mar 27 12:22:42 2012
@@ -21,6 +21,7 @@
import org.amdatu.auth.oauth.server.OAuthServerConfig;
import org.amdatu.auth.tokenprovider.TokenProvider;
import org.amdatu.core.itest.base.TestContext;
+import org.amdatu.auth.useradmin.rest.service.UsersResource;
import java.util.Properties;
@@ -73,6 +74,7 @@
public void configureOAuthServer(TestContext testContext) throws Exception
{
testContext.updateConfig(OAuthServerConfig.PID, getOAuthServerCfg());
testContext.updateConfig(TokenProvider.PID, getTokenProviderCfg());
+ testContext.updateConfig(UsersResource.PID, getUserAdminCfg());
}
private Properties getOAuthServerCfg() {
@@ -117,4 +119,13 @@
properties.put("org.apache.felix.log.storeDebug", "true");
return properties;
}
+
+ private Properties getUserAdminCfg() {
+ Properties properties = new Properties();
+ properties.put("password.minimum_length", "6");
+ properties.put("password.require_mixed_case", "false");
+ properties.put("password.minimum_digits", "0");
+ properties.put("password.minimum_special_characters", "0");
+ return properties;
+ }
}
Modified:
trunk/amdatu-auth/useradmin-gadget/src/main/resources/static/js/useradmin.js
==============================================================================
---
trunk/amdatu-auth/useradmin-gadget/src/main/resources/static/js/useradmin.js
(original)
+++
trunk/amdatu-auth/useradmin-gadget/src/main/resources/static/js/useradmin.js
Tue Mar 27 12:22:42 2012
@@ -37,7 +37,7 @@
var prevIcon = "ui-icon-seek-prev' style='float: left;";
var nextIcon = "ui-icon-seek-next' style='float: right;";
var endIcon = "ui-icon-seek-end' style='float: right;";
-
+
var links = jsonData["links"];
for (i=0; i<links.length; i++) {
if (links[i]["rel"] == "Start") {
@@ -70,7 +70,7 @@
if (start == null) {
start = getDisabledNavigationLink(startIcon);
}
-
+
var html = "";
var roles = ensureArray(jsonData["roles"]);
if (roles) {
@@ -79,7 +79,7 @@
} else {
nameLabel = prefs.getMsg('group_name');
}
-
+
html += "<table width='100%'><tr class='ui-widget-header'><th
width='80%' align='left'>" + nameLabel + "</th><th width='20%'>" +
prefs.getMsg('delete') + "</th></tr>";
for (i = 0; i < roles.length; ++i) {
var role = roles[i];
@@ -92,14 +92,14 @@
}
html += "<tr class='ui-widget-header'><td /><td /></tr></table>";
}
-
+
html += "<center id='"+currentView+"display'>";
html += start + " " + prev + " ";
html += prefs.getMsg('results_nav_1') + " <b>" + pageStartIndex + "-" +
pageEndIndex + "</b> ";
html += prefs.getMsg('results_nav_2') + " <b>" + resultCount + "</b>";
html += " " + end + " " + next + "</center>";
-
- document.getElementById(currentView).innerHTML = html;
+
+ document.getElementById(currentView).innerHTML = html;
}
else if (response.rc == 401) {
hide('add_' + currentView + '_button');
@@ -107,7 +107,7 @@
+"<span class='ui-icon ui-icon-alert' style='float: left;
margin-right: .3em;'></span><span>"+prefs.getMsg('unauthorized')+"</span>"
+"</p></div>";
document.getElementById(currentView).innerHTML = html;
- }
+ }
else {
showError(prefs.getMsg('unexpected_error') + response.errors);
}
@@ -127,7 +127,7 @@
}
html += "</table>";
document.getElementById('implied_roles').innerHTML = html;
- }
+ }
else {
selectedRole = role["name"];
var requiredMembers = ensureArray(role["requiredMembers"]);
@@ -171,6 +171,9 @@
function onPasswordChanged(response) {
if (response.rc == 200) {
showInfo(prefs.getMsg('password_changed'));
+ } else if (response.rc == 400) {
+ var msg = prefs.getMsg('password_strength_violation');
+ showPasswordPolicy(msg);
} else if (response.rc == 404) {
showError(prefs.getMsg('role_not_found'));
} else if (response.rc == 401) {
@@ -231,6 +234,9 @@
showInfo(prefs.getMsg('role_added'));
loadRoles(currentUrl, onRolesLoaded);
gadgets.window.adjustHeight();
+ } else if (response.rc == 400) {
+ showError(prefs.getMsg('password_strength_violation'));
+ getPasswordPolicy();
} else if (response.rc == 404) {
showError(prefs.getMsg('role_not_found'));
} else if (response.rc == 401) {
Modified:
trunk/amdatu-auth/useradmin-gadget/src/main/resources/static/js/useradmin_rest.js
==============================================================================
---
trunk/amdatu-auth/useradmin-gadget/src/main/resources/static/js/useradmin_rest.js
(original)
+++
trunk/amdatu-auth/useradmin-gadget/src/main/resources/static/js/useradmin_rest.js
Tue Mar 27 12:22:42 2012
@@ -116,6 +116,20 @@
gadgets.io.makeRequest(url, callback, params);
}
+function showPasswordPolicy(basemsg) {
+ var url = "/rest/users/passwordpolicy";
+ url = getBaseUrl() + addNoCache(url);
+
+ var params = {};
+ params[gadgets.io.RequestParameters.CONTENT_TYPE] =
gadgets.io.ContentType.JSON;
+ params[gadgets.io.RequestParameters.METHOD] = gadgets.io.MethodType.GET;
+ addAmdatuToken(params);
+ gadgets.io.makeRequest(url, function(response) {
+ var policy = response.data.passwordpolicy.description.label.content;
+ showError(basemsg + ' ' + policy);
+ }, params);
+}
+
function addNoCache(url) {
var ts = new Date().getTime();
if (url.indexOf("?") == -1) {
Modified:
trunk/amdatu-auth/useradmin-gadget/src/main/resources/static/labels/useradmin_ALL_ALL.xml
==============================================================================
---
trunk/amdatu-auth/useradmin-gadget/src/main/resources/static/labels/useradmin_ALL_ALL.xml
(original)
+++
trunk/amdatu-auth/useradmin-gadget/src/main/resources/static/labels/useradmin_ALL_ALL.xml
Tue Mar 27 12:22:42 2012
@@ -28,6 +28,7 @@
<msg name="password">Password</msg>
<msg name="new_password">New password</msg>
<msg name="changepwd_upon_login">User must change password at first
login</msg>
+ <msg name="password_strength_violation">The entered password is not strong
enough.</msg>
<msg name="implied_roles">Implied roles</msg>
<msg name="details">Details</msg>
Modified: trunk/amdatu-auth/useradmin-rest/pom.xml
==============================================================================
--- trunk/amdatu-auth/useradmin-rest/pom.xml (original)
+++ trunk/amdatu-auth/useradmin-rest/pom.xml Tue Mar 27 12:22:42 2012
@@ -66,6 +66,12 @@
<scope>compile</scope>
</dependency>
<dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-lang3</artifactId>
+ <version>3.1</version>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.5.6</version>
@@ -89,7 +95,7 @@
<Bundle-Activator>org.amdatu.auth.useradmin.rest.osgi.Activator</Bundle-Activator>
<Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
<Private-Package>org.amdatu.auth.useradmin.rest.*,org.apache.wink.common.internal.uri</Private-Package>
-
<Embed-Dependency>json,org.amdatu.libraries.utilities;scope=compile</Embed-Dependency>
+
<Embed-Dependency>commons-lang3,json,org.amdatu.libraries.utilities;scope=compile</Embed-Dependency>
<Export-Package>
!*
</Export-Package>
Added:
trunk/amdatu-auth/useradmin-rest/src/main/java/org/amdatu/auth/useradmin/rest/bean/Label.java
==============================================================================
--- (empty file)
+++
trunk/amdatu-auth/useradmin-rest/src/main/java/org/amdatu/auth/useradmin/rest/bean/Label.java
Tue Mar 27 12:22:42 2012
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2010, 2011 The Amdatu Foundation
+ *
+ * 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 org.amdatu.auth.useradmin.rest.bean;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlValue;
+
+/**
+ * This means represents a language label.
+ *
+ * @author <a href="mailto:[email protected]">Amdatu Project
Team</a>
+ */
+@SuppressWarnings("restriction")
+@XmlAccessorType(XmlAccessType.NONE)
+public class Label {
+ @XmlAttribute(name="locale")
+
+ private String m_locale;
+
+ @XmlValue
+ private String m_content;
+
+ public Label() {
+ }
+
+ public Label(String locale, String content) {
+ m_locale = locale;
+ m_content = content;
+ }
+
+ public String getLocale() {
+ return m_locale;
+ }
+
+ public void setLocale(String locale) {
+ m_locale = locale;
+ }
+
+ public String getContent() {
+ return m_content;
+ }
+
+ public void setContent(String content) {
+ m_content = content;
+ }
+}
Added:
trunk/amdatu-auth/useradmin-rest/src/main/java/org/amdatu/auth/useradmin/rest/bean/PasswordPolicy.java
==============================================================================
--- (empty file)
+++
trunk/amdatu-auth/useradmin-rest/src/main/java/org/amdatu/auth/useradmin/rest/bean/PasswordPolicy.java
Tue Mar 27 12:22:42 2012
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2010, 2011 The Amdatu Foundation
+ *
+ * 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 org.amdatu.auth.useradmin.rest.bean;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import javax.xml.bind.annotation.XmlRootElement;
+
+/**
+ * This bean represents the current password policy, which is configurable
using
+ * OSGi config admin. It contains all properties of the policy but also
provides
+ * a language specific description of the policy (used by clients to describe
the
+ * policy)
+ *
+ * @author <a href="mailto:[email protected]">Amdatu Project
Team</a>
+ */
+@SuppressWarnings("restriction")
+@XmlRootElement(name = "passwordpolicy")
+@XmlAccessorType(XmlAccessType.PUBLIC_MEMBER)
+public class PasswordPolicy {
+ private int m_minimumlength;
+ private int m_minimumdigits;
+ private int m_minimumspecialcharacters;
+ private boolean m_requiremixedcase;
+ private List<Label> m_description;
+
+ public int getMinimumlength() {
+ return m_minimumlength;
+ }
+
+ public void setMinimumlength(int minimumlength) {
+ m_minimumlength = minimumlength;
+ }
+
+ public int getMinimumdigits() {
+ return m_minimumdigits;
+ }
+
+ public void setMinimumdigits(int minimumdigits) {
+ m_minimumdigits = minimumdigits;
+ }
+
+ public int getMinimumspecialcharacters() {
+ return m_minimumspecialcharacters;
+ }
+
+ public void setMinimumspecialcharacters(int minimumspecialcharacters) {
+ m_minimumspecialcharacters = minimumspecialcharacters;
+ }
+
+ public boolean isRequiremixedcase() {
+ return m_requiremixedcase;
+ }
+
+ public void setRequiremixedcase(boolean requiremixedcase) {
+ m_requiremixedcase = requiremixedcase;
+ }
+
+ @XmlElementWrapper(name = "description")
+ @XmlElement(name = "label")
+ public List<Label> getDescription() {
+ return m_description;
+ }
+
+ public void setDescription(List<Label> titles) {
+ m_description = titles;
+ }
+
+ public void addDescription(Label description) {
+ if (m_description == null) {
+ m_description = new ArrayList<Label>();
+ }
+ m_description.add(description);
+ }
+}
Modified:
trunk/amdatu-auth/useradmin-rest/src/main/java/org/amdatu/auth/useradmin/rest/osgi/Activator.java
==============================================================================
---
trunk/amdatu-auth/useradmin-rest/src/main/java/org/amdatu/auth/useradmin/rest/osgi/Activator.java
(original)
+++
trunk/amdatu-auth/useradmin-rest/src/main/java/org/amdatu/auth/useradmin/rest/osgi/Activator.java
Tue Mar 27 12:22:42 2012
@@ -23,9 +23,11 @@
import org.amdatu.libraries.utilities.osgi.ServiceDependentActivator;
import org.amdatu.web.rest.jaxrs.JaxRsSpi;
import org.amdatu.web.rest.jaxrs.RESTService;
+
import org.apache.felix.dm.DependencyManager;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
+import org.osgi.service.cm.ManagedService;
import org.osgi.service.log.LogService;
import org.osgi.service.useradmin.UserAdmin;
@@ -50,10 +52,11 @@
// Create the users resource service and register it as REST service
manager.add(createAdapterService(UserAdmin.class, filter)
- .setInterface(RESTService.class.getName(), null)
+ .setInterface(new String[]{ManagedService.class.getName(),
RESTService.class.getName()}, null)
.setImplementation(UsersResource.class)
.add(createServiceDependency().setService(LogService.class).setRequired(true))
-
.add(createServiceDependency().setService(TenantManagementService.class).setRequired(true)));
+
.add(createServiceDependency().setService(TenantManagementService.class).setRequired(true))
+ .add(createConfigurationDependency().setPid(UsersResource.PID)));
// Create the groups resource service and register it as REST service
manager.add(createAdapterService(UserAdmin.class, filter)
Modified:
trunk/amdatu-auth/useradmin-rest/src/main/java/org/amdatu/auth/useradmin/rest/service/ResourceBase.java
==============================================================================
---
trunk/amdatu-auth/useradmin-rest/src/main/java/org/amdatu/auth/useradmin/rest/service/ResourceBase.java
(original)
+++
trunk/amdatu-auth/useradmin-rest/src/main/java/org/amdatu/auth/useradmin/rest/service/ResourceBase.java
Tue Mar 27 12:22:42 2012
@@ -15,6 +15,17 @@
*/
package org.amdatu.auth.useradmin.rest.service;
+import org.amdatu.auth.tokenprovider.InvalidTokenException;
+import org.amdatu.auth.tokenprovider.TokenProvider;
+import org.amdatu.auth.tokenprovider.TokenProviderException;
+import org.amdatu.auth.useradmin.rest.bean.RoleBean;
+import org.amdatu.auth.useradmin.rest.bean.SearchResultBean;
+import org.amdatu.core.tenant.Tenant;
+import org.amdatu.core.tenant.TenantException;
+import org.amdatu.core.tenant.TenantManagementService;
+import org.amdatu.libraries.utilities.rest.AtomSyndicationLink;
+import org.amdatu.web.rest.jaxrs.RESTService;
+
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collections;
@@ -27,17 +38,8 @@
import javax.ws.rs.core.CacheControl;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
-import org.amdatu.auth.tokenprovider.InvalidTokenException;
-import org.amdatu.auth.tokenprovider.TokenProvider;
-import org.amdatu.auth.tokenprovider.TokenProviderException;
-import org.amdatu.auth.useradmin.rest.bean.RoleBean;
-import org.amdatu.auth.useradmin.rest.bean.SearchResultBean;
-import org.amdatu.core.tenant.Tenant;
-import org.amdatu.core.tenant.TenantException;
-import org.amdatu.core.tenant.TenantManagementService;
-import org.amdatu.libraries.utilities.rest.AtomSyndicationLink;
-import org.amdatu.web.rest.jaxrs.RESTService;
import org.apache.felix.dm.Component;
import org.apache.felix.dm.DependencyManager;
import org.apache.felix.dm.ServiceDependency;
@@ -292,7 +294,7 @@
return buildNotModified("Role to remove not found");
}
}
-
+
protected Response buildOK() {
return Response.ok().cacheControl(NO_CACHE_CONTROL).build();
}
@@ -312,6 +314,16 @@
protected Response buildServerError() {
return Response.serverError().cacheControl(NO_CACHE_CONTROL).build();
+ }
+
+ // Returns a 200 - OK
+ protected Response get200(Object entity) {
+ return
Response.status(Status.OK).entity(entity).cacheControl(NO_CACHE_CONTROL).build();
+ }
+
+ // Returns a 400 - BAD REQUEST
+ protected Response get400() {
+ return
Response.status(Status.BAD_REQUEST).cacheControl(NO_CACHE_CONTROL).build();
}
class Paging {
@@ -469,5 +481,9 @@
protected UserAdmin getUserAdmin() {
return m_userAdmin;
+ }
+
+ protected LogService getLogService() {
+ return m_logService;
}
}
Modified:
trunk/amdatu-auth/useradmin-rest/src/main/java/org/amdatu/auth/useradmin/rest/service/UsersResource.java
==============================================================================
---
trunk/amdatu-auth/useradmin-rest/src/main/java/org/amdatu/auth/useradmin/rest/service/UsersResource.java
(original)
+++
trunk/amdatu-auth/useradmin-rest/src/main/java/org/amdatu/auth/useradmin/rest/service/UsersResource.java
Tue Mar 27 12:22:42 2012
@@ -15,6 +15,14 @@
*/
package org.amdatu.auth.useradmin.rest.service;
+import org.amdatu.auth.useradmin.rest.bean.Label;
+import org.amdatu.auth.useradmin.rest.bean.PasswordPolicy;
+import org.amdatu.auth.useradmin.rest.util.ConfigurationUtil;
+import org.amdatu.auth.useradmin.rest.util.ConfigurationUtil.ConfigProperty;
+import org.amdatu.core.tenant.Tenant;
+
+import java.util.Dictionary;
+
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
@@ -31,8 +39,10 @@
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
-import org.amdatu.core.tenant.Tenant;
+import org.apache.commons.lang3.StringUtils;
import org.apache.felix.dm.Component;
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.cm.ManagedService;
import org.osgi.service.useradmin.Role;
/**
@@ -46,18 +56,55 @@
*
* @author ivol
*/
+@SuppressWarnings("rawtypes")
@Path("users")
-public class UsersResource extends ResourceBase {
+public class UsersResource extends ResourceBase implements ManagedService {
+ // Configuration PID and property keys
+ public final static String PID = "org.amdatu.auth.useradmin.rest";
+ private final static String PASSWORD_MIN_LENGTH =
"password.minimum_length";
+ private final static String PASSWORD_REQ_MIXED_CASE =
"password.require_mixed_case";
+ private final static String PASSWORD_MIN_DIGITS =
"password.minimum_digits";
+ private final static String PASSWORD_MIN_SPEC_CHAR =
"password.minimum_special_characters";
+ private static ConfigProperty[] CONFIG_PROPERTIES =
+ new ConfigProperty[] {
+ new ConfigProperty(PASSWORD_MIN_LENGTH, Integer.class, 6),
+ new ConfigProperty(PASSWORD_REQ_MIXED_CASE, Boolean.class, false),
+ new ConfigProperty(PASSWORD_MIN_DIGITS, Integer.class, 0),
+ new ConfigProperty(PASSWORD_MIN_SPEC_CHAR, Integer.class, 0)};
+
+ private final static String PASSWORD_CREDENTIAL_KEY = "password";
+
+ private Dictionary m_properties;
+ private ConfigurationUtil m_configUtil;
+
// Service dependencies injected by the dependency manager
private volatile Component m_component;
+ public void updated(Dictionary properties) throws ConfigurationException {
+ if (properties == null) {
+ // This is just initialization of the configuration
+ m_properties = properties;
+ }
+ else {
+ m_properties = properties;
+ if (m_configUtil != null) {
+ m_configUtil.init(CONFIG_PROPERTIES, properties);
+ }
+ }
+ }
+
+ public void start() {
+ m_configUtil = new ConfigurationUtil(getLogService());
+ m_configUtil.init(CONFIG_PROPERTIES, m_properties);
+ }
+
/**
* This method can be used to check the availability of the Users
management service.
*
* @return The text "UserAdmin Users management service online"
*/
@GET
- @Produces({ MediaType.TEXT_PLAIN })
+ @Produces({MediaType.TEXT_PLAIN})
@Path("status")
public String status() {
String tenantId = (String)
m_component.getServiceProperties().get(Tenant.TENANT_ID_SERVICEPROPERTY);
@@ -66,6 +113,52 @@
}
/**
+ * This method can be used to return the password policy.
+ *
+ * @return <ul>
+ * <li>200 (OK) : The password policy is returned.</li>
+ * <li>401 (UNAUTHORIZED) : The user is not authenticated, the
password policy can only
+ * be retrieved by authenticated users.</li>
+ * </ul>
+ */
+ @GET
+ @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
+ @Path("passwordpolicy")
+ public Response getPasswordPolicy(@Context final HttpServletRequest
request) {
+ if (!isAuthorized(request)) {
+ return Response.status(Response.Status.UNAUTHORIZED).build();
+ }
+
+ PasswordPolicy policy = new PasswordPolicy();
+ policy.setMinimumdigits(m_configUtil.get(PASSWORD_MIN_DIGITS,
Integer.class));
+ policy.setMinimumlength(m_configUtil.get(PASSWORD_MIN_LENGTH,
Integer.class));
+
policy.setMinimumspecialcharacters(m_configUtil.get(PASSWORD_MIN_SPEC_CHAR,
Integer.class));
+ policy.setRequiremixedcase(m_configUtil.get(PASSWORD_REQ_MIXED_CASE,
Boolean.class));
+
+ // Append en_US description
+ String descr = "";
+ if (m_configUtil.get(PASSWORD_MIN_LENGTH, Integer.class) > 0) {
+ descr += "Minimum length: " +
m_configUtil.get(PASSWORD_MIN_LENGTH, Integer.class) + ". ";
+ }
+ if (m_configUtil.get(PASSWORD_REQ_MIXED_CASE, Boolean.class)) {
+ descr +=
+ "Password must contain both lowercase and uppercase
characters. ";
+ }
+ if (m_configUtil.get(PASSWORD_MIN_DIGITS, Integer.class) > 0) {
+ descr += "Minimum amount of digits: " +
m_configUtil.get(PASSWORD_MIN_DIGITS, Integer.class) + ". ";
+ }
+ if (m_configUtil.get(PASSWORD_MIN_SPEC_CHAR, Integer.class) > 0) {
+ descr +=
+ "Minimum amount of non-alphanumeric characters: "
+ + m_configUtil.get(PASSWORD_MIN_SPEC_CHAR, Integer.class)
+ ". ";
+ }
+ Label label = new Label("en_US", descr);
+ policy.addDescription(label);
+
+ return get200(policy);
+ }
+
+ /**
* Returns all users that match the specified filter options. This method
can be invoked by making the following
* REST call:<code><pre>
* GET
/rest/users?filter={filter}&sortOrder={sortOrder}&startIndex={startIndex}&maxResults={maxResults}
@@ -103,7 +196,7 @@
* </pre></code> a 500 response in case any exception occurred.
*/
@GET
- @Produces({ MediaType.APPLICATION_JSON })
+ @Produces({MediaType.APPLICATION_JSON})
public Response getUsers(
@QueryParam("filter") final String filter,
@QueryParam("sortOrder") final String sortOrder,
@@ -134,7 +227,7 @@
*/
@GET
@Path("{name}")
- @Produces({ MediaType.APPLICATION_JSON })
+ @Produces({MediaType.APPLICATION_JSON})
public Response getUser(@PathParam("name") final String name, @Context
final HttpServletRequest request) {
if (!isAuthorized(request)) {
return Response.status(Response.Status.UNAUTHORIZED).build();
@@ -156,7 +249,7 @@
*/
@PUT
@Path("{name}")
- @Produces({ MediaType.APPLICATION_JSON })
+ @Produces({MediaType.APPLICATION_JSON})
public Response createUser(@PathParam("name") final String name, @Context
final HttpServletRequest request) {
if (!isAuthorized(request)) {
return Response.status(Response.Status.UNAUTHORIZED).build();
@@ -176,18 +269,26 @@
* The key of the credential to set
* @param value
* The value of the credential to set (this should be a posted form
field named 'value')
- * @return a 400 response in case the credential was updated.<br/>
+ * @return a 200 response in case the credential was updated.<br/>
+ * a 400 in case the password violates the password policy
* a 404 response in case the user with the specified name does
not exist.
*/
@POST
@Path("{name}/credentials/{key}")
- @Produces({ MediaType.APPLICATION_JSON })
+ @Produces({MediaType.APPLICATION_JSON})
public Response setCredential(@PathParam("name") final String name,
@PathParam("key") final String key, @FormParam("value") final String
value,
@Context final HttpServletRequest request) {
if (!isAuthorized(request)) {
return Response.status(Response.Status.UNAUTHORIZED).build();
}
+
+ // Verify that the password value matches our constraints
+ if (PASSWORD_CREDENTIAL_KEY.equals(key)) {
+ if (!validatePasswordStrength(value)) {
+ return get400();
+ }
+ }
return setCredential(name, key, value, Role.USER);
}
@@ -208,8 +309,8 @@
*/
@PUT
@Path("{name}/properties/{key}")
- @Consumes({ MediaType.APPLICATION_FORM_URLENCODED })
- @Produces({ MediaType.APPLICATION_JSON })
+ @Consumes({MediaType.APPLICATION_FORM_URLENCODED})
+ @Produces({MediaType.APPLICATION_JSON})
public Response setProperty(@PathParam("name") final String name,
@PathParam("key") final String key,
@FormParam("value") final String value, @Context final
HttpServletRequest request) {
if (!isAuthorized(request)) {
@@ -231,7 +332,7 @@
*/
@DELETE
@Path("{name}")
- @Produces({ MediaType.APPLICATION_JSON })
+ @Produces({MediaType.APPLICATION_JSON})
public Response removeUser(@PathParam("name") final String name, @Context
final HttpServletRequest request) {
if (!isAuthorized(request)) {
return Response.status(Response.Status.UNAUTHORIZED).build();
@@ -243,4 +344,49 @@
protected String getBaseUrl(final String contextPath) {
return contextPath + "/rest/users";
}
+
+ private boolean validatePasswordStrength(String password) {
+ // Verify password length
+ int minLen = m_configUtil.get(PASSWORD_MIN_LENGTH, Integer.class);
+ if (password.length() < minLen) {
+ return false;
+ }
+
+ // Verify mixed case
+ if (m_configUtil.get(PASSWORD_REQ_MIXED_CASE, Boolean.class)) {
+ if (password.toLowerCase().equals(password) ||
password.toUpperCase().equals(password)) {
+ return false;
+ }
+ }
+
+ // Verify minimum amount of digits
+ int minDigits = m_configUtil.get(PASSWORD_MIN_DIGITS, Integer.class);
+ if (minDigits > 0) {
+ int count = 0;
+ for (int i = 0; i < password.length(); i++) {
+ if (StringUtils.isNumeric(password.subSequence(i, i + 1))) {
+ count++;
+ }
+ }
+ if (count < minDigits) {
+ return false;
+ }
+ }
+
+ // Verify minimum amount of special characters
+ int minSpecialCharacters = m_configUtil.get(PASSWORD_MIN_SPEC_CHAR,
Integer.class);
+ if (minSpecialCharacters > 0) {
+ int count = 0;
+ for (int i = 0; i < password.length(); i++) {
+ if (!StringUtils.isAlphanumeric(password.subSequence(i, i +
1))) {
+ count++;
+ }
+ }
+ if (count < minSpecialCharacters) {
+ return false;
+ }
+ }
+
+ return true;
+ }
}
Added:
trunk/amdatu-auth/useradmin-rest/src/main/java/org/amdatu/auth/useradmin/rest/util/ConfigurationUtil.java
==============================================================================
--- (empty file)
+++
trunk/amdatu-auth/useradmin-rest/src/main/java/org/amdatu/auth/useradmin/rest/util/ConfigurationUtil.java
Tue Mar 27 12:22:42 2012
@@ -0,0 +1,154 @@
+package org.amdatu.auth.useradmin.rest.util;
+
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.Hashtable;
+
+import org.osgi.service.log.LogService;
+
+/**
+ * This class provides a handy utility method for handling configuration
properties managed by OSGi
+ * config admin. It adds support for providing default values in case
configuration properties are
+ * not available in OSGi config admin (useful for backwards compatibility) and
provides type-safe
+ * casting the properties to required types.
+ * This utility class can be reused.
+ *
+ * @author <a href="mailto:[email protected]">Amdatu Project
Team</a>
+ */
+public class ConfigurationUtil {
+ private LogService m_logService;
+ private Dictionary<?, ?> m_config = null;
+ private Dictionary<String, Object> m_values = new Hashtable<String,
Object>();
+
+ public static class ConfigProperty {
+ private String m_name;
+ private Class<?> m_type;
+ private Object m_defaultValue;
+
+ public ConfigProperty(final String aName, final Class<?> aType, final
Object aDefaultValue) {
+ m_name = aName;
+ m_type = aType;
+ m_defaultValue = aDefaultValue;
+ }
+
+ public String getName() {
+ return m_name;
+ }
+
+ public Class<?> getType() {
+ return m_type;
+ }
+
+ public Object getDefaultValue() {
+ return m_defaultValue;
+ }
+ }
+
+ public ConfigurationUtil(final LogService logService) {
+ m_logService = logService;
+ }
+
+ public void init(final ConfigProperty[] configProperties, final
Dictionary<?, ?> config) {
+ m_config = config;
+ for (ConfigProperty property : configProperties) {
+ if (property.getType().isAssignableFrom(String.class)) {
+ m_values.put(property.getName(),
getStringProperty(property.getName(),
+ (String) property.getDefaultValue()));
+ }
+ else if (property.getType().isAssignableFrom(Integer.class)) {
+ m_values.put(property.getName(),
getIntProperty(property.getName(),
+ (Integer) property.getDefaultValue()));
+ }
+ else if (property.getType().isAssignableFrom(Long.class)) {
+ m_values.put(property.getName(),
getLongProperty(property.getName(),
+ (Long) property.getDefaultValue()));
+ }
+ else if (property.getType().isAssignableFrom(Boolean.class)) {
+ m_values.put(property.getName(),
getBooleanProperty(property.getName(),
+ (Boolean) property.getDefaultValue()));
+ }
+ }
+
+ // Log the config properties
+ Enumeration<String> keys = m_values.keys();
+ m_logService.log(LogService.LOG_DEBUG, "Starting Cassandra Client with
configuration: ");
+ while (keys.hasMoreElements()) {
+ String key = keys.nextElement();
+ m_logService.log(LogService.LOG_DEBUG, " " + key + " = " +
m_values.get(key).toString());
+ }
+ }
+
+ /**
+ * Returns a property from the configuration.
+ *
+ * @param key The key of the property to retrieve.
+ * @param class The type of the property value to retrieve
+ * @return The value of this property as type T
+ */
+ @SuppressWarnings("unchecked")
+ public <T> T get(final String key, final Class<T> clazz) {
+ Object val = m_values.get(key);
+ if (val != null && val.getClass().isAssignableFrom(clazz)) {
+ return (T) val;
+ }
+ m_logService.log(LogService.LOG_ERROR, "Property '" + key + "'
retrieved as '" + clazz.toString()
+ + "', but its value is of type '" + val.getClass() + "'. Returning
null.");
+ return null;
+ }
+
+ private String getStringProperty(final String key, final String
defaultValue) {
+ Object value = m_config.get(key);
+ if (value == null) {
+ String msg = "No value set for property '" + key + "', switching
to default value '" + defaultValue + "'";
+ m_logService.log(LogService.LOG_INFO, msg);
+ return defaultValue;
+ }
+ return value.toString();
+ }
+
+ private int getIntProperty(final String key, final int defaultValue) {
+ Object value = m_config.get(key);
+ if (value == null) {
+ String msg = "No value set for property '" + key + "', switching
to default value '" + defaultValue + "'";
+ m_logService.log(LogService.LOG_INFO, msg);
+ return defaultValue;
+ }
+ try {
+ return Integer.parseInt(value.toString().trim());
+ }
+ catch (NumberFormatException e) {
+ String msg = "Invalid value set for property '" + key + "' ('" +
value.toString()
+ + "'), switching to default value '" + defaultValue + "'";
+ m_logService.log(LogService.LOG_ERROR, msg);
+ return defaultValue;
+ }
+ }
+
+ private long getLongProperty(final String key, final long defaultValue) {
+ Object value = m_config.get(key);
+ if (value == null) {
+ String msg = "No value set for property '" + key + "', switching
to default value '" + defaultValue + "'";
+ m_logService.log(LogService.LOG_INFO, msg);
+ return defaultValue;
+ }
+ try {
+ return Long.parseLong(value.toString().trim());
+ }
+ catch (NumberFormatException e) {
+ String msg = "Invalid value set for property '" + key + "' ('" +
value.toString()
+ + "'), switching to default value '" + defaultValue + "'";
+ m_logService.log(LogService.LOG_ERROR, msg);
+ return defaultValue;
+ }
+ }
+
+ private boolean getBooleanProperty(final String key, final boolean
defaultValue) {
+ Object value = m_config.get(key);
+ if (value == null) {
+ String msg = "No value set for property '" + key + "', switching
to default value '" + defaultValue + "'";
+ m_logService.log(LogService.LOG_INFO, msg);
+ return defaultValue;
+ }
+ return "true".equalsIgnoreCase(value.toString()) ||
"1".equals(value.toString());
+ }
+}
_______________________________________________
Amdatu-commits mailing list
[email protected]
http://lists.amdatu.org/mailman/listinfo/amdatu-commits