diff --git a/changelog.txt b/changelog.txt index 5b221f6e8530d5e8fba13fbae287265a88f0e6a7..e95ccd24fd6cd6a1838a32a67883cd6e17b11463 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,7 @@ -Domibus 4.2 +eDelivery SMP 4.2 - added new properties: + smp.ui.authentication.types: Set list of '|' separated UI authentication types. Currently supported PASSWORD, SSO: ex. PASSWORD|SSO + smp.automation.authentication.types: Set list of '|' separated automation authentication types (Web-Service integration). Currently supported PASSWORD, CERT: ex. PASSWORD|CERT 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 @@ -7,7 +9,6 @@ Domibus 4.2 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}. diff --git a/pom.xml b/pom.xml index 756d58f1f56416703afa83f3e20ca30ae79de794..de06f3cc2fba4eba68cd3c248c021c86bb96982a 100644 --- a/pom.xml +++ b/pom.xml @@ -22,10 +22,9 @@ <version>4.2-SNAPSHOT</version> <modules> - <!-- module>smp-parent-pom</module --> <module>smp-api</module> - <module>smp-angular</module> <module>smp-server-library</module> + <module>smp-angular</module> <module>smp-webapp</module> </modules> @@ -35,7 +34,7 @@ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <!-- Only selected modules are deployed --> <maven.deploy.skip>true</maven.deploy.skip> - <edelivery.ssl-auth.version>1.9</edelivery.ssl-auth.version> + <edelivery.ssl-auth.version>1.10-SNAPSHOT</edelivery.ssl-auth.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <ant-commons-net.version>1.6.5</ant-commons-net.version> @@ -83,7 +82,7 @@ <!-- jacoco, sonar code coverage settings start --> <sonar.java.coveragePlugin>jacoco</sonar.java.coveragePlugin> - <sonar.jacoco.codeCoveragePath>${maven.multiModuleProjectDirectory}/code-coverage</sonar.jacoco.codeCoveragePath> + <sonar.jacoco.codeCoveragePath>${basedir}/target/code-coverage</sonar.jacoco.codeCoveragePath> <sonar.jacoco.reportPath>${sonar.jacoco.codeCoveragePath}/jacoco-ut.exec</sonar.jacoco.reportPath> <sonar.jacoco.itReportPath>${sonar.jacoco.codeCoveragePath}/jacoco-it.exec</sonar.jacoco.itReportPath> <sonar.language>java</sonar.language> @@ -604,8 +603,7 @@ <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <configuration> - <reportsDirectory>${maven.multiModuleProjectDirectory}/code-coverage/surefire-reports - </reportsDirectory> + <reportsDirectory>${basedir}/target/code-coverage/surefire-reports</reportsDirectory> </configuration> <executions> <execution> @@ -630,8 +628,7 @@ <artifactId>maven-failsafe-plugin</artifactId> <version>2.19.1</version> <configuration> - <reportsDirectory>${maven.multiModuleProjectDirectory}/code-coverage/failsafe-reports - </reportsDirectory> + <reportsDirectory>${basedir}/target/code-coverage/failsafe-reports</reportsDirectory> </configuration> <executions> <execution> @@ -727,11 +724,11 @@ <outputFolder>${sonar.jacoco.codeCoveragePath}/surefire-reports</outputFolder> <junitReport>true</junitReport> <printReport>true</printReport> - <settingsFile>${maven.multiModuleProjectDirectory}/smp-soapui-tests/soapui/soapui-settings.xml</settingsFile> + <settingsFile>${basedir}/soapui/soapui-settings.xml</settingsFile> <soapuiProperties> <property> <name>soapui.scripting.library</name> - <value>${maven.multiModuleProjectDirectory}/smp-soapui-tests/groovy</value> + <value>${basedir}/groovy</value> </property> <property> <name>soapui.logroot</name> @@ -743,7 +740,7 @@ </property> </soapuiProperties> <testFailIgnore>true</testFailIgnore> - <projectFile>${maven.multiModuleProjectDirectory}/smp-soapui-tests/soapui/SMP4.0-Generic-soapui-project.xml</projectFile> + <projectFile>${basedir}/soapui/SMP4.0-Generic-soapui-project.xml</projectFile> <testSuite>PASSING_AUTO_BAMBOO</testSuite> <!--If you want to execute single test case <testCase>SMP001-Create ServiceGroup-Basic Flow-Admin Service Group specified</testCase>--> <projectProperties> diff --git a/smp-angular/src/app/app-info/smp-info.model.ts b/smp-angular/src/app/app-info/smp-info.model.ts index 7c04ee5a7b0a395d5b48b3013f30062274fdbe09..03b62b5c390625336598f260426928eb016ebef2 100644 --- a/smp-angular/src/app/app-info/smp-info.model.ts +++ b/smp-angular/src/app/app-info/smp-info.model.ts @@ -3,7 +3,6 @@ export interface SmpInfo { smlIntegrationOn?: boolean; contextPath?: string; smlParticipantMultiDomainOn?: boolean - ssoAuthentication?: boolean; + authTypes?: string[]; ssoAuthenticationLabel?: string; - } diff --git a/smp-angular/src/app/login/login.component.html b/smp-angular/src/app/login/login.component.html index 9100ba7a6a32a564ec5b165a5494c8000e01f818..c886e43444908cac7ebf3cd3579a60e1783dc177 100644 --- a/smp-angular/src/app/login/login.component.html +++ b/smp-angular/src/app/login/login.component.html @@ -1,18 +1,15 @@ <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 *ngIf="isUserAuthSSOEnabled() == true" 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 *ngIf="isUserAuthPasswdEnabled() == true" 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()"> @@ -36,7 +33,8 @@ </tr> <tr> <td> - <button mat-raised-button color="primary" [disabled]="!loginForm.form.valid" id="loginbutton_id" [style]="'width=150px'"> + <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> diff --git a/smp-angular/src/app/login/login.component.ts b/smp-angular/src/app/login/login.component.ts index b240f58db9009599df49e86614c12886fb55a33a..ddcfc7da64ef95100c6e32b666a596ff91d8f250 100644 --- a/smp-angular/src/app/login/login.component.ts +++ b/smp-angular/src/app/login/login.component.ts @@ -100,4 +100,12 @@ export class LoginComponent implements OnInit, OnDestroy { ngOnDestroy(): void { this.sub.unsubscribe(); } + + isUserAuthSSOEnabled(): boolean { + return this.lookups.cachedApplicationInfo.authTypes.includes('SSO'); + } + + isUserAuthPasswdEnabled():boolean { + return this.lookups.cachedApplicationInfo.authTypes.includes('PASSWORD'); + } } diff --git a/smp-angular/src/app/smp.constants.ts b/smp-angular/src/app/smp.constants.ts index 42511d3990c2d3cd965f8c141e6a95d21d1a42f3..0be8f1cb327450b114bb67b9a0ecd57f5fc07bd1 100644 --- a/smp-angular/src/app/smp.constants.ts +++ b/smp-angular/src/app/smp.constants.ts @@ -6,7 +6,8 @@ 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_CAS_AUTHENTICATION = 'rest/security/cas'; + 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'; diff --git a/smp-api/pom.xml b/smp-api/pom.xml index 689af035f4af97570bd92001a21f03a333dada36..b75507ffc0334c5b2ff3672498ef3992e97429d8 100644 --- a/smp-api/pom.xml +++ b/smp-api/pom.xml @@ -88,33 +88,6 @@ </plugins> </configuration> </plugin> - - <!-- just clean code coverage folder before collecting any information. - placed in this module as is the first in maven reactor order to collect info - about code coverage. - do not move this to parent pom or root pom! - --> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-clean-plugin</artifactId> - <version>3.0.0</version> - <executions> - <execution> - <id>default-clean</id> - <phase>clean</phase> - <goals> - <goal>clean</goal> - </goals> - </execution> - </executions> - <configuration> - <filesets> - <fileset> - <directory>${maven.multiModuleProjectDirectory}/code-coverage</directory> - </fileset> - </filesets> - </configuration> - </plugin> </plugins> </build> </project> diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/auth/enums/SMPAutomationAuthenticationTypes.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/auth/enums/SMPAutomationAuthenticationTypes.java new file mode 100644 index 0000000000000000000000000000000000000000..e0ccbeb75c83380eb120584b5cf69d20de68681f --- /dev/null +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/auth/enums/SMPAutomationAuthenticationTypes.java @@ -0,0 +1,18 @@ +package eu.europa.ec.edelivery.smp.auth.enums; + +/** + * Authentication types for application accounts supporting automated application functionalities. The application accounts + * are used for SMP web-service integrations. + * + * Supported authentication types + * - PASSWORD: the application username/password (Note:automation-user authentication is different than ui-user + * password and it can be used only for web-services!). + * - CERTIFICATE: certificate authentication username/password., + * + * @author Joze Rihtarsic + * @since 4.2 + */ +public enum SMPAutomationAuthenticationTypes { + PASSWORD, + CERTIFICATE; +} \ No newline at end of file diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/auth/enums/SMPUserAuthenticationTypes.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/auth/enums/SMPUserAuthenticationTypes.java new file mode 100644 index 0000000000000000000000000000000000000000..7b916df865a8670376016d96858a3afd986174cd --- /dev/null +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/auth/enums/SMPUserAuthenticationTypes.java @@ -0,0 +1,18 @@ +package eu.europa.ec.edelivery.smp.auth.enums; + +/** + * Authentication types for application accounts supporting automated application functionalities. The application accounts + * are used for SMP web-service integrations. + * + * Supported authentication types + * - PASSWORD: the user password authentication (Note:automation-user authentication is different than ui-user + * password and it can be used only for the UI!). + * - SSO: Single sign-on authentication using CAS server. , + * + * @author Joze Rihtarsic + * @since 4.2 + */ +public enum SMPUserAuthenticationTypes { + PASSWORD, + SSO; +} \ No newline at end of file diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/SmpInfoRO.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/SmpInfoRO.java index 37abd823a103980859f52612bf16303e2a9965ce..7748006fcd58e4520ae3ad4200b7916f3a0ee0fe 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/SmpInfoRO.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/SmpInfoRO.java @@ -1,15 +1,16 @@ package eu.europa.ec.edelivery.smp.data.ui; import java.io.Serializable; +import java.util.List; public class SmpInfoRO implements Serializable { private static final long serialVersionUID = -49712226560325302L; String version; boolean smlIntegrationOn; boolean smlParticipantMultiDomainOn; - boolean ssoAuthentication; String ssoAuthenticationLabel; String contextPath; + List<String> authTypes; public String getVersion() { return version; @@ -43,14 +44,6 @@ public class SmpInfoRO implements Serializable { this.smlParticipantMultiDomainOn = smlParticipantMultidomainOn; } - public boolean isSsoAuthentication() { - return ssoAuthentication; - } - - public void setSsoAuthentication(boolean ssoAuthentication) { - this.ssoAuthentication = ssoAuthentication; - } - public String getSsoAuthenticationLabel() { return ssoAuthenticationLabel; } @@ -58,4 +51,12 @@ public class SmpInfoRO implements Serializable { public void setSsoAuthenticationLabel(String ssoAuthenticationLabel) { this.ssoAuthenticationLabel = ssoAuthenticationLabel; } + + public List<String> getAuthTypes() { + return authTypes; + } + + public void setAuthTypes(List<String> authTypes) { + this.authTypes = authTypes; + } } diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/enums/SMPPropertyEnum.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/enums/SMPPropertyEnum.java index 5b74e6c29336bba0582c02d2a794e4c2f9a67b0f..ce93e47bcdd44f4dbe794610bf47526698176460 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/enums/SMPPropertyEnum.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/ui/enums/SMPPropertyEnum.java @@ -6,76 +6,75 @@ import java.util.Arrays; import java.util.Optional; public enum SMPPropertyEnum { - BLUE_COAT_ENABLED ("authentication.blueCoat.enabled","false","Authentication with Blue Coat means that all HTTP requests " + + BLUE_COAT_ENABLED("authentication.blueCoat.enabled", "false", "Authentication with Blue Coat means that all HTTP requests " + "having 'Client-Cert' header will be authenticated as username placed in the header.Never expose SMP to the WEB " + - "without properly configured reverse-proxy and active blue coat.", false, false,false, SMPPropertyTypeEnum.BOOLEAN), - - OUTPUT_CONTEXT_PATH ("contextPath.output","true","This property controls pattern of URLs produced by SMP in GET ServiceGroup responses." , true, false,true, SMPPropertyTypeEnum.BOOLEAN), - HTTP_FORWARDED_HEADERS_ENABLED ("smp.http.forwarded.headers.enabled","false","Use (value true) or remove (value false) forwarded headers! There are security considerations for forwarded headers since an application cannot know if the headers were added by a proxy, as intended, or by a malicious client." , false, false,false, SMPPropertyTypeEnum.BOOLEAN), - HTTP_HSTS_MAX_AGE("smp.http.httpStrictTransportSecurity.maxAge","31536000","How long(in seconds) HSTS should last in the browser's cache(default one year)", false, false,true, SMPPropertyTypeEnum.INTEGER), - - HTTP_PROXY_HOST("smp.proxy.host", "", "The http proxy host", false,false,false, SMPPropertyTypeEnum.STRING), - HTTP_NO_PROXY_HOSTS("smp.noproxy.hosts", "localhost|127.0.0.1", "list of nor proxy hosts. Ex.: localhost|127.0.0.1", false,false,false, SMPPropertyTypeEnum.STRING), - HTTP_PROXY_PASSWORD("smp.proxy.password", "", "Base64 encrypted password for Proxy.", false, true,false, SMPPropertyTypeEnum.STRING), - HTTP_PROXY_PORT("smp.proxy.port", "80", "The http proxy port", false, false,false, SMPPropertyTypeEnum.INTEGER), - HTTP_PROXY_USER("smp.proxy.user", "", "The proxy user", false, false,false, SMPPropertyTypeEnum.STRING), - - PARTC_SCH_REGEXP ("identifiersBehaviour.ParticipantIdentifierScheme.validationRegex","^((?!^.{26})([a-z0-9]+-[a-z0-9]+-[a-z0-9]+)|urn:oasis:names:tc:ebcore:partyid-type:(iso6523|unregistered)(:.+)?$)","Participant Identifier Schema of each PUT ServiceGroup request is validated against this schema.", false, false,false, SMPPropertyTypeEnum.REGEXP), - PARTC_SCH_REGEXP_MSG ("identifiersBehaviour.ParticipantIdentifierScheme.validationRegexMessage", - "Participant scheme must start with:urn:oasis:names:tc:ebcore:partyid-type:(iso6523:|unregistered:) OR must be up to 25 characters long with form [domain]-[identifierArea]-[identifierType] (ex.: 'busdox-actorid-upis') and may only contain the following characters: [a-z0-9].", "Error message for UI",false, false,false, SMPPropertyTypeEnum.STRING), - - CS_PARTICIPANTS("identifiersBehaviour.caseSensitive.ParticipantIdentifierSchemes","sensitive-participant-sc1|sensitive-participant-sc2","Specifies schemes of participant identifiers that must be considered CASE-SENSITIVE.", false, false,false, SMPPropertyTypeEnum.LIST_STRING), - CS_DOCUMENTS("identifiersBehaviour.caseSensitive.DocumentIdentifierSchemes","casesensitive-doc-scheme1|casesensitive-doc-scheme2","Specifies schemes of document identifiers that must be considered CASE-SENSITIVE.", false, false,false, SMPPropertyTypeEnum.LIST_STRING), - - SML_ENABLED("bdmsl.integration.enabled","false","BDMSL (SML) integration ON/OFF switch", false, false,false, SMPPropertyTypeEnum.BOOLEAN), - SML_PARTICIPANT_MULTIDOMAIN("bdmsl.participant.multidomain.enabled","false","Set to true if SML support participant on multidomain", false, false,true, SMPPropertyTypeEnum.BOOLEAN), - SML_URL("bdmsl.integration.url","http://localhost:8080/edelivery-sml","BDMSL (SML) endpoint", false, false,false, SMPPropertyTypeEnum.URL), - SML_TLS_DISABLE_CN_CHECK("bdmsl.integration.tls.disableCNCheck","false","If SML Url is HTTPs - Disable CN check if needed.", false, false,false, SMPPropertyTypeEnum.BOOLEAN), - SML_TLS_SERVER_CERT_SUBJECT_REGEXP("bdmsl.integration.tls.serverSubjectRegex",".*","Regular expression for server TLS certificate subject verification CertEx. .*CN=acc.edelivery.tech.ec.europa.eu.*.", false, false,false, SMPPropertyTypeEnum.REGEXP), - - SML_LOGICAL_ADDRESS("bdmsl.integration.logical.address","http://localhost:8080/smp/","Logical SMP endpoint which will be registered on SML when registering new domain", false, false,false, SMPPropertyTypeEnum.URL), - SML_PHYSICAL_ADDRESS("bdmsl.integration.physical.address","0.0.0.0","Physical SMP endpoint which will be registered on SML when registering new domain.", false, false,false, SMPPropertyTypeEnum.STRING), - - KEYSTORE_PASSWORD("smp.keystore.password","","Encrypted keystore (and keys) password ", false, true,false, SMPPropertyTypeEnum.STRING), - KEYSTORE_FILENAME("smp.keystore.filename","smp-keystore.jks","Keystore filename ", true, false,false, SMPPropertyTypeEnum.FILENAME), - TRUSTSTORE_PASSWORD("smp.truststore.password","","Encrypted truststore password ", false, true,false, SMPPropertyTypeEnum.STRING), - TRUSTSTORE_FILENAME("smp.truststore.filename","","Truststore filename ", false, false,false, SMPPropertyTypeEnum.FILENAME), - CERTIFICATE_CRL_FORCE("smp.certificate.crl.force","false","If false then if CRL is not reachable ignore CRL validation", false, false,false, SMPPropertyTypeEnum.BOOLEAN), + "without properly configured reverse-proxy and active blue coat.", false, false, false, SMPPropertyTypeEnum.BOOLEAN), + + OUTPUT_CONTEXT_PATH("contextPath.output", "true", "This property controls pattern of URLs produced by SMP in GET ServiceGroup responses.", true, false, true, SMPPropertyTypeEnum.BOOLEAN), + HTTP_FORWARDED_HEADERS_ENABLED("smp.http.forwarded.headers.enabled", "false", "Use (value true) or remove (value false) forwarded headers! There are security considerations for forwarded headers since an application cannot know if the headers were added by a proxy, as intended, or by a malicious client.", false, false, false, SMPPropertyTypeEnum.BOOLEAN), + HTTP_HSTS_MAX_AGE("smp.http.httpStrictTransportSecurity.maxAge", "31536000", "How long(in seconds) HSTS should last in the browser's cache(default one year)", false, false, true, SMPPropertyTypeEnum.INTEGER), + // http proxy configuration + HTTP_PROXY_HOST("smp.proxy.host", "", "The http proxy host", false, false, false, SMPPropertyTypeEnum.STRING), + HTTP_NO_PROXY_HOSTS("smp.noproxy.hosts", "localhost|127.0.0.1", "list of nor proxy hosts. Ex.: localhost|127.0.0.1", false, false, false, SMPPropertyTypeEnum.STRING), + HTTP_PROXY_PASSWORD("smp.proxy.password", "", "Base64 encrypted password for Proxy.", false, true, false, SMPPropertyTypeEnum.STRING), + HTTP_PROXY_PORT("smp.proxy.port", "80", "The http proxy port", false, false, false, SMPPropertyTypeEnum.INTEGER), + HTTP_PROXY_USER("smp.proxy.user", "", "The proxy user", false, false, false, SMPPropertyTypeEnum.STRING), + + PARTC_SCH_REGEXP("identifiersBehaviour.ParticipantIdentifierScheme.validationRegex", "^((?!^.{26})([a-z0-9]+-[a-z0-9]+-[a-z0-9]+)|urn:oasis:names:tc:ebcore:partyid-type:(iso6523|unregistered)(:.+)?$)", "Participant Identifier Schema of each PUT ServiceGroup request is validated against this schema.", false, false, false, SMPPropertyTypeEnum.REGEXP), + PARTC_SCH_REGEXP_MSG("identifiersBehaviour.ParticipantIdentifierScheme.validationRegexMessage", + "Participant scheme must start with:urn:oasis:names:tc:ebcore:partyid-type:(iso6523:|unregistered:) OR must be up to 25 characters long with form [domain]-[identifierArea]-[identifierType] (ex.: 'busdox-actorid-upis') and may only contain the following characters: [a-z0-9].", "Error message for UI", false, false, false, SMPPropertyTypeEnum.STRING), + CS_PARTICIPANTS("identifiersBehaviour.caseSensitive.ParticipantIdentifierSchemes", "sensitive-participant-sc1|sensitive-participant-sc2", "Specifies schemes of participant identifiers that must be considered CASE-SENSITIVE.", false, false, false, SMPPropertyTypeEnum.LIST_STRING), + CS_DOCUMENTS("identifiersBehaviour.caseSensitive.DocumentIdentifierSchemes", "casesensitive-doc-scheme1|casesensitive-doc-scheme2", "Specifies schemes of document identifiers that must be considered CASE-SENSITIVE.", false, false, false, SMPPropertyTypeEnum.LIST_STRING), + // SML integration! + SML_ENABLED("bdmsl.integration.enabled", "false", "BDMSL (SML) integration ON/OFF switch", false, false, false, SMPPropertyTypeEnum.BOOLEAN), + SML_PARTICIPANT_MULTIDOMAIN("bdmsl.participant.multidomain.enabled", "false", "Set to true if SML support participant on multidomain", false, false, true, SMPPropertyTypeEnum.BOOLEAN), + SML_URL("bdmsl.integration.url", "http://localhost:8080/edelivery-sml", "BDMSL (SML) endpoint", false, false, false, SMPPropertyTypeEnum.URL), + SML_TLS_DISABLE_CN_CHECK("bdmsl.integration.tls.disableCNCheck", "false", "If SML Url is HTTPs - Disable CN check if needed.", false, false, false, SMPPropertyTypeEnum.BOOLEAN), + SML_TLS_SERVER_CERT_SUBJECT_REGEXP("bdmsl.integration.tls.serverSubjectRegex", ".*", "Regular expression for server TLS certificate subject verification CertEx. .*CN=acc.edelivery.tech.ec.europa.eu.*.", false, false, false, SMPPropertyTypeEnum.REGEXP), + SML_LOGICAL_ADDRESS("bdmsl.integration.logical.address", "http://localhost:8080/smp/", "Logical SMP endpoint which will be registered on SML when registering new domain", false, false, false, SMPPropertyTypeEnum.URL), + SML_PHYSICAL_ADDRESS("bdmsl.integration.physical.address", "0.0.0.0", "Physical SMP endpoint which will be registered on SML when registering new domain.", false, false, false, SMPPropertyTypeEnum.STRING), + // keystore truststore + KEYSTORE_PASSWORD("smp.keystore.password", "", "Encrypted keystore (and keys) password ", false, true, false, SMPPropertyTypeEnum.STRING), + KEYSTORE_FILENAME("smp.keystore.filename", "smp-keystore.jks", "Keystore filename ", true, false, false, SMPPropertyTypeEnum.FILENAME), + TRUSTSTORE_PASSWORD("smp.truststore.password", "", "Encrypted truststore password ", false, true, false, SMPPropertyTypeEnum.STRING), + TRUSTSTORE_FILENAME("smp.truststore.filename", "", "Truststore filename ", false, false, false, SMPPropertyTypeEnum.FILENAME), + CERTIFICATE_CRL_FORCE("smp.certificate.crl.force", "false", "If false then if CRL is not reachable ignore CRL validation", false, false, false, SMPPropertyTypeEnum.BOOLEAN), + CONFIGURATION_DIR("configuration.dir", "smp", "Path to the folder containing all the configuration files (keystore and encryption key)", true, false, true, SMPPropertyTypeEnum.PATH), + ENCRYPTION_FILENAME("encryption.key.filename", "encryptionPrivateKey.private", "Key filename to encrypt passwords", false, false, true, SMPPropertyTypeEnum.FILENAME), + KEYSTORE_PASSWORD_DECRYPTED("smp.keystore.password.decrypted", "", "Only for backup purposes when password is automatically created. Store password somewhere save and delete this entry!", false, false, false, SMPPropertyTypeEnum.STRING), + TRUSTSTORE_PASSWORD_DECRYPTED("smp.truststore.password.decrypted", "", "Only for backup purposes when password is automatically created. Store password somewhere save and delete this entry!", false, false, false, SMPPropertyTypeEnum.STRING), CERTIFICATE_ALLOWED_CERTIFICATEPOLICY_OIDS("smp.certificate.validation.allowedCertificatePolicyOIDs","","List of certificate policy OIDs separated by comma where at least one must be in the CertifictePolicy extension", false, false,false, SMPPropertyTypeEnum.STRING), CERTIFICATE_SUBJECT_REGULAR_EXPRESSION("smp.certificate.validation.subjectRegex",".*","Regular expression to validate subject of the certificate", false, false,false, SMPPropertyTypeEnum.REGEXP), - CONFIGURATION_DIR("configuration.dir","smp","Path to the folder containing all the configuration files (keystore and encryption key)", true, false,true, SMPPropertyTypeEnum.PATH), - ENCRYPTION_FILENAME("encryption.key.filename","encryptionPrivateKey.private","Key filename to encrypt passwords", false, false,true, SMPPropertyTypeEnum.FILENAME), - KEYSTORE_PASSWORD_DECRYPTED("smp.keystore.password.decrypted","","Only for backup purposes when password is automatically created. Store password somewhere save and delete this entry!", false, false,false, SMPPropertyTypeEnum.STRING), - TRUSTSTORE_PASSWORD_DECRYPTED("smp.truststore.password.decrypted","","Only for backup purposes when password is automatically created. Store password somewhere save and delete this entry!", false, false,false, SMPPropertyTypeEnum.STRING), - - SML_KEYSTORE_PASSWORD("bdmsl.integration.keystore.password","","Deprecated", false, false,false, SMPPropertyTypeEnum.STRING), - SML_KEYSTORE_PATH("bdmsl.integration.keystore.path","","Deprecated", false, false,false, SMPPropertyTypeEnum.STRING), - SIGNATURE_KEYSTORE_PASSWORD("xmldsig.keystore.password","","Deprecated", false, false,false, SMPPropertyTypeEnum.STRING), - SIGNATURE_KEYSTORE_PATH("xmldsig.keystore.classpath","","Deprecated", false, false,false, SMPPropertyTypeEnum.STRING), - SML_PROXY_HOST("bdmsl.integration.proxy.server","","Deprecated", false, false,false, SMPPropertyTypeEnum.STRING), - SML_PROXY_PORT("bdmsl.integration.proxy.port","","Deprecated", false, false,false, SMPPropertyTypeEnum.INTEGER), - SML_PROXY_USER("bdmsl.integration.proxy.user","","Deprecated", false, false,false, SMPPropertyTypeEnum.STRING), - SML_PROXY_PASSWORD("bdmsl.integration.proxy.password","","Deprecated", false, false,false, SMPPropertyTypeEnum.STRING), - - SMP_PROPERTY_REFRESH_CRON("smp.property.refresh.cronJobExpression","0 48 */1 * * *","Property refresh cron expression (def 12 minutes to each hour). Property change is refreshed at restart!", false, false,true, SMPPropertyTypeEnum.STRING), + SMP_PROPERTY_REFRESH_CRON("smp.property.refresh.cronJobExpression", "0 48 */1 * * *", "Property refresh cron expression (def 12 minutes to each hour). Property change is refreshed at restart!", false, false, true, SMPPropertyTypeEnum.STRING), // UI COOKIE configuration - UI_COOKIE_SESSION_SECURE("smp.ui.session.secure","false","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.", false, false,false, SMPPropertyTypeEnum.BOOLEAN), - UI_COOKIE_SESSION_MAX_AGE("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", false, false,false, SMPPropertyTypeEnum.INTEGER), - UI_COOKIE_SESSION_SITE("smp.ui.session.strict","Lax","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)!!)", false, false,false, SMPPropertyTypeEnum.STRING), - UI_COOKIE_SESSION_PATH("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", false, false,false, SMPPropertyTypeEnum.STRING), - - UI_COOKIE_SESSION_IDLE_TIMEOUT_ADMIN("smp.ui.session.idle_timeout.admin","300","Specifies the time, in seconds, between client requests before the SMP will invalidate session for ADMIN users (System and SMP Admin)!", false, false,false, SMPPropertyTypeEnum.INTEGER), - UI_COOKIE_SESSION_IDLE_TIMEOUT_USER("smp.ui.session.idle_timeout.user","1800","Specifies the time, in seconds, between client requests before the SMP will invalidate session for users (Service group)", false, false,false, SMPPropertyTypeEnum.INTEGER), + UI_COOKIE_SESSION_SECURE("smp.ui.session.secure", "false", "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.", false, false, false, SMPPropertyTypeEnum.BOOLEAN), + UI_COOKIE_SESSION_MAX_AGE("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", false, false, false, SMPPropertyTypeEnum.INTEGER), + UI_COOKIE_SESSION_SITE("smp.ui.session.strict", "Lax", "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)!!)", false, false, false, SMPPropertyTypeEnum.STRING), + UI_COOKIE_SESSION_PATH("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", false, false, false, SMPPropertyTypeEnum.STRING), + UI_COOKIE_SESSION_IDLE_TIMEOUT_ADMIN("smp.ui.session.idle_timeout.admin", "300", "Specifies the time, in seconds, between client requests before the SMP will invalidate session for ADMIN users (System)!", false, false, false, SMPPropertyTypeEnum.INTEGER), + UI_COOKIE_SESSION_IDLE_TIMEOUT_USER("smp.ui.session.idle_timeout.user", "1800", "Specifies the time, in seconds, between client requests before the SMP will invalidate session for users (Service group, SMP Admin)", false, false, false, SMPPropertyTypeEnum.INTEGER), + // authentication + UI_AUTHENTICATION_TYPES("smp.ui.authentication.types", "PASSWORD", "Set list of '|' separated authentication types: PASSWORD|SSO.", false, false, false, SMPPropertyTypeEnum.LIST_STRING), + AUTOMATION_AUTHENTICATION_TYPES("smp.automation.authentication.types", "PASSWORD|CERTIFICATE", "Set list of '|' separated application-automation authentication types (Web-Service integration). Currently supported PASSWORD, CERT: ex. PASSWORD|CERT", false, false, false, SMPPropertyTypeEnum.LIST_STRING), // SSO configuration - SSO_CAS_ENABLED("smp.sso.cas.enabled","false","Enable/disable CAS authentication.", false, false,true, SMPPropertyTypeEnum.BOOLEAN), - SSO_CAS_UI_LABEL("smp.sso.cas.ui.label","EU Login","The SSO service provider label.", false, false,true, SMPPropertyTypeEnum.STRING), - SSO_CAS_URL("smp.sso.cas.url","http://localhost:8080/cas/","The SSO CAS URL enpoint", false, false,true, SMPPropertyTypeEnum.URL), - SSO_CAS_URLPATH_LOGIN("smp.sso.cas.urlpath.login","login","The CAS URL path for login. Complete URL is composed from parameters: ${smp.sso.cas.url}/${smp.sso.cas.urlpath.login}.", false, false,true, SMPPropertyTypeEnum.STRING), - SSO_CAS_CALLBACK_URL("smp.sso.cas.callback.url","http://localhost:8080/smp/ui/rest/security/cas","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'", false, false,true, SMPPropertyTypeEnum.URL), - SSO_CAS_TOKEN_VALIDATION_URLPATH("smp.sso.cas.token.validation.urlpath","http://localhost:8080/cas/","The CAS URL path for login. Complete URL is composed from parameters: ${smp.sso.cas.url}/${smp.sso.cas.urlpath.token.validation}.", false, false,true, SMPPropertyTypeEnum.STRING), - SSO_CAS_TOKEN_VALIDATION_PARAMS("smp.sso.cas.token.validation.params","acceptStrengths:BASIC,CLIENT_CERT|assuranceLevel:TOP","The CAS token validation key:value properties separated with '|'.Ex: 'acceptStrengths:BASIC,CLIENT_CERT|assuranceLevel:TOP'", false, false,true, SMPPropertyTypeEnum.MAP_STRING), - SSO_CAS_TOKEN_VALIDATION_GROUPS("smp.sso.cas.token.validation.groups","DIGIT_SMP|DIGIT_ADMIN","'|' separated CAS groups user must belong to.", false, false,true, SMPPropertyTypeEnum.LIST_STRING), + SSO_CAS_UI_LABEL("smp.sso.cas.ui.label", "EU Login", "The SSO service provider label.", false, false, true, SMPPropertyTypeEnum.STRING), + SSO_CAS_URL("smp.sso.cas.url", "http://localhost:8080/cas/", "The SSO CAS URL enpoint", false, false, true, SMPPropertyTypeEnum.URL), + SSO_CAS_URLPATH_LOGIN("smp.sso.cas.urlpath.login", "login", "The CAS URL path for login. Complete URL is composed from parameters: ${smp.sso.cas.url}/${smp.sso.cas.urlpath.login}.", false, false, true, SMPPropertyTypeEnum.STRING), + SSO_CAS_CALLBACK_URL("smp.sso.cas.callback.url", "http://localhost:8080/smp/ui/rest/security/cas", "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'", false, false, true, SMPPropertyTypeEnum.URL), + SSO_CAS_TOKEN_VALIDATION_URLPATH("smp.sso.cas.token.validation.urlpath", "http://localhost:8080/cas/", "The CAS URL path for login. Complete URL is composed from parameters: ${smp.sso.cas.url}/${smp.sso.cas.urlpath.token.validation}.", false, false, true, SMPPropertyTypeEnum.STRING), + SSO_CAS_TOKEN_VALIDATION_PARAMS("smp.sso.cas.token.validation.params", "acceptStrengths:BASIC,CLIENT_CERT|assuranceLevel:TOP", "The CAS token validation key:value properties separated with '|'.Ex: 'acceptStrengths:BASIC,CLIENT_CERT|assuranceLevel:TOP'", false, false, true, SMPPropertyTypeEnum.MAP_STRING), + SSO_CAS_TOKEN_VALIDATION_GROUPS("smp.sso.cas.token.validation.groups", "DIGIT_SMP|DIGIT_ADMIN", "'|' separated CAS groups user must belong to.", false, false, true, SMPPropertyTypeEnum.LIST_STRING), + + //deprecated properties + SML_KEYSTORE_PASSWORD("bdmsl.integration.keystore.password", "", "Deprecated", false, false, false, SMPPropertyTypeEnum.STRING), + SML_KEYSTORE_PATH("bdmsl.integration.keystore.path", "", "Deprecated", false, false, false, SMPPropertyTypeEnum.STRING), + SIGNATURE_KEYSTORE_PASSWORD("xmldsig.keystore.password", "", "Deprecated", false, false, false, SMPPropertyTypeEnum.STRING), + SIGNATURE_KEYSTORE_PATH("xmldsig.keystore.classpath", "", "Deprecated", false, false, false, SMPPropertyTypeEnum.STRING), + SML_PROXY_HOST("bdmsl.integration.proxy.server", "", "Deprecated", false, false, false, SMPPropertyTypeEnum.STRING), + SML_PROXY_PORT("bdmsl.integration.proxy.port", "", "Deprecated", false, false, false, SMPPropertyTypeEnum.INTEGER), + SML_PROXY_USER("bdmsl.integration.proxy.user", "", "Deprecated", false, false, false, SMPPropertyTypeEnum.STRING), + SML_PROXY_PASSWORD("bdmsl.integration.proxy.password", "", "Deprecated", false, false, false, SMPPropertyTypeEnum.STRING), ; @@ -92,10 +91,10 @@ public enum SMPPropertyEnum { this.property = property; this.defValue = defValue; this.desc = desc; - this.isEncrypted=isEncrypted; - this.isMandatory=isMandatory; - this.restartNeeded=restartNeeded; - this.propertyType=propertyType; + this.isEncrypted = isEncrypted; + this.isMandatory = isMandatory; + this.restartNeeded = restartNeeded; + this.propertyType = propertyType; } public String getProperty() { diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ConfigurationService.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ConfigurationService.java index 99e7512c4a2c952b1b1a39fa37da754ac89bea8e..be234e8088e12dc29690dfc624eafffbf401cee2 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ConfigurationService.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ConfigurationService.java @@ -1,5 +1,6 @@ package eu.europa.ec.edelivery.smp.services; +import eu.europa.ec.edelivery.smp.auth.enums.SMPUserAuthenticationTypes; import eu.europa.ec.edelivery.smp.data.dao.ConfigurationDao; import eu.europa.ec.edelivery.smp.data.model.DBConfiguration; import eu.europa.ec.edelivery.smp.data.ui.enums.SMPPropertyEnum; @@ -137,7 +138,13 @@ public class ConfigurationService { public boolean forceCRLValidation() { Boolean value = (Boolean) configurationDAO.getCachedPropertyValue(CERTIFICATE_CRL_FORCE); - // by default is not forced + // by default is not forced -> if missing is false! + return value != null && value; + } + + public boolean isAuthenticationWithClientCertHeaderEnabled() { + Boolean value = (Boolean) configurationDAO.getCachedPropertyValue(SMPPropertyEnum.BLUE_COAT_ENABLED); + // by default is not forced -> if missing is false! return value != null && value; } @@ -205,9 +212,9 @@ public class ConfigurationService { return (Integer) configurationDAO.getCachedPropertyValue(UI_COOKIE_SESSION_IDLE_TIMEOUT_USER); } - public boolean isCasEnabled() { - Boolean value = (Boolean) configurationDAO.getCachedPropertyValue(SSO_CAS_ENABLED); - return value != null && value; + public boolean isSSOEnabledForUserAuthentication() { + List<String> userAuthenticationTypes = getUIAuthenticationTypes(); + return userAuthenticationTypes != null && userAuthenticationTypes.contains(SMPUserAuthenticationTypes.SSO.name()); } public String getCasUILabel() { @@ -237,4 +244,12 @@ public class ConfigurationService { public List<String> getCasURLTokenValidationGroups() { return (List<String>) configurationDAO.getCachedPropertyValue(SSO_CAS_TOKEN_VALIDATION_GROUPS); } + + public List<String> getUIAuthenticationTypes() { + return (List<String>) configurationDAO.getCachedPropertyValue(UI_AUTHENTICATION_TYPES); + } + + public List<String> getAutomationAuthenticationTypes() { + return (List<String>) configurationDAO.getCachedPropertyValue(AUTOMATION_AUTHENTICATION_TYPES); + } } diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UITruststoreService.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UITruststoreService.java index f5c17935700501c2c9f5d46d668869941c6fd048..16d10272a5b925e46bcb396e61ccbb49b50e3241 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UITruststoreService.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UITruststoreService.java @@ -66,6 +66,7 @@ public class UITruststoreService { long lastUpdateTrustoreFileTime = 0; File lastUpdateTrustStoreFile = null; TrustManager[] trustManagers; + KeyStore trustStore = null; @PostConstruct @@ -81,7 +82,7 @@ public class UITruststoreService { } } - private boolean useTrustStore() { + public boolean useTrustStore() { File truststoreFile = configurationService.getTruststoreFile(); return truststoreFile != null; } @@ -99,7 +100,7 @@ public class UITruststoreService { // load keystore File truststoreFile = getTruststoreFile(); - KeyStore trustStore = loadTruststore(truststoreFile); + trustStore = loadTruststore(truststoreFile); if (trustStore == null) { LOG.error("Keystore: '" + truststoreFile.getAbsolutePath() + "' is not loaded! Check the truststore filename" + " and the configuration!"); @@ -131,7 +132,6 @@ public class UITruststoreService { X509Certificate x509Certificate = (X509Certificate) cert; String subject = x509Certificate.getSubjectX500Principal().getName(); - subject = DistinguishedNamesCodingUtil.normalizeDN(subject, DistinguishedNamesCodingUtil.getCommonAttributesDN()); tmpList.add(subject); @@ -193,7 +193,7 @@ public class UITruststoreService { // test if certificate is valid cert.checkValidity(); // check if certificate or its issuer is on trusted list - // check only issuer because using bluecoat Client-cert we do not have whole chain. + // check only issuer because using Client-cert header we do not have whole chain. // if the truststore is empty then truststore validation is ignored // backward compatibility if (!normalizedTrustedList.isEmpty() && !(isSubjectOnTrustedList(cert.getSubjectX500Principal().getName()) @@ -359,6 +359,10 @@ public class UITruststoreService { return null; } + public KeyStore getTrustStore() { + return trustStore; + } + public String createAliasFromCert(X509Certificate x509cert, KeyStore truststore) { diff --git a/smp-server-library/src/test/resources/log4j.properties b/smp-server-library/src/test/resources/log4j.properties deleted file mode 100644 index db1d3eea673c2a20416fc8e9ebf622521f3ae12e..0000000000000000000000000000000000000000 --- a/smp-server-library/src/test/resources/log4j.properties +++ /dev/null @@ -1,21 +0,0 @@ -# -# 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. -# - -log4j.rootLogger=DEBUG, stdout - -# A1 is set to be a ConsoleAppender. -log4j.appender.stdout=org.apache.log4j.ConsoleAppender - -# A1 uses PatternLayout. -log4j.appender.stdout.layout=org.apache.log4j.PatternLayout -log4j.appender.stdout.layout.ConversionPattern=%d [%p] %c %x - %m%n diff --git a/smp-server-library/src/main/resources/logback.xml b/smp-server-library/src/test/resources/logback-test.xml similarity index 89% rename from smp-server-library/src/main/resources/logback.xml rename to smp-server-library/src/test/resources/logback-test.xml index 10dd2cf59904b282efdc42c1ed8cd220c06f6e6d..8df82e49f56fda7cd57d1787e614f2ed5c4081ea 100644 --- a/smp-server-library/src/main/resources/logback.xml +++ b/smp-server-library/src/test/resources/logback-test.xml @@ -6,7 +6,7 @@ <property name="consolePattern" value="%d{ISO8601} [%X{d_user}] [%X{d_domain}] [%X{d_messageId}] [%thread] %5p %c{1}:%L - %m%n" scope="global"/> <appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender"> - <file>${log.folder:-logs}/edelivery-smp.log</file> + <file>${project.build.directory}/logs/edelivery-smp.log</file> <filter class="ch.qos.logback.core.filter.EvaluatorFilter"> <evaluator class="ch.qos.logback.classic.boolex.OnMarkerEvaluator"> <marker>SECURITY</marker> @@ -37,7 +37,8 @@ </encoder> </appender> - <logger name="eu.europa.ec.edelivery.smp" level="INFO" /> + <logger name="eu.europa.ec.edelivery" level="DEBUG" /> + <logger name="org.springframework.security.cas" level="DEBUG" /> <root level="WARN"> <appender-ref ref="file"/> <appender-ref ref="stdout"/> diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/SMPAuthenticationProvider.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/SMPAuthenticationProvider.java index 539c9e5fd7804c796da3087ffd04b1dfdaacdf16..8b31317dcab1823a802fb479aadfed3e625c81aa 100644 --- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/SMPAuthenticationProvider.java +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/SMPAuthenticationProvider.java @@ -1,6 +1,7 @@ package eu.europa.ec.edelivery.smp.auth; import eu.europa.ec.edelivery.security.PreAuthenticatedCertificatePrincipal; +import eu.europa.ec.edelivery.security.cert.CertificateValidator; import eu.europa.ec.edelivery.smp.config.SmpAppConfig; import eu.europa.ec.edelivery.smp.data.dao.UserDao; import eu.europa.ec.edelivery.smp.data.model.DBCertificate; @@ -10,6 +11,7 @@ import eu.europa.ec.edelivery.smp.logging.SMPLogger; import eu.europa.ec.edelivery.smp.logging.SMPLoggerFactory; import eu.europa.ec.edelivery.smp.logging.SMPMessageCode; import eu.europa.ec.edelivery.smp.services.CRLVerifierService; +import eu.europa.ec.edelivery.smp.services.ConfigurationService; import eu.europa.ec.edelivery.smp.services.ui.UITruststoreService; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.exception.ExceptionUtils; @@ -22,7 +24,10 @@ import org.springframework.security.crypto.bcrypt.BCrypt; import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; import org.springframework.stereotype.Component; +import java.security.KeyStore; +import java.security.cert.CertificateException; import java.security.cert.CertificateRevokedException; +import java.security.cert.X509Certificate; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.*; @@ -45,18 +50,19 @@ public class SMPAuthenticationProvider implements AuthenticationProvider { private final String dummyPasswordHash; private final String dummyPassword; - UserDao mUserDao; - CRLVerifierService crlVerifierService; - UITruststoreService truststoreService; + final UserDao mUserDao; + final CRLVerifierService crlVerifierService; + final UITruststoreService truststoreService; + final ConfigurationService configurationService; @Autowired - public SMPAuthenticationProvider(UserDao mUserDao, CRLVerifierService crlVerifierService, UITruststoreService truststoreService) { + public SMPAuthenticationProvider(UserDao mUserDao, CRLVerifierService crlVerifierService, UITruststoreService truststoreService, ConfigurationService configurationService) { this.dummyPassword = UUID.randomUUID().toString(); this.dummyPasswordHash = BCrypt.hashpw(dummyPassword, BCrypt.gensalt()); - this.mUserDao = mUserDao; this.crlVerifierService = crlVerifierService; this.truststoreService = truststoreService; + this.configurationService = configurationService; } @Override @@ -88,15 +94,30 @@ public class SMPAuthenticationProvider implements AuthenticationProvider { /** - * Authenticate by certificate token got by BlueCoat or X509Certificate authentication) + * Authenticated using the X509Certificate or ClientCert header certificate) * * @param principal - certificate principal * @return authentication value. */ public Authentication authenticateByCertificateToken(PreAuthenticatedCertificatePrincipal principal) { LOG.info("authenticateByCertificateToken:" + principal.getName()); + + KeyStore truststore = truststoreService.getTrustStore(); + DBUser user; + X509Certificate x509Certificate = principal.getCertificate(); String userToken = principal.getName(); + + if (truststore != null && x509Certificate != null) { + CertificateValidator certificateValidator = new CertificateValidator( + null, truststore, null); + try { + certificateValidator.validateCertificate(x509Certificate); + } catch (CertificateException e) { + throw new BadCredentialsException("Certificate is not trusted!"); + } + } + try { Optional<DBUser> oUsr = mUserDao.findUserByCertificateId(userToken, true); @@ -113,7 +134,6 @@ public class SMPAuthenticationProvider implements AuthenticationProvider { } catch (RuntimeException ex) { LOG.error("Database connection error", ex); throw new AuthenticationServiceException("Internal server error occurred while user authentication!"); - } DBCertificate certificate = user.getCertificate(); diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/cas/SMPCasConfigurer.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/cas/SMPCasConfigurer.java index ffc817d0e100ff8f0cce1098533a72e7ceeea034..ae9fae979bf6ec0637adb62e938b5efcb1eb1e7d 100644 --- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/cas/SMPCasConfigurer.java +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/cas/SMPCasConfigurer.java @@ -55,7 +55,7 @@ public class SMPCasConfigurer { URL path = configurationService.getCasCallbackUrl(); ServiceProperties serviceProperties = new ServiceProperties(); serviceProperties.setArtifactParameter(ServiceProperties.DEFAULT_CAS_ARTIFACT_PARAMETER); - serviceProperties.setService(path!=null? path.toExternalForm():"null"); + serviceProperties.setService(path != null ? path.toExternalForm() : "null"); serviceProperties.setAuthenticateAllArtifacts(true); LOG.info("Configured CAS ServiceProperties with callback Url: [{}]", serviceProperties.getService()); return serviceProperties; @@ -70,7 +70,7 @@ public class SMPCasConfigurer { @Bean public CasAuthenticationEntryPoint casAuthenticationEntryPoint(@Nullable @Qualifier(SMP_CAS_PROPERTIES_BEAN) ServiceProperties serviceProperties, ConfigurationService configService) { - if (!configService.isCasEnabled()) { + if (!configService.isSSOEnabledForUserAuthentication()) { LOG.debug("Bean CasAuthenticationEntryPoint is not configured because SSO CAS authentication is not enabled!", SMP_CAS_PROPERTIES_BEAN); return null; } @@ -88,11 +88,11 @@ public class SMPCasConfigurer { @Bean public SMPCas20ServiceTicketValidator ecasServiceTicketValidator(ConfigurationService configService) { - if (!configService.isCasEnabled()) { + if (!configService.isSSOEnabledForUserAuthentication()) { LOG.debug("Bean SMPCas20ServiceTicketValidator is not configured because SSO CAS authentication is not enabled!", SMP_CAS_PROPERTIES_BEAN); return null; } - if (configService.getCasURL()==null){ + if (configService.getCasURL() == null) { LOG.error("Bean SMPCas20ServiceTicketValidator is not created! Missing Service parameter [{}]!", SSO_CAS_URL.getProperty()); return null; } @@ -135,7 +135,7 @@ public class SMPCasConfigurer { @Nullable SMPCasUserService smpCasUserService, ConfigurationService configService) { - if (!configService.isCasEnabled()) { + if (!configService.isSSOEnabledForUserAuthentication()) { LOG.debug("Bean [CasAuthenticationProvider:{}] is not configured because SSO CAS authentication is not enabled!", SMP_CAS_PROPERTIES_BEAN); return null; } diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/config/SMPSecurityPropertyUpdateListener.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/config/SMPSecurityPropertyUpdateListener.java index 8e3158163b773193c1e3593ca8c06b9efbe2817f..9a73ce27c38935a93d0ffd6ed8f08d0b2c0bcf1d 100644 --- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/config/SMPSecurityPropertyUpdateListener.java +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/config/SMPSecurityPropertyUpdateListener.java @@ -1,6 +1,6 @@ package eu.europa.ec.edelivery.smp.config; -import eu.europa.ec.edelivery.security.BlueCoatAuthenticationFilter; +import eu.europa.ec.edelivery.security.ClientCertAuthenticationFilter; import eu.europa.ec.edelivery.smp.data.dao.ConfigurationDao; import eu.europa.ec.edelivery.smp.data.ui.enums.SMPPropertyEnum; import eu.europa.ec.edelivery.smp.logging.SMPLogger; @@ -11,8 +11,6 @@ import org.springframework.web.server.adapter.ForwardedHeaderTransformer; import javax.annotation.PostConstruct; -import static eu.europa.ec.edelivery.smp.config.SMPSecurityConstants.SMP_FORWARDED_HEADER_TRANSFORMER_BEAN; - /** * Class update security configuration on property update event @@ -24,14 +22,14 @@ import static eu.europa.ec.edelivery.smp.config.SMPSecurityConstants.SMP_FORWARD public class SMPSecurityPropertyUpdateListener implements PropertyUpdateListener { private static final SMPLogger LOG = SMPLoggerFactory.getLogger(SMPSecurityPropertyUpdateListener.class); - final BlueCoatAuthenticationFilter blueCoatAuthenticationFilter; + final ClientCertAuthenticationFilter ClientCertAuthenticationFilter; final ConfigurationDao configurationDao; final ForwardedHeaderTransformer forwardedHeaderTransformer; - public SMPSecurityPropertyUpdateListener(BlueCoatAuthenticationFilter blueCoatAuthenticationFilter, + public SMPSecurityPropertyUpdateListener(ClientCertAuthenticationFilter ClientCertAuthenticationFilter, ConfigurationDao configurationDao, ForwardedHeaderTransformer forwardedHeaderTransformer) { - this.blueCoatAuthenticationFilter = blueCoatAuthenticationFilter; + this.ClientCertAuthenticationFilter = ClientCertAuthenticationFilter; this.configurationDao = configurationDao; this.forwardedHeaderTransformer = forwardedHeaderTransformer; } @@ -47,9 +45,9 @@ public class SMPSecurityPropertyUpdateListener implements PropertyUpdateListener boolean setForwardHeadersEnabled = BooleanUtils.toBoolean((Boolean) configurationDao.getCachedPropertyValue(SMPPropertyEnum.HTTP_FORWARDED_HEADERS_ENABLED)); if (setBlueCoatEnabled) { - LOG.warn("Set blue coat enabled: [true]. Do not enable this option when using SMP without reverse-proxy and HTTP header protection!"); + LOG.warn("Set Client-Cert HTTP header enabled: [true]. Do not enable this option when using SMP without reverse-proxy and HTTP header protection!"); } - blueCoatAuthenticationFilter.setBlueCoatEnabled(setBlueCoatEnabled); + ClientCertAuthenticationFilter.setClientCertAuthenticationEnabled(setBlueCoatEnabled); LOG.info("Set http forward headers enabled: [{}]." + setForwardHeadersEnabled); if (setForwardHeadersEnabled) { diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/config/SpringSecurityConfig.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/config/SpringSecurityConfig.java index 41d56537e86b85adf011fdc2dd1683739b61d15e..6202bb127bb0f8c0e546f5157210e5e92eb39ca6 100644 --- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/config/SpringSecurityConfig.java +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/config/SpringSecurityConfig.java @@ -13,7 +13,7 @@ package eu.europa.ec.edelivery.smp.config; -import eu.europa.ec.edelivery.security.BlueCoatAuthenticationFilter; +import eu.europa.ec.edelivery.security.ClientCertAuthenticationFilter; import eu.europa.ec.edelivery.security.EDeliveryX509AuthenticationFilter; import eu.europa.ec.edelivery.smp.auth.SMPAuthenticationProvider; import eu.europa.ec.edelivery.smp.auth.URLCsrfMatcher; @@ -30,7 +30,6 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Lazy; import org.springframework.http.HttpMethod; -import org.springframework.lang.Nullable; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.cas.authentication.CasAuthenticationProvider; import org.springframework.security.cas.web.CasAuthenticationEntryPoint; @@ -68,7 +67,7 @@ public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { SMPAuthenticationProvider smpAuthenticationProvider; CasAuthenticationProvider casAuthenticationProvider; - BlueCoatAuthenticationFilter blueCoatAuthenticationFilter; + ClientCertAuthenticationFilter ClientCertAuthenticationFilter; EDeliveryX509AuthenticationFilter x509AuthenticationFilter; CasAuthenticationFilter casAuthenticationFilter; CasAuthenticationEntryPoint casAuthenticationEntryPoint; @@ -86,13 +85,13 @@ public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { * Initialize beans. Use lazy initialization for filter to avoid circular dependencies * * @param smpAuthenticationProvider - * @param blueCoatAuthenticationFilter + * @param ClientCertAuthenticationFilter * @param x509AuthenticationFilter */ @Autowired public SpringSecurityConfig(SMPAuthenticationProvider smpAuthenticationProvider, ConfigurationService configurationService, - @Lazy BlueCoatAuthenticationFilter blueCoatAuthenticationFilter, + @Lazy ClientCertAuthenticationFilter ClientCertAuthenticationFilter, @Lazy EDeliveryX509AuthenticationFilter x509AuthenticationFilter, @Lazy CsrfTokenRepository csrfTokenRepository, @Lazy RequestMatcher csrfURLMatcher, @@ -106,7 +105,7 @@ public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { this.configurationService = configurationService; this.smpAuthenticationProvider = smpAuthenticationProvider; this.casAuthenticationProvider = casAuthenticationProvider; - this.blueCoatAuthenticationFilter = blueCoatAuthenticationFilter; + this.ClientCertAuthenticationFilter = ClientCertAuthenticationFilter; this.x509AuthenticationFilter = x509AuthenticationFilter; this.casAuthenticationFilter = casAuthenticationFilter; this.casAuthenticationEntryPoint = casAuthenticationEntryPoint; @@ -128,7 +127,7 @@ public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { .and(); ExceptionHandlingConfigurer<HttpSecurity> exceptionHandlingConfigurer = httpSecurity.exceptionHandling(); - if (configurationService.isCasEnabled()) { + if (configurationService.isSSOEnabledForUserAuthentication()) { LOG.debug("The CAS authentication is enabled. Set casAuthenticationEntryPoint!"); exceptionHandlingConfigurer = exceptionHandlingConfigurer.defaultAuthenticationEntryPointFor(casAuthenticationEntryPoint, new AntPathRequestMatcher(SMP_SECURITY_PATH_CAS_AUTHENTICATE)); } @@ -142,24 +141,24 @@ public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { .xssProtection().xssProtectionEnabled(true).and() .and(); - if (configurationService.isCasEnabled()) { + if (configurationService.isSSOEnabledForUserAuthentication()) { LOG.debug("The CAS authentication is enabled. Add CAS filter!"); httpSecurity = httpSecurity.addFilter(casAuthenticationFilter); } // set HstsMAxAge Integer maxAge = configurationService.getHttpHeaderHstsMaxAge(); - if (maxAge == null || maxAge < 0){ - LOG.info("The httpStrictTransportSecurity (HSTS) policy is set for HTTPS/1Y!" ); + if (maxAge == null || maxAge < 0) { + LOG.info("The httpStrictTransportSecurity (HSTS) policy is set for HTTPS/1Y!"); httpSecurity = httpSecurity.headers() .httpStrictTransportSecurity() .includeSubDomains(true) .preload(false) .maxAgeInSeconds(31536000).and().and(); - }else if ( maxAge == 0){ - LOG.warn("The httpStrictTransportSecurity (HSTS) policy is disabled!" ); + } else if (maxAge == 0) { + LOG.warn("The httpStrictTransportSecurity (HSTS) policy is disabled!"); httpSecurity = httpSecurity.headers().httpStrictTransportSecurity().disable().and(); } else { - LOG.info("The httpStrictTransportSecurity (HSTS) policy is set to [{}] for http and https!",maxAge ); + LOG.info("The httpStrictTransportSecurity (HSTS) policy is set to [{}] for http and https!", maxAge); httpSecurity = httpSecurity.headers() .httpStrictTransportSecurity() .includeSubDomains(true) @@ -168,7 +167,7 @@ public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { .requestMatcher(AnyRequestMatcher.INSTANCE).and().and(); } - httpSecurity.addFilter(blueCoatAuthenticationFilter) + httpSecurity.addFilter(ClientCertAuthenticationFilter) .addFilter(x509AuthenticationFilter) .httpBasic().authenticationEntryPoint(new SpringSecurityExceptionHandler()).and() // username .anonymous().authorities(SMPAuthority.S_AUTHORITY_ANONYMOUS.getAuthority()).and() @@ -204,7 +203,7 @@ public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(AuthenticationManagerBuilder auth) { LOG.info("configureAuthenticationManagerBuilder, set SMP provider "); - if (configurationService.isCasEnabled()) { + if (configurationService.isSSOEnabledForUserAuthentication()) { LOG.info("[CAS] Authentication Provider enabled"); auth.authenticationProvider(casAuthenticationProvider); } @@ -225,11 +224,11 @@ public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { } @Bean - public BlueCoatAuthenticationFilter getClientCertAuthenticationFilter(@Qualifier(SMP_AUTHENTICATION_MANAGER_BEAN) AuthenticationManager authenticationManager) { - BlueCoatAuthenticationFilter blueCoatAuthenticationFilter = new BlueCoatAuthenticationFilter(); - blueCoatAuthenticationFilter.setAuthenticationManager(authenticationManager); - blueCoatAuthenticationFilter.setBlueCoatEnabled(clientCertEnabled); - return blueCoatAuthenticationFilter; + public ClientCertAuthenticationFilter getClientCertAuthenticationFilter(@Qualifier(SMP_AUTHENTICATION_MANAGER_BEAN) AuthenticationManager authenticationManager) { + ClientCertAuthenticationFilter ClientCertAuthenticationFilter = new ClientCertAuthenticationFilter(); + ClientCertAuthenticationFilter.setAuthenticationManager(authenticationManager); + ClientCertAuthenticationFilter.setClientCertAuthenticationEnabled(clientCertEnabled); + return ClientCertAuthenticationFilter; } @Bean @@ -292,8 +291,8 @@ public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { //@Bean(SMP_FORWARDED_HEADER_TRANSFORMER_BEAN) @Bean public ForwardedHeaderTransformer smpForwardedHeaderTransformer() { - ForwardedHeaderTransformer forwardedHeaderTransformer = new ForwardedHeaderTransformer(); - // WebHttpHandlerBuilder.forwardedHeaderTransformer(ForwardedHeaderTransformer); + ForwardedHeaderTransformer forwardedHeaderTransformer = new ForwardedHeaderTransformer(); + // WebHttpHandlerBuilder.forwardedHeaderTransformer(ForwardedHeaderTransformer); forwardedHeaderTransformer.setRemoveOnly(false); return forwardedHeaderTransformer; diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/controllers/ServiceGroupController.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/controllers/ServiceGroupController.java index d7202f04f13ca559c1e5841e04fb643b11e12c98..a7265a7064044b57bc1b018678188479532dcb28 100644 --- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/controllers/ServiceGroupController.java +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/controllers/ServiceGroupController.java @@ -72,12 +72,12 @@ public class ServiceGroupController { String host = httpReq.getRemoteHost(); - LOG.businessInfo(SMPMessageCode.BUS_HTTP_GET_SERVICE_GROUP,host, serviceGroupId); + LOG.businessInfo(SMPMessageCode.BUS_HTTP_GET_SERVICE_GROUP, host, serviceGroupId); ServiceGroup serviceGroup = serviceGroupService.getServiceGroup(asParticipantId(serviceGroupId)); addReferences(serviceGroup); - LOG.businessInfo(SMPMessageCode.BUS_HTTP_GET_END_SERVICE_GROUP,host, serviceGroupId); + LOG.businessInfo(SMPMessageCode.BUS_HTTP_GET_END_SERVICE_GROUP, host, serviceGroupId); return serviceGroup; } @@ -85,14 +85,14 @@ public class ServiceGroupController { @PutMapping @Secured({SMPAuthority.S_AUTHORITY_TOKEN_SYSTEM_ADMIN, SMPAuthority.S_AUTHORITY_TOKEN_SMP_ADMIN}) public ResponseEntity saveServiceGroup(HttpServletRequest httpReq, - @PathVariable String serviceGroupId, - @RequestHeader(name = HTTP_PARAM_OWNER, required = false) String serviceGroupOwner, - @RequestHeader(name = HTTP_PARAM_DOMAIN, required = false) String domain, - @RequestBody byte[] body) throws XmlInvalidAgainstSchemaException { + @PathVariable String serviceGroupId, + @RequestHeader(name = HTTP_PARAM_OWNER, required = false) String serviceGroupOwner, + @RequestHeader(name = HTTP_PARAM_DOMAIN, required = false) String domain, + @RequestBody byte[] body) throws XmlInvalidAgainstSchemaException { String authentUser = SecurityContextHolder.getContext().getAuthentication().getName(); String host = getRemoteHost(httpReq); - LOG.businessInfo(SMPMessageCode.BUS_HTTP_PUT_SERVICE_GROUP,authentUser, host, serviceGroupOwner, domain, serviceGroupId); + LOG.businessInfo(SMPMessageCode.BUS_HTTP_PUT_SERVICE_GROUP, authentUser, host, serviceGroupOwner, domain, serviceGroupId); // Validations BdxSmpOasisValidator.validateXSD(body); @@ -100,9 +100,9 @@ public class ServiceGroupController { serviceGroupValidator.validate(serviceGroupId, serviceGroup); // Service action - boolean newServiceGroupCreated = serviceGroupService.saveServiceGroup(serviceGroup, domain, serviceGroupOwner, authentUser); + boolean newServiceGroupCreated = serviceGroupService.saveServiceGroup(serviceGroup, domain, serviceGroupOwner, authentUser); - LOG.businessInfo(SMPMessageCode.BUS_HTTP_PUT_SERVICE_GROUP,authentUser, host, serviceGroupOwner, domain, serviceGroupId, newServiceGroupCreated); + LOG.businessInfo(SMPMessageCode.BUS_HTTP_PUT_SERVICE_GROUP, authentUser, host, serviceGroupOwner, domain, serviceGroupId, newServiceGroupCreated); return newServiceGroupCreated ? created(pathBuilder.getCurrentUri()).build() : ok().build(); } @@ -111,13 +111,13 @@ public class ServiceGroupController { public ResponseEntity deleteServiceGroup(HttpServletRequest httpReq, @PathVariable String serviceGroupId) { String authentUser = SecurityContextHolder.getContext().getAuthentication().getName(); String host = getRemoteHost(httpReq); - LOG.businessInfo(SMPMessageCode.BUS_HTTP_DELETE_SERVICE_GROUP,authentUser, host, serviceGroupId); + LOG.businessInfo(SMPMessageCode.BUS_HTTP_DELETE_SERVICE_GROUP, authentUser, host, serviceGroupId); final ParticipantIdentifierType aServiceGroupID = Identifiers.asParticipantId(serviceGroupId); serviceGroupService.deleteServiceGroup(aServiceGroupID); - LOG.businessInfo(SMPMessageCode.BUS_HTTP_DELETE_END_SERVICE_GROUP,authentUser, host, serviceGroupId); + LOG.businessInfo(SMPMessageCode.BUS_HTTP_DELETE_END_SERVICE_GROUP, authentUser, host, serviceGroupId); return ok().build(); } @@ -131,9 +131,8 @@ public class ServiceGroupController { } } - public String getRemoteHost(HttpServletRequest httpReq){ + public String getRemoteHost(HttpServletRequest httpReq) { String host = httpReq.getHeader("X-Forwarded-For"); - return StringUtils.isBlank(host)?httpReq.getRemoteHost():host; + return StringUtils.isBlank(host) ? httpReq.getRemoteHost() : host; } - } diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/controllers/ServiceMetadataController.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/controllers/ServiceMetadataController.java index e3daf54c91ee32f063db3367441c169b2edc067a..f59d525d83e37302feda6968ae1d0c89e17ad8c7 100644 --- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/controllers/ServiceMetadataController.java +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/controllers/ServiceMetadataController.java @@ -63,11 +63,11 @@ public class ServiceMetadataController { @PathVariable String serviceMetadataId) throws TransformerException, UnsupportedEncodingException { String host = httpReq.getRemoteHost(); - LOG.businessInfo(SMPMessageCode.BUS_HTTP_GET_SERVICE_METADATA,host, serviceGroupId, serviceMetadataId); + LOG.businessInfo(SMPMessageCode.BUS_HTTP_GET_SERVICE_METADATA, host, serviceGroupId, serviceMetadataId); Document serviceMetadata = serviceMetadataService.getServiceMetadataDocument(asParticipantId(serviceGroupId), asDocumentId(serviceMetadataId)); - LOG.businessInfo(SMPMessageCode.BUS_HTTP_GET_END_SERVICE_METADATA,host, serviceGroupId, serviceMetadataId); + LOG.businessInfo(SMPMessageCode.BUS_HTTP_GET_END_SERVICE_METADATA, host, serviceGroupId, serviceMetadataId); return ServiceMetadataConverter.toString(serviceMetadata); } @@ -75,45 +75,45 @@ public class ServiceMetadataController { @PreAuthorize("hasAnyAuthority(T(eu.europa.ec.edelivery.smp.data.ui.auth.SMPAuthority).S_AUTHORITY_TOKEN_SMP_ADMIN) OR" + " @serviceGroupService.isServiceGroupOwner(authentication.name, #serviceGroupId)") public ResponseEntity saveServiceMetadata(HttpServletRequest httpReq, - @PathVariable String serviceGroupId, - @PathVariable String serviceMetadataId, - @RequestHeader(name = HTTP_PARAM_DOMAIN, required = false) String domain, - @RequestBody byte[] body) throws XmlInvalidAgainstSchemaException { + @PathVariable String serviceGroupId, + @PathVariable String serviceMetadataId, + @RequestHeader(name = HTTP_PARAM_DOMAIN, required = false) String domain, + @RequestBody byte[] body) throws XmlInvalidAgainstSchemaException { String authentUser = SecurityContextHolder.getContext().getAuthentication().getName(); String host = getRemoteHost(httpReq); - LOG.businessInfo(SMPMessageCode.BUS_HTTP_PUT_SERVICE_METADATA,authentUser, host, domain, serviceGroupId, serviceMetadataId); + LOG.businessInfo(SMPMessageCode.BUS_HTTP_PUT_SERVICE_METADATA, authentUser, host, domain, serviceGroupId, serviceMetadataId); serviceMetadataValidator.validate(serviceGroupId, serviceMetadataId, body); boolean newServiceMetadataCreated = serviceMetadataService.saveServiceMetadata(domain, asParticipantId(serviceGroupId), asDocumentId(serviceMetadataId), body); - LOG.businessInfo(SMPMessageCode.BUS_HTTP_PUT_END_SERVICE_METADATA,authentUser, host, domain, serviceGroupId, serviceMetadataId, newServiceMetadataCreated); + LOG.businessInfo(SMPMessageCode.BUS_HTTP_PUT_END_SERVICE_METADATA, authentUser, host, domain, serviceGroupId, serviceMetadataId, newServiceMetadataCreated); return newServiceMetadataCreated ? created(pathBuilder.getCurrentUri()).build() : ok().build(); } @DeleteMapping - @PreAuthorize("hasAnyAuthority(T(eu.europa.ec.edelivery.smp.data.ui.auth.SMPAuthority).S_AUTHORITY_TOKEN_SMP_ADMIN) OR" + - " @serviceGroupService.isServiceGroupOwner(authentication.name, #serviceGroupId)") + @PreAuthorize("hasAnyAuthority(T(eu.europa.ec.edelivery.smp.data.ui.auth.SMPAuthority).S_AUTHORITY_TOKEN_SMP_ADMIN) OR" + + " @serviceGroupService.isServiceGroupOwner(authentication.name, #serviceGroupId)") public ResponseEntity deleteServiceMetadata(HttpServletRequest httpReq, - @PathVariable String serviceGroupId, - @PathVariable String serviceMetadataId, - @RequestHeader(name = "Domain", required = false) String domain ) { + @PathVariable String serviceGroupId, + @PathVariable String serviceMetadataId, + @RequestHeader(name = "Domain", required = false) String domain) { String authentUser = SecurityContextHolder.getContext().getAuthentication().getName(); String host = getRemoteHost(httpReq); - LOG.businessInfo(SMPMessageCode.BUS_HTTP_DELETE_SERVICE_METADATA,authentUser, host, domain, serviceGroupId, serviceMetadataId); + LOG.businessInfo(SMPMessageCode.BUS_HTTP_DELETE_SERVICE_METADATA, authentUser, host, domain, serviceGroupId, serviceMetadataId); serviceMetadataService.deleteServiceMetadata(domain, asParticipantId(serviceGroupId), asDocumentId(serviceMetadataId)); - LOG.businessInfo(SMPMessageCode.BUS_HTTP_DELETE_END_SERVICE_METADATA,authentUser, host, domain, serviceGroupId, serviceMetadataId); + LOG.businessInfo(SMPMessageCode.BUS_HTTP_DELETE_END_SERVICE_METADATA, authentUser, host, domain, serviceGroupId, serviceMetadataId); return ok().build(); } - public String getRemoteHost(HttpServletRequest httpReq){ + public String getRemoteHost(HttpServletRequest httpReq) { String host = httpReq.getHeader("X-Forwarded-For"); - return StringUtils.isBlank(host)?httpReq.getRemoteHost():host; + return StringUtils.isBlank(host) ? httpReq.getRemoteHost() : host; } } diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/ApplicationResource.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/ApplicationResource.java index 5d2db9043031398a375076335b95c9b0aacd25cc..61e4734b576a2c1ff0f3ec477da35b8bc5aed934 100644 --- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/ApplicationResource.java +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/ApplicationResource.java @@ -1,9 +1,9 @@ package eu.europa.ec.edelivery.smp.ui; -import eu.europa.ec.edelivery.smp.data.ui.auth.SMPAuthority; import eu.europa.ec.edelivery.smp.data.ui.SmpConfigRO; import eu.europa.ec.edelivery.smp.data.ui.SmpInfoRO; +import eu.europa.ec.edelivery.smp.data.ui.auth.SMPAuthority; import eu.europa.ec.edelivery.smp.services.ConfigurationService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; @@ -19,7 +19,6 @@ import java.util.TimeZone; * @author Joze Rihtarsic * @since 4.1 */ - @RestController @RequestMapping(value = "/ui/rest/application") public class ApplicationResource { @@ -54,14 +53,14 @@ public class ApplicationResource { info.setVersion(getDisplayVersion()); info.setSmlIntegrationOn(configurationService.isSMLIntegrationEnabled()); info.setSmlParticipantMultiDomainOn(configurationService.isSMLMultiDomainEnabled()); - info.setSsoAuthentication(configurationService.isCasEnabled()); + info.setAuthTypes(configurationService.getUIAuthenticationTypes()); info.setSsoAuthenticationLabel(configurationService.getCasUILabel()); info.setContextPath(getRootContext()); return info; } @RequestMapping(method = RequestMethod.GET, path = "config") - @Secured({SMPAuthority.S_AUTHORITY_TOKEN_SYSTEM_ADMIN,SMPAuthority.S_AUTHORITY_TOKEN_SMP_ADMIN, + @Secured({SMPAuthority.S_AUTHORITY_TOKEN_SYSTEM_ADMIN, SMPAuthority.S_AUTHORITY_TOKEN_SMP_ADMIN, SMPAuthority.S_AUTHORITY_TOKEN_SERVICE_GROUP_ADMIN}) public SmpConfigRO getApplicationConfig() { SmpConfigRO info = new SmpConfigRO(); @@ -74,7 +73,6 @@ public class ApplicationResource { return info; } - public String getDisplayVersion() { StringBuilder display = new StringBuilder(); display.append(artifactName); diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/ServiceGroupResource.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/ServiceGroupResource.java index e47e24036ca7a0c19582e45c67301588186198a1..e49148be3be9a23b96e658abce76718d21e53605 100644 --- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/ServiceGroupResource.java +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/ServiceGroupResource.java @@ -2,13 +2,13 @@ package eu.europa.ec.edelivery.smp.ui; import eu.europa.ec.edelivery.smp.auth.SMPAuthenticationToken; -import eu.europa.ec.edelivery.smp.data.ui.auth.SMPAuthority; -import eu.europa.ec.edelivery.smp.data.ui.auth.SMPRole; import eu.europa.ec.edelivery.smp.data.dao.DomainDao; import eu.europa.ec.edelivery.smp.data.model.DBUser; -import eu.europa.ec.edelivery.smp.data.ui.ServiceGroupValidationRO; import eu.europa.ec.edelivery.smp.data.ui.ServiceGroupRO; +import eu.europa.ec.edelivery.smp.data.ui.ServiceGroupValidationRO; import eu.europa.ec.edelivery.smp.data.ui.ServiceResult; +import eu.europa.ec.edelivery.smp.data.ui.auth.SMPAuthority; +import eu.europa.ec.edelivery.smp.data.ui.auth.SMPRole; import eu.europa.ec.edelivery.smp.logging.SMPLogger; import eu.europa.ec.edelivery.smp.logging.SMPLoggerFactory; import eu.europa.ec.edelivery.smp.services.ui.UIServiceGroupService; @@ -62,12 +62,12 @@ public class ServiceGroupResource { @RequestParam(value = "domain", required = false) String domainCode ) { - String participantIdentifierDecoded =decodeUrlToUTF8(participantIdentifier); + String participantIdentifierDecoded = decodeUrlToUTF8(participantIdentifier); String participantSchemeDecoded = decodeUrlToUTF8(participantScheme); String domainCodeDecoded = decodeUrlToUTF8(domainCode); - LOG.info("Search for page: {}, page size: {}, part. id: {}, part sch: {}, domain {}",page, pageSize, participantIdentifierDecoded, - participantSchemeDecoded, domainCodeDecoded ); + LOG.info("Search for page: {}, page size: {}, part. id: {}, part sch: {}, domain {}", page, pageSize, participantIdentifierDecoded, + participantSchemeDecoded, domainCodeDecoded); ServiceGroupFilter sgf = new ServiceGroupFilter(); sgf.setParticipantIdentifierLike(participantIdentifierDecoded); sgf.setParticipantSchemeLike(participantSchemeDecoded); @@ -79,12 +79,12 @@ public class ServiceGroupResource { // show all service groups only for SMP Admin // SMP admin can edit all service groups. For others return only services groups they own. - if (!request.isUserInRole(SMPRole.SMP_ADMIN.getCode())){ + if (!request.isUserInRole(SMPRole.SMP_ADMIN.getCode())) { SMPAuthenticationToken authToken = (SMPAuthenticationToken) authentication; DBUser user = authToken.getUser(); sgf.setOwner(user); } - return uiServiceGroupService.getTableList(page,pageSize, orderBy, orderType, sgf); + return uiServiceGroupService.getTableList(page, pageSize, orderBy, orderType, sgf); } @ResponseBody @@ -102,6 +102,7 @@ public class ServiceGroupResource { public ServiceGroupValidationRO getExtensionServiceGroupById(@PathVariable Long serviceGroupId) { return uiServiceGroupService.getServiceGroupExtensionById(serviceGroupId); } + @RequestMapping(path = "extension/validate", method = RequestMethod.POST) @Secured({SMPAuthority.S_AUTHORITY_TOKEN_SYSTEM_ADMIN, SMPAuthority.S_AUTHORITY_TOKEN_SMP_ADMIN, SMPAuthority.S_AUTHORITY_TOKEN_SERVICE_GROUP_ADMIN}) public ServiceGroupValidationRO getExtensionServiceGroupById(@RequestBody(required = true) ServiceGroupValidationRO sg) { @@ -118,18 +119,18 @@ public class ServiceGroupResource { @PutMapping(produces = {"application/json"}) @RequestMapping(method = RequestMethod.PUT) @Secured({SMPAuthority.S_AUTHORITY_TOKEN_SYSTEM_ADMIN, SMPAuthority.S_AUTHORITY_TOKEN_SMP_ADMIN, SMPAuthority.S_AUTHORITY_TOKEN_SERVICE_GROUP_ADMIN}) - public void updateDomainList(@RequestBody(required = true) ServiceGroupRO[] updateEntities ){ + public void updateDomainList(@RequestBody(required = true) ServiceGroupRO[] updateEntities) { LOG.info("Update ServiceGroupRO count: " + updateEntities.length); uiServiceGroupService.updateServiceGroupList(Arrays.asList(updateEntities)); } - private String decodeUrlToUTF8(String value){ - if (StringUtils.isBlank(value)){ + private String decodeUrlToUTF8(String value) { + if (StringUtils.isBlank(value)) { return null; } try { return URLDecoder.decode(value, "UTF-8"); - } catch (UnsupportedEncodingException ex){ + } catch (UnsupportedEncodingException ex) { LOG.error("Unsupported UTF-8 encoding while converting: " + value, ex); } return value; diff --git a/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/auth/SMPAuthenticationProviderTest.java b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/auth/SMPAuthenticationProviderTest.java index ed55fbf9c1cfe78633ba09adaffba4854eae59f1..95f7134a08df749ad4f59682506f95a4d8f07b00 100644 --- a/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/auth/SMPAuthenticationProviderTest.java +++ b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/auth/SMPAuthenticationProviderTest.java @@ -3,6 +3,7 @@ package eu.europa.ec.edelivery.smp.auth; import eu.europa.ec.edelivery.smp.data.dao.UserDao; import eu.europa.ec.edelivery.smp.data.model.DBUser; import eu.europa.ec.edelivery.smp.services.CRLVerifierService; +import eu.europa.ec.edelivery.smp.services.ConfigurationService; import eu.europa.ec.edelivery.smp.services.ui.UITruststoreService; import org.hamcrest.Matchers; import org.junit.Test; @@ -16,7 +17,6 @@ import java.util.Calendar; import java.util.Optional; import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doReturn; @@ -25,9 +25,9 @@ public class SMPAuthenticationProviderTest { UserDao mockUserDao = Mockito.mock(UserDao.class); CRLVerifierService mockCrlVerifierService = Mockito.mock(CRLVerifierService.class); UITruststoreService mockTruststoreService = Mockito.mock(UITruststoreService.class); + ConfigurationService mockConfigurationService = Mockito.mock(ConfigurationService.class); - SMPAuthenticationProvider testInstance = new SMPAuthenticationProvider(mockUserDao, mockCrlVerifierService, mockTruststoreService); - + SMPAuthenticationProvider testInstance = new SMPAuthenticationProvider(mockUserDao, mockCrlVerifierService, mockTruststoreService, mockConfigurationService); @Test // response time for existing and non existing user should be "approx. equal" diff --git a/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/config/SMPSecurityPropertyUpdateListenerTest.java b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/config/SMPSecurityPropertyUpdateListenerTest.java index 371d4f065c225c31b8ce1ee2422afb6b96b94d8f..cc53f19cbdbed9dc2176e97771895b3dae9b584e 100644 --- a/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/config/SMPSecurityPropertyUpdateListenerTest.java +++ b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/config/SMPSecurityPropertyUpdateListenerTest.java @@ -1,6 +1,6 @@ package eu.europa.ec.edelivery.smp.config; -import eu.europa.ec.edelivery.security.BlueCoatAuthenticationFilter; +import eu.europa.ec.edelivery.security.ClientCertAuthenticationFilter; import eu.europa.ec.edelivery.smp.data.dao.ConfigurationDao; import eu.europa.ec.edelivery.smp.data.ui.enums.SMPPropertyEnum; import org.junit.Test; @@ -11,10 +11,10 @@ public class SMPSecurityPropertyUpdateListenerTest { - BlueCoatAuthenticationFilter blueCoatAuthenticationFilter = Mockito.mock(BlueCoatAuthenticationFilter.class); + ClientCertAuthenticationFilter ClientCertAuthenticationFilter = Mockito.mock(ClientCertAuthenticationFilter.class); ConfigurationDao configurationDao = Mockito.mock(ConfigurationDao.class); ForwardedHeaderTransformer forwardedHeaderTransformer = Mockito.mock(ForwardedHeaderTransformer.class); - SMPSecurityPropertyUpdateListener testInstance = new SMPSecurityPropertyUpdateListener(blueCoatAuthenticationFilter,configurationDao,forwardedHeaderTransformer ); + SMPSecurityPropertyUpdateListener testInstance = new SMPSecurityPropertyUpdateListener(ClientCertAuthenticationFilter,configurationDao,forwardedHeaderTransformer ); @Test public void testInit() { @@ -27,7 +27,7 @@ public class SMPSecurityPropertyUpdateListenerTest { Mockito.doReturn(Boolean.TRUE ).when(configurationDao).getCachedPropertyValue(SMPPropertyEnum.BLUE_COAT_ENABLED); Mockito.doReturn(Boolean.TRUE ).when(configurationDao).getCachedPropertyValue(SMPPropertyEnum.HTTP_FORWARDED_HEADERS_ENABLED); testInstance.propertiesUpdate(); - Mockito.verify(blueCoatAuthenticationFilter, Mockito.times(1)).setBlueCoatEnabled(true); + Mockito.verify(ClientCertAuthenticationFilter, Mockito.times(1)).setClientCertAuthenticationEnabled(true); Mockito.verify(forwardedHeaderTransformer, Mockito.times(1)).setRemoveOnly(false); } @@ -36,7 +36,7 @@ public class SMPSecurityPropertyUpdateListenerTest { Mockito.doReturn(Boolean.FALSE ).when(configurationDao).getCachedPropertyValue(SMPPropertyEnum.BLUE_COAT_ENABLED); Mockito.doReturn(Boolean.FALSE ).when(configurationDao).getCachedPropertyValue(SMPPropertyEnum.HTTP_FORWARDED_HEADERS_ENABLED); testInstance.propertiesUpdate(); - Mockito.verify(blueCoatAuthenticationFilter, Mockito.times(1)).setBlueCoatEnabled(false); + Mockito.verify(ClientCertAuthenticationFilter, Mockito.times(1)).setClientCertAuthenticationEnabled(false); Mockito.verify(forwardedHeaderTransformer, Mockito.times(1)).setRemoveOnly(true); } } \ No newline at end of file diff --git a/smp-webapp/src/test/resources/logback-test.xml b/smp-webapp/src/test/resources/logback-test.xml new file mode 100644 index 0000000000000000000000000000000000000000..8df82e49f56fda7cd57d1787e614f2ed5c4081ea --- /dev/null +++ b/smp-webapp/src/test/resources/logback-test.xml @@ -0,0 +1,46 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<configuration> + <!-- pattern definition --> + <property name="encoderPattern" value="%d{ISO8601} [%X{d_user}] [%X{d_domain}] [%X{d_messageId}] [%thread] %5p %c{1}:%L - %m%n" scope="global"/> + <property name="consolePattern" value="%d{ISO8601} [%X{d_user}] [%X{d_domain}] [%X{d_messageId}] [%thread] %5p %c{1}:%L - %m%n" scope="global"/> + + <appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender"> + <file>${project.build.directory}/logs/edelivery-smp.log</file> + <filter class="ch.qos.logback.core.filter.EvaluatorFilter"> + <evaluator class="ch.qos.logback.classic.boolex.OnMarkerEvaluator"> + <marker>SECURITY</marker> + <marker>BUSINESS</marker> + </evaluator> + <onMismatch>NEUTRAL</onMismatch> + <onMatch>DENY</onMatch> + </filter> + <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> + <!-- rollover daily --> + <fileNamePattern>${log.folder:-logs}/edelivery-smp-%d{yyyy-MM-dd}.%i.log</fileNamePattern> + <!-- each file should be at most 30MB, keep 60 days worth of history, but at most 20GB --> + <maxFileSize>30MB</maxFileSize> + <maxHistory>60</maxHistory> + <totalSizeCap>20GB</totalSizeCap> + </rollingPolicy> + <encoder> + <pattern>${encoderPattern}</pattern> + </encoder> + </appender> + + + + <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender"> + <Target>System.out</Target> + <encoder> + <pattern>${consolePattern}</pattern> + </encoder> + </appender> + + <logger name="eu.europa.ec.edelivery" level="DEBUG" /> + <logger name="org.springframework.security.cas" level="DEBUG" /> + <root level="WARN"> + <appender-ref ref="file"/> + <appender-ref ref="stdout"/> + </root> +</configuration> \ No newline at end of file diff --git a/upgrade-info.txt b/upgrade-info.txt index 8e3bb46f974e1c8ff78f2f26e26a20c96fc02c1f..1606ec37aee7be6cb7bd386bfc3a2e32755a80f0 100644 --- a/upgrade-info.txt +++ b/upgrade-info.txt @@ -1,2 +1,2 @@ -SMP 4.2 from 4.1.1) +eDelivery SMP 4.2 from 4.1.1) - Set: smp.http.forwarded.headers.enabled to true if SMP is set behind RP/LoadBalancer \ No newline at end of file