merge with branch issue-153

This commit is contained in:
Sebastian Sdorra
2012-05-29 11:10:21 +02:00
11 changed files with 408 additions and 101 deletions

View File

@@ -2,6 +2,7 @@ Name: ${user.name}
Display Name: ${user.displayName}
Type: ${user.type}
E-Mail: ${user.mail!""}
Active: ${user.admin?string}
Administrator: ${user.admin?string}
Creation-Date: <#if user.creationDate??>${user.creationDate?string("yyyy-MM-dd HH:mm:ss")}</#if>
Last-Modified: <#if user.lastModified??>${user.lastModified?string("yyyy-MM-dd HH:mm:ss")}</#if>

View File

@@ -3,6 +3,7 @@ Name: ${user.name}
Display Name: ${user.displayName}
Type: ${user.type}
E-Mail: ${user.mail!""}
Active: ${user.admin?string}
Administrator: ${user.admin?string}
Creation-Date: <#if user.creationDate??>${user.creationDate?string("yyyy-MM-dd HH:mm:ss")}</#if>
Last-Modified: <#if user.lastModified??>${user.lastModified?string("yyyy-MM-dd HH:mm:ss")}</#if>

View File

@@ -155,6 +155,12 @@ public class User extends BasicPropertiesAware implements Principal, ModelObject
user.setAdmin(admin);
}
if (user.isActive() != active)
{
result = true;
user.setActive(active);
}
if (Util.isNotEquals(user.getDisplayName(), displayName))
{
result = true;
@@ -216,6 +222,7 @@ public class User extends BasicPropertiesAware implements Principal, ModelObject
&& Objects.equal(mail, other.mail)
&& Objects.equal(type, other.type)
&& Objects.equal(admin, other.admin)
&& Objects.equal(active, other.active)
&& Objects.equal(password, other.password)
&& Objects.equal(creationDate, other.creationDate)
&& Objects.equal(lastModified, other.lastModified)
@@ -232,7 +239,7 @@ public class User extends BasicPropertiesAware implements Principal, ModelObject
public int hashCode()
{
return Objects.hashCode(name, displayName, mail, type, admin, password,
creationDate, lastModified, properties);
active, creationDate, lastModified, properties);
}
/**
@@ -256,6 +263,7 @@ public class User extends BasicPropertiesAware implements Principal, ModelObject
.add("password", pwd)
.add("admin", admin)
.add("type", type)
.add("active", active)
.add("creationDate", creationDate)
.add("lastModified", lastModified)
.add("properties", properties)
@@ -357,6 +365,18 @@ public class User extends BasicPropertiesAware implements Principal, ModelObject
return type;
}
/**
* Returns false if the user is deactivated.
*
*
* @return false if the user is deactivated
* @since 1.16
*/
public boolean isActive()
{
return active;
}
/**
* Method description
*
@@ -384,6 +404,18 @@ public class User extends BasicPropertiesAware implements Principal, ModelObject
//~--- set methods ----------------------------------------------------------
/**
* Activate or deactive this user.
*
*
* @param active false to deactivate the user.
* @since 1.6
*/
public void setActive(boolean active)
{
this.active = active;
}
/**
* Method description
*
@@ -476,7 +508,10 @@ public class User extends BasicPropertiesAware implements Principal, ModelObject
//~--- fields ---------------------------------------------------------------
/** Field description */
private boolean admin;
private boolean active = true;
/** Field description */
private boolean admin = false;
/** Field description */
private Long creationDate;

View File

@@ -30,6 +30,7 @@
*/
package sonia.scm.user.orientdb;
//~--- non-JDK imports --------------------------------------------------------
@@ -59,6 +60,9 @@ public class UserConverter extends AbstractConverter implements Converter<User>
/** Field description */
public static final String DOCUMENT_CLASS = "User";
/** Field description */
public static final String FIELD_ACTIVE = "active";
/** Field description */
public static final String FIELD_ADMIN = "admin";
@@ -115,6 +119,7 @@ public class UserConverter extends AbstractConverter implements Converter<User>
appendField(doc, FIELD_MAIL, user.getMail());
appendField(doc, FIELD_PASSWORD, user.getPassword());
appendField(doc, FIELD_ADMIN, user.isAdmin());
appendField(doc, FIELD_ACTIVE, user.isActive());
appendField(doc, FIELD_CREATIONDATE, user.getCreationDate(), OType.LONG);
appendPropertiesField(doc, user);
@@ -140,6 +145,7 @@ public class UserConverter extends AbstractConverter implements Converter<User>
user.setPassword(getStringField(doc, FIELD_PASSWORD));
user.setType(getStringField(doc, FIELD_TYPE));
user.setAdmin(getBooleanField(doc, FIELD_ADMIN));
user.setAdmin(getBooleanField(doc, FIELD_ACTIVE));
user.setLastModified(getLongField(doc, FIELD_LASTMODIFIED));
user.setCreationDate(getLongField(doc, FIELD_CREATIONDATE));
@@ -176,6 +182,7 @@ public class UserConverter extends AbstractConverter implements Converter<User>
oclass.createProperty(FIELD_CREATIONDATE, OType.LONG);
oclass.createProperty(FIELD_DISPLAYNAME, OType.STRING);
oclass.createProperty(FIELD_MAIL, OType.STRING);
oclass.createProperty(FIELD_ACTIVE, OType.STRING);
oclass.createProperty(FIELD_PASSWORD, OType.STRING);
oclass.createProperty(FIELD_PROPERTIES, OType.EMBEDDEDMAP);

View File

@@ -35,6 +35,7 @@ package sonia.scm.web.security;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.collect.Sets;
import com.google.inject.Inject;
import com.google.inject.servlet.SessionScoped;
@@ -46,11 +47,14 @@ import sonia.scm.group.Group;
import sonia.scm.group.GroupManager;
import sonia.scm.security.CipherUtil;
import sonia.scm.user.User;
import sonia.scm.user.UserException;
import sonia.scm.user.UserManager;
import sonia.scm.util.Util;
//~--- JDK imports ------------------------------------------------------------
import java.io.IOException;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
@@ -125,101 +129,7 @@ public class BasicSecurityContext implements WebSecurityContext
if ((ar != null) && (ar.getState() == AuthenticationState.SUCCESS))
{
user = ar.getUser();
try
{
Set<String> groupSet = new HashSet<String>();
// load external groups
Collection<String> extGroups = ar.getGroups();
if (extGroups != null)
{
groupSet.addAll(extGroups);
}
// load internal groups
loadGroups(groupSet);
// check for admin user
if (!user.isAdmin())
{
user.setAdmin(isAdmin(groupSet));
if (logger.isDebugEnabled() && user.isAdmin())
{
logger.debug("user '{}' is marked as admin by configuration",
user.getName());
}
}
else if (logger.isDebugEnabled())
{
logger.debug("authenticator {} marked user '{}' as admin",
user.getType(), user.getName());
}
// store user
User dbUser = userManager.get(user.getName());
if (dbUser != null)
{
// if database user is an admin, set admin for the current user
if (dbUser.isAdmin())
{
if (logger.isDebugEnabled())
{
logger.debug(
"user '{}' of type '{}' is marked as admin by local database",
user.getName(), user.getType());
}
user.setAdmin(true);
}
// modify existing user, copy properties except password and admin
if (user.copyProperties(dbUser, false))
{
userManager.modify(dbUser);
}
}
// create new user
else if (dbUser == null)
{
userManager.create(user);
}
groups = groupSet;
if (logger.isDebugEnabled())
{
logGroups();
}
// store encrypted credentials in session
String credentials = user.getName();
if (Util.isNotEmpty(password))
{
credentials = credentials.concat(":").concat(password);
}
credentials = CipherUtil.getInstance().encode(credentials);
request.getSession(true).setAttribute(SCM_CREDENTIALS, credentials);
}
catch (Exception ex)
{
user = null;
if (groups != null)
{
groups.clear();
}
logger.error("authentication failed", ex);
}
authenticate(request, password, ar);
}
return user;
@@ -296,6 +206,193 @@ public class BasicSecurityContext implements WebSecurityContext
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param request
* @param password
* @param ar
*/
private void authenticate(HttpServletRequest request, String password,
AuthenticationResult ar)
{
user = ar.getUser();
try
{
Set<String> groupSet = createGroupSet(ar);
// check for admin user
checkForAuthenticatedAdmin(user, groupSet);
// store user
User dbUser = userManager.get(user.getName());
if (dbUser != null)
{
checkDBForAdmin(user, dbUser);
checkDBForActive(user, dbUser);
}
// create new user
else
{
userManager.create(user);
}
if (user.isActive())
{
groups = groupSet;
if (logger.isDebugEnabled())
{
logGroups();
}
// store encrypted credentials in session
String credentials = user.getName();
if (Util.isNotEmpty(password))
{
credentials = credentials.concat(":").concat(password);
}
credentials = CipherUtil.getInstance().encode(credentials);
request.getSession(true).setAttribute(SCM_CREDENTIALS, credentials);
}
else
{
if (logger.isWarnEnabled())
{
logger.warn("user {} is deactivated", user.getName());
}
user = null;
groups = null;
}
}
catch (Exception ex)
{
user = null;
if (groups != null)
{
groups.clear();
}
logger.error("authentication failed", ex);
}
}
/**
* Method description
*
*
* @param user
* @param dbUser
*/
private void checkDBForActive(User user, User dbUser)
{
// user is deactivated by database
if (!dbUser.isActive())
{
if (logger.isDebugEnabled())
{
logger.debug("user {} is marked as deactivated by local database",
user.getName());
}
user.setActive(false);
}
}
/**
* Method description
*
*
* @param user
* @param dbUser
*
* @throws IOException
* @throws UserException
*/
private void checkDBForAdmin(User user, User dbUser)
throws UserException, IOException
{
// if database user is an admin, set admin for the current user
if (dbUser.isAdmin())
{
if (logger.isDebugEnabled())
{
logger.debug("user {} of type {} is marked as admin by local database",
user.getName(), user.getType());
}
user.setAdmin(true);
}
// modify existing user, copy properties except password and admin
if (user.copyProperties(dbUser, false))
{
userManager.modify(dbUser);
}
}
/**
* Method description
*
*
* @param user
* @param groupSet
*/
private void checkForAuthenticatedAdmin(User user, Set<String> groupSet)
{
if (!user.isAdmin())
{
user.setAdmin(isAdmin(groupSet));
if (logger.isDebugEnabled() && user.isAdmin())
{
logger.debug("user {} is marked as admin by configuration",
user.getName());
}
}
else if (logger.isDebugEnabled())
{
logger.debug("authenticator {} marked user {} as admin", user.getType(),
user.getName());
}
}
/**
* Method description
*
*
* @param ar
*
* @return
*/
private Set<String> createGroupSet(AuthenticationResult ar)
{
Set<String> groupSet = Sets.newHashSet();
// load external groups
Collection<String> extGroups = ar.getGroups();
if (extGroups != null)
{
groupSet.addAll(extGroups);
}
// load internal groups
loadGroups(groupSet);
return groupSet;
}
/**
* Method description
*

View File

@@ -208,7 +208,7 @@ public class DefaultAuthenticationHandler implements AuthenticationHandler
{
if (logger.isDebugEnabled())
{
logger.debug("user {} logged in successfully", username);
logger.debug("user {} successfully prepared for login", username);
}
user.setPassword(null);

View File

@@ -357,6 +357,7 @@ if (Sonia.user.Grid){
colNameText: 'Name',
colDisplayNameText: 'Anzeigename',
colMailText: 'E-Mail',
colActiveText: 'Aktiv',
colAdminText: 'Admin',
colCreationDateText: 'Erstellungsdatum',
colLastModifiedText: 'Letzte Änderung',
@@ -373,6 +374,7 @@ if (Sonia.user.FormPanel){
mailText: 'E-Mail',
passwordText: 'Passwort',
adminText: 'Administrator',
activeText: 'Aktiv',
errorTitleText: 'Fehler',
updateErrorMsgText: 'Benutzer Aktualisierung fehlgeschlagen',
createErrorMsgText: 'Benutzer Erstellung fehlgeschlagen',
@@ -384,7 +386,8 @@ if (Sonia.user.FormPanel){
mailHelpText: 'E-Mail Adresse des Benutzers.',
passwordHelpText: 'Passwort des Benutzers.',
passwordConfirmHelpText: 'Passwortwiederholung zur Kontrolle.',
adminHelpText: 'Ein Administrator kann Repositories, Gruppen und Benutzer erstellen, bearbeiten und löschen.'
adminHelpText: 'Ein Administrator kann Repositories, Gruppen und Benutzer erstellen, bearbeiten und löschen.',
activeHelpText: 'User deaktivieren oder aktivieren.'
});
}
@@ -396,6 +399,7 @@ if (Sonia.user.Panel){
emptyText: 'Es wurde kein Benutzer selektiert',
removeTitleText: 'Benutzer entfernen',
removeMsgText: 'Benutzer "{0}" entfernen?',
showOnlyActiveText: 'Nur aktive anzeigen: ',
errorTitleText: 'Fehler',
errorMsgText: 'Entfernen des Benutzers fehlgeschlagen'
});

View File

@@ -36,6 +36,7 @@ Sonia.user.FormPanel = Ext.extend(Sonia.rest.FormPanel,{
mailText: 'Mail',
passwordText: 'Password',
adminText: 'Administrator',
activeText: 'Active',
errorTitleText: 'Error',
updateErrorMsgText: 'User update failed',
createErrorMsgText: 'User creation failed',
@@ -48,6 +49,7 @@ Sonia.user.FormPanel = Ext.extend(Sonia.rest.FormPanel,{
passwordHelpText: 'Plain text password of the user.',
passwordConfirmHelpText: 'Repeat the password for validation.',
adminHelpText: 'An administrator is able to create, modify and delete repositories, groups and users.',
activeHelpText: 'Activate or deactive the user.',
initComponent: function(){
@@ -100,6 +102,12 @@ Sonia.user.FormPanel = Ext.extend(Sonia.rest.FormPanel,{
name: 'admin',
xtype: 'checkbox',
helpText: this.adminHelpText
},{
fieldLabel: this.activeText,
name: 'active',
xtype: 'checkbox',
helpText: this.activeHelpText,
checked: true
});
Ext.apply(this, Ext.apply(this.initialConfig, {items: items}));

View File

@@ -37,6 +37,7 @@ Sonia.user.Grid = Ext.extend(Sonia.rest.Grid, {
colDisplayNameText: 'Display Name',
colMailText: 'Mail',
colAdminText: 'Admin',
colActiveText: 'Active',
colCreationDateText: 'Creation Date',
colLastModifiedText: 'Last modified',
colTypeText: 'Type',
@@ -52,7 +53,7 @@ Sonia.user.Grid = Ext.extend(Sonia.rest.Grid, {
disableCaching: false
}),
idProperty: 'name',
fields: [ 'name', 'displayName', 'mail', 'admin', 'creationDate', 'lastModified', 'type', 'properties'],
fields: [ 'name', 'displayName', 'mail', 'admin', 'active', 'creationDate', 'lastModified', 'type', 'properties'],
sortInfo: {
field: 'name'
}
@@ -69,6 +70,7 @@ Sonia.user.Grid = Ext.extend(Sonia.rest.Grid, {
{id: 'displayName', header: this.colDisplayNameText, dataIndex: 'displayName', width: 250},
{id: 'mail', header: this.colMailText, dataIndex: 'mail', renderer: this.renderMailto, width: 200},
{id: 'admin', header: this.colAdminText, dataIndex: 'admin', renderer: this.renderCheckbox, width: 50},
{id: 'active', header: this.colActiveText, dataIndex: 'active', renderer: this.renderCheckbox, width: 50},
{id: 'creationDate', header: this.colCreationDateText, dataIndex: 'creationDate', renderer: Ext.util.Format.formatTimestamp},
{id: 'lastModified', header: this.colLastModifiedText, dataIndex: 'lastModified', renderer: Ext.util.Format.formatTimestamp},
{id: 'type', header: this.colTypeText, dataIndex: 'type', width: 80}

View File

@@ -37,6 +37,7 @@ Sonia.user.Panel = Ext.extend(Sonia.rest.Panel, {
removeMsgText: 'Remove User "{0}"?',
errorTitleText: 'Error',
errorMsgText: 'User deletion failed',
showOnlyActiveText: 'Show only active: ',
// userGrid for history
userGrid: null,
@@ -49,7 +50,11 @@ Sonia.user.Panel = Ext.extend(Sonia.rest.Panel, {
{xtype: 'tbbutton', text: this.addText, icon: this.addIcon, scope: this, handler: this.showAddPanel},
{xtype: 'tbbutton', text: this.removeText, icon: this.removeIcon, scope: this, handler: this.removeUser},
'-',
{xtype: 'tbbutton', text: this.reloadText, icon: this.reloadIcon, scope: this, handler: this.reload}
{xtype: 'tbbutton', text: this.reloadText, icon: this.reloadIcon, scope: this, handler: this.reload},
'-',
{xtype: 'label', text: this.showOnlyActiveText, cls: 'ytb-text'},
' ',
{xtype: 'checkbox', text: this.reloadText, scope: this, handler: this.toggleActive}
],
items: [{
id: 'userGrid',
@@ -77,6 +82,16 @@ Sonia.user.Panel = Ext.extend(Sonia.rest.Panel, {
Sonia.user.Panel.superclass.initComponent.apply(this, arguments);
},
toggleActive: function(checkbox, value){
if (value){
this.getGrid().getStore().filterBy(function(record){
return record.get('active');
});
} else {
this.getGrid().getStore().clearFilter();
}
},
getGrid: function(){
if (!this.userGrid){
if (debug){

View File

@@ -0,0 +1,137 @@
/**
* Copyright (c) 2010, Sebastian Sdorra All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer. 2. Redistributions in
* binary form must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other
* materials provided with the distribution. 3. Neither the name of SCM-Manager;
* nor the names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://bitbucket.org/sdorra/scm-manager
*
*/
package sonia.scm.it;
//~--- non-JDK imports --------------------------------------------------------
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import sonia.scm.user.User;
import sonia.scm.user.UserTestData;
import static org.junit.Assert.*;
import static sonia.scm.it.IntegrationTestUtil.*;
//~--- JDK imports ------------------------------------------------------------
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
import javax.ws.rs.core.MediaType;
/**
*
* @author Sebastian Sdorra
*/
public class DeactivatedUserITCase
{
/**
* Method description
*
*/
@Before
public void createDeactivatedUser()
{
Client client = createAdminClient();
try
{
WebResource wr = createResource(client, "users");
slarti = UserTestData.createSlarti();
slarti.setPassword("slart123");
slarti.setActive(false);
ClientResponse response =
wr.type(MediaType.APPLICATION_XML).post(ClientResponse.class, slarti);
assertNotNull(response);
assertEquals(201, response.getStatus());
response.close();
}
finally
{
client.destroy();
}
}
/**
* Method description
*
*/
@After
public void destroyDeactivatedUser()
{
Client client = createAdminClient();
try
{
WebResource wr = createResource(client,
"users/".concat(slarti.getName()));
ClientResponse response =
wr.type(MediaType.APPLICATION_XML).delete(ClientResponse.class);
assertNotNull(response);
assertEquals(204, response.getStatus());
response.close();
}
finally
{
client.destroy();
}
}
/**
* Method description
*
*/
@Test
public void testFailedAuthentication()
{
Client client = createClient();
ClientResponse response = authenticate(client, slarti.getName(),
"slart123");
assertNotNull(response);
assertEquals(401, response.getStatus());
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private User slarti;
}