diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 45e5fd67fe1055a03f7f5daeca663cdbf47ddf96..80e43de02e6b333441c54db6298fa4dd3888e8ed 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -21,8 +21,8 @@ before_script:
   - sed 's!<server>.*</server>!<server>postgres</server>!;s!<password>.*</password>!<password>maarch</password>!;s!<name>.*</name>!<name>MaarchParapheur</name>!;s!<user>.*</user>!<user>maarch</user>!;s!<enable>.*</enable>!<enable>true</enable>!' config/config.xml.default > config/config.xml
   - sed -i 's/rights="none" pattern="PDF"/rights="read | write" pattern="PDF"/' /etc/ImageMagick-6/policy.xml
 
-job_php-7.4:
-  image: php:7.4-apache
+job_php-8.1:
+  image: php:8.1-apache
   stage: test
   services:
     - name: postgres:10.1
@@ -30,18 +30,22 @@ job_php-7.4:
   script:
     - curl --location -s --output /usr/local/bin/phpunit https://phar.phpunit.de/phpunit-9.phar
     - chmod +x /usr/local/bin/phpunit
-    - phpunit --coverage-text --colors=never -c phpunit.xml
-  only:
-    - develop
-  except:
-    - schedules
+    - phpunit --coverage-text --colors=never
+  # only:
+  #   - develop
+  # except:
+  #   - schedules
+  rules:
+  - if: '$CI_COMMIT_BRANCH =~ /(feat|fix)\/[0-9]{4,5}\/develop/'
+
   artifacts:
     paths:
       - test/unitTests/build/
     expire_in: 2h
+  # coverage: '^\s*Lines:\s*\d+.\d+\%'
 
-job_php-7.3:
-  image: php:7.3-apache
+job_php-8.0:
+  image: php:8.0-apache
   stage: test
   services:
     - name: postgres:10.1
@@ -50,90 +54,118 @@ job_php-7.3:
     - curl --location -s --output /usr/local/bin/phpunit https://phar.phpunit.de/phpunit-9.phar
     - chmod +x /usr/local/bin/phpunit
     - phpunit --coverage-text --colors=never
+  # only:
+  #   - develop
+  # except:
+  #   - schedules
+  rules:
+    - if: '$CI_COMMIT_BRANCH =~ /(feat|fix)\/[0-9]{4,5}\/develop/'
+  artifacts:
+    paths:
+      - test/unitTests/build/
+    expire_in: 2h
+  # coverage: '^\s*Lines:\s*\d+.\d+\%'
+
+job_php-7.4:
+  image: php:7.4-apache
+  stage: test
+  services:
+    - name: postgres:10.1
+      command: [ "-c", "datestyle=iso,dmy" ]
+  script:
+    - curl --location -s --output /usr/local/bin/phpunit https://phar.phpunit.de/phpunit-9.phar
+    - chmod +x /usr/local/bin/phpunit
+    - phpunit --coverage-text --colors=never -c phpunit.xml
+  # only:
+  #   - develop
+  # except:
+  #   - schedules
+  rules:
+    - if: '$CI_COMMIT_BRANCH =~ /(feat|fix)\/[0-9]{4,5}\/develop/'
+  artifacts:
+    paths:
+      - test/unitTests/build/
+    expire_in: 2h
+  # coverage: '^\s*Lines:\s*\d+.\d+\%'
+
+commits:
+  image: debian:10-slim
+  stage: synchronization
   only:
     - develop
+    - "21.03"
   except:
+    - tags
     - schedules
-
-
-commits:
- image: debian:10-slim
- stage: synchronization
- only:
-   - develop
-   - "21.03"
- except:
-   - tags
-   - schedules
- before_script:
-   # Skip the synchronisation if it is not enabled
-   - if [ $SYNC_ENABLED = "true" ]; then echo "Sync enabled"; else echo "Sync disabled, stopping the job" && exit 0; fi
-   # Configure ssh, with the private key to push to the private repository
-   - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
-   - eval $(ssh-agent -s)
-   - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
-   - mkdir -p ~/.ssh
-   - chmod 700 ~/.ssh
-   - ssh-keyscan "$GITLAB_URL" >> ~/.ssh/known_hosts
-   - chmod 644 ~/.ssh/known_hosts
-   - '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" >> ~/.ssh/config'
-   # Install git command
-   - apt install -y git
- script:
-   - chmod +x ./ci/commit_synchronization.sh
-   - ./ci/commit_synchronization.sh
+  before_script:
+    # Skip the synchronisation if it is not enabled
+    - if [ $SYNC_ENABLED = "true" ]; then echo "Sync enabled"; else echo "Sync disabled, stopping the job" && exit 0; fi
+    # Configure ssh, with the private key to push to the private repository
+    - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
+    - eval $(ssh-agent -s)
+    - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
+    - mkdir -p ~/.ssh
+    - chmod 700 ~/.ssh
+    - ssh-keyscan "$GITLAB_URL" >> ~/.ssh/known_hosts
+    - chmod 644 ~/.ssh/known_hosts
+    - '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" >> ~/.ssh/config'
+    # Install git command
+    - apt install -y git
+  script:
+    - chmod +x ./ci/commit_synchronization.sh
+    - ./ci/commit_synchronization.sh
 
 tags:
- image: debian:10-slim
- stage: synchronization
- only:
-   - tags
- except:
-   - schedules
- before_script:
-   # Skip the synchronisation if it is not enabled
-   - if [ $SYNC_ENABLED = "true" ]; then echo "Sync enabled"; else echo "Sync disabled, stopping the job" && exit 0; fi
-   # Configure ssh, with the private key to push to the private repository
-   - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
-   - eval $(ssh-agent -s)
-   - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
-   - mkdir -p ~/.ssh
-   - chmod 700 ~/.ssh
-   - ssh-keyscan "$GITLAB_URL" >> ~/.ssh/known_hosts
-   - chmod 644 ~/.ssh/known_hosts
-   - '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" >> ~/.ssh/config'
-   # Install git and curl command
-   - apt install -y git
-   - apt install -y curl
-   # Install npm
-   - curl -sL https://deb.nodesource.com/setup_14.x | bash -
-   - apt install -y nodejs
- script:
-   - git config --global user.email "$CI_EMAIL" && git config --global user.name "$CI_USER"
-   # We will work in another directory, to avoid git conflicts
-   - mkdir tmp
-   - cd tmp
-   # Find the branch name from tag name
-   - VERSION1=$(echo $CI_COMMIT_TAG| cut -d'.' -f 1)
-   - VERSION2=$(echo $CI_COMMIT_TAG| cut -d'.' -f 2)
-   - VERSION="${VERSION1}.${VERSION2}"
-   # Pull the private repository
-   - git init && git remote add origin $PRIVATE_REPOSITORY_URL_SSH
-   - git pull origin $VERSION
-   # Update and push build prod
-   - npm install
-   - npm run build-prod
-   - git status
-   - git add -f dist/
-   - git status
-   - git commit -m "Build prod for tag ${CI_COMMIT_TAG}"
-   - git show-ref
-   - git push origin HEAD:$VERSION
-   - git status
-   # Do the tag on the private repo
-   - git tag $CI_COMMIT_TAG
-   - git status
-   - git push origin --tags
+  image: debian:10-slim
+  stage: synchronization
+  only:
+    - tags
+  except:
+    - schedules
+  before_script:
+    # Skip the synchronisation if it is not enabled
+    - if [ $SYNC_ENABLED = "true" ]; then echo "Sync enabled"; else echo "Sync disabled, stopping the job" && exit 0; fi
+    # Configure ssh, with the private key to push to the private repository
+    - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
+    - eval $(ssh-agent -s)
+    - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
+    - mkdir -p ~/.ssh
+    - chmod 700 ~/.ssh
+    - ssh-keyscan "$GITLAB_URL" >> ~/.ssh/known_hosts
+    - chmod 644 ~/.ssh/known_hosts
+    - '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" >> ~/.ssh/config'
+    # Install git and curl command
+    - apt install -y git
+    - apt install -y curl
+    # Install npm
+    - curl -sL https://deb.nodesource.com/setup_14.x | bash -
+    - apt install -y nodejs
+  script:
+    - git config --global user.email "$CI_EMAIL" && git config --global user.name "$CI_USER"
+    # We will work in another directory, to avoid git conflicts
+    - mkdir tmp
+    - cd tmp
+    # Find the branch name from tag name
+    - VERSION1=$(echo $CI_COMMIT_TAG| cut -d'.' -f 1)
+    - VERSION2=$(echo $CI_COMMIT_TAG| cut -d'.' -f 2)
+    - VERSION="${VERSION1}.${VERSION2}"
+    # Pull the private repository
+    - git init && git remote add origin $PRIVATE_REPOSITORY_URL_SSH
+    - git pull origin $VERSION
+    # Update and push build prod
+    - npm install
+    - npm run build-prod
+    - git status
+    - git add -f dist/
+    - git status
+    - git commit -m "Build prod for tag ${CI_COMMIT_TAG}"
+    - git show-ref
+    - git push origin HEAD:$VERSION
+    - git status
+    # Do the tag on the private repo
+    - git tag $CI_COMMIT_TAG
+    - git status
+    - git push origin --tags
 
 
 logs:
@@ -177,39 +209,39 @@ logs:
     - curl -v -H 'Content-Type:application/json' -H "X-Redmine-API-Key:$REDMINE_API_KEY" -d "$BODY" -X PUT https://forge.maarch.org/issues/$ISSUE_ID.json
 
 new_branch:
- image: debian:10-slim
- stage: new_branch
- only:
-   - branches
- before_script:
-   # Install git and curl command
-   - apt-get update -yqq > /dev/null
-   - apt install -y curl
-   - apt install -y jq
- script:
-   - chmod +x ./ci/create_mr.sh
-   - ./ci/create_mr.sh
+  image: debian:10-slim
+  stage: new_branch
+  only:
+    - branches
+  before_script:
+    # Install git and curl command
+    - apt-get update -yqq > /dev/null
+    - apt install -y curl
+    - apt install -y jq
+  script:
+    - chmod +x ./ci/create_mr.sh
+    - ./ci/create_mr.sh
 
 new_tag:
- image: debian:10-slim
- stage: new_tag
- only:
-   - tags
- before_script:
-   # Install git and curl command
-   - apt-get update -yqq > /dev/null
-   - 'which ssh-agent || ( apt-get install openssh-client -y )'
-   - eval $(ssh-agent -s)
-   - echo "$SSH_PRIVATE_KEY_2" | tr -d '\r' | ssh-add -
-   - mkdir -p ~/.ssh
-   - chmod 700 ~/.ssh
-   - ssh-keyscan "$GITLAB_URL" >> ~/.ssh/known_hosts
-   - chmod 644 ~/.ssh/known_hosts
-   - '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" >> ~/.ssh/config'
-   - apt install -y git
-   - apt install -y curl
-   - apt install -y jq
- script:
-   - chmod +x ./ci/new_tag.sh
-   - ./ci/new_tag.sh
+  image: debian:10-slim
+  stage: new_tag
+  only:
+    - tags
+  before_script:
+    # Install git and curl command
+    - apt-get update -yqq > /dev/null
+    - 'which ssh-agent || ( apt-get install openssh-client -y )'
+    - eval $(ssh-agent -s)
+    - echo "$SSH_PRIVATE_KEY_2" | tr -d '\r' | ssh-add -
+    - mkdir -p ~/.ssh
+    - chmod 700 ~/.ssh
+    - ssh-keyscan "$GITLAB_URL" >> ~/.ssh/known_hosts
+    - chmod 644 ~/.ssh/known_hosts
+    - '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" >> ~/.ssh/config'
+    - apt install -y git
+    - apt install -y curl
+    - apt install -y jq
+  script:
+    - chmod +x ./ci/new_tag.sh
+    - ./ci/new_tag.sh
 
diff --git a/lang/en.json b/lang/en.json
index 9d210c5bedf32b4c874c19d207e97898c06b0aee..194886f70f7c4b212e3a8926ff6e5f9eedaf8111 100755
--- a/lang/en.json
+++ b/lang/en.json
@@ -650,6 +650,8 @@
         "groupsToManage": "Choose the authorized assignment groups",
 		"unlinkGroup": "Unlink group",
         "emptyGroups": "No groups available to associate",
-        "errorConvertingDocument": "Error converting document"
+        "errorConvertingDocument": "Error converting document",
+        "emptyGroupUsers": "No users associated with this group",
+        "emptyUsers": "No users available to associate"
     }
 }
\ No newline at end of file
diff --git a/lang/fr.json b/lang/fr.json
index ec399440da4daac570badc2325e790235d096a31..c645ffa04533f504883585f3ec56ba737846946c 100755
--- a/lang/fr.json
+++ b/lang/fr.json
@@ -649,6 +649,8 @@
 		"groupsToManage": "Choisir les groupes d'affectations autorisés",
 		"unlinkGroup": "Dissocier le groupe",
 		"emptyGroups": "Aucun groupe disponible à associer",
-		"errorConvertingDocument": "Erreur lors de la conversion du document"
+		"errorConvertingDocument": "Erreur lors de la conversion du document",
+		"emptyGroupUsers": "Aucun utilisateur associé à ce groupe",
+		"emptyUsers": "Aucun utilisateur disponible à associer"
 	}
 }
diff --git a/phpunit.xml b/phpunit.xml
index f062ec810c67a21709f308afaeb05b6273df07de..8648b358c763e5e56ae2432487ee1c87ec7cd8c4 100755
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -1,5 +1,9 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" colors="true" bootstrap="test/unitTests/define.php" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd">
+  <php>
+    <server name='HTTP_HOST' value='http://localhost/MaarchParapheur' />
+    <server name='SERVER_PORT' value='80' />
+  </php>
   <coverage>
     <include>
       <directory suffix="Test.php">test</directory>
diff --git a/src/app/email/controllers/EmailController.php b/src/app/email/controllers/EmailController.php
index 8601bbad15c93bf1ec57b8472437354d4dcca8dc..b4cc19f7e9b6cac8f25bc82c286efd26fa290a7d 100644
--- a/src/app/email/controllers/EmailController.php
+++ b/src/app/email/controllers/EmailController.php
@@ -132,10 +132,10 @@ class EmailController
         $email['cci']         = json_decode($email['cci']);
 
         $configuration = ConfigurationModel::getByIdentifier(['identifier' => 'emailServer', 'select' => ['value']]);
-        $configuration = json_decode($configuration[0]['value'], true);
         if (empty($configuration)) {
             return ['errors' => 'Configuration is missing'];
         }
+        $configuration = json_decode($configuration[0]['value'], true);
 
         $phpmailer = new PHPMailer();
         $phpmailer->setFrom($configuration['from']);
diff --git a/src/app/user/controllers/UserController.php b/src/app/user/controllers/UserController.php
index 3cae1c2259c90d7f90d9a0711ab19bd98b226956..ed9398ada46fd0c28a6943bd3b2d84a1aa0aa170 100755
--- a/src/app/user/controllers/UserController.php
+++ b/src/app/user/controllers/UserController.php
@@ -179,7 +179,11 @@ class UserController
             return $response->withStatus(400)->withJson(['errors' => 'Login already exists', 'lang' => 'userLoginAlreadyExists']);
         }
 
-        $body['x509_fingerprint'] = $body['x509Fingerprint'];
+        $body['x509_fingerprint'] = !empty($body['x509Fingerprint']) ? $body['x509Fingerprint'] : null;
+
+        if (empty($body['phone'])) {
+            $body['phone'] = null;
+        }
 
         if (!empty($body['isRest'])) {
             $body['"isRest"'] = true;
@@ -254,7 +258,6 @@ class UserController
             'firstname'       => $body['firstname'],
             'lastname'        => $body['lastname'],
             'email'           => $body['email'],
-            'phone'           => $body['phone'],
             'signature_modes' => []
         ];
 
diff --git a/src/app/user/models/UserModel.php b/src/app/user/models/UserModel.php
index cf400b123e8cc27ea472e0b568ce2f1d7b817481..8cef89e10253b8a781bf7a5f12f832544eb70b92 100755
--- a/src/app/user/models/UserModel.php
+++ b/src/app/user/models/UserModel.php
@@ -103,7 +103,7 @@ class UserModel
                 'picture'                       => $args['picture'],
                 'password_modification_date'    => 'CURRENT_TIMESTAMP',
                 'signature_modes'               => $args['signatureModes'],
-                'x509_fingerprint'              => $args['x509_fingerprint'],
+                'x509_fingerprint'              => $args['x509_fingerprint']
             ]
         ]);
 
diff --git a/src/core/controllers/AuthenticationController.php b/src/core/controllers/AuthenticationController.php
index 32dde88a04da6ff6514252c465a40d18c0c5adbe..fed906f671102396ab98c4c002c01778e69e7980 100755
--- a/src/core/controllers/AuthenticationController.php
+++ b/src/core/controllers/AuthenticationController.php
@@ -105,7 +105,7 @@ class AuthenticationController
 
         $connection = ConfigurationModel::getConnection();
         if (in_array($connection, ['default', 'ldap'])) {
-            if (!Validator::stringType()->notEmpty()->validate($body['login']) || !Validator::stringType()->notEmpty()->validate($body['password'])) {
+            if (!array_key_exists('login', $body) || !array_key_exists('password', $body) || !Validator::stringType()->notEmpty()->validate($body['login']) || !Validator::stringType()->notEmpty()->validate($body['password'])) {
                 return $response->withStatus(400)->withJson(['errors' => 'Bad Request']);
             }
         }
diff --git a/src/core/models/DatabasePDO.php b/src/core/models/DatabasePDO.php
index ba426ebf7c06933a36d764b6ca5872e84a616bd4..e47f6c31a80b3ac35ea81f79bf5eeff31f055ebe 100755
--- a/src/core/models/DatabasePDO.php
+++ b/src/core/models/DatabasePDO.php
@@ -69,7 +69,7 @@ class DatabasePDO
                 \PDO::ATTR_ERRMODE      => \PDO::ERRMODE_EXCEPTION,
                 \PDO::ATTR_CASE         => \PDO::CASE_LOWER
             ];
-    
+            
             try {
                 self::$pdo = new \PDO($dsn, $user, $password, $options);
                 break;
@@ -79,7 +79,7 @@ class DatabasePDO
                     self::$pdo = new \PDO($dsn, $user, $password, $options);
                     break;
                 } catch (\PDOException $PDOException) {
-                    if (!empty($loadedXml->database[$key + 1])) {
+                    if (!empty($loadedXml->{$key.'1'})) {
                         continue;
                     } else {
                         throw new \Exception($PDOException->getMessage());
diff --git a/src/core/models/PasswordModel.php b/src/core/models/PasswordModel.php
index 2ddb2fae0a3e11f0a603c5555daccf3515514158..c2e2ee351e48d5d24c250afba3b5f97385256557 100755
--- a/src/core/models/PasswordModel.php
+++ b/src/core/models/PasswordModel.php
@@ -23,8 +23,8 @@ class PasswordModel
         $aRules = DatabaseModel::select([
             'select'    => empty($aArgs['select']) ? ['*'] : $aArgs['select'],
             'table'     => ['password_rules'],
-            'where'     => $aArgs['where'],
-            'data'      => $aArgs['data'],
+            'where'     => $aArgs['where'] ?? [],
+            'data'      => $aArgs['data'] ?? [],
         ]);
 
         return $aRules;
diff --git a/src/frontend/app/administration/group/group.component.html b/src/frontend/app/administration/group/group.component.html
index 4643365fa63bd6b55163dd8d7d3bc9abb3b39f23..2d2fd6526dd72e574b4a8e27bc1ff80d2aa3e024 100644
--- a/src/frontend/app/administration/group/group.component.html
+++ b/src/frontend/app/administration/group/group.component.html
@@ -29,52 +29,61 @@
                 </ion-button>
             </ion-item>
         </div>
-        <ion-item lines="none" *ngIf="!creationMode">
-            <ion-label color="secondary">{{'lang.linkedUsers' | translate}} :</ion-label>
-        </ion-item>
-        <ion-searchbar [placeholder]="'lang.filter' | translate" style="margin-left: 4x; display: flex; width: 50%;"
-            (ionChange)="applyFilter($event.detail.value)">
-        </ion-searchbar>
-        <ion-card *ngIf="!creationMode" style="height: 400px; overflow-y: auto;">
-            <ion-list>
-                <ion-item style="position: sticky;top:0px;z-index:1;">
-                    <ng-container style="display: flex;align-items: center;justify-content: center;width: 100%;background: white;">
-                        <ion-label color="primary" matSort [matSortActive]="displayedColumns[1]" matSortDirection='asc'
-                        style="display: flex;font-size: 12px;align-items: center;" (matSortChange)="sortData($event)">
-                        <ng-container *ngFor="let col of displayedColumns">
-                            <div [mat-sort-header]="col" disableClear style="flex: 1" *ngIf="col!=='actions'">
-                                {{'lang.' + col | translate}}
-                            </div>
-                        </ng-container>
-                        <div style="flex: 1;text-align: right;" *ngIf="displayedColumns.indexOf('actions') > -1">
-                            <ion-button slot="end" color="primary" fille="outline" shape="round"
-                                (click)="openUserList()">
-                                {{'lang.add' | translate}}
-                            </ion-button>
-                        </div>
-                    </ion-label>
-                    </ng-container>
-                    <ion-button slot="end" fill="clear" shape="round" disabled>
-                        <ion-icon></ion-icon>
-                    </ion-button>
-                </ion-item>
-                <ion-virtual-scroll [items]="sortedData" approxItemHeight="50px">
-                    <ion-item *virtualItem="let element" style="display: flex;">
-                        <ion-label style="display: flex;cursor: pointer;"
-                            routerLink="/administration/users/{{element.id}}">
-                            <div style="flex: 1" *ngFor="let col of displayedColumns">
-                                {{element[col]}}
+        <ng-container *ngIf="!creationMode">
+            <ion-item lines="none">
+                <ion-label color="secondary">{{'lang.linkedUsers' | translate}} :</ion-label>
+            </ion-item>
+            <ion-searchbar [placeholder]="'lang.filter' | translate" style="margin-left: 4x; display: flex; width: 50%;"
+                (ionChange)="applyFilter($event.detail.value)">
+            </ion-searchbar>
+            <ion-content style="height: 400px; overflow-y: auto;">
+                <ion-list>
+                    <ion-item style="position: sticky;top:0px;z-index:1;">
+                        <ng-container style="display: flex;align-items: center;justify-content: center;width: 100%;background: white;">
+                            <ion-label color="primary" matSort [matSortActive]="displayedColumns[1]" matSortDirection='asc'
+                            style="display: flex;font-size: 12px;align-items: center;" (matSortChange)="sortData($event)">
+                            <ng-container *ngFor="let col of displayedColumns">
+                                <div [mat-sort-header]="col" disableClear style="flex: 1" *ngIf="col!=='actions'">
+                                    {{'lang.' + col | translate}}
+                                </div>
+                            </ng-container>
+                            <div style="flex: 1;text-align: right;" *ngIf="displayedColumns.indexOf('actions') > -1">
+                                <ion-button slot="end" color="primary" fille="outline" shape="round"
+                                    (click)="openUserList()">
+                                    {{'lang.add' | translate}}
+                                </ion-button>
                             </div>
                         </ion-label>
-                        <ion-button slot="end" fill="clear" shape="round"
-                            (click)="$event.stopPropagation();unlinkUser(element)"
-                            title="{{'lang.unlinkUser' | translate}}">
-                            <ion-icon color="danger" slot="icon-only" name="close-outline"></ion-icon>
+                        </ng-container>
+                        <ion-button slot="end" fill="clear" shape="round" disabled>
+                            <ion-icon></ion-icon>
                         </ion-button>
                     </ion-item>
-                </ion-virtual-scroll>
-            </ion-list>
-        </ion-card>
+                    <ion-virtual-scroll [items]="sortedData" approxItemHeight="50px">
+                        <ion-item *virtualItem="let element" style="display: flex;">
+                            <ion-label style="display: flex;cursor: pointer;"
+                                routerLink="/administration/users/{{element.id}}">
+                                <div style="flex: 1" *ngFor="let col of displayedColumns">
+                                    {{element[col]}}
+                                </div>
+                            </ion-label>
+                            <ion-button slot="end" fill="clear" shape="round"
+                                (click)="$event.stopPropagation();unlinkUser(element)"
+                                title="{{'lang.unlinkUser' | translate}}">
+                                <ion-icon color="danger" slot="icon-only" name="close-outline"></ion-icon>
+                            </ion-button>
+                        </ion-item>
+                    </ion-virtual-scroll>
+                    <ion-infinite-scroll threshold="100px" (ionInfinite)="loadData($event)" *ngIf="group.users.length > 7">
+                        <ion-infinite-scroll-content loadingSpinner="bubbles" [loadingText]="'lang.loadingMoreData' | translate">
+                        </ion-infinite-scroll-content>
+                    </ion-infinite-scroll>
+                </ion-list>
+                <ion-item lines="none" *ngIf="group.users.length === 0" style="text-align: center; font-size: 20px; color: gray; margin-top: 5px;">
+                    <ion-label>{{ 'lang.emptyGroupUsers' | translate }}</ion-label>
+                </ion-item>
+            </ion-content>
+        </ng-container>
         <ion-item text-center lines="none" style="position: sticky;bottom:0px;z-index:1;">
             <div style="display: flex;align-items: center;justify-content: center;width: 100%;background: white;">
                 <ion-button type="submit" shape="round" size="large" fill="outline" color="primary"
diff --git a/src/frontend/app/administration/group/group.component.ts b/src/frontend/app/administration/group/group.component.ts
index d17dee44095f02f094368a2768cf93b649fd7ea6..4f7916496dfd136f71a46c28a9f61d1ab93850de 100644
--- a/src/frontend/app/administration/group/group.component.ts
+++ b/src/frontend/app/administration/group/group.component.ts
@@ -84,7 +84,7 @@ export class GroupComponent implements OnInit {
                 this.creationMode = false;
                 this.usersList = [];
 
-                this.http.get('../rest/groups/' + params['id'])
+                this.http.get(`../rest/groups/${params['id']}`)
                     .pipe(
                         map((data: any) => data.group),
                         finalize(() => {
@@ -462,6 +462,11 @@ export class GroupComponent implements OnInit {
     canManage(privilege: any) {
         return privilege.id === 'manage_users' && privilege.checked;
     }
+
+    loadData(event: any) {
+        event.target.complete();
+        event.target.disabled = true;
+    }
 }
 
 
diff --git a/src/frontend/app/administration/group/list/users.component.html b/src/frontend/app/administration/group/list/users.component.html
index 2af53a9763a8c074cdb47950f5e74a4dbad21a1d..7d5ba32e6c5388a536e983d87cc83ac976aaf380 100644
--- a/src/frontend/app/administration/group/list/users.component.html
+++ b/src/frontend/app/administration/group/list/users.component.html
@@ -4,7 +4,7 @@
     </ion-toolbar>
 </ion-header>
 <ion-content>
-    <ion-list>
+    <ion-list *ngIf="usersList.length > 0">
         <ion-virtual-scroll [items]="usersList" approxItemHeight="50px" style="height: 450px;">
             <ion-item button *virtualItem="let element" (click)="selectUser(element)">
                 <ion-label>
@@ -13,4 +13,7 @@
             </ion-item>
         </ion-virtual-scroll>
     </ion-list>
+    <ion-item lines="none" *ngIf="usersList.length === 0" style="text-align: center; font-size: 20px; color: gray; margin-top: 35%;">
+        <ion-label>{{'lang.emptyUsers' | translate}}</ion-label>
+    </ion-item>
 </ion-content>
\ No newline at end of file
diff --git a/test/unitTests/app/user/UserControllerTest.php b/test/unitTests/app/user/UserControllerTest.php
index 1d573d5b63e680f81feace4edfd08aab1600e66d..6bd12b4a1bc05f96d3f7cab7e3df7a7f7ed8dde3 100755
--- a/test/unitTests/app/user/UserControllerTest.php
+++ b/test/unitTests/app/user/UserControllerTest.php
@@ -13,6 +13,7 @@ class UserControllerTest extends TestCase
 {
     private static $signatureId = null;
     private static $userId = null;
+    private static $userIdToDelete = null;
 
     public function testCreateUser()
     {
@@ -22,10 +23,11 @@ class UserControllerTest extends TestCase
         $request        = \Slim\Http\Request::createFromEnvironment($environment);
 
         $aArgs = [
-            'login'     => 'emailLogin',
-            'firstname' => 'Prénom',
-            'lastname'  => 'Nom',
-            'email'     => 'email@test.fr'
+            'login'             => 'emailLoginFingerprint',
+            'firstname'         => 'Prénom',
+            'lastname'          => 'Nom',
+            'email'             => 'email@test.fr',
+            'phone'             => '0701020304'
         ];
 
         $fullRequest = \httpRequestCustom::addContentInBody($aArgs, $request);
@@ -35,6 +37,23 @@ class UserControllerTest extends TestCase
         $this->assertIsInt($responseBody->id);
         self::$userId = $responseBody->id;
 
+        //with x509Fingerprint
+        $aArgs = [
+            'login'             => 'emailLogin',
+            'firstname'         => 'Prénom',
+            'lastname'          => 'Nom',
+            'email'             => 'email@test.fr',
+            'x509Fingerprint'   => 'fingerprint',
+            'isRest'            => true
+        ];
+
+        $fullRequest    = \httpRequestCustom::addContentInBody($aArgs, $request);
+        $response       = $userController->create($fullRequest, new \Slim\Http\Response());
+        $responseBody   = json_decode((string)$response->getBody());
+
+        $this->assertIsInt($responseBody->id);
+        self::$userIdToDelete = $responseBody->id;
+
         //Mail missing
         $aArgs = [
             'login'     => 'failLogin',
@@ -162,11 +181,27 @@ class UserControllerTest extends TestCase
         $this->assertSame('email@test.fr', $responseBody->user->email);
         $this->assertSame('Prénom', $responseBody->user->firstname);
         $this->assertSame('Nom', $responseBody->user->lastname);
+        $this->assertSame('0701020304', $responseBody->user->phone);
 
         $response     = $userController->getById($request, new \Slim\Http\Response(), ['id' => -1]);
+        $this->assertSame(400, $response->getStatusCode());
         $responseBody = json_decode((string)$response->getBody());
+        $this->assertSame('User does not exist', $responseBody->errors);
+    }
 
-        $this->assertEmpty($responseBody->users);
+    public function testGetFingerprintById()
+    {
+        $previousUserId = $GLOBALS['id'];
+        $GLOBALS['id']  = self::$userIdToDelete;
+        $userController = new \User\controllers\UserController();
+
+        //find "userIdToDelete" fingerprint before user gets deleted
+        $response       = $userController->getUserInformationsById(['id' => self::$userIdToDelete]);
+
+        $this->assertNotEmpty($response);
+        $this->assertNotEmpty($response['x509Fingerprint']);
+        $this->assertSame('fingerprint', $response['x509Fingerprint']);
+        $GLOBALS['id']  = $previousUserId;
     }
 
     public function testUpdate()
@@ -181,13 +216,14 @@ class UserControllerTest extends TestCase
         $request        = \Slim\Http\Request::createFromEnvironment($environment);
 
         $aArgs = [
-            'writingMode'   => 'stylus',
-            'writingSize'   => 2,
-            'writingColor'  => '#F1F1F1',
-            'lang'          => 'fr',
-            'notifications' => [
-                'instant'   => true,
-                'summaries' => [],
+            'writingMode'       => 'stylus',
+            'writingSize'       => 2,
+            'writingColor'      => '#F1F1F1',
+            'lang'              => 'fr',
+            'signatureScaling'  => false,
+            'notifications'     => [
+                'instant'       => true,
+                'summaries'     => [],
             ],
         ];
 
@@ -203,7 +239,8 @@ class UserControllerTest extends TestCase
         $aArgs = [
             'firstname'     => 'Jolly',
             'lastname'      => 'Jumper',
-            'email'         => 'email@test.fr'
+            'email'         => 'email@test.fr',
+            'phone'         => '0701020304'
         ];
 
         $fullRequest = \httpRequestCustom::addContentInBody($aArgs, $request);
@@ -241,7 +278,7 @@ class UserControllerTest extends TestCase
         $aArgs = [
             'encodedSignature'  => base64_encode(file_get_contents('test/unitTests/samples/signature.jpg')),
             'format'            => 'jpg'
-        ];
+        ];     
 
         $fullRequest = \httpRequestCustom::addContentInBody($aArgs, $request);
         $response     = $signatureController->create($fullRequest, new \Slim\Http\Response(), ['id' => self::$userId]);
@@ -290,4 +327,30 @@ class UserControllerTest extends TestCase
         ]);
         $GLOBALS['id'] = $previousUserId;
     }
+
+    public function testDelete()
+    {
+        $userController = new \User\controllers\UserController();
+
+        $environment    = \Slim\Http\Environment::mock(['REQUEST_METHOD' => 'DELETE']);
+        $request        = \Slim\Http\Request::createFromEnvironment($environment);
+        
+        $response       = $userController->delete($request, new \Slim\Http\Response(), ['id' => self::$userIdToDelete]);
+        $this->assertSame(204, $response->getStatusCode());
+
+        $response       = $userController->delete($request, new \Slim\Http\Response(), ['id' => self::$userIdToDelete]);
+        $responseBody   = json_decode((string)$response->getBody());
+        $this->assertSame(400, $response->getStatusCode());
+        $this->assertSame('User does not exist', $responseBody->errors);
+        
+        $response       = $userController->delete($request, new \Slim\Http\Response(), ['id' => '1']);
+        $responseBody   = json_decode((string)$response->getBody());
+        $this->assertSame(403, $response->getStatusCode());
+        $this->assertSame('Privilege forbidden', $responseBody->errors);
+
+        $response       = $userController->delete($request, new \Slim\Http\Response(), ['id' => 1.1]);
+        $responseBody   = json_decode((string)$response->getBody());
+        $this->assertSame(400, $response->getStatusCode());
+        $this->assertSame('Route id is not an integer', $responseBody->errors);
+    }
 }