diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/model/DBCertificate.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/model/DBCertificate.java index e552eb609411eb9e3fdcd190641829bb4b9ca373..541553a963d81a1a36ec0ededeb57e5668e71f2b 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/model/DBCertificate.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/model/DBCertificate.java @@ -45,29 +45,26 @@ public class DBCertificate extends BaseEntity { @Column(name = "VALID_TO") @ColumnDescription(comment = "Certificate valid to date.") private LocalDateTime validTo; - @Column(name = "SUBJECT", length = CommonColumnsLengths.MAX_MEDIUM_TEXT_LENGTH) - @ColumnDescription(comment = "Certificate subject (canonical form)" ) - private String subject; + @ColumnDescription(comment = "Certificate subject (canonical form)") + private String subject; @Column(name = "ISSUER", length = CommonColumnsLengths.MAX_MEDIUM_TEXT_LENGTH) - @ColumnDescription(comment = "Certificate issuer (canonical form)" ) - private String issuer; + @ColumnDescription(comment = "Certificate issuer (canonical form)") + private String issuer; @Column(name = "SERIALNUMBER", length = CommonColumnsLengths.MAX_TEXT_LENGTH_128) - @ColumnDescription(comment = "Certificate serial number" ) - private String serialNumber; + @ColumnDescription(comment = "Certificate serial number") + private String serialNumber; - @Column(name = "pem_encoding") - @ColumnDescription(comment = "PEM encoding for the certificate") + @Column(name = "PEM_ENCODED_CERT") + @ColumnDescription(comment = "PEM encoded certificate") @Lob private String pemEncoding; - @Column(name = "crl_url", length = CommonColumnsLengths.MAX_FREE_TEXT_LENGTH) + @Column(name = "CRL_URL", length = CommonColumnsLengths.MAX_FREE_TEXT_LENGTH) @ColumnDescription(comment = "URL to the certificate revocation list (CRL)") private String crlUrl; - - - @Column(name = "CREATED_ON" , nullable = false) + @Column(name = "CREATED_ON", nullable = false) LocalDateTime createdOn; @Column(name = "LAST_UPDATED_ON", nullable = false) LocalDateTime lastUpdatedOn; @@ -179,7 +176,7 @@ public class DBCertificate extends BaseEntity { @PrePersist public void prePersist() { - if(createdOn == null) { + if (createdOn == null) { createdOn = LocalDateTime.now(); } lastUpdatedOn = LocalDateTime.now(); 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 a627668565791ba577eaca47e1ea86ae4e826670..a056aa974256cf4de209f8711ba1e9d4baff07e5 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,36 +1,156 @@ package eu.europa.ec.edelivery.smp.auth; +import eu.europa.ec.edelivery.security.PreAuthenticatedCertificatePrincipal; import eu.europa.ec.edelivery.smp.data.dao.UserDao; import eu.europa.ec.edelivery.smp.data.model.DBUser; 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 org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.authentication.AuthenticationServiceException; -import org.springframework.security.authentication.BadCredentialsException; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.authentication.*; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.crypto.bcrypt.BCrypt; +import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Calendar; import java.util.Collections; +import java.util.Date; import java.util.Optional; +import static java.util.Locale.US; + public class SMPAuthenticationProvider implements AuthenticationProvider { private static final SMPLogger LOG = SMPLoggerFactory.getLogger(AuthenticationProvider.class); + + /** + * thread safe validator + */ + private static final ThreadLocal<DateFormat> dateFormatLocal = ThreadLocal.withInitial( () -> { + return new SimpleDateFormat("MMM d hh:mm:ss yyyy zzz", US); + } ); + @Autowired UserDao mUserDao; @Override - public Authentication authenticate(Authentication auth) + public Authentication authenticate(Authentication authenticationToken) + throws AuthenticationException { + + Authentication authentication = null; + // PreAuthentication token for the rest service certificate authentication + if (authenticationToken instanceof PreAuthenticatedAuthenticationToken) { + + Object principal = authenticationToken.getPrincipal(); + if (principal instanceof PreAuthenticatedCertificatePrincipal) { + authentication = authenticateByCertificateToken((PreAuthenticatedCertificatePrincipal) principal); + } else { + LOG.warn("Unknown or null PreAuthenticatedAuthenticationToken principal type: " + principal); + } + } else if (authenticationToken instanceof UsernamePasswordAuthenticationToken) { + authentication = authenticateByUsernameToken((UsernamePasswordAuthenticationToken)authenticationToken); + } + + + // set anonymous token + if (authentication == null) { + authentication = new AnonymousAuthenticationToken(authenticationToken.toString(), authenticationToken.getPrincipal(), + Collections.singleton(SMPAuthority.S_AUTHORITY_ANONYMOUS)); + authentication.setAuthenticated(false); + } + /* + + if (principal instanceof PreAuthenticatedCertificatePrincipal) { + // get principal + LOG.info("Authenticate: PreAuthenticatedCertificatePrincipal"); + authentication = authenticateCertificate((PreAuthenticatedCertificatePrincipal) principal); + } else if (principal instanceof PreAuthenticatedTokenPrincipal) { + authentication = authenticateSecurityToken((PreAuthenticatedTokenPrincipal) principal); + + } else if (principal instanceof PreAuthenticatedAnonymousPrincipal) { + authentication = new UnsecureAuthentication(); + authentication.setAuthenticated(configurationBusiness.isUnsecureLoginEnabled()); + } + else { + // unknown principal type + authentication = new UnsecureAuthentication(); + authentication.setAuthenticated(false); + }*/ + + + return authentication; + } + + + /** + * Authenticate by certificate token got by BlueCoat or X509Certificate authentication) + * @param principal - certificate principal + * @return authentication value. + */ + public Authentication authenticateByCertificateToken(PreAuthenticatedCertificatePrincipal principal) { + mUserDao.findUserByCertificateId(principal.getName()); + + DBUser user; + String userToken = principal.getName(); + try { + + Optional<DBUser> oUsr = mUserDao.findUserByCertificateId(userToken); + if (!oUsr.isPresent()) { + LOG.securityWarn(SMPMessageCode.SEC_USER_NOT_EXISTS, userToken); + //https://www.owasp.org/index.php/Authentication_Cheat_Sheet + // Do not reveal the status of an existing account. Not to use UsernameNotFoundException + throw new BadCredentialsException("Login failed; Invalid userID or password"); + } + + + user = oUsr.get(); + } catch (AuthenticationException ex) { + throw ex; + + } catch (RuntimeException ex) { + LOG.error("Database connection error", ex); + throw new AuthenticationServiceException("Internal server error occurred while user authentication!"); + + } + // check if certificate is valid + Date currentDate = Calendar.getInstance().getTime(); + // validate dates + if (principal.getNotBefore().after(currentDate)) { + throw new AuthenticationServiceException("Invalid certificate: NotBefore: " + dateFormatLocal.get().format(principal.getNotBefore())); + } else if (principal.getNotAfter().before(currentDate)) { + throw new AuthenticationServiceException("Invalid certificate: NotAfter: " + dateFormatLocal.get().format(principal.getNotAfter())); + } + // check if issuer is on trust list. + + // Check crl list + String url = user.getCertificate().getCrlUrl(); + if (url!= null) { + + } + + + // get role + String role = user.getRole(); + LOG.securityInfo(SMPMessageCode.SEC_USER_AUTHENTICATED, userToken, role); + SMPCertificateAuthentication authentication = new SMPCertificateAuthentication(principal, Collections.singletonList(new SMPAuthority(role)), user); + + authentication.setAuthenticated(true); + return authentication; + } + + + public Authentication authenticateByUsernameToken(UsernamePasswordAuthenticationToken auth) throws AuthenticationException { // get user // test credentials // get and return user roles. + + String username = auth.getName(); String password = auth.getCredentials().toString(); @@ -72,6 +192,12 @@ public class SMPAuthenticationProvider implements AuthenticationProvider { @Override public boolean supports(Class<?> auth) { - return auth.equals(UsernamePasswordAuthenticationToken.class) || auth.equals(SMPAuthenticationToken.class); + LOG.info("Support authentication: " + auth); + boolean supportAuthentication = auth.equals(UsernamePasswordAuthenticationToken.class) || auth.equals(PreAuthenticatedAuthenticationToken.class); + if (!supportAuthentication) { + LOG.warn("SMP does not support authentication type: " + auth); + } + + return supportAuthentication; } } diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/SMPAuthority.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/SMPAuthority.java index 38ac2682072b3cbb49ce618231480c91bda43dea..b0ca43ee578247676bd50d5ef778fee20e02741f 100644 --- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/SMPAuthority.java +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/SMPAuthority.java @@ -9,12 +9,13 @@ public class SMPAuthority implements GrantedAuthority { public static final String S_AUTHORITY_TOKEN_SYSTEM_ADMIN = "ROLE_SYSTEM_ADMIN"; public static final String S_AUTHORITY_TOKEN_SMP_ADMIN = "ROLE_SMP_ADMIN"; public static final String S_AUTHORITY_TOKEN_SERVICE_GROUP_ADMIN = "ROLE_SERVICE_GROUP_ADMIN"; + public static final String S_AUTHORITY_TOKEN_ROLE_ANONYMOUS = "ROLE_ANONYMOUS"; // static constants for verification... - public static final SMPAuthority S_AUTHORITY_SYSTEM_ADMIN = new SMPAuthority(SMPRole.SYSTEM_ADMIN.getCode()); - public static final SMPAuthority S_AUTHORITY_SMP_ADMIN = new SMPAuthority(SMPRole.SMP_ADMIN.getCode()); - public static final SMPAuthority S_AUTHORITY_SERVICE_GROUP = new SMPAuthority(SMPRole.SERVICE_GROUP_ADMIN.getCode()); - + public static final SMPAuthority S_AUTHORITY_SYSTEM_ADMIN = new SMPAuthority(SMPRole.SYSTEM_ADMIN.getCode()); + public static final SMPAuthority S_AUTHORITY_SMP_ADMIN = new SMPAuthority(SMPRole.SMP_ADMIN.getCode()); + public static final SMPAuthority S_AUTHORITY_SERVICE_GROUP = new SMPAuthority(SMPRole.SERVICE_GROUP_ADMIN.getCode()); + public static final SMPAuthority S_AUTHORITY_ANONYMOUS = new SMPAuthority(SMPRole.ANONYMOUS.getCode()); String role; @@ -24,6 +25,6 @@ public class SMPAuthority implements GrantedAuthority { @Override public String getAuthority() { - return "ROLE_"+role; + return "ROLE_" + role; } } diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/SMPCertificateAuthentication.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/SMPCertificateAuthentication.java new file mode 100644 index 0000000000000000000000000000000000000000..0602230c963351cf576d9905777cdc54f1c5d7b0 --- /dev/null +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/SMPCertificateAuthentication.java @@ -0,0 +1,86 @@ +/** + * (C) Copyright 2018 - European Commission | CEF eDelivery + * <p> + * Licensed under the EUPL, Version 1.2 (the "License"); + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * <p> + * \BDMSL\bdmsl-parent-pom\LICENSE-EUPL-v1.2.pdf or https://joinup.ec.europa.eu/sites/default/files/custom-page/attachment/eupl_v1.2_en.pdf + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +package eu.europa.ec.edelivery.smp.auth; + +import eu.europa.ec.edelivery.security.PreAuthenticatedCertificatePrincipal; +import eu.europa.ec.edelivery.smp.data.model.DBUser; +import org.apache.commons.lang3.time.DateUtils; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +public class SMPCertificateAuthentication implements Authentication { + + PreAuthenticatedCertificatePrincipal principal; + DBUser dbUser; + + List<GrantedAuthority> listAuthorities = new ArrayList<>(); + boolean isAuthenticated; + private static final int SERIAL_PADDING_SIZE =16; + + + public SMPCertificateAuthentication(PreAuthenticatedCertificatePrincipal principal, List<GrantedAuthority> listAuthorities, DBUser user) { + this.principal = principal; + this.listAuthorities.addAll(listAuthorities); + this.dbUser = user; + } + + @Override + public Collection<? extends GrantedAuthority> getAuthorities() { + return listAuthorities; + } + + @Override + public Object getCredentials() { + return this.principal!=null?this.principal.getCredentials():null; + } + + @Override + public Object getDetails() { + return this.principal; + } + + @Override + public Object getPrincipal() { + return this.principal; + } + + @Override + public boolean isAuthenticated() { + return isAuthenticated; + } + + @Override + public void setAuthenticated(boolean b) throws IllegalArgumentException { + isAuthenticated = b; + } + + @Override + public String getName() { + return principal.getName(SERIAL_PADDING_SIZE); + } + + @Override + public String toString() { + return getName(); + } + + +} diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/SMPRole.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/SMPRole.java index 12d1d4e2725e91ee83a6b179c211074da3dadce6..1f296a054a2e2d9312e278f1b1a3b1dbb8df1082 100644 --- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/SMPRole.java +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/SMPRole.java @@ -2,22 +2,18 @@ package eu.europa.ec.edelivery.smp.auth; public enum SMPRole { + ANONYMOUS("ANONYMOUS"), SMP_ADMIN("SMP_ADMIN"), SERVICE_GROUP_ADMIN("SERVICE_GROUP_ADMIN"), SYSTEM_ADMIN("SYSTEM_ADMIN"); - - - String code; - SMPRole(String code){ + + SMPRole(String code) { this.code = code; } public String getCode() { return code; } - - - } diff --git a/smp-webapp/src/main/resources/spring-security.xml b/smp-webapp/src/main/resources/spring-security.xml index fe56dc85107363be001ebc83dfd210b02fa1e8f4..3c4997decac0bafb6b6ae17a53c05c4fa0f25cc6 100644 --- a/smp-webapp/src/main/resources/spring-security.xml +++ b/smp-webapp/src/main/resources/spring-security.xml @@ -38,26 +38,9 @@ <authentication-manager alias="smpAuthenticationManager"> <authentication-provider ref="smpAuthProvider"/> - <authentication-provider ref="preauthAuthProvider"/> - </authentication-manager> - <!-- user detail service is used only in preAhtProviders for cert authentication that is why search is only on cert table--> - <!-- database Cert ID search must be case insensitive --> - <jdbc-user-service id="smpJdbcUserDetailsService" - data-source-ref="dataSource" - users-by-username-query="SELECT c.CERTIFICATE_ID AS USERNAME, 'dummy' AS PASWORD, u.ACTIVE FROM SMP_CERTIFICATE c INNER JOIN SMP_USER u ON (u.id = c.id) WHERE lower(c.CERTIFICATE_ID) = lower(?)" - authorities-by-username-query="SELECT c.CERTIFICATE_ID AS USERNAME, u.ROLE FROM SMP_CERTIFICATE c INNER JOIN SMP_USER u ON (u.id = c.id) WHERE lower(c.CERTIFICATE_ID) = lower(?)"/> - - <b:bean id="preauthAuthProvider" - class="org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider"> - <b:property name="preAuthenticatedUserDetailsService"> - <b:bean id="userDetailsServiceWrapper" - class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper"> - <b:property name="userDetailsService" ref="smpJdbcUserDetailsService"/> - </b:bean> - </b:property> - </b:bean> + <b:bean id="smpAuthProvider" class="eu.europa.ec.edelivery.smp.auth.SMPAuthenticationProvider" /> <b:bean id="blueCoatReverseProxyAuthFilter" class="eu.europa.ec.edelivery.security.BlueCoatAuthenticationFilter"> @@ -70,15 +53,13 @@ <b:property name="authenticationManager" ref="smpAuthenticationManager"/> </b:bean> - <!-- Slashes in participant or document identifiers are disallowed by default --> + <!-- encoded Slashes are disallowed by default but SMP is using + them in participant or document identifiers --> <http-firewall ref="httpFirewall"/> <b:bean id="httpFirewall" class="org.springframework.security.web.firewall.DefaultHttpFirewall"> <b:property name="allowUrlEncodedSlash" value="${encodedSlashesAllowedInUrl}"/> </b:bean> - <b:bean id="smpAuthProvider" class="eu.europa.ec.edelivery.smp.auth.SMPAuthenticationProvider"> - </b:bean> - </b:beans>