Code development platform for open source projects from the European Union institutions

Skip to content
Snippets Groups Projects
Commit 5ae79605 authored by Joze RIHTARSIC's avatar Joze RIHTARSIC
Browse files

Pull request #177: Implement SSO-CAS authentication

Merge in EDELIVERY/smp from feature/EDELIVERY-8880-use-eu-login-for-smp-console-implementation-part-2 to development

* commit '26ad743bdf7071ad9609d6b0e04dddeb4dfec6c7':
  remove parent pom
  HSTS small fix
  update favicon ico
  Enable HSTS and add property for max age
  Implement SSO-CAS authentication
parents 0438007d 74e98839
No related branches found
No related tags found
No related merge requests found
Showing
with 714 additions and 928 deletions
Domibus 4.2
- added new properties:
smp.http.forwarded.headers.enabled to control usage of Forwarded parameters RP/LoadBalancer.
smp.ui.session.secure: Cookie is only sent to the server when a request is made with the https: scheme (except on localhost), and therefore is more resistant to man-in-the-middle attacks.
smp.ui.session.max-age: Number of seconds until the cookie expires. A zero or negative number will expire the cookie immediately. Empty value will not set parameter
smp.ui.session.strict: Controls whether a cookie is sent with cross-origin requests, providing some protection against cross-site request forgery attacks. Possible values are: Strict, None, Lax. (Cookies with SameSite=None require a secure context/HTTPS)!!)
smp.ui.session.path: A path that must exist in the requested URL, or the browser won't send the Cookie header. Null/Empty value sets the authentication requests context by default. The forward slash (/) character is interpreted as a directory separator, and subdirectories will be matched as well: for Path=/docs, /docs, /docs/Web/, and /docs/Web/HTTP will all match.
smp.ui.session.idle_timeout.admin: Specifies the time, in seconds, between client requests before the SMP will invalidate session for ADMIN users (System)!
smp.ui.session.idle_timeout.user: Specifies the time, in seconds, between client requests before the SMP will invalidate session for users (Service group, SMP Admin)
smp.sso.cas.enabled: Enable/disable CAS authentication.
smp.sso.cas.ui.label: The SSO service provider label.
smp.sso.cas.url: The SSO CAS URL enpoint
smp.sso.cas.urlpath.login: The CAS URL path for login. Complete URL is composed from parameters: ${smp.sso.cas.url}/${smp.sso.cas.urlpath.login}.
smp.sso.cas.callback.url: The URL is the callback URL belonging to the local SMP Security System. If using RP make sure it target SMP path '/ui/rest/security/cas'
smp.sso.cas.token.validation.urlpath: The CAS URL path for login. Complete URL is composed from parameters: ${smp.sso.cas.url}/${smp.sso.cas.urlpath.token.validation}.
smp.sso.cas.token.validation.params: The CAS token validation key:value properties separated with '|'.Ex: 'acceptStrengths:BASIC,CLIENT_CERT|assuranceLevel:TOP'
smp.sso.cas.token.validation.groups: The '|' separated CAS groups user must belong to.
smp.http.httpStrictTransportSecurity.maxAge: How long(in seconds) HSTS should last in the browser's cache(default one year)
\ No newline at end of file
This diff is collapsed.
......@@ -3,5 +3,7 @@ export interface SmpInfo {
smlIntegrationOn?: boolean;
contextPath?: string;
smlParticipantMultiDomainOn?: boolean
ssoAuthentication?: boolean;
ssoAuthenticationLabel?: string;
}
<mat-sidenav-container >
<mat-sidenav-container>
<mat-sidenav mode="side" opened="true" ngClass="{{menuClass}}">
<!-- sidenav content -->
<div id="topLogo">
<img src="assets/images/smp_logo_icon.svg" [attr.height]="fullMenu ? '74px' : '40px'" [attr.width]="fullMenu ? '74px' : '40px'"/>
<img src="assets/images/smp_logo_icon.svg" [attr.height]="fullMenu ? '74px' : '40px'"
[attr.width]="fullMenu ? '74px' : '40px'"/>
<div id="topLogoText" *ngIf="fullMenu">
<div style="font-weight: bold;font-size: 15px">eDelivery SMP <span style="font-weight: normal;font-size: 14px">Administration <br>Console</span></div>
<div style="font-weight: bold;font-size: 15px">eDelivery SMP <span style="font-weight: normal;font-size: 14px">Administration <br>Console</span>
</div>
</div>
</div>
......@@ -13,17 +15,20 @@
<mat-icon matTooltip="Search" matTooltipDisabled="{{fullMenu}}" [matTooltipPosition]="'right'">search</mat-icon>
<span>Search</span>
</button>
<button mat-raised-button class="sideNavButton" *ngIf="isCurrentUserSMPAdmin() || isCurrentUserServiceGroupAdmin()" [routerLink]="['/edit']" id="sidebar_edit_id">
<button mat-raised-button class="sideNavButton" *ngIf="isCurrentUserSMPAdmin() || isCurrentUserServiceGroupAdmin()"
[routerLink]="['/edit']" id="sidebar_edit_id">
<mat-icon matTooltip="Edit" matTooltipDisabled="{{fullMenu}}" [matTooltipPosition]="'right'">edit</mat-icon>
<span>Edit</span>
</button>
<button mat-raised-button class="sideNavButton" [routerLink]="['/domain']" *ngIf="isCurrentUserSystemAdmin()" id="sidebar_domain_id">
<button mat-raised-button class="sideNavButton" [routerLink]="['/domain']" *ngIf="isCurrentUserSystemAdmin()"
id="sidebar_domain_id">
<mat-icon matTooltip="Domain" matTooltipDisabled="{{fullMenu}}" [matTooltipPosition]="'right'">domain</mat-icon>
<span>Domain</span>
</button>
<!-- button mat-raised-button class="sideNavButton" [routerLink]="['/user']" *ngIf="hasAdmin()" id="user_id" -->
<button mat-raised-button class="sideNavButton" [routerLink]="['/user']" *ngIf="isCurrentUserSystemAdmin()" id="sidebar_user_id">
<button mat-raised-button class="sideNavButton" [routerLink]="['/user']" *ngIf="isCurrentUserSystemAdmin()"
id="sidebar_user_id">
<mat-icon matTooltip="Users" matTooltipDisabled="{{fullMenu}}" [matTooltipPosition]="'right'">people</mat-icon>
<span>Users</span>
</button>
......@@ -60,11 +65,10 @@
[style.margin-left]="fullMenu? '225px' : '65px'">
<alert></alert>
<div id="sandwichMenuHolder" style="z-index: 500">
<div id="sandwichMenuHolder" style="z-index: 500">
<div id="sandwichMenu">
<a *ngIf="!currentUser" [routerLink]="['/login']" (click)="clearWarning()" > Login </a>
<span *ngIf="currentUser" >{{currentUserRoleDescription}}: {{currentUser}} </span >
<a *ngIf="!currentUser" [routerLink]="['/login']" (click)="clearWarning()"> Login </a>
<span *ngIf="currentUser">{{currentUserRoleDescription}}: {{currentUser}} </span>
<button mat-icon-button [mat-menu-trigger-for]="settingsMenu" id="settingsmenu_id" matTooltip="Menu">
<mat-icon>menu</mat-icon>
......@@ -97,8 +101,8 @@
</div>
</div>
<div fxFill="100" fxFlex="<grow> <shrink> <basis>" id="routerHolder" style="min-height: 100%" >
<router-outlet></router-outlet>
<div fxFill="100" fxFlex="<grow> <shrink> <basis>" id="routerHolder" style="min-height: 100%">
<router-outlet></router-outlet>
</div>
</div>
</mat-sidenav-container>
......@@ -33,6 +33,7 @@ export class GlobalLookups implements OnInit {
constructor(protected alertService: AlertService, protected securityService: SecurityService, protected http: HttpClient) {
securityService.refreshLoggedUserFromServer();
this.refreshDomainLookup();
this.refreshUserLookup();
this.refreshCertificateLookup();
......@@ -156,6 +157,4 @@ export class GlobalLookups implements OnInit {
});
}
}
}
a:link { text-decoration: none; }
a:visited { text-decoration: none; }
a:hover { text-decoration: none; }
a:active { text-decoration: none; }
<div id="page" class="login-page">
<div id="formContainer">
<form name="loginForm" #loginForm="ngForm" (ngSubmit)="login()">
<table cellspacing="0">
<tr>
<td>
<mat-form-field>
<input matInput placeholder="Username" name="username" [(ngModel)]="model.username" #username="ngModel"
required id="username_id">
</mat-form-field>
</td>
</tr>
<tr>
<td>
<mat-form-field>
<input type="password" matInput placeholder="Password" name="password" [(ngModel)]="model.password"
#password="ngModel" required id="password_id">
</mat-form-field>
</td>
</tr>
<tr>
<td>
<button mat-raised-button color="primary" [disabled]="!loginForm.form.valid" id="loginbutton_id">
<mat-icon>input</mat-icon>
<span>Login</span>
</button>
</td>
</tr>
</table>
<footer></footer>
</form>
<div id="page" class="login-page" [style]="'justify-content:center; align-items:center; height:100%'">
<div fxLayout="row" [style]="'justify-content:center; align-items:center; height:100%'">
<mat-card *ngIf="lookups.cachedApplicationInfo.ssoAuthentication" fxFlex="400px" [style]="'width:400px;height:300px;margin:10px'">
<mat-card-title>SSO Login: {{lookups.cachedApplicationInfo.ssoAuthenticationLabel}}</mat-card-title>
<mat-card-content style="align-items: center;justify-content: center;display: flex;height: 200px;">
<a mat-raised-button color="primary" href="/smp/ui/rest/security/cas" [style]="'width=150px'">
<mat-icon>input</mat-icon>
<span> SSO Login</span>
</a>
</mat-card-content>
</mat-card>
<mat-card fxFlex="400px" [style]="'width:400px;height:300px;margin:10px'">
<mat-card-title>SMP Login</mat-card-title>
<mat-card-content style="align-items: center;justify-content: center;display: flex;height: 200px;">
<form name="loginForm" #loginForm="ngForm" (ngSubmit)="login()">
<table cellspacing="0">
<tr>
<td>
<mat-form-field>
<input matInput placeholder="Username" name="username" [(ngModel)]="model.username"
#username="ngModel"
required id="username_id">
</mat-form-field>
</td>
</tr>
<tr>
<td>
<mat-form-field>
<input type="password" matInput placeholder="Password" name="password" [(ngModel)]="model.password"
#password="ngModel" required id="password_id">
</mat-form-field>
</td>
</tr>
<tr>
<td>
<button mat-raised-button color="primary" [disabled]="!loginForm.form.valid" id="loginbutton_id" [style]="'width=150px'">
<mat-icon>input</mat-icon>
<span> Login</span>
</button>
</td>
</tr>
</table>
</form>
</mat-card-content>
</mat-card>
</div>
<footer></footer>
</div>
......@@ -9,10 +9,12 @@ import {MatDialogRef, MatDialog} from '@angular/material/dialog';
import {DefaultPasswordDialogComponent} from 'app/security/default-password-dialog/default-password-dialog.component';
import {Subscription} from 'rxjs';
import {ExpiredPasswordDialogComponent} from '../common/expired-password-dialog/expired-password-dialog.component';
import {GlobalLookups} from "../common/global-lookups";
@Component({
moduleId: module.id,
templateUrl: './login.component.html'
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit, OnDestroy {
......@@ -23,6 +25,7 @@ export class LoginComponent implements OnInit, OnDestroy {
constructor(private route: ActivatedRoute,
private router: Router,
public lookups: GlobalLookups,
private securityService: SecurityService,
private httpEventService: HttpEventService,
private alertService: AlertService,
......
......@@ -38,6 +38,17 @@ export class SecurityService {
});
}
refreshLoggedUserFromServer() {
let subject = new ReplaySubject<string>();
this.getCurrentUsernameFromServer().subscribe((res: string) => {
this.updateUserDetails(res);
}, (error: any) => {
//console.log('getCurrentUsernameFromServer:' + error);
this.securityEventService.notifyLoginErrorEvent(error);
});
}
logout() {
this.http.delete(SmpConstants.REST_SECURITY_AUTHENTICATION).subscribe((res: Response) => {
this.clearLocalStorage();
......
......@@ -6,6 +6,7 @@ export class SmpConstants {
public static readonly REST_EDIT = 'rest/servicegroup';
public static readonly REST_METADATA = 'rest/servicemetadata';
public static readonly REST_SECURITY_AUTHENTICATION = 'rest/security/authentication';
public static readonly REST_SECURITY_CAS_AUTHENTICATION = 'rest/security/saml';
public static readonly REST_SECURITY_USER = 'rest/security/user';
public static readonly REST_APPLICATION = 'rest/application/info';
public static readonly REST_CONFIG = 'rest/application/config';
......
......@@ -70,6 +70,7 @@
<args>
<arg>-Xvalue-constructor</arg><!-- generates value constructors -->
<arg>-Xequals</arg><!-- generates equals() metods -->
<arg>-XtoString</arg>
</args>
<plugins>
<!-- generates value constructors -->
......
......@@ -65,5 +65,5 @@ echo "Start compose"
docker-compose -p ${PREFIX} up -d --force-recreate
# wait until service is up
for i in `seq 100`; do timeout 1 bash -c ' curl --silent --fail http://localhost:8982/smp/'; if [ $? -eq 0 ] ; then break;fi; echo "$i. Wait for tomcat to start!"; sleep 5; done;
for i in `seq 100`; do timeout 1 bash -c 'curl --silent --fail http://localhost:8982/smp/'; if [ $? -eq 0 ] ; then break;fi; echo "$i. Wait for tomcat to start!"; sleep 5; done;
......@@ -106,5 +106,5 @@ docker-compose -p ${PREFIX} up -d --force-recreate
# wait until service is up
for i in `seq 200`; do timeout 10 bash -c ' curl --head --silent --fail http://localhost:7901/smp/'; if [ $? -eq 0 ] ; then break;fi; echo "$i. Wait for weblogic to start!"; sleep 10; done;
for i in `seq 200`; do timeout 10 bash -c ' curl --silent --fail http://localhost:7901/smp/'; if [ $? -eq 0 ] ; then break;fi; echo "$i. Wait for weblogic to start!"; sleep 10; done;
This diff is collapsed.
<!--
~ Copyright 2017 European Commission | CEF eDelivery
~
~ Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European Commission - subsequent versions of the EUPL (the "Licence");
~ You may not use this work except in compliance with the Licence.
~
~ You may obtain a copy of the Licence attached in file: LICENCE-EUPL-v1.2.pdf
~
~ Unless required by applicable law or agreed to in writing, software distributed under the Licence is distributed on an "AS IS" basis,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the Licence for the specific language governing permissions and limitations under the Licence.
-->
<settings>
<proxies>
<proxy>
<id>httpProxy</id>
<active>true</active>
<protocol>http</protocol>
<host>158.169.9.13</host>
<port>8012</port>
<username>j50b107</username>
<password>{AP1hP8sE97kHFzAatQGzaaiQPGFrOMDFUZFfG3xTxOs=}</password>
</proxy>
<proxy>
<id>httpsProxy</id>
<active>true</active>
<protocol>https</protocol>
<host>158.169.9.13</host>
<port>8012</port>
<username>j50b107</username>
<password>{AP1hP8sE97kHFzAatQGzaaiQPGFrOMDFUZFfG3xTxOs=}</password>
</proxy>
</proxies>
<mirrors>
<mirror>
<id>internal-repository</id>
<name>Maven Repository Manager running on repo.mycompany.com</name>
<url>https://joinup.ec.europa.eu/nexus/content/groups/public</url>
<mirrorOf>open-saml</mirrorOf>
</mirror>
</mirrors>
<servers>
<server>
<id>edelivery-snapshots</id>
<username>gutowpa</username>
<password>{2y/aQkOSNGYHhuusKhaDwLP/lm5YFzlJwyjUWXFDh74=}</password>
</server>
<server>
<id>edelivery-releases</id>
<username>gutowpa</username>
<password>{2y/aQkOSNGYHhuusKhaDwLP/lm5YFzlJwyjUWXFDh74=}</password>
</server>
<server>
<id>cefdigital-snapshots</id>
<username>j50b107</username>
<password>{AP1hP8sE97kHFzAatQGzaaiQPGFrOMDFUZFfG3xTxOs=}</password>
</server>
<server>
<id>cefdigital-releases</id>
<username>j50b107</username>
<password>{AP1hP8sE97kHFzAatQGzaaiQPGFrOMDFUZFfG3xTxOs=}</password>
</server>
<server>
<id>cefdigital-thirdparty</id>
<username>gutowpa</username>
<password>{2y/aQkOSNGYHhuusKhaDwLP/lm5YFzlJwyjUWXFDh74=}</password>
</server>
<server>
<id>webgate-digit</id>
<username>j50b107</username>
<password>{AP1hP8sE97kHFzAatQGzaaiQPGFrOMDFUZFfG3xTxOs=}</password>
</server>
<server>
<id>webgate-digit-third-party</id>
<username>j50b107</username>
<password>{AP1hP8sE97kHFzAatQGzaaiQPGFrOMDFUZFfG3xTxOs=}</password>
</server>
<server>
<id>webgate-digit-soapui</id>
<username>j50b107</username>
<password>{AP1hP8sE97kHFzAatQGzaaiQPGFrOMDFUZFfG3xTxOs=}</password>
</server>
<server>
<id>joinup-releases</id>
<username>j50b107</username>
<password>{AP1hP8sE97kHFzAatQGzaaiQPGFrOMDFUZFfG3xTxOs=}</password>
</server>
<server>
<id>joinup-snapshots</id>
<username>j50b107</username>
<password>{AP1hP8sE97kHFzAatQGzaaiQPGFrOMDFUZFfG3xTxOs=}</password>
</server>
<server>
<id>joinup-3rdparty</id>
<username>j50b107</username>
<password>{AP1hP8sE97kHFzAatQGzaaiQPGFrOMDFUZFfG3xTxOs=}</password>
</server>
</servers>
</settings>
\ No newline at end of file
......@@ -28,7 +28,6 @@ public class DatabaseProperties extends Properties {
}
lastUpdate = (lastUpdate==null || lastUpdate.isBefore(dc.getLastUpdatedOn()) )? dc.getLastUpdatedOn() :lastUpdate;
}
}
public LocalDateTime getLastUpdate() {
......
......@@ -202,8 +202,7 @@ public class ConfigurationDao extends BaseDao<DBConfiguration> {
}
if (!lstMissingProperties.isEmpty()) {
throw new SMPRuntimeException(CONFIGURATION_ERROR, String.format("Missing mandatory properties: %s",
String.join(",", lstMissingProperties)));
LOG.error("Missing mandatory properties: [{}]. Fix the SMP configuration!", lstMissingProperties);
}
......
......@@ -7,7 +7,8 @@ public class SmpInfoRO implements Serializable {
String version;
boolean smlIntegrationOn;
boolean smlParticipantMultiDomainOn;
boolean ssoAuthentication;
String ssoAuthenticationLabel;
String contextPath;
public String getVersion() {
......@@ -41,4 +42,20 @@ public class SmpInfoRO implements Serializable {
public void setSmlParticipantMultiDomainOn(boolean smlParticipantMultidomainOn) {
this.smlParticipantMultiDomainOn = smlParticipantMultidomainOn;
}
public boolean isSsoAuthentication() {
return ssoAuthentication;
}
public void setSsoAuthentication(boolean ssoAuthentication) {
this.ssoAuthentication = ssoAuthentication;
}
public String getSsoAuthenticationLabel() {
return ssoAuthenticationLabel;
}
public void setSsoAuthenticationLabel(String ssoAuthenticationLabel) {
this.ssoAuthenticationLabel = ssoAuthenticationLabel;
}
}
package eu.europa.ec.edelivery.smp.data.ui;
import com.fasterxml.jackson.annotation.JsonIgnore;
import eu.europa.ec.edelivery.smp.data.ui.auth.SMPAuthority;
import eu.europa.ec.edelivery.smp.data.ui.enums.EntityROStatus;
import org.springframework.security.core.userdetails.UserDetails;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Collection;
/**
* @author Joze Rihtarsic
* @since 4.1
*/
public class UserRO extends BaseRO {
public class UserRO extends BaseRO implements UserDetails {
private static final long serialVersionUID = 2821447495333163882L;
private String username;
private String password;
private String emailAddress;
private List<String> authorities;
private Collection<SMPAuthority> authorities;
private boolean active = true;
private String role;
private Long id;
......@@ -89,11 +91,12 @@ public class UserRO extends BaseRO {
this.certificate = certificate;
}
public List<String> getAuthorities() {
@Override
public Collection<SMPAuthority> getAuthorities() {
return authorities;
}
public void setAuthorities(List<String> authorities) {
public void setAuthorities(Collection<SMPAuthority> authorities) {
this.authorities = authorities;
}
......@@ -104,4 +107,28 @@ public class UserRO extends BaseRO {
public void setStatusPassword(int statusPassword) {
this.statusPassword = statusPassword;
}
@Override
@JsonIgnore
public boolean isAccountNonExpired() {
return true;
}
@Override
@JsonIgnore
public boolean isAccountNonLocked() {
return true;
}
@Override
@JsonIgnore
public boolean isCredentialsNonExpired() {
return true;
}
@Override
@JsonIgnore
public boolean isEnabled() {
return active;
}
}
package eu.europa.ec.edelivery.smp.auth;
package eu.europa.ec.edelivery.smp.data.ui.auth;
import com.fasterxml.jackson.annotation.JsonValue;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import eu.europa.ec.edelivery.smp.data.ui.databind.SMPAuthorityDeserializer;
import org.springframework.security.core.GrantedAuthority;
@JsonDeserialize(using = SMPAuthorityDeserializer.class)
public class SMPAuthority implements GrantedAuthority {
// static constants for annotations!
......@@ -24,6 +28,7 @@ public class SMPAuthority implements GrantedAuthority {
}
@Override
@JsonValue
public String getAuthority() {
return "ROLE_" + role;
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment