From 0c83817d18b4a47c3e63ddaac0d21c8903419cd2 Mon Sep 17 00:00:00 2001
From: TINCU Sebastian-Ion <Sebastian-Ion.TINCU@ext.ec.europa.eu>
Date: Fri, 23 Nov 2018 16:41:53 +0100
Subject: [PATCH] EDELIVERY-3653 SMP: CTL.C8 Administrator accounts Password
 Management:

Add code to reset the password expiration date when a
SystemAdministrator changes passwords.
Add code to update the password change date when users change their own
passwords.
Extract common dialog component and add expired password warning dialog
component.
Add codelyzer dependency back and configure TSLint.
---
 smp-angular/package-lock.json                 | 107 ++++++++++++++++-
 smp-angular/package.json                      |   3 +-
 smp-angular/src/app/app.module.ts             |  25 ++--
 .../cancel-dialog.component.html              |  30 +----
 .../cancel-dialog/cancel-dialog.component.ts  |   3 +-
 .../dialog.component.css}                     |   7 +-
 .../app/common/dialog/dialog.component.html   |  32 ++++++
 .../common/dialog/dialog.component.spec.ts    |  25 ++++
 .../src/app/common/dialog/dialog.component.ts |  25 ++++
 .../expired-password-dialog.component.html    |   4 +
 .../expired-password-dialog.component.spec.ts |  25 ++++
 .../expired-password-dialog.component.ts      |  14 +++
 .../save-dialog/save-dialog.component.css     |  42 -------
 .../save-dialog/save-dialog.component.html    |  31 +----
 .../save-dialog/save-dialog.component.ts      |   3 +-
 smp-angular/src/app/login/login.component.ts  |  32 +++---
 smp-angular/tslint.json                       |  11 +-
 .../conversion/DBUserToUserROConverter.java   |  18 ++-
 .../conversion/UserROToDBUserConverter.java   |  19 ++-
 .../ec/edelivery/smp/data/ui/UserRO.java      |  18 ++-
 .../smp/services/ui/UIUserService.java        |  11 +-
 .../DBUserToUserROConverterTest.java          | 108 ++++++++++++++++++
 .../UserROToDBUserConverterTest.java          |  54 +++++++++
 .../ui/UIUserServiceIntegrationTest.java      |  13 +--
 .../ec/edelivery/smp/ui/UserResource.java     |   7 +-
 25 files changed, 491 insertions(+), 176 deletions(-)
 rename smp-angular/src/app/common/{cancel-dialog/cancel-dialog.component.css => dialog/dialog.component.css} (92%)
 create mode 100644 smp-angular/src/app/common/dialog/dialog.component.html
 create mode 100644 smp-angular/src/app/common/dialog/dialog.component.spec.ts
 create mode 100644 smp-angular/src/app/common/dialog/dialog.component.ts
 create mode 100644 smp-angular/src/app/common/expired-password-dialog/expired-password-dialog.component.html
 create mode 100644 smp-angular/src/app/common/expired-password-dialog/expired-password-dialog.component.spec.ts
 create mode 100644 smp-angular/src/app/common/expired-password-dialog/expired-password-dialog.component.ts
 delete mode 100644 smp-angular/src/app/common/save-dialog/save-dialog.component.css
 create mode 100644 smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/conversion/DBUserToUserROConverterTest.java
 create mode 100644 smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/conversion/UserROToDBUserConverterTest.java

diff --git a/smp-angular/package-lock.json b/smp-angular/package-lock.json
index 6eab6f6b0..48204c253 100644
--- a/smp-angular/package-lock.json
+++ b/smp-angular/package-lock.json
@@ -891,6 +891,11 @@
         "normalize-path": "2.1.1"
       }
     },
+    "app-root-path": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-2.1.0.tgz",
+      "integrity": "sha1-mL9lmTJ+zqGZMJhm6BQDaP0uZGo="
+    },
     "aproba": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
@@ -1898,6 +1903,26 @@
       "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
       "dev": true
     },
+    "codelyzer": {
+      "version": "4.5.0",
+      "resolved": "https://registry.npmjs.org/codelyzer/-/codelyzer-4.5.0.tgz",
+      "integrity": "sha512-oO6vCkjqsVrEsmh58oNlnJkRXuA30hF8cdNAQV9DytEalDwyOFRvHMnlKFzmOStNerOmPGZU9GAHnBo4tGvtiQ==",
+      "requires": {
+        "app-root-path": "2.1.0",
+        "css-selector-tokenizer": "0.7.1",
+        "cssauron": "1.4.0",
+        "semver-dsl": "1.0.1",
+        "source-map": "0.5.7",
+        "sprintf-js": "1.1.1"
+      },
+      "dependencies": {
+        "sprintf-js": {
+          "version": "1.1.1",
+          "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.1.tgz",
+          "integrity": "sha1-Nr54Mgr+WAH2zqPueLblqrlA6gw="
+        }
+      }
+    },
     "collection-visit": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz",
@@ -2276,12 +2301,35 @@
         "nth-check": "1.0.1"
       }
     },
+    "css-selector-tokenizer": {
+      "version": "0.7.1",
+      "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.1.tgz",
+      "integrity": "sha512-xYL0AMZJ4gFzJQsHUKa5jiWWi2vH77WVNg7JYRyewwj6oPh4yb/y6Y9ZCw9dsj/9UauMhtuxR+ogQd//EdEVNA==",
+      "requires": {
+        "cssesc": "0.1.0",
+        "fastparse": "1.1.2",
+        "regexpu-core": "1.0.0"
+      }
+    },
     "css-what": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.0.tgz",
       "integrity": "sha1-lGfQMsOM+u+58teVASUwYvh/ob0=",
       "dev": true
     },
+    "cssauron": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/cssauron/-/cssauron-1.4.0.tgz",
+      "integrity": "sha1-pmAt/34EqDBtwNuaVR6S6LVmKtg=",
+      "requires": {
+        "through": "2.3.8"
+      }
+    },
+    "cssesc": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-0.1.0.tgz",
+      "integrity": "sha1-yBSQPkViM3GgR3tAEJqq++6t27Q="
+    },
     "cuint": {
       "version": "0.2.2",
       "resolved": "https://registry.npmjs.org/cuint/-/cuint-0.2.2.tgz",
@@ -3383,6 +3431,11 @@
       "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
       "dev": true
     },
+    "fastparse": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz",
+      "integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ=="
+    },
     "faye-websocket": {
       "version": "0.10.0",
       "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz",
@@ -7331,6 +7384,11 @@
       "integrity": "sha512-n+IyV+nGz3+0q3/Yf1ra12KpCyi001bi4XFxSjbiWWjfqb52iTTtpGXmCCAOWWIAn9KEuFZKGqBERHmrtScZ3A==",
       "dev": true
     },
+    "regenerate": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz",
+      "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg=="
+    },
     "regenerator-runtime": {
       "version": "0.11.1",
       "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz",
@@ -7356,6 +7414,36 @@
         "safe-regex": "1.1.0"
       }
     },
+    "regexpu-core": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-1.0.0.tgz",
+      "integrity": "sha1-hqdj9Y7k18L2sQLkdkBQ3n7ZDGs=",
+      "requires": {
+        "regenerate": "1.4.0",
+        "regjsgen": "0.2.0",
+        "regjsparser": "0.1.5"
+      }
+    },
+    "regjsgen": {
+      "version": "0.2.0",
+      "resolved": "http://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz",
+      "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc="
+    },
+    "regjsparser": {
+      "version": "0.1.5",
+      "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz",
+      "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=",
+      "requires": {
+        "jsesc": "0.5.0"
+      },
+      "dependencies": {
+        "jsesc": {
+          "version": "0.5.0",
+          "resolved": "http://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz",
+          "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0="
+        }
+      }
+    },
     "relateurl": {
       "version": "0.2.7",
       "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz",
@@ -7736,8 +7824,15 @@
     "semver": {
       "version": "5.6.0",
       "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz",
-      "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==",
-      "dev": true
+      "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg=="
+    },
+    "semver-dsl": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/semver-dsl/-/semver-dsl-1.0.1.tgz",
+      "integrity": "sha1-02eN5VVeimH2Ke7QJTZq5fJzQKA=",
+      "requires": {
+        "semver": "5.6.0"
+      }
     },
     "semver-intersect": {
       "version": "1.4.0",
@@ -8156,8 +8251,7 @@
     "source-map": {
       "version": "0.5.7",
       "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
-      "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
-      "dev": true
+      "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
     },
     "source-map-loader": {
       "version": "0.2.4",
@@ -8585,6 +8679,11 @@
         "inherits": "2.0.3"
       }
     },
+    "through": {
+      "version": "2.3.8",
+      "resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz",
+      "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU="
+    },
     "through2": {
       "version": "2.0.3",
       "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz",
diff --git a/smp-angular/package.json b/smp-angular/package.json
index b4350405b..a4044ec6f 100644
--- a/smp-angular/package.json
+++ b/smp-angular/package.json
@@ -19,6 +19,7 @@
     "@angular/common": "^6.1.0",
     "@angular/compiler": "^6.1.0",
     "@angular/core": "^6.1.0",
+    "@angular/flex-layout": "^6.0.0-beta.16",
     "@angular/forms": "^6.1.0",
     "@angular/http": "^6.1.0",
     "@angular/material": "^6.4.7",
@@ -26,8 +27,8 @@
     "@angular/platform-browser-dynamic": "^6.1.0",
     "@angular/platform-server": "^6.1.0",
     "@angular/router": "^6.1.0",
-    "@angular/flex-layout": "^6.0.0-beta.16",
     "@swimlane/ngx-datatable": "^13.0.0",
+    "codelyzer": "^4.5.0",
     "core-js": "^2.5.7",
     "file-saver": "1.3.3",
     "hammerjs": "^2.0.8",
diff --git a/smp-angular/src/app/app.module.ts b/smp-angular/src/app/app.module.ts
index f127a35ee..ba7800171 100644
--- a/smp-angular/src/app/app.module.ts
+++ b/smp-angular/src/app/app.module.ts
@@ -2,7 +2,7 @@ import {BrowserModule} from '@angular/platform-browser';
 import {NgModule} from '@angular/core';
 import {FormsModule, ReactiveFormsModule} from '@angular/forms';
 import {HttpClient, HttpClientModule} from '@angular/common/http';
-import {FlexLayoutModule} from "@angular/flex-layout";
+import {FlexLayoutModule} from '@angular/flex-layout';
 import {
   MatButtonModule,
   MatCardModule,
@@ -20,7 +20,7 @@ import {
   MatToolbarModule,
   MatTooltipModule,
 } from '@angular/material';
-import "hammerjs";
+import 'hammerjs';
 
 import {NgxDatatableModule} from '@swimlane/ngx-datatable';
 
@@ -70,13 +70,15 @@ import {DomainDetailsDialogComponent} from './domain/domain-details-dialog/domai
 import {UserDetailsDialogComponent} from './user/user-details-dialog/user-details-dialog.component';
 import {DownloadService} from './download/download.service';
 import {CertificateService} from './user/certificate.service';
-import {GlobalLookups} from "./common/global-lookups";
-import {ServiceGroupExtensionWizardDialogComponent} from "./service-group-edit/service-group-extension-wizard-dialog/service-group-extension-wizard-dialog.component";
-import {ServiceMetadataWizardDialogComponent} from "./service-group-edit/service-metadata-wizard-dialog/service-metadata-wizard-dialog.component";
-import {ConfirmationDialogComponent} from "./common/confirmation-dialog/confirmation-dialog.component";
-import {SpinnerComponent} from "./common/spinner/spinner.component";
-import {UserService} from "./user/user.service";
-import {UserDetailsService} from "./user/user-details-dialog/user-details.service";
+import {GlobalLookups} from './common/global-lookups';
+import {ServiceGroupExtensionWizardDialogComponent} from './service-group-edit/service-group-extension-wizard-dialog/service-group-extension-wizard-dialog.component';
+import {ServiceMetadataWizardDialogComponent} from './service-group-edit/service-metadata-wizard-dialog/service-metadata-wizard-dialog.component';
+import {ConfirmationDialogComponent} from './common/confirmation-dialog/confirmation-dialog.component';
+import {SpinnerComponent} from './common/spinner/spinner.component';
+import {UserService} from './user/user.service';
+import {UserDetailsService} from './user/user-details-dialog/user-details.service';
+import { ExpiredPasswordDialogComponent } from './common/expired-password-dialog/expired-password-dialog.component';
+import { DialogComponent } from './common/dialog/dialog.component';
 
 @NgModule({
   declarations: [
@@ -110,7 +112,9 @@ import {UserDetailsService} from "./user/user-details-dialog/user-details.servic
     DomainSelectorComponent,
     AlertsComponent,
     SearchTableComponent,
-    UserDetailsDialogComponent
+    UserDetailsDialogComponent,
+    ExpiredPasswordDialogComponent,
+    DialogComponent,
   ],
   entryComponents: [
     AppComponent,
@@ -124,6 +128,7 @@ import {UserDetailsService} from "./user/user-details-dialog/user-details.servic
     ConfirmationDialogComponent,
     SaveDialogComponent,
     DefaultPasswordDialogComponent,
+    ExpiredPasswordDialogComponent,
   ],
   imports: [
     BrowserModule,
diff --git a/smp-angular/src/app/common/cancel-dialog/cancel-dialog.component.html b/smp-angular/src/app/common/cancel-dialog/cancel-dialog.component.html
index d8bc1fd3c..c01a602f0 100644
--- a/smp-angular/src/app/common/cancel-dialog/cancel-dialog.component.html
+++ b/smp-angular/src/app/common/cancel-dialog/cancel-dialog.component.html
@@ -1,26 +1,4 @@
-<div style="width: 500px;text-align: center">
-  <h1 mat-dialog-title>Do you want to cancel all unsaved operations?</h1>
-
-  <div class="divTable">
-    <div class="divTableBody">
-
-      <div class="divTableRow">
-
-        <div class="divTableCell">
-          <button mat-raised-button color="primary" (click)="dialogRef.close(true)" id="yesbuttondialog_id">
-            <mat-icon>check_circle</mat-icon>
-            <span>Yes</span>
-          </button>
-        </div>
-
-        <div class="divTableCell">
-          <button mat-raised-button color="primary" (click)="dialogRef.close(false)" id="nobuttondialog_id">
-            <mat-icon>cancel</mat-icon>
-            <span>No</span>
-          </button>
-        </div>
-
-      </div>
-    </div>
-  </div>
-</div>
+<smp-dialog [title]="'Do you want to cancel all unsaved operations?'"
+            [type]="'confirmation'"
+            [dialogRef]="dialogRef">
+</smp-dialog>
diff --git a/smp-angular/src/app/common/cancel-dialog/cancel-dialog.component.ts b/smp-angular/src/app/common/cancel-dialog/cancel-dialog.component.ts
index add7e0939..b91918d65 100644
--- a/smp-angular/src/app/common/cancel-dialog/cancel-dialog.component.ts
+++ b/smp-angular/src/app/common/cancel-dialog/cancel-dialog.component.ts
@@ -2,9 +2,8 @@ import {Component} from '@angular/core';
 import {MatDialogRef} from '@angular/material';
 
 @Component({
-  selector: 'app-cancel-dialog',
+  selector: 'smp-cancel-dialog',
   templateUrl: './cancel-dialog.component.html',
-  styleUrls: ['./cancel-dialog.component.css']
 })
 export class CancelDialogComponent {
 
diff --git a/smp-angular/src/app/common/cancel-dialog/cancel-dialog.component.css b/smp-angular/src/app/common/dialog/dialog.component.css
similarity index 92%
rename from smp-angular/src/app/common/cancel-dialog/cancel-dialog.component.css
rename to smp-angular/src/app/common/dialog/dialog.component.css
index 5e50dc67f..6286aff86 100644
--- a/smp-angular/src/app/common/cancel-dialog/cancel-dialog.component.css
+++ b/smp-angular/src/app/common/dialog/dialog.component.css
@@ -1,3 +1,8 @@
+.dialog {
+  width: 500px;
+  text-align: center;
+}
+
 label:hover, label:active, input:hover + label, input:active + label {
   color: #3f51b5;
 }
@@ -38,5 +43,3 @@ label:hover, label:active, input:hover + label, input:active + label {
 .divTableBody {
   display: table-row-group;
 }
-
-
diff --git a/smp-angular/src/app/common/dialog/dialog.component.html b/smp-angular/src/app/common/dialog/dialog.component.html
new file mode 100644
index 000000000..7b22fd1ec
--- /dev/null
+++ b/smp-angular/src/app/common/dialog/dialog.component.html
@@ -0,0 +1,32 @@
+<div class="dialog">
+  <h1 mat-dialog-title>{{title}}</h1>
+
+  <div class="divTable">
+    <div class="divTableBody">
+
+      <div class="divTableRow">
+
+        <div *ngIf="isConfirmationDialog()" class="divTableCell" >
+          <button mat-raised-button color="primary" (click)="dialogRef.close(true)" id="yesbuttondialog_id" tabindex="0">
+            <mat-icon>check_circle</mat-icon>
+            <span>Yes</span>
+          </button>
+        </div>
+
+        <div *ngIf="isConfirmationDialog()" class="divTableCell">
+          <button mat-raised-button color="primary" (click)="dialogRef.close(false)" id="nobuttondialog_id" tabindex="1">
+            <mat-icon>cancel</mat-icon>
+            <span>No</span>
+          </button>
+        </div>
+
+        <div *ngIf="isInformationDialog()" class="divTableCell">
+          <button mat-raised-button color="primary" (click)="dialogRef.close(true)" id="okbuttondialog_id" tabindex="3">
+            <mat-icon></mat-icon>
+            <span>OK</span>
+          </button>
+        </div>
+      </div>
+    </div>
+  </div>
+</div>
diff --git a/smp-angular/src/app/common/dialog/dialog.component.spec.ts b/smp-angular/src/app/common/dialog/dialog.component.spec.ts
new file mode 100644
index 000000000..a6bce8db3
--- /dev/null
+++ b/smp-angular/src/app/common/dialog/dialog.component.spec.ts
@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { DialogComponent } from './dialog.component';
+
+describe('DialogComponent', () => {
+  let component: DialogComponent;
+  let fixture: ComponentFixture<DialogComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [ DialogComponent ]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(DialogComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/smp-angular/src/app/common/dialog/dialog.component.ts b/smp-angular/src/app/common/dialog/dialog.component.ts
new file mode 100644
index 000000000..6bfee560e
--- /dev/null
+++ b/smp-angular/src/app/common/dialog/dialog.component.ts
@@ -0,0 +1,25 @@
+import { Component, Input } from '@angular/core';
+import { MatDialogRef } from '@angular/material';
+
+@Component({
+  selector: 'smp-dialog',
+  templateUrl: './dialog.component.html',
+  styleUrls: ['./dialog.component.css']
+})
+export class DialogComponent {
+
+  @Input() title: String;
+
+  @Input() type: string;
+
+  @Input() dialogRef: MatDialogRef<any>;
+
+  public isConfirmationDialog() {
+    return this.type === 'confirmation';
+  }
+
+  public isInformationDialog() {
+    return this.type === 'information';
+  }
+
+}
diff --git a/smp-angular/src/app/common/expired-password-dialog/expired-password-dialog.component.html b/smp-angular/src/app/common/expired-password-dialog/expired-password-dialog.component.html
new file mode 100644
index 000000000..cd140de69
--- /dev/null
+++ b/smp-angular/src/app/common/expired-password-dialog/expired-password-dialog.component.html
@@ -0,0 +1,4 @@
+<smp-dialog [title]="'Your password is more than three months old. Please change it as soon as possible!'"
+            [type]="'information'"
+            [dialogRef]="dialogRef">
+</smp-dialog>
diff --git a/smp-angular/src/app/common/expired-password-dialog/expired-password-dialog.component.spec.ts b/smp-angular/src/app/common/expired-password-dialog/expired-password-dialog.component.spec.ts
new file mode 100644
index 000000000..f55b8fd72
--- /dev/null
+++ b/smp-angular/src/app/common/expired-password-dialog/expired-password-dialog.component.spec.ts
@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { ExpiredPasswordDialogComponent } from './expired-password-dialog.component';
+
+describe('ExpiredPasswordDialogComponent', () => {
+  let component: ExpiredPasswordDialogComponent;
+  let fixture: ComponentFixture<ExpiredPasswordDialogComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [ ExpiredPasswordDialogComponent ]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(ExpiredPasswordDialogComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/smp-angular/src/app/common/expired-password-dialog/expired-password-dialog.component.ts b/smp-angular/src/app/common/expired-password-dialog/expired-password-dialog.component.ts
new file mode 100644
index 000000000..2d2d5fd1c
--- /dev/null
+++ b/smp-angular/src/app/common/expired-password-dialog/expired-password-dialog.component.ts
@@ -0,0 +1,14 @@
+import { Component, OnInit } from '@angular/core';
+import { MatDialogRef } from '@angular/material';
+
+@Component({
+  selector: 'smp-expired-password-dialog',
+  templateUrl: './expired-password-dialog.component.html',
+})
+export class ExpiredPasswordDialogComponent {
+
+  constructor(
+    public dialogRef: MatDialogRef<ExpiredPasswordDialogComponent>,
+  ) { }
+
+}
diff --git a/smp-angular/src/app/common/save-dialog/save-dialog.component.css b/smp-angular/src/app/common/save-dialog/save-dialog.component.css
deleted file mode 100644
index 5e50dc67f..000000000
--- a/smp-angular/src/app/common/save-dialog/save-dialog.component.css
+++ /dev/null
@@ -1,42 +0,0 @@
-label:hover, label:active, input:hover + label, input:active + label {
-  color: #3f51b5;
-}
-
-.divTable {
-  display: table;
-  width: 100%;
-}
-
-.divTableRow {
-  display: table-row;
-}
-
-.divTableHeading {
-  background-color: #EEE;
-  display: table-header-group;
-}
-
-.divTableCell, .divTableHead {
-  /*border: 1px solid #999999;*/
-  display: table-cell;
-  padding: 3px 3px;
-  text-align: center;
-}
-
-.divTableHeading {
-  background-color: #EEE;
-  display: table-header-group;
-  font-weight: bold;
-}
-
-.divTableFoot {
-  background-color: #EEE;
-  display: table-footer-group;
-  font-weight: bold;
-}
-
-.divTableBody {
-  display: table-row-group;
-}
-
-
diff --git a/smp-angular/src/app/common/save-dialog/save-dialog.component.html b/smp-angular/src/app/common/save-dialog/save-dialog.component.html
index d174d3e73..c7ff33a66 100644
--- a/smp-angular/src/app/common/save-dialog/save-dialog.component.html
+++ b/smp-angular/src/app/common/save-dialog/save-dialog.component.html
@@ -1,27 +1,4 @@
-<div style="width: 500px;text-align: center">
-  <h1 mat-dialog-title>Do you want to save your changes?</h1>
-
-  <div class="divTable">
-    <div class="divTableBody">
-
-      <div class="divTableRow">
-
-        <div class="divTableCell">
-          <button mat-raised-button color="primary" (click)="dialogRef.close(true)" id="yesbuttondialog_id">
-            <mat-icon>check_circle</mat-icon>
-            <span>Yes</span>
-          </button>
-        </div>
-
-        <div class="divTableCell">
-          <button mat-raised-button color="primary" (click)="dialogRef.close(false)" id="nobuttondialog_id">
-            <mat-icon>cancel</mat-icon>
-            <span>No</span>
-          </button>
-        </div>
-
-      </div>
-    </div>
-  </div>
-</div>
-
+<smp-dialog [title]="'Do you want to save your changes?'"
+            [type]="'confirmation'"
+            [dialogRef]="dialogRef">
+</smp-dialog>
diff --git a/smp-angular/src/app/common/save-dialog/save-dialog.component.ts b/smp-angular/src/app/common/save-dialog/save-dialog.component.ts
index f4a76bd36..32ecd098a 100644
--- a/smp-angular/src/app/common/save-dialog/save-dialog.component.ts
+++ b/smp-angular/src/app/common/save-dialog/save-dialog.component.ts
@@ -2,9 +2,8 @@ import {Component} from '@angular/core';
 import {MatDialogRef} from '@angular/material';
 
 @Component({
-  selector: 'app-messagefilter-dialog',
+  selector: 'smp-save-dialog',
   templateUrl: './save-dialog.component.html',
-  styleUrls: ['./save-dialog.component.css']
 })
 export class SaveDialogComponent {
 
diff --git a/smp-angular/src/app/login/login.component.ts b/smp-angular/src/app/login/login.component.ts
index b76c44a36..0951c9536 100644
--- a/smp-angular/src/app/login/login.component.ts
+++ b/smp-angular/src/app/login/login.component.ts
@@ -7,7 +7,8 @@ import {SecurityEventService} from '../security/security-event.service';
 import {User} from '../security/user.model';
 import {MatDialogRef, MatDialog} from '@angular/material';
 import {DefaultPasswordDialogComponent} from 'app/security/default-password-dialog/default-password-dialog.component';
-import {Subscription} from "rxjs";
+import {Subscription} from 'rxjs';
+import {ExpiredPasswordDialogComponent} from '../common/expired-password-dialog/expired-password-dialog.component';
 
 @Component({
   moduleId: module.id,
@@ -30,24 +31,23 @@ export class LoginComponent implements OnInit, OnDestroy {
   }
 
   ngOnInit() {
-    // get return url from route parameters or default to '/'
     this.returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/';
 
     this.httpEventService.subscribe((error) => {
-      console.log("Received forbidden request event")
       this.securityService.logout();
     });
 
     this.sub = this.securityEventService.onLoginSuccessEvent().subscribe(
       data => {
-        console.log("Authentication successfull");
-        //this.verifyDefaultLoginUsed();
-        this.router.navigate([this.returnUrl]);
+        if (data && data.passwordExpired) {
+          this.dialog.open(ExpiredPasswordDialogComponent).afterClosed().subscribe(() => this.router.navigate([this.returnUrl]));
+        } else {
+          this.router.navigate([this.returnUrl]);
+        }
       });
 
     this.securityEventService.onLoginErrorEvent().subscribe(
       error => {
-        console.error("Error authenticating:" + error);
         let message;
         const HTTP_UNAUTHORIZED = 401;
         const HTTP_FORBIDDEN = 403;
@@ -58,28 +58,27 @@ export class LoginComponent implements OnInit, OnDestroy {
         switch (error.status) {
           case HTTP_UNAUTHORIZED:
           case HTTP_FORBIDDEN:
-            let forbiddenCode = error.message;
-            console.log("User forbiden code " + forbiddenCode);
+            const forbiddenCode = error.message;
             switch (forbiddenCode) {
               case USER_INACTIVE:
-                message = "The user is inactive. Please contact your administrator.";
+                message = 'The user is inactive. Please contact your administrator.';
                 break;
               case USER_SUSPENDED:
-                message = "The user is suspended. Please try again later or contact your administrator.";
+                message = 'The user is suspended. Please try again later or contact your administrator.';
                 break;
               default:
-                message = "The username/password combination you provided are not valid. Please try again or contact your administrator.";
+                message = 'The username/password combination you provided are not valid. Please try again or contact your administrator.';
                 // clear the password
-                this.model.password='';
+                this.model.password = '';
                 break;
             }
             break;
           case HTTP_GATEWAY_TIMEOUT:
           case HTTP_NOTFOUND:
-            message = "Unable to login. SMP is not running.";
+            message = 'Unable to login. SMP is not running.';
             break;
           default:
-            message = "Default error (" + error.status + ") occurred during login.";
+            message = 'Default error (' + error.status + ') occurred during login.';
             break;
         }
         this.alertService.error(message);
@@ -93,14 +92,13 @@ export class LoginComponent implements OnInit, OnDestroy {
   }
 
   verifyDefaultLoginUsed() {
-    let currentUser: User = this.securityService.getCurrentUser();
+    const currentUser: User = this.securityService.getCurrentUser();
     if (currentUser.defaultPasswordUsed) {
       this.dialog.open(DefaultPasswordDialogComponent);
     }
   }
 
   ngOnDestroy(): void {
-    console.log("Destroying login component");
     this.sub.unsubscribe();
   }
 }
diff --git a/smp-angular/tslint.json b/smp-angular/tslint.json
index bb84fcf3c..cd0e0dbce 100644
--- a/smp-angular/tslint.json
+++ b/smp-angular/tslint.json
@@ -1,7 +1,5 @@
 {
-  "rulesDirectory": [
-    "node_modules/codelyzer"
-  ],
+  "extends": ["codelyzer"],
   "rules": {
     "callable-types": true,
     "class-name": true,
@@ -22,7 +20,7 @@
     "label-position": true,
     "max-line-length": [
       true,
-      140
+      200
     ],
     "member-access": false,
     "member-ordering": [
@@ -34,6 +32,7 @@
     "no-bitwise": true,
     "no-console": [
       true,
+      "log",
       "debug",
       "info",
       "time",
@@ -98,8 +97,8 @@
       "check-type"
     ],
 
-    "directive-selector": [true, "attribute", "app", "camelCase"],
-    "component-selector": [true, "element", "app", "kebab-case"],
+    "directive-selector": [true, "attribute", "smp", "camelCase"],
+    "component-selector": [true, "element", "smp", "kebab-case"],
     "use-input-property-decorator": true,
     "use-output-property-decorator": true,
     "use-host-property-decorator": true,
diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/conversion/DBUserToUserROConverter.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/conversion/DBUserToUserROConverter.java
index e3d69649b..030d29152 100644
--- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/conversion/DBUserToUserROConverter.java
+++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/conversion/DBUserToUserROConverter.java
@@ -3,11 +3,14 @@ package eu.europa.ec.edelivery.smp.conversion;
 import eu.europa.ec.edelivery.smp.data.model.DBUser;
 import eu.europa.ec.edelivery.smp.data.ui.CertificateRO;
 import eu.europa.ec.edelivery.smp.data.ui.UserRO;
+import org.apache.commons.lang.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.core.convert.ConversionService;
 import org.springframework.core.convert.converter.Converter;
 import org.springframework.stereotype.Component;
 
+import java.time.LocalDateTime;
+
 /**
  * @author Sebastian-Ion TINCU
  */
@@ -24,7 +27,7 @@ public class DBUserToUserROConverter implements Converter<DBUser, UserRO> {
         target.setUsername(source.getUsername());
         target.setRole(source.getRole());
         target.setPassword(source.getPassword());
-        target.setPasswordChanged(source.getPasswordChanged());
+        target.setPasswordExpired(isPasswordExpired(source));
         target.setActive(source.isActive());
         target.setId(source.getId());
         if (source.getCertificate() != null) {
@@ -33,4 +36,17 @@ public class DBUserToUserROConverter implements Converter<DBUser, UserRO> {
         }
         return target;
     }
+
+    private boolean isPasswordExpired(DBUser source) {
+        return StringUtils.isNotEmpty(source.getPassword())
+                && (isPasswordRecentlyReset(source) || isPasswordChangedLongerThanThreeMonthsAgo(source));
+    }
+
+    private boolean isPasswordRecentlyReset(DBUser source) {
+        return source.getPasswordChanged() == null;
+    }
+
+    private boolean isPasswordChangedLongerThanThreeMonthsAgo(DBUser source) {
+        return LocalDateTime.now().minusMonths(3).isAfter(source.getPasswordChanged());
+    }
 }
diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/conversion/UserROToDBUserConverter.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/conversion/UserROToDBUserConverter.java
index 7dc34f62a..ebd07559d 100644
--- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/conversion/UserROToDBUserConverter.java
+++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/conversion/UserROToDBUserConverter.java
@@ -19,18 +19,17 @@ public class UserROToDBUserConverter implements Converter<UserRO, DBUser> {
 
     @Override
     public DBUser convert(UserRO source) {
-        DBUser dro = new DBUser();
-        dro.setEmailAddress(source.getEmailAddress());
-        dro.setUsername(source.getUsername());
-        dro.setRole(source.getRole());
-        dro.setPassword(source.getPassword());
-        dro.setActive(source.isActive());
-        dro.setId(source.getId());
-        dro.setPasswordChanged(source.getPasswordChanged());
+        DBUser target = new DBUser();
+        target.setEmailAddress(source.getEmailAddress());
+        target.setUsername(source.getUsername());
+        target.setRole(source.getRole());
+        target.setPassword(source.getPassword());
+        target.setActive(source.isActive());
+        target.setId(source.getId());
         if (source.getCertificate() != null) {
             DBCertificate certData = conversionService.convert(source.getCertificate(), DBCertificate.class);
-            dro.setCertificate(certData);
+            target.setCertificate(certData);
         }
-        return dro;
+        return target;
     }
 }
diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/UserRO.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/UserRO.java
index 0204221bf..9b4827f48 100644
--- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/UserRO.java
+++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/UserRO.java
@@ -12,23 +12,18 @@ import java.util.List;
  */
 public class UserRO extends BaseRO {
 
+    private static final long serialVersionUID = 2821447495333163882L;
 
-
-    private static final long serialVersionUID = -4971552086560325302L;
     private String username;
     private String password;
     private String emailAddress;
     private List<String> authorities;
-    private LocalDateTime passwordChanged;
     private boolean active = true;
     private String role;
     private Long id;
     private CertificateRO certificate;
     private int statusPassword = EntityROStatus.PERSISTED.getStatusNumber();
-
-    public UserRO(){
-
-    }
+    private boolean passwordExpired;
 
     public Long getId() {
         return id;
@@ -62,12 +57,12 @@ public class UserRO extends BaseRO {
         this.emailAddress = email;
     }
 
-    public LocalDateTime getPasswordChanged() {
-        return passwordChanged;
+    public boolean isPasswordExpired() {
+        return passwordExpired;
     }
 
-    public void setPasswordChanged(LocalDateTime passwordChanged) {
-        this.passwordChanged = passwordChanged;
+    public void setPasswordExpired(boolean passwordExpired) {
+        this.passwordExpired = passwordExpired;
     }
 
     public boolean isActive() {
@@ -93,6 +88,7 @@ public class UserRO extends BaseRO {
     public void setCertificate(CertificateRO certificate) {
         this.certificate = certificate;
     }
+
     public List<String> getAuthorities() {
         return authorities;
     }
diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIUserService.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIUserService.java
index d47773cb2..91ab340fa 100644
--- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIUserService.java
+++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIUserService.java
@@ -75,13 +75,12 @@ public class UIUserService extends UIServiceBase<DBUser, UserRO> {
     }
 
     @Transactional
-    public void updateUserList(List<UserRO> lst) {
-        boolean suc = false;
+    public void updateUserList(List<UserRO> lst, LocalDateTime passwordChange) {
         for (UserRO userRO : lst) {
             if (userRO.getStatus() == EntityROStatus.NEW.getStatusNumber()) {
                 DBUser dbUser = convertFromRo(userRO);
                 if (!StringUtils.isBlank(userRO.getPassword())) {
-                        dbUser.setPassword(BCryptPasswordHash.hashPassword(userRO.getPassword()));
+                    dbUser.setPassword(BCryptPasswordHash.hashPassword(userRO.getPassword()));
                 }
                 userDao.persistFlushDetach(dbUser);
             } else if (userRO.getStatus() == EntityROStatus.UPDATED.getStatusNumber()) {
@@ -93,10 +92,10 @@ public class UIUserService extends UIServiceBase<DBUser, UserRO> {
                 if (StringUtils.isBlank(userRO.getUsername()) ){
                     // if username is empty than clear the password
                     dbUser.setPassword("");
-                }
-                // check for new password
-                else if (!StringUtils.isBlank(userRO.getPassword())) {
+                }else if (!StringUtils.isBlank(userRO.getPassword())) {
+                    // check for new password
                     dbUser.setPassword(BCryptPasswordHash.hashPassword(userRO.getPassword()));
+                    dbUser.setPasswordChanged(passwordChange);
                 }
                 // update certificate data
                 if (userRO.getCertificate() == null || StringUtils.isBlank(userRO.getCertificate().getCertificateId())) {
diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/conversion/DBUserToUserROConverterTest.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/conversion/DBUserToUserROConverterTest.java
new file mode 100644
index 000000000..b4c997865
--- /dev/null
+++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/conversion/DBUserToUserROConverterTest.java
@@ -0,0 +1,108 @@
+package eu.europa.ec.edelivery.smp.conversion;
+
+import eu.europa.ec.edelivery.smp.data.model.DBCertificate;
+import eu.europa.ec.edelivery.smp.data.model.DBUser;
+import eu.europa.ec.edelivery.smp.data.ui.UserRO;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.core.convert.ConversionService;
+
+import java.time.LocalDateTime;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author Sebastian-Ion TINCU
+ */
+
+@RunWith(MockitoJUnitRunner.class)
+public class DBUserToUserROConverterTest {
+
+    private DBUser source;
+
+    private UserRO target;
+
+    @Mock
+    private ConversionService conversionService;
+
+    @InjectMocks
+    private DBUserToUserROConverter converter = new DBUserToUserROConverter();
+
+    @Test
+    public void returnsThePasswordAsNotExpiredForCertificateOnlyUsers() {
+        givenAnExistingCertificateOnlyUser();
+
+        whenConvertingTheExistingUser();
+
+        thenThePasswordIsNotMarkedAsExpired("The password should have not been marked as expired when the user has no password");
+    }
+
+    @Test
+    public void returnsThePasswordAsExpiredWhenConvertingAnExistingUserThatHasAPasswordThatHasBeenRecentlyReset() {
+        givenAnExistingUserHavingAPasswordThatHasJustBeenReset();
+
+        whenConvertingTheExistingUser();
+
+        thenThePasswordIsMarkedAsExpired("The passwords should be marked as expired when converting users having passwords that have been reset by SystemAdministrators");
+    }
+
+    @Test
+    public void returnsThePasswordAsNotExpiredWhenConvertingAnExistingUserThatHasAPasswordChangedNoLongerThanThreeMonthsAgo() {
+        givenAnExistingUserHavingAPasswordThatChangedNoLongerThanThreeMonthsAgo();
+
+        whenConvertingTheExistingUser();
+
+        thenThePasswordIsNotMarkedAsExpired("The passwords should not be marked as expired when converting users having password they have changed in the previous 3 months");
+    }
+
+    @Test
+    public void returnsThePasswordAsExpiredWhenConvertingAnExistingUserThatHasAPasswordChangedMoreThanThreeMonthsAgo() {
+        givenAnExistingUserHavingAPasswordThatChangedMoreThanThreeMonthsAgo();
+
+        whenConvertingTheExistingUser();
+
+        thenThePasswordIsMarkedAsExpired("The passwords should be marked as expired when converting users having password they have changed more than 3 months ago");
+    }
+
+    private void givenAnExistingCertificateOnlyUser() {
+        givenAnExistingUser(null, null, new DBCertificate());
+    }
+
+    private void givenAnExistingUserHavingAPasswordThatHasJustBeenReset() {
+        givenAnExistingUser("password", null, null);
+    }
+
+    private void givenAnExistingUserHavingAPasswordThatChangedNoLongerThanThreeMonthsAgo() {
+        givenAnExistingUser("password", LocalDateTime.now().minusMonths(2).minusDays(29), null);
+    }
+
+    private void givenAnExistingUserHavingAPasswordThatChangedMoreThanThreeMonthsAgo() {
+        givenAnExistingUser("password", LocalDateTime.now().minusMonths(3).minusDays(10), null);
+    }
+
+    private void givenAnExistingUser(String password, LocalDateTime passwordChange, DBCertificate certificate) {
+        source = new DBUser();
+        source.setCertificate(certificate);
+        source.setPassword(password);
+        source.setPasswordChanged(passwordChange);
+    }
+
+    private void whenConvertingTheExistingUser() {
+        target = converter.convert(source);
+    }
+
+    private void thenThePasswordIsMarkedAsExpired(String failureDescription) {
+        assertThat(target.isPasswordExpired())
+                .describedAs(failureDescription)
+                .isTrue();
+    }
+
+    private void thenThePasswordIsNotMarkedAsExpired(String failureDescription) {
+        assertThat(target.isPasswordExpired())
+                .describedAs(failureDescription)
+                .isFalse();
+    }
+}
\ No newline at end of file
diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/conversion/UserROToDBUserConverterTest.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/conversion/UserROToDBUserConverterTest.java
new file mode 100644
index 000000000..3ae5846a5
--- /dev/null
+++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/conversion/UserROToDBUserConverterTest.java
@@ -0,0 +1,54 @@
+package eu.europa.ec.edelivery.smp.conversion;
+
+import eu.europa.ec.edelivery.smp.data.model.DBUser;
+import eu.europa.ec.edelivery.smp.data.ui.UserRO;
+import org.assertj.core.api.Assertions;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.core.convert.ConversionService;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author Sebastian-Ion TINCU
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class UserROToDBUserConverterTest {
+
+    private UserRO source;
+
+    private DBUser target;
+
+    @Mock
+    private ConversionService conversionService;
+
+    @InjectMocks
+    private UserROToDBUserConverter converter = new UserROToDBUserConverter();
+
+    @Test
+    public void doesNotSetPasswordChangedWhenConvertingUser() {
+        givenUser();
+
+        whenConvertingTheUserRoSource();
+
+        thenThePasswordChangeTimeIsNotSet();
+    }
+
+    private void givenUser() {
+        source = new UserRO();
+    }
+
+    private void whenConvertingTheUserRoSource() {
+        target = converter.convert(source);
+    }
+
+    private void thenThePasswordChangeTimeIsNotSet() {
+        assertThat(target.getPasswordChanged())
+                .describedAs("The last time the password changed should not be set by the converter as it is controlled when the user details are updated " +
+                        "and it depends if it's done by the SystemAdministrators or by the users themselves")
+                .isNull();
+    }
+}
\ No newline at end of file
diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/ui/UIUserServiceIntegrationTest.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/ui/UIUserServiceIntegrationTest.java
index 6791adeb2..5d9ce494f 100644
--- a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/ui/UIUserServiceIntegrationTest.java
+++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/ui/UIUserServiceIntegrationTest.java
@@ -95,7 +95,6 @@ public class UIUserServiceIntegrationTest extends AbstractServiceIntegrationTest
         assertNotNull(res.getServiceEntities().get(0).getUsername());
         assertNotNull(res.getServiceEntities().get(0).getEmailAddress());
         assertNull(res.getServiceEntities().get(0).getPassword()); // Service list must not return passwords
-        assertNotNull(res.getServiceEntities().get(0).getPasswordChanged());
         assertNotNull(res.getServiceEntities().get(0).getRole());
     }
 
@@ -112,7 +111,7 @@ public class UIUserServiceIntegrationTest extends AbstractServiceIntegrationTest
         //when
         usr.setPassword(newPassword);
         usr.setStatus(EntityROStatus.UPDATED.getStatusNumber());
-        testInstance.updateUserList(Collections.singletonList(usr));
+        testInstance.updateUserList(Collections.singletonList(usr), null);
 
         // then
         DBUser dbuser = userDao.find(usr.getId());
@@ -134,7 +133,7 @@ public class UIUserServiceIntegrationTest extends AbstractServiceIntegrationTest
         user.setStatus(EntityROStatus.NEW.getStatusNumber());
 
         //when
-        testInstance.updateUserList(Collections.singletonList(user));
+        testInstance.updateUserList(Collections.singletonList(user), null);
 
         // then
         long  iCntNew  = userDao.getDataListCount(null);
@@ -176,7 +175,7 @@ public class UIUserServiceIntegrationTest extends AbstractServiceIntegrationTest
         user.setStatus(EntityROStatus.NEW.getStatusNumber());
 
         //when
-        testInstance.updateUserList(Collections.singletonList(user));
+        testInstance.updateUserList(Collections.singletonList(user), null);
 
         // then
         long  iCntNew  = userDao.getDataListCount(null);
@@ -222,7 +221,7 @@ public class UIUserServiceIntegrationTest extends AbstractServiceIntegrationTest
         user.setStatus(EntityROStatus.NEW.getStatusNumber());
 
         //when
-        testInstance.updateUserList(Collections.singletonList(user));
+        testInstance.updateUserList(Collections.singletonList(user), null);
 
         // then
         long  iCntNew  = userDao.getDataListCount(null);
@@ -270,7 +269,7 @@ public class UIUserServiceIntegrationTest extends AbstractServiceIntegrationTest
         userRO.setCertificate(null);
         userRO.setStatus(EntityROStatus.UPDATED.getStatusNumber());
 
-        testInstance.updateUserList(Collections.singletonList(userRO));
+        testInstance.updateUserList(Collections.singletonList(userRO), null);
 
         // then
         ServiceResult<UserRO> res  =  testInstance.getTableList(-1,-1,null, null, null);
@@ -291,7 +290,7 @@ public class UIUserServiceIntegrationTest extends AbstractServiceIntegrationTest
         user.setStatus(EntityROStatus.REMOVE.getStatusNumber());
 
         //when
-        testInstance.updateUserList(Collections.singletonList(user));
+        testInstance.updateUserList(Collections.singletonList(user), null);
 
         // then
         long  iCntNew  = userDao.getDataListCount(null);
diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/UserResource.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/UserResource.java
index 8fd5eeac0..6d5363676 100644
--- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/UserResource.java
+++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/UserResource.java
@@ -22,6 +22,7 @@ import org.springframework.web.bind.annotation.*;
 
 import java.io.IOException;
 import java.security.cert.CertificateException;
+import java.time.LocalDateTime;
 import java.util.Arrays;
 import java.util.List;
 
@@ -73,7 +74,8 @@ public class UserResource {
     public UserRO updateCurrentUser(@PathVariable("id") Long id, @RequestBody UserRO user) {
         LOG.info("Update current user: {}", user);
 
-        uiUserService.updateUserList(Arrays.asList(user));
+        // Update the user and mark the password as changed at this very instant of time
+        uiUserService.updateUserList(Arrays.asList(user), LocalDateTime.now());
 
         DBUser updatedUser = uiUserService.findUser(id);
         UserRO userRO = uiUserService.convertToRo(updatedUser);
@@ -85,7 +87,8 @@ public class UserResource {
     @Secured({SMPAuthority.S_AUTHORITY_TOKEN_SYSTEM_ADMIN})
     public void updateUserList(@RequestBody UserRO[] updateEntities ){
         LOG.info("Update user list, count: {}", updateEntities.length);
-        uiUserService.updateUserList(Arrays.asList(updateEntities));
+        // Pass the users and mark the passwords of the ones being updated as expired by passing the passwordChange as null
+        uiUserService.updateUserList(Arrays.asList(updateEntities), null);
     }
 
     @PostMapping("/{id}/certdata")
-- 
GitLab