Add first version of group user_management
authorymh <ymh.work@gmail.com>
Wed, 11 Dec 2013 10:23:05 +0100
branchuser_management
changeset 235 f8746a482459
parent 234 d92a90b2ad53
child 236 73150fdbcafc
Add first version of group
server/src/main/java/org/iri_research/renkan/controller/admin/GroupsAdminController.java
server/src/main/java/org/iri_research/renkan/forms/GroupForm.java
server/src/main/java/org/iri_research/renkan/forms/GroupFormValidator.java
server/src/main/java/org/iri_research/renkan/models/Group.java
server/src/main/java/org/iri_research/renkan/repositories/GroupsRepository.java
server/src/main/webapp/WEB-INF/i18n/messages.properties
server/src/main/webapp/WEB-INF/i18n/messages_en.properties
server/src/main/webapp/WEB-INF/i18n/messages_fr.properties
server/src/main/webapp/WEB-INF/templates/admin/adminIndex.html
server/src/main/webapp/WEB-INF/templates/admin/groupDeleteConfirm.html
server/src/main/webapp/WEB-INF/templates/admin/groupEdit.html
server/src/main/webapp/WEB-INF/templates/admin/groupsList.html
server/src/main/webapp/WEB-INF/templates/admin/spaceDeleteConfirm.html
server/src/main/webapp/WEB-INF/templates/admin/spaceEdit.html
server/src/main/webapp/WEB-INF/templates/admin/spacesList.html
server/src/main/webapp/WEB-INF/templates/admin/userDeleteConfirm.html
server/src/main/webapp/WEB-INF/templates/admin/userEdit.html
server/src/main/webapp/WEB-INF/templates/admin/usersList.html
server/src/main/webapp/WEB-INF/templates/fragment/groupForm.html
server/src/test/java/org/iri_research/renkan/test/controller/GroupsAdminControllerTest.java
server/src/test/java/org/iri_research/renkan/test/controller/UsersAdminControllerTest.java
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/server/src/main/java/org/iri_research/renkan/controller/admin/GroupsAdminController.java	Wed Dec 11 10:23:05 2013 +0100
@@ -0,0 +1,188 @@
+package org.iri_research.renkan.controller.admin;
+
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.Locale;
+
+import javax.inject.Inject;
+import javax.servlet.http.HttpServletRequest;
+import javax.validation.Valid;
+
+import org.apache.commons.codec.binary.Hex;
+import org.iri_research.renkan.Constants;
+import org.iri_research.renkan.RenkanException;
+import org.iri_research.renkan.controller.Utils;
+import org.iri_research.renkan.forms.GroupForm;
+import org.iri_research.renkan.forms.GroupFormValidator;
+import org.iri_research.renkan.models.Group;
+import org.iri_research.renkan.repositories.GroupsRepository;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.propertyeditors.StringTrimmerEditor;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort.Direction;
+import org.springframework.data.web.PageableDefault;
+import org.springframework.http.HttpStatus;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.validation.BindingResult;
+import org.springframework.web.bind.WebDataBinder;
+import org.springframework.web.bind.annotation.InitBinder;
+import org.springframework.web.bind.annotation.ModelAttribute;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.client.HttpClientErrorException;
+
+@Controller
+@RequestMapping("/admin/groups")
+public class GroupsAdminController {
+
+    private final Logger logger = LoggerFactory
+            .getLogger(GroupsAdminController.class);
+
+    @Inject
+    private GroupsRepository groupsRepository;
+
+
+    @InitBinder(value = { "group" })
+    protected void initBinder(WebDataBinder binder) {
+        binder.setValidator(new GroupFormValidator());
+    }
+
+    @InitBinder
+    public void initDateBinder(final WebDataBinder dataBinder, final Locale locale) {
+        dataBinder.registerCustomEditor(String.class, new StringTrimmerEditor(true));
+    }
+
+    @RequestMapping(value = "", method = RequestMethod.GET, produces = { "text/html;charset=UTF-8" })
+    public String groupsList(
+            Model model,
+            @PageableDefault(sort = { "title" }, direction = Direction.DESC, page = 0, value = Constants.PAGINATION_SIZE) Pageable p,
+            HttpServletRequest request) {
+
+        Page<Group> page = this.groupsRepository.findAll(p);
+
+        model.addAttribute("page", page);
+        model.addAttribute("baseUrl", Utils.buildBaseUrl(request));
+        //TODO: add user count
+
+        return "admin/groupsList";
+    }
+
+    @RequestMapping(value = "/edit/", method = RequestMethod.GET, produces = { "text/html;charset=UTF-8" })
+    public String editGroup(Model model) {
+        return editGroup(model, null);
+    }
+
+    @RequestMapping(value = "/edit/{groupId}", method = RequestMethod.GET, produces = { "text/html;charset=UTF-8" })
+    public String editGroup(Model model,
+            @PathVariable(value = "groupId") String groupId) {
+
+        GroupForm groupForm = null;
+        Group group = null;
+
+        if (groupId != null && groupId.length() > 0 && !"_".equals(groupId)) {
+            group = this.groupsRepository.findOne(groupId);
+            if (group == null) {
+                throw new HttpClientErrorException(HttpStatus.NOT_FOUND,
+                        "group " + groupId + " not found");
+            }
+        }
+        groupForm = new GroupForm(group);
+
+        model.addAttribute("group", groupForm);
+
+        return "admin/groupEdit";
+    }
+
+    @RequestMapping(value = "/save", method = RequestMethod.POST)
+    public String saveGroup(Model model,
+            @ModelAttribute("group") @Valid GroupForm groupForm,
+            BindingResult bindingResult) {
+
+        logger.debug("group title " + groupForm.getTitle());
+        logger.debug("user description " + groupForm.getDescription());
+
+        if (bindingResult.hasErrors()) {
+            return "admin/groupEdit";
+        }
+
+        groupForm.setGroupsRepository(groupsRepository);
+
+        try {
+            groupForm.save();
+        } catch (RenkanException e) {
+            throw new HttpClientErrorException(HttpStatus.NOT_FOUND, "group "
+                    + groupForm.getId()==null?"":groupForm.getId() + " not found");
+        }
+
+        return "redirect:/admin/groups";
+    }
+
+    @RequestMapping(value = "/delete/{groupId}")
+    public String deleteGroup(HttpServletRequest request, Model model,
+            @PathVariable(value = "groupId") String groupId,
+            @RequestParam(value = "key", required = false) String key,
+            @RequestParam(value = "salt", required = false) String salt)
+            throws NoSuchAlgorithmException, RenkanException {
+
+        if (groupId == null || groupId.length() == 0) {
+            throw new HttpClientErrorException(HttpStatus.BAD_REQUEST,
+                    "Null or empty user id");
+        }
+
+        RequestMethod method = RequestMethod.valueOf(request.getMethod());
+        
+        //TODO: check that group have no user
+
+
+        if (RequestMethod.GET.equals(method)) {
+
+            Group group = this.groupsRepository.findOne(groupId);
+
+            if (group == null) {
+                throw new HttpClientErrorException(HttpStatus.NOT_FOUND,
+                        "group " + groupId + " not found");
+            }
+
+            SecureRandom rand = SecureRandom.getInstance("SHA1PRNG");
+            rand.setSeed(System.currentTimeMillis());
+            byte[] rawSalt = new byte[50];
+            rand.nextBytes(rawSalt);
+            String newSalt = Hex.encodeHexString(rawSalt);
+
+            model.addAttribute("groupObj", group);
+            model.addAttribute("salt", newSalt);
+            model.addAttribute("key", group.getKey(newSalt));
+
+            return "admin/groupDeleteConfirm";
+
+        } else if (RequestMethod.POST.equals(method) && key != null
+                && !key.isEmpty() && salt != null && !salt.isEmpty()) {
+
+            if (groupId != null && groupId.length() > 0) {
+
+                Group group = this.groupsRepository.findOne(groupId);
+                if (group != null) {
+                    if (group.checkKey(key, salt)) {
+                        this.groupsRepository.delete(groupId);
+                    } else {
+                        throw new HttpClientErrorException(
+                                HttpStatus.BAD_REQUEST, "Key not ckecked");
+                    }
+                }
+
+            }
+            return "redirect:/admin/groups";
+
+        } else {
+            throw new HttpClientErrorException(HttpStatus.BAD_REQUEST,
+                    "Bad request method or parameters");
+        }
+
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/server/src/main/java/org/iri_research/renkan/forms/GroupForm.java	Wed Dec 11 10:23:05 2013 +0100
@@ -0,0 +1,66 @@
+package org.iri_research.renkan.forms;
+
+import org.iri_research.renkan.Constants;
+import org.iri_research.renkan.models.Group;
+import org.iri_research.renkan.repositories.GroupsRepository;
+import org.iri_research.renkan.repositories.IRenkanRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+
+public class GroupForm extends RenkanForm<String, Group> {
+
+    private String avatar;
+
+    private GroupsRepository groupsRepository;
+    
+    
+    public GroupForm() {
+        super();
+    }
+    
+    public GroupForm(Group model) {
+        super(model);
+        if (model != null) {
+            this.avatar = model.getAvatar();
+        }
+    }
+
+    public String getAvatar() {
+        return avatar;
+    }
+
+    @Override
+    protected Group getModelInstance() {
+        return new Group();
+   }
+
+    @Override
+    protected IRenkanRepository<Group, String> getRepository() {
+        return this.groupsRepository;
+    }
+
+    public GroupsRepository getGroupsRepository() {
+        return groupsRepository;
+    }
+
+
+    @Override
+    protected void saveToModel() {
+        if (this.getId() == null || this.getId().length() == 0) {
+            this.model.setId(Constants.UUID_GENERATOR.generate().toString());
+        }
+        this.model.setAvatar(this.avatar);
+
+    }
+
+    public void setAvatar(String avatar) {
+        this.avatar = avatar;
+    }
+
+
+    @Autowired
+    public void setGroupsRepository(GroupsRepository groupsRepository) {
+        this.groupsRepository = groupsRepository;
+    }
+
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/server/src/main/java/org/iri_research/renkan/forms/GroupFormValidator.java	Wed Dec 11 10:23:05 2013 +0100
@@ -0,0 +1,27 @@
+package org.iri_research.renkan.forms;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+import org.springframework.validation.Errors;
+import org.springframework.validation.ValidationUtils;
+import org.springframework.validation.Validator;
+
+@Component
+public class GroupFormValidator implements Validator {
+
+    @SuppressWarnings("unused")
+    private Logger logger = LoggerFactory.getLogger(GroupFormValidator.class);
+
+    @Override
+    public boolean supports(Class<?> clazz) {
+        return GroupForm.class.equals(clazz);
+    }
+
+    @Override
+    public void validate(Object target, Errors errors) {
+        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "title",
+                "renkan.error.name.empty");
+    }
+
+}
--- a/server/src/main/java/org/iri_research/renkan/models/Group.java	Mon Dec 09 10:23:30 2013 +0100
+++ b/server/src/main/java/org/iri_research/renkan/models/Group.java	Wed Dec 11 10:23:05 2013 +0100
@@ -5,6 +5,24 @@
 @Document(collection = "groups")
 public class Group extends AbstractRenkanModel<String> {
 
+    private String avatar;
+    
+    public Group() {
+    }
+    
+    public Group(String id, String title, String description, String uri,
+            String color) {
+        super(id, title, description, uri, color);
+    }
+
+    public String getAvatar() {
+        return avatar;
+    }
+    
+    public void setAvatar(String avatar) {
+        this.avatar = avatar;
+    }
+
     public String getGroupName() {
         return this.getTitle();
     }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/server/src/main/java/org/iri_research/renkan/repositories/GroupsRepository.java	Wed Dec 11 10:23:05 2013 +0100
@@ -0,0 +1,15 @@
+package org.iri_research.renkan.repositories;
+
+import java.util.List;
+
+import org.iri_research.renkan.models.Group;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+
+public interface GroupsRepository extends IRenkanRepository<Group, String> {
+
+    public List<Group> findByTitle(String title);
+
+    public Page<Group> findByTitle(String title, Pageable p);
+
+}
--- a/server/src/main/webapp/WEB-INF/i18n/messages.properties	Mon Dec 09 10:23:30 2013 +0100
+++ b/server/src/main/webapp/WEB-INF/i18n/messages.properties	Wed Dec 11 10:23:05 2013 +0100
@@ -106,3 +106,17 @@
 renkan.user.roles.ROLE_SPACES_ADMIN = Space admin
 renkan.user.roles.ROLE_GROUPS_ADMIN = Groups admin
 
+renkanAdmin.user_objects_name = Users
+renkanAdmin.user_add = Add user
+renkanAdmin.user_edit = Edit user
+renkanAdmin.user_delete = Del. user
+renkanIndex.user_url = Url
+renkanAdmin.user_confirm_delete = Do you want to delete the user with username "{0}" ?
+
+renkanAdmin.group_objects_name = Groups
+renkanAdmin.group_add = Add group
+renkanAdmin.group_edit = Edit group
+renkanAdmin.group_delete = Del. group
+renkanIndex.group_url = Url
+renkanAdmin.group_confirm_delete = Do you want to delete the group with groupname "{0}" ?
+
--- a/server/src/main/webapp/WEB-INF/i18n/messages_en.properties	Mon Dec 09 10:23:30 2013 +0100
+++ b/server/src/main/webapp/WEB-INF/i18n/messages_en.properties	Wed Dec 11 10:23:05 2013 +0100
@@ -116,3 +116,11 @@
 renkan.user.roles.ROLE_SPACES_ADMIN = Space admin
 renkan.user.roles.ROLE_GROUPS_ADMIN = Groups admin
 
+
+renkanAdmin.group_objects_name = Groups
+renkanAdmin.group_add = Add group
+renkanAdmin.group_edit = Edit group
+renkanAdmin.group_delete = Del. group
+renkanIndex.group_url = Url
+renkanAdmin.group_confirm_delete = Do you want to delete the group with groupname "{0}" ?
+
--- a/server/src/main/webapp/WEB-INF/i18n/messages_fr.properties	Mon Dec 09 10:23:30 2013 +0100
+++ b/server/src/main/webapp/WEB-INF/i18n/messages_fr.properties	Wed Dec 11 10:23:05 2013 +0100
@@ -113,3 +113,9 @@
 renkan.user.roles.ROLE_SPACES_ADMIN =  Admin. espace
 renkan.user.roles.ROLE_GROUPS_ADMIN = Admin. groupes
 
+renkanAdmin.group_objects_name = Groupes
+renkanAdmin.group_add = aj. group
+renkanAdmin.group_edit = Edit. group
+renkanAdmin.group_delete = eff. group
+renkanIndex.group_url = Url
+renkanAdmin.group_confirm_delete = Voulez vous effacer le groupe "{0}" ?
--- a/server/src/main/webapp/WEB-INF/templates/admin/adminIndex.html	Mon Dec 09 10:23:30 2013 +0100
+++ b/server/src/main/webapp/WEB-INF/templates/admin/adminIndex.html	Wed Dec 11 10:23:05 2013 +0100
@@ -35,6 +35,9 @@
                 <tr>
                   <td colspan="2"><a href="usersList.html" th:href="@{/admin/users}" th:text="#{renkanAdmin.user_objects_name}">Users</a></td>
                 </tr>
+                <tr>
+                  <td colspan="2"><a href="groupsList.html" th:href="@{/admin/groups}" th:text="#{renkanAdmin.group_objects_name}">Groups</a></td>
+                </tr>
               </tbody>        
         </table>
       </div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/server/src/main/webapp/WEB-INF/templates/admin/groupDeleteConfirm.html	Wed Dec 11 10:23:05 2013 +0100
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" th:lang="${#ctx.getLocale().toLanguageTag()}" >
+  <head>
+    <title>Renkan Admin - delete group</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta charset="utf-8"/>
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"/>
+
+    <link rel="shortcut icon" href="../../../static/img/favicon.ico" th:href="@{/static/img/favicon.ico}" />
+
+    <link href="../../../static/css/style.css" rel="stylesheet" th:href="@{/static/css/style.css}"/>
+    <link href="../../../static/css/index.css" rel="stylesheet" th:href="@{/static/css/index.css}"/>
+        
+    <script th:remove="all" type="text/javascript" src="../../../static/lib/jquery.min.js" th:src="@{/static/lib/jquery.min.js}" ></script>
+    <script th:remove="all" type="text/javascript" src="../../../static/js/thymol.js"></script>
+  </head>
+  <body>
+    <div id="container">
+      <div id="wrapper">
+        <header id="header">
+            <h1><a href="renkanIndex.html" th:href="@{/admin}" th:text="#{renkanAdmin.renkan_admin}" id="home-link">Renkan administration</a></h1>
+            <div id="headerNav" th:include="fragment/pageFragment :: headerNavFragment"></div>
+        </header>
+        <h2><a href="groupsList.html" th:href="@{/admin/groups}" th:text="#{renkanAdmin.object_list(#{renkanAdmin.group_objects_name})}">Groups List</a>&nbsp;/&nbsp;<span th:text="#{renkanAdmin.group_delete}">Delete group</span></h2>
+        <div id="object-delete-container">
+            <div id="object-delete-question" th:text="#{renkanAdmin.group_confirm_delete(${groupObj.title})}">Do you want to Delete group with name</div>
+            <div id="object-delete-confirm-buttons"><form action="groupsList.html" th:action="@{'/admin/groups/delete/'+${groupObj.id}(key=${key},salt=${salt})}" method="post" id="yes-form"><input type="submit" name="ok" value="yes" th:value="#{question.yes}" id="yes-button"/></form><form action="groupsList.html" method="get" th:action="@{/admin/groups}" id="no-form" onsubmit="return false"><input type="submit" name="ok" value="no" th:value="#{question.no}" id="no-button" onclick="window.location.href='groupsList.html'" th:onclick="'window.location.href=\''+@{/admin/groups}+'\''"/></form></div>
+        </div>
+      </div>
+      <footer id="footer" th:substituteby="fragment/pageFragment::footerFragment">
+        <div id="version">© <span class="version-date">2013</span> <a href="http://www.iri.centrepompidou.fr" target="_blank">IRI</a> - Version <span class="version-version">0.0</span></div>
+      </footer>
+    </div>
+  </body>
+</html>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/server/src/main/webapp/WEB-INF/templates/admin/groupEdit.html	Wed Dec 11 10:23:05 2013 +0100
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" th:lang="${#ctx.getLocale().toLanguageTag()}" >
+  <head>
+    <title>Renkan Admin - edit user</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta charset="utf-8"/>
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"/>
+
+    <link rel="shortcut icon" href="../../../static/img/favicon.ico" th:href="@{/static/img/favicon.ico}" />
+
+    <link href="../../../static/css/style.css" rel="stylesheet" th:href="@{/static/css/style.css}"/>
+    <link href="../../../static/css/index.css" rel="stylesheet" th:href="@{/static/css/index.css}"/>
+    <link href='../../../static/css/spectrum.css' rel="stylesheet" th:href="@{/static/css/spectrum.css}"/>
+    <link href='../../../static/lib/jquery-ui/css/ui-lightness/jquery-ui.min.css' rel="stylesheet" th:href="@{/static/lib/jquery-ui/css/ui-lightness/jquery-ui.min.css}"/>
+    
+    <script type="text/javascript" src="../../../static/lib/jquery.min.js" th:src="@{/static/lib/jquery.min.js}" ></script>
+    <script type="text/javascript" src="../../../static/lib/jquery-ui/jquery-ui.min.js" th:src="@{/static/lib/jquery-ui/jquery-ui.min.js}" ></script>
+    <script type="text/javascript" src="../../../static/lib/jquery-ui/i18n/jquery-ui-i18n.min.js" th:src="@{/static/lib/jquery-ui/i18n/jquery-ui-i18n.min.js}" ></script>
+    <script type="text/javascript" src='../../../static/lib/spectrum.js' th:src="@{/static/lib/spectrum.js}"></script>
+    <script type="text/javascript" src='../../../static/js/admin_form.js' th:src="@{/static/js/admin_form.js}"></script>
+    <script th:remove="all" type="text/javascript" src="../../../static/js/thymol.js"></script>
+  </head>
+  <body>
+    <div id="container">
+      <div id="wrapper">
+        <header id="header">
+            <h1><a href="renkanIndex.html" th:href="@{/admin}" th:text="#{renkanAdmin.renkan_admin}" id="home-link">Renkan administration</a></h1>
+            <div id="headerNav" th:include="fragment/pageFragment :: headerNavFragment"></div>
+        </header>
+        <h2><a href="groupsList.html" th:href="@{/admin/groups}" th:text="#{renkanAdmin.object_list(#{renkanAdmin.group_objects_name})}">Groups List</a>&nbsp;/&nbsp;<span th:text="#{renkanAdmin.group_edit}">Edit group</span></h2>
+        <div th:include="fragment/groupForm::groupFormFragment" id="inner-container">
+        </div>
+      </div>
+      <footer id="footer" th:substituteby="fragment/pageFragment::footerFragment">
+        <div id="version">© <span class="version-date">2013</span> <a href="http://www.iri.centrepompidou.fr" target="_blank">IRI</a> - Version <span class="version-version">0.0</span></div>
+      </footer>
+    </div>
+  </body>
+</html>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/server/src/main/webapp/WEB-INF/templates/admin/groupsList.html	Wed Dec 11 10:23:05 2013 +0100
@@ -0,0 +1,68 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" th:lang="${#ctx.getLocale().toLanguageTag()}" >
+  <head>
+    <title>Renkan Admin - Groups</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta charset="utf-8"/>
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"/>
+
+    <link rel="shortcut icon" href="../../../static/img/favicon.ico" th:href="@{/static/img/favicon.ico}"/>
+
+    <link href="../../../static/css/style.css" rel="stylesheet" th:href="@{/static/css/style.css}"/>
+    <link href="../../../static/css/index.css" rel="stylesheet" th:href="@{/static/css/index.css}"/>
+    
+    <script th:remove="all" type="text/javascript" src="../../../static/lib/jquery.min.js"></script>
+    <script th:remove="all" type="text/javascript" src="../../../static/js/thymol.js"></script>
+  </head>
+  <body>
+    <div id="container">
+      <div id="wrapper">
+        <header id="header">
+            <h1><a href="renkanIndex.html" th:href="@{/admin}" th:text="#{renkanAdmin.renkan_admin}" id="home-link">Renkan administration</a></h1>
+            <div id="headerNav" th:include="fragment/pageFragment :: headerNavFragment"></div>
+        </header>
+        <h2 th:text="#{renkanAdmin.object_list(#{renkanAdmin.user_objects_name})}">List of objects</h2>
+        <div th:include="fragment/paginationFragment :: paginationFragment" class="pagination-container">
+          <div>               
+            <a href="#?p.page=1">&lt;&lt;</a>
+            <a href="#?p.page=3">&lt;</a>
+            <span>...</span>
+            <a href="#?p.page=2">2</a>
+            <a href="#?p.page=3">3</a>
+            <span>4</span>
+            <a href="#?p.page=5">5</a>
+            <a href="#?p.page=6">6</a>
+            <span>...</span>
+            <a href="#?p.page=5">&gt;</a>
+            <a href="#?p.page=7">&gt;&gt;</a> 
+          </div>
+        </div>
+        <div id="objects-content">
+          <ul class="object-tools">
+            <li><a href="groupEdit.html" th:href="@{/admin/groups/edit/}" th:text="#{renkanAdmin.group_add}">Add group</a></li>
+          </ul>
+          <table>
+            <thead>
+              <tr>
+                <th th:text="#{renkanAdmin.object_name}" class="object-table-title">Groupname</th>
+                <th th:text="#{renkanAdmin.object_edit}" class="object-table-actions">Edit</th>
+                <th th:text="#{renkanAdmin.object_delete}" class="object-table-actions">Delete</th>
+              </tr>
+            </thead>
+            <tbody>
+              <tr th:each="object: ${page.content}" >
+                <td th:text="${object.title}" class="object-table-title" >groupname</td>
+                <td><a href="groupEdit.html" th:href="@{'/admin/groups/edit/'+${object.id}}" th:text="#{renkanAdmin.object_edit_link}" class="object-table-actions">Edit</a></td>
+                <td><a href="#" th:href="@{'/admin/groups/delete/'+${object.id}}" th:text="#{renkanAdmin.object_delete_link}" class="users-table-actions">Delete</a></td>
+              </tr>
+            </tbody>
+          </table>
+        </div>
+        
+      </div>
+      <footer id="footer" th:substituteby="fragment/pageFragment::footerFragment">
+        <div id="version">© <span class="version-date">2013</span> <a href="http://www.iri.centrepompidou.fr" target="_blank">IRI</a> - Version <span class="version-version">0.0</span></div>
+      </footer>
+    </div>
+  </body>
+</html>
\ No newline at end of file
--- a/server/src/main/webapp/WEB-INF/templates/admin/spaceDeleteConfirm.html	Mon Dec 09 10:23:30 2013 +0100
+++ b/server/src/main/webapp/WEB-INF/templates/admin/spaceDeleteConfirm.html	Wed Dec 11 10:23:05 2013 +0100
@@ -28,7 +28,7 @@
         </div>
       </div>
       <footer id="footer" th:substituteby="fragment/pageFragment::footerFragment">
-        <div id="version">© <span class="version-date">2013</span> <a href="http://www.iri.centrepompidou.fr" target="_blanck">IRI</a> - Version <span class="version-version">0.0</span></div>
+        <div id="version">© <span class="version-date">2013</span> <a href="http://www.iri.centrepompidou.fr" target="_blank">IRI</a> - Version <span class="version-version">0.0</span></div>
       </footer>      
     </div>
   </body>
--- a/server/src/main/webapp/WEB-INF/templates/admin/spaceEdit.html	Mon Dec 09 10:23:30 2013 +0100
+++ b/server/src/main/webapp/WEB-INF/templates/admin/spaceEdit.html	Wed Dec 11 10:23:05 2013 +0100
@@ -31,7 +31,7 @@
         </div>
       </div>
       <footer id="footer" th:substituteby="fragment/pageFragment::footerFragment">
-        <div id="version">© <span class="version-date">2013</span> <a href="http://www.iri.centrepompidou.fr" target="_blanck">IRI</a> - Version <span class="version-version">0.0</span></div>
+        <div id="version">© <span class="version-date">2013</span> <a href="http://www.iri.centrepompidou.fr" target="_blank">IRI</a> - Version <span class="version-version">0.0</span></div>
       </footer>      
     </div>
   </body>
--- a/server/src/main/webapp/WEB-INF/templates/admin/spacesList.html	Mon Dec 09 10:23:30 2013 +0100
+++ b/server/src/main/webapp/WEB-INF/templates/admin/spacesList.html	Wed Dec 11 10:23:05 2013 +0100
@@ -67,7 +67,7 @@
         
       </div>
       <footer id="footer" th:substituteby="fragment/pageFragment::footerFragment">
-        <div id="version">© <span class="version-date">2013</span> <a href="http://www.iri.centrepompidou.fr" target="_blanck">IRI</a> - Version <span class="version-version">0.0</span></div>
+        <div id="version">© <span class="version-date">2013</span> <a href="http://www.iri.centrepompidou.fr" target="_blank">IRI</a> - Version <span class="version-version">0.0</span></div>
       </footer>
     </div>
   </body>
--- a/server/src/main/webapp/WEB-INF/templates/admin/userDeleteConfirm.html	Mon Dec 09 10:23:30 2013 +0100
+++ b/server/src/main/webapp/WEB-INF/templates/admin/userDeleteConfirm.html	Wed Dec 11 10:23:05 2013 +0100
@@ -28,7 +28,7 @@
         </div>
       </div>
       <footer id="footer" th:substituteby="fragment/pageFragment::footerFragment">
-        <div id="version">© <span class="version-date">2013</span> <a href="http://www.iri.centrepompidou.fr" target="_blanck">IRI</a> - Version <span class="version-version">0.0</span></div>
+        <div id="version">© <span class="version-date">2013</span> <a href="http://www.iri.centrepompidou.fr" target="_blank">IRI</a> - Version <span class="version-version">0.0</span></div>
       </footer>
     </div>
   </body>
--- a/server/src/main/webapp/WEB-INF/templates/admin/userEdit.html	Mon Dec 09 10:23:30 2013 +0100
+++ b/server/src/main/webapp/WEB-INF/templates/admin/userEdit.html	Wed Dec 11 10:23:05 2013 +0100
@@ -32,7 +32,7 @@
         </div>
       </div>
       <footer id="footer" th:substituteby="fragment/pageFragment::footerFragment">
-        <div id="version">© <span class="version-date">2013</span> <a href="http://www.iri.centrepompidou.fr" target="_blanck">IRI</a> - Version <span class="version-version">0.0</span></div>
+        <div id="version">© <span class="version-date">2013</span> <a href="http://www.iri.centrepompidou.fr" target="_blank">IRI</a> - Version <span class="version-version">0.0</span></div>
       </footer>
     </div>
   </body>
--- a/server/src/main/webapp/WEB-INF/templates/admin/usersList.html	Mon Dec 09 10:23:30 2013 +0100
+++ b/server/src/main/webapp/WEB-INF/templates/admin/usersList.html	Wed Dec 11 10:23:05 2013 +0100
@@ -63,7 +63,7 @@
         
       </div>
       <footer id="footer" th:substituteby="fragment/pageFragment::footerFragment">
-        <div id="version">© <span class="version-date">2013</span> <a href="http://www.iri.centrepompidou.fr" target="_blanck">IRI</a> - Version <span class="version-version">0.0</span></div>
+        <div id="version">© <span class="version-date">2013</span> <a href="http://www.iri.centrepompidou.fr" target="_blank">IRI</a> - Version <span class="version-version">0.0</span></div>
       </footer>
     </div>
   </body>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/server/src/main/webapp/WEB-INF/templates/fragment/groupForm.html	Wed Dec 11 10:23:05 2013 +0100
@@ -0,0 +1,77 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" th:lang="${#ctx.getLocale().toLanguageTag()}" >
+  <head>
+    <meta charset="utf-8"/>
+    <title>User form</title>
+  </head>
+<body>
+<div id="groupForm" th:fragment="groupFormFragment" >
+  <script type="text/javascript" th:inline="javascript">
+    //<![CDATA[
+
+        function groupFormSubmit() {
+
+            var errors = {};
+            var valid = true;
+
+            if(!$('#title').val()) {
+                errors['title'] = /*[[#{renkan.error.title.empty}]]*/"renkan.error.title.empty";
+                valid = false;
+            }
+
+            showformErrors(errors);
+
+            return valid;
+        }
+        
+        
+        $(function(){
+            
+            $('#color').spectrum({
+                showInput: true,
+                showAlpha: true,
+                showPalette: true,
+                showInitial: true,
+                preferredFormat: 'hex'
+            });
+            $("#model-form").submit(function(e) {
+                return groupFormSubmit();
+            });
+            
+        });
+    //]]>
+  </script>
+  <form action="#" th:object="${group}" th:action="@{/admin/groups/save}" method="post" id="model-form">
+     <fieldset class="form-fields">
+       <input type="hidden" th:field="*{id}" th:if="*{id}" />
+       <div>
+         <label for="title" th:text="#{renkanAdmin.form.name}">Name: </label> 
+         <input type="text" th:field="*{title}" />
+         <div th:if="${#fields.hasErrors('title')}" th:errors="*{title}" class="form-error"></div>
+       </div> 
+       <div>
+         <label for="uri" th:text="#{renkanAdmin.form.uri}">Uri: </label> 
+         <input type="text" th:field="*{uri}" /> 
+       </div> 
+       <div>
+         <label for="description" th:text="#{renkanAdmin.form.description}">Description: </label> 
+         <textarea th:field="*{description}"></textarea> 
+       </div> 
+       <div>
+         <label for="color" th:text="#{renkanAdmin.form.color}">Color: </label> 
+         <input type="text" th:field="*{color}" /> 
+       </div>
+       <div>
+         <label for="avatar" th:text="#{renkanAdmin.form.avatar}">Avatar: </label> 
+         <input type="text" th:field="*{avatar}" /> 
+       </div>
+       <div class="submit"> 
+         <button type="submit" name="save" th:text="#{renkanAdmin.form.user.submit}">Save</button>
+         <!--button type="button" name="cancel" th:text="#{renkanAdmin.form.user.cancel}" th:onclick="location">Cancel</button-->
+       </div> 
+      
+     </fieldset>
+  </form>
+</div>
+</body>
+</html>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/server/src/test/java/org/iri_research/renkan/test/controller/GroupsAdminControllerTest.java	Wed Dec 11 10:23:05 2013 +0100
@@ -0,0 +1,451 @@
+package org.iri_research.renkan.test.controller;
+
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.TimeZone;
+import java.util.UUID;
+
+import org.apache.commons.codec.binary.Hex;
+import org.iri_research.renkan.models.Group;
+import org.iri_research.renkan.models.Project;
+import org.iri_research.renkan.models.Space;
+import org.iri_research.renkan.models.User;
+import org.iri_research.renkan.repositories.GroupsRepository;
+import org.iri_research.renkan.repositories.ProjectsRepository;
+import org.iri_research.renkan.repositories.SpacesRepository;
+import org.iri_research.renkan.repositories.UsersRepository;
+import org.joda.time.DateTime;
+import org.joda.time.LocalDate;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.web.WebAppConfiguration;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.MvcResult;
+import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+import org.springframework.web.client.HttpClientErrorException;
+import org.springframework.web.context.WebApplicationContext;
+import org.springframework.web.util.NestedServletException;
+
+
+@RunWith(SpringJUnit4ClassRunner.class)
+@WebAppConfiguration
+@ContextConfiguration(locations = { "controller-context.xml",
+        "file:src/main/webapp/WEB-INF/spring-servlet.xml" })
+public class GroupsAdminControllerTest {
+
+    private final static int SPACE_NB = 3;
+    private final static int USER_NB = 3;
+    private final static int GROUP_NB = 3;
+
+    private Logger logger = LoggerFactory.getLogger(GroupsAdminControllerTest.class);
+
+    @Autowired
+    private SpacesRepository spacesRepository;
+    @Autowired
+    private ProjectsRepository projectsRepository;
+    @Autowired
+    private UsersRepository usersRepository;
+    @Autowired
+    private GroupsRepository groupsRepository;
+
+    
+    
+    @Autowired
+    private PasswordEncoder renkanPasswordEncoder;
+
+    private Map<String, Space> spacesList = new HashMap<String, Space>(SPACE_NB);
+    private List<String> spacesUuids = new ArrayList<>(SPACE_NB);
+
+    private ArrayList<Project> testProjects = new ArrayList<Project>();
+    
+    private Map<String, User> usersList = new HashMap<String, User>(USER_NB);
+    private List<String> usersUuids = new ArrayList<>(USER_NB);
+
+    private Map<String, Group> groupsList = new HashMap<String, Group>(GROUP_NB);
+    private List<String> groupsUuids = new ArrayList<>(GROUP_NB);
+    
+    
+
+    @Autowired
+    private WebApplicationContext context;
+    private MockMvc mvc;
+
+    @Before
+    public void setup() {
+
+        logger.debug("Setup");
+        TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
+        spacesRepository.deleteAll();
+        projectsRepository.deleteAll();
+        usersRepository.deleteAll();
+        groupsRepository.deleteAll();
+
+        for(int i=0; i < USER_NB; i++) {
+            String uuid = UUID.randomUUID().toString();
+            User user = new User(uuid, "user" + i, "User nb " + i, "http://www.iri.centrepompidou.fr", "#ababab");
+            user.setLocked(false);
+            user.setEnabled(true);
+            user.setAvatar("A pretty picture");
+            user.setExpirationDate(new LocalDate());
+            user.setCredentialExpirationDate(new LocalDate());
+            user.setEmail(String.format("user%d@mail.com", i));
+            user = usersRepository.save(user);
+            this.usersUuids.add(uuid);
+            this.usersList.put(uuid, user);
+        }
+        
+        
+        for(int i=0; i < GROUP_NB; i++) {
+            String uuid = UUID.randomUUID().toString();
+            Group group = new Group(uuid, "group" + i, "Group nb " + i, "http://www.iri.centrepompidou.fr/group/"+uuid, "#ababab");
+            group.setAvatar("A pretty group picture " + i);
+            group = groupsRepository.save(group);
+            this.groupsUuids.add(uuid);
+            this.groupsList.put(uuid, group);
+        }
+        
+        ArrayList<Project> pl = new ArrayList<Project>();
+        for (int i = 0; i < SPACE_NB; i++) {
+            DateTime creationDate = new DateTime();
+            String uuid = UUID.randomUUID().toString();
+            spacesUuids.add(uuid);
+            Space testSpace = new Space(uuid, "test " + i, "Test space " + 1,
+                    "{}", "http://ldt.iri.centrepompidou.fr", "#ababab",
+                    "test_user", "http://ldt.iri.centrepompidou.fr",
+                    creationDate);
+            testSpace = spacesRepository.save(testSpace);
+            this.spacesList.put(uuid, testSpace);
+            for (int j = 0; j < SPACE_NB - 1 - i; j++) {
+                Project p = new Project(testSpace.getId(), UUID.randomUUID()
+                        .toString(), "test" + ((SPACE_NB - 1) * i + j + 1),
+                        "desc" + ((SPACE_NB - 1) * i + j + 1),
+                        "http://localhost:8080/rest/projects/id"
+                                + ((SPACE_NB - 1) * i + j + 1), creationDate);
+                p.addUser(this.usersList.get(this.usersUuids.get(0)));
+                pl.add(p);
+            }
+            try {
+                Thread.sleep(1);
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+        }
+
+        for (Project p : projectsRepository.save(pl)) {
+            this.testProjects.add(p);
+        }
+        
+        
+
+        this.mvc = MockMvcBuilders.webAppContextSetup(this.context).build();
+    }
+
+    @After
+    public void teardown() {
+        spacesRepository.deleteAll();
+        projectsRepository.deleteAll();
+        usersRepository.deleteAll();
+        groupsRepository.deleteAll();
+
+    }
+
+    @Test
+    public void testGroupPostUpdate() throws Exception {
+
+        MockHttpServletRequestBuilder post = MockMvcRequestBuilders
+                .post("/admin/groups/save");
+        post = post.param("id", this.groupsUuids.get(0));
+        post = post.param("title", "New name");
+        post = post.param("description", "New description");
+        post = post.param("uri", "http://ldt.iri.centrepompidou.fr/new/uri");
+        post = post.param("color", "#ffffff");
+
+        this.mvc.perform(post)
+                .andExpect(MockMvcResultMatchers.status().isSeeOther())
+                .andExpect(MockMvcResultMatchers.redirectedUrl("/admin/groups"));
+
+        Group group = this.groupsRepository.findOne(this.groupsUuids.get(0));
+
+        Assert.assertNotNull("Should find space", group);
+        Assert.assertEquals("Title equals", "New name", group.getTitle());
+        Assert.assertEquals("Description equals", "New description",
+                group.getDescription());
+        Assert.assertEquals("Uri equals",
+                "http://ldt.iri.centrepompidou.fr/new/uri", group.getUri());
+        Assert.assertEquals("Color equals", "#ffffff", group.getColor());
+    }
+
+    @Test
+    public void testGroupPostCreate() throws Exception {
+
+        MockHttpServletRequestBuilder post = MockMvcRequestBuilders
+                .post("/admin/groups/save")
+                    .param("title", "New name")
+                    .param("description", "New description")
+                    .param("uri", "http://ldt.iri.centrepompidou.fr/new/uri")
+                    .param("color", "#ffffff")
+                    .param("avatar", "A pretty avatar");
+
+        this.mvc.perform(post)
+                .andExpect(MockMvcResultMatchers.status().isSeeOther())
+                .andExpect(MockMvcResultMatchers.redirectedUrl("/admin/groups"));
+
+        Assert.assertEquals("Must have one more group", GROUP_NB + 1,
+                this.groupsRepository.count());
+
+        for (Group group : this.groupsRepository.findAll()) {
+            if (this.groupsList.containsKey(group.getId())) {
+                continue;
+            }
+            else {
+                Assert.assertNotNull("Should find group", group);
+                Assert.assertEquals("Title equals", "New name", group.getTitle());
+                Assert.assertEquals("Description equals", "New description",
+                        group.getDescription());
+                Assert.assertEquals("Uri equals",
+                        "http://ldt.iri.centrepompidou.fr/new/uri", group.getUri());
+                Assert.assertEquals("Color equals", "#ffffff", group.getColor());
+                Assert.assertEquals("Avatar equals", "A pretty avatar", group.getAvatar());
+                Assert.assertTrue(
+                        "id sould match uuid regex",
+                        group.getId()
+                                .matches(
+                                        "[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}"));
+            }
+        }
+    }
+
+    @Test
+    public void testGroupPostUpdateEmptyTitle() throws Exception {
+
+        MockHttpServletRequestBuilder post = MockMvcRequestBuilders
+                .post("/admin/groups/save")
+                .param("id", this.groupsUuids.get(0))
+                .param("title", "")
+                .param("description", "New description")
+                .param("uri", "http://ldt.iri.centrepompidou.fr/new/uri")
+                .param("color", "#ffffff")
+                .param("avatar", "A pretty avatar");
+
+        this.mvc.perform(post)
+                .andExpect(MockMvcResultMatchers.status().isOk())
+                .andExpect(MockMvcResultMatchers.view().name("admin/groupEdit"))
+                .andExpect(MockMvcResultMatchers.model().hasErrors())
+                .andExpect(MockMvcResultMatchers.model().errorCount(1))
+                .andExpect(MockMvcResultMatchers.model().attributeHasErrors("group"))
+                .andExpect(MockMvcResultMatchers.model().attributeHasFieldErrors("group", "title"));
+
+        Group group = this.groupsRepository.findOne(this.groupsUuids.get(0));
+
+        Assert.assertNotNull("Should find group", group);
+        Assert.assertEquals("name equals", "group0", group.getTitle());
+        
+        Assert.assertEquals("name equals", "Group nb 0", group.getDescription());
+
+    }
+
+    @Test
+    public void testGroupPostCreateEmptyName() throws Exception {
+
+        MockHttpServletRequestBuilder post = MockMvcRequestBuilders
+                .post("/admin/groups/save")
+                .param("title", "")
+                .param("description", "New description")
+                .param("uri", "http://ldt.iri.centrepompidou.fr/new/uri")
+                .param("color", "#ffffff")
+                .param("avatar", "A pretty avatar");
+                
+        
+        this.mvc.perform(post)
+                .andExpect(MockMvcResultMatchers.status().isOk())
+                .andExpect(MockMvcResultMatchers.view().name("admin/groupEdit"))
+                .andExpect(MockMvcResultMatchers.model().hasErrors())
+                .andExpect(MockMvcResultMatchers.model().errorCount(1))
+                .andExpect(MockMvcResultMatchers.model().attributeHasErrors("group"))
+                .andExpect(MockMvcResultMatchers.model().attributeHasFieldErrors("group", "title"));
+
+        Assert.assertEquals("Must not have one more group", GROUP_NB,this.groupsRepository.count());
+
+    }
+
+    @Test
+    public void testUserPostCreateBadPassword() throws Exception {
+
+        MockHttpServletRequestBuilder post = MockMvcRequestBuilders
+                .post("/admin/users/save")
+                .param("title", "user")
+                .param("description", "New description")
+                .param("uri", "http://ldt.iri.centrepompidou.fr/new/uri")
+                .param("color", "#ffffff")
+                .param("expirationDate","2007-11-24")
+                .param("credentialExpirationDate","2009-11-29")
+                .param("password", "test")
+                .param("passwordConfirm", "test2");
+
+        this.mvc.perform(post)
+            .andExpect(MockMvcResultMatchers.status().isOk())
+            .andExpect(MockMvcResultMatchers.view().name("admin/userEdit"))
+            .andExpect(MockMvcResultMatchers.model().hasErrors())
+            .andExpect(MockMvcResultMatchers.model().errorCount(1))
+            .andExpect(MockMvcResultMatchers.model().attributeHasErrors("user"))
+            .andExpect(MockMvcResultMatchers.model().attributeHasFieldErrors("user", "password"));
+        
+        Assert.assertEquals("Must not have one more user", USER_NB,this.usersRepository.count());
+
+    }
+
+
+    @Test
+    public void testDeleteGroup() throws Exception {
+
+        MockHttpServletRequestBuilder get = MockMvcRequestBuilders
+                .get("/admin/groups/delete/"
+                        + this.groupsUuids.get(GROUP_NB - 1));
+
+        MvcResult res = this.mvc
+                .perform(get)
+                .andExpect(MockMvcResultMatchers.status().isOk())
+                .andExpect(
+                        MockMvcResultMatchers.view().name(
+                                "admin/groupDeleteConfirm"))
+                .andExpect(
+                        MockMvcResultMatchers.model().attributeExists(
+                                "groupObj", "key", "salt")).andReturn();
+
+        Map<String, Object> model = res.getModelAndView().getModel();
+
+        Group group = (Group) model.get("groupObj");
+        Assert.assertNotNull("Group is not null", group);
+        Assert.assertEquals("Must be first group id",
+                this.groupsUuids.get(GROUP_NB - 1), group.getId());
+
+        String key = (String) model.get("key");
+        Assert.assertNotNull("key is not null", key);
+
+        String salt = (String) model.get("salt");
+        Assert.assertNotNull("salt is not null", salt);
+
+        Assert.assertTrue("Key must be checked", group.checkKey(key, salt));
+
+    }
+
+    @Test
+    public void testDeleteFakeGroup() throws Exception {
+
+        MockHttpServletRequestBuilder get = MockMvcRequestBuilders
+                .get("/admin/groups/delete/" + UUID.randomUUID().toString());
+
+        try {
+            this.mvc.perform(get).andExpect(
+                    MockMvcResultMatchers.status().isNotFound());
+        } catch (NestedServletException e) {
+            Assert.assertNotNull("Nested exception must not be null",
+                    e.getCause());
+            Assert.assertEquals(
+                    "Inner exception must be a HttpClientErrorException",
+                    HttpClientErrorException.class, e.getCause().getClass());
+            Assert.assertEquals("Exception error status must be not found",
+                    HttpStatus.NOT_FOUND,
+                    ((HttpClientErrorException) e.getCause()).getStatusCode());
+        }
+
+    }
+
+    @Test
+    public void testDoDeleteGroupNoKey() throws Exception {
+        MockHttpServletRequestBuilder post = MockMvcRequestBuilders
+                .post("/admin/groups/delete/"
+                        + this.spacesUuids.get(GROUP_NB - 1));
+
+        try {
+            this.mvc.perform(post).andExpect(
+                    MockMvcResultMatchers.status().isBadRequest());
+        } catch (NestedServletException e) {
+            Assert.assertNotNull("Nested exception must not be null",
+                    e.getCause());
+            Assert.assertEquals(
+                    "Inner exception must be a HttpClientErrorException",
+                    HttpClientErrorException.class, e.getCause().getClass());
+            Assert.assertEquals("Exception error status must be not found",
+                    HttpStatus.BAD_REQUEST,
+                    ((HttpClientErrorException) e.getCause()).getStatusCode());
+        }
+
+        Assert.assertEquals("Must have same nb of group", GROUP_NB,
+                this.groupsRepository.count());
+
+    }
+
+    @Test
+    public void testDoDeleteGroup() throws Exception {
+
+        Group group = this.groupsList.get(this.groupsUuids.get(GROUP_NB - 1));
+
+        SecureRandom rand = SecureRandom.getInstance("SHA1PRNG");
+        rand.setSeed(System.currentTimeMillis());
+        byte[] rawSalt = new byte[50];
+        rand.nextBytes(rawSalt);
+        String salt = Hex.encodeHexString(rawSalt);
+        String key = group.getKey(salt);
+
+        MockHttpServletRequestBuilder post = MockMvcRequestBuilders.post(String
+                .format("/admin/groups/delete/%s?key=%s&salt=%s",
+                        this.groupsUuids.get(GROUP_NB - 1), key, salt));
+
+        this.mvc.perform(post)
+                .andExpect(MockMvcResultMatchers.status().isSeeOther())
+                .andExpect(MockMvcResultMatchers.redirectedUrl("/admin/groups"));
+
+        Assert.assertEquals("Must have one less space", GROUP_NB - 1,
+                this.groupsRepository.count());
+
+        group = this.groupsRepository.findOne(this.groupsUuids
+                .get(GROUP_NB - 1));
+
+        Assert.assertNull("Group " + this.groupsUuids.get(GROUP_NB - 1)
+                + " deleted", group);
+
+    }
+
+    @Test
+    public void testDoDeleteGroupFake() throws Exception {
+
+        Group group = this.groupsList.get(this.groupsUuids.get(GROUP_NB - 1));
+
+        SecureRandom rand = SecureRandom.getInstance("SHA1PRNG");
+        rand.setSeed(System.currentTimeMillis());
+        byte[] rawSalt = new byte[50];
+        rand.nextBytes(rawSalt);
+        String salt = Hex.encodeHexString(rawSalt);
+        String key = group.getKey(salt);
+
+        MockHttpServletRequestBuilder post = MockMvcRequestBuilders.post(String
+                .format("/admin/groups/delete/%s?key=%s&salt=%s",
+                        UUID.randomUUID(), key, salt));
+
+        this.mvc.perform(post)
+                .andExpect(MockMvcResultMatchers.status().isSeeOther())
+                .andExpect(MockMvcResultMatchers.redirectedUrl("/admin/groups"));
+
+        Assert.assertEquals("Must have the same nb of group", GROUP_NB,
+                this.groupsRepository.count());
+
+    }
+
+}
--- a/server/src/test/java/org/iri_research/renkan/test/controller/UsersAdminControllerTest.java	Mon Dec 09 10:23:30 2013 +0100
+++ b/server/src/test/java/org/iri_research/renkan/test/controller/UsersAdminControllerTest.java	Wed Dec 11 10:23:05 2013 +0100
@@ -132,6 +132,7 @@
     public void teardown() {
         spacesRepository.deleteAll();
         projectsRepository.deleteAll();
+        usersRepository.deleteAll();
     }
 
     @Test
@@ -151,7 +152,7 @@
 
         User user = this.usersRepository.findOne(this.usersUuids.get(0));
 
-        Assert.assertNotNull("Should find space", user);
+        Assert.assertNotNull("Should find user", user);
         Assert.assertEquals("Title equals", "New name", user.getTitle());
         Assert.assertEquals("Description equals", "New description",
                 user.getDescription());
@@ -186,7 +187,7 @@
                 continue;
             }
             else {
-                Assert.assertNotNull("Should find space", user);
+                Assert.assertNotNull("Should find user", user);
                 Assert.assertEquals("Title equals", "New name", user.getTitle());
                 Assert.assertEquals("Description equals", "New description",
                         user.getDescription());