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

Skip to content
Snippets Groups Projects
Commit 4c059c58 authored by Sebastian-Ion TINCU's avatar Sebastian-Ion TINCU
Browse files

EDELIVERY-3687 SMP UI Add/Edit user

Rename validUntil to validTo as present in the back-end.
Add support for fingerprints when reading the uploaded certificate.
Add Jackson 2 support for JSR310 dates and configure a Jackson 2
HttpMessageConverter.
parent 74227142
No related branches found
No related tags found
No related merge requests found
Showing
with 107 additions and 46 deletions
......@@ -4,4 +4,6 @@ export class SmpConstants {
public static readonly REST_USER = 'rest/user';
public static readonly REST_SEARCH = 'rest/search';
public static readonly REST_EDIT = 'rest/servicegroup';
public static readonly REST_CERTIFICATE = `${SmpConstants.REST_USER}/certdata`;
}
......@@ -2,7 +2,7 @@ export interface CertificateRo {
certificateId: string;
subject: string;
validFrom: Date;
validUntil: Date;
validTo: Date;
issuer: string;
serialNumber: string;
fingerprints: string;
......
......@@ -2,6 +2,7 @@ import {Injectable} from '@angular/core';
import {Observable} from 'rxjs';
import {CertificateRo} from './certificate-ro.model';
import {HttpClient} from '@angular/common/http';
import {SmpConstants} from "../smp.constants";
@Injectable()
export class CertificateService {
......@@ -9,6 +10,6 @@ export class CertificateService {
constructor(private http: HttpClient) {}
uploadCertificate$(payload): Observable<CertificateRo> {
return this.http.post<CertificateRo>('rest/user/certdata', payload);
return this.http.post<CertificateRo>(SmpConstants.REST_CERTIFICATE, payload);
}
}
......@@ -11,7 +11,7 @@
width: 100%;
}
.certificate-valid-from, .certificate-valid-until {
.certificate-valid-from, .certificate-valid-to {
width: 40%;
}
......
......@@ -54,8 +54,8 @@
<mat-form-field class="certificate-valid-from">
<input matInput placeholder="Valid From" [formControl]="userForm.controls['validFrom']">
</mat-form-field>
<mat-form-field class="certificate-valid-until">
<input matInput placeholder="Valid To" [formControl]="userForm.controls['validUntil']">
<mat-form-field class="certificate-valid-to">
<input matInput placeholder="Valid To" [formControl]="userForm.controls['validTo']">
</mat-form-field>
<mat-form-field class="certificate-issuer">
<input matInput placeholder="Issuer" [formControl]="userForm.controls['issuer']">
......@@ -65,7 +65,7 @@
</mat-form-field>
<label class="custom-file-upload">
<input #fileInput type="file" id="custom-file-upload" accept=".cer" (change)="uploadCertificate()" [disabled]="!userForm.controls['certificateToggle']?.value">
<input #fileInput type="file" id="custom-file-upload" accept=".cer" (change)="uploadCertificate($event)" [disabled]="!userForm.controls['certificateToggle']?.value">
<button mat-flat-button color="primary" (click)="fileInput.click()" [disabled]="!userForm.controls['certificateToggle']?.value">Import</button>
</label>
</div>
......
import {Component, Inject, ViewChild} from '@angular/core';
import {MAT_DIALOG_DATA, MatDialogRef, MatSlideToggle, MatSlideToggleChange} from '@angular/material';
import {MAT_DIALOG_DATA, MatDialogRef, MatSlideToggleChange} from '@angular/material';
import {FormBuilder, FormControl, FormGroup, ValidationErrors, ValidatorFn, Validators} from '@angular/forms';
import {UserService} from '../user.service';
import {Role} from '../../security/role.model';
......@@ -52,11 +52,11 @@ export class UserDetailsDialogComponent {
const certificateToggle = control.get('certificateToggle');
const subject = control.get('subject');
const validFrom = control.get('validFrom');
const validUntil = control.get('validUntil');
const validTo = control.get('validTo');
const issuer = control.get('issuer');
const fingerprints = control.get('fingerprints');
return certificateToggle && subject && validFrom && validUntil && issuer && fingerprints
&& certificateToggle.value && !(subject.value && validFrom.value && validUntil.value && issuer.value && fingerprints.value) ? { certificateDetailsRequired: true} : null;
return certificateToggle && subject && validFrom && validTo && issuer && fingerprints
&& certificateToggle.value && !(subject.value && validFrom.value && validTo.value && issuer.value && fingerprints.value) ? { certificateDetailsRequired: true} : null;
};
constructor(private dialogRef: MatDialogRef<UserDetailsDialogComponent>,
......@@ -77,7 +77,7 @@ export class UserDetailsDialogComponent {
certificate: {
subject: data.row.subject,
validFrom: data.row.validFrom,
validUntil: data.row.validUntil,
validTo: data.row.validTo,
issuer: data.row.issuer,
fingerprints: data.row.fingerprints,
}
......@@ -103,7 +103,7 @@ export class UserDetailsDialogComponent {
'certificateToggle': new FormControl(user && user.certificate && !!user.certificate.subject),
'subject': new FormControl({ value: user.certificate.subject, disabled: true }, Validators.required),
'validFrom': new FormControl({ value: user.certificate.validFrom, disabled: true }, Validators.required),
'validUntil': new FormControl({ value: user.certificate.validUntil, disabled: true }, Validators.required),
'validTo': new FormControl({ value: user.certificate.validTo, disabled: true }, Validators.required),
'issuer': new FormControl({ value: user.certificate.issuer, disabled: true }, Validators.required),
'fingerprints': new FormControl({ value: user.certificate.fingerprints, disabled: true }, Validators.required),
}, {
......@@ -122,22 +122,16 @@ export class UserDetailsDialogComponent {
this.dialogRef.close(true);
}
uploadCertificate() {
const fi = this.fileInput.nativeElement;
const file = fi.files[0];
uploadCertificate(event) {
const file = event.target.files[0];
const reader = new FileReader();
reader.onload = (e) => {
const arrayBuffer = reader.result;
const array = new Uint8Array(arrayBuffer);
const binaryString = String.fromCharCode.apply(null, array);
this.certificateService.uploadCertificate$({content: binaryString})
.subscribe((res: CertificateRo) => {
this.certificateService.uploadCertificate$(reader.result).subscribe((res: CertificateRo) => {
this.userForm.patchValue({
'subject': res.subject,
'validFrom': this.datePipe.transform(res.validFrom.toString(), this.dateFormat),
'validUntil': this.datePipe.transform(res.validUntil.toString(), this.dateFormat),
'validTo': this.datePipe.transform(res.validTo.toString(), this.dateFormat),
'issuer': res.issuer,
'fingerprints': res.fingerprints
});
......@@ -151,7 +145,7 @@ export class UserDetailsDialogComponent {
this.alertService.exception('Error reading certificate file ' + file.name, err);
};
reader.readAsArrayBuffer(file);
reader.readAsBinaryString(file);
}
onUserToggleChanged({checked}: MatSlideToggleChange) {
......
......@@ -451,6 +451,11 @@
<artifactId>jackson-annotations</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>${jackson.version}</version>
</dependency>
<!-- End Jackson-->
<dependency>
<groupId>org.apache.commons</groupId>
......@@ -676,7 +681,7 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<!-- fork needs to be false to compile JAXB resources. Otherwise the
<!-- fork needs to be false to compile JAXB resources. Otherwise the
problem with the forward slash in package-info.java on Windows occurs! -->
<fork>false</fork>
<source>1.8</source>
......
......@@ -112,6 +112,10 @@
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
......
package eu.europa.ec.edelivery.smp.data.ui;
import eu.europa.ec.edelivery.smp.data.model.CommonColumnsLengths;
import javax.persistence.Column;
import javax.persistence.Id;
import java.math.BigInteger;
import java.time.LocalDateTime;
/**
* @author Joze Rihtarsic
* @since 4.1
*/
public class CertificateRO extends BaseRO {
private static final long serialVersionUID = -4971552086560325302L;
private String certificateId;
private String subject;
private String issuer;
private String serialNumber;
private String fingerprints;
private LocalDateTime validFrom;
private LocalDateTime validTo;
public CertificateRO(){
}
public static long getSerialVersionUID() {
......@@ -69,6 +57,14 @@ public class CertificateRO extends BaseRO {
this.serialNumber = serialNumber;
}
public String getFingerprints() {
return fingerprints;
}
public void setFingerprints(String fingerprints) {
this.fingerprints = fingerprints;
}
public LocalDateTime getValidFrom() {
return validFrom;
}
......
......@@ -23,7 +23,8 @@ public enum ErrorCode {
ILLEGAL_STATE_CERT_ID_MULTIPLE_ENTRY(504,"SMP:122",ErrorBusinessCode.TECHNICAL,"More than one certificate entry (cert. id: '%s') is defined in database!"),
USER_NOT_EXISTS(400,"SMP:123",ErrorBusinessCode.USER_NOT_FOUND,"User not exists or wrong password!"), // OWASP recommendation\
USER_IS_NOT_OWNER(400,"SMP:124",ErrorBusinessCode.UNAUTHORIZED,"User %s is not owner of service group (part. id: %s, part. sch.: '%s')!"), // OWASP recommendation
INVALID_CERTIFICATE_MESSAGE_DIGEST(500, "SMP:125", ErrorBusinessCode.TECHNICAL, "Could not initialize MessageDigest"),
INVALID_CERTIFICATE_ENCODING(500, "SMP:126", ErrorBusinessCode.TECHNICAL, "Could not encode certificate"),
// service group error
ILLEGAL_STATE_SG_MULTIPLE_ENTRY (500,"SMP:130",ErrorBusinessCode.TECHNICAL,"More than one service group ( part. id: %s, part. sch.: '%s') is defined in database!"),
......
......@@ -5,16 +5,15 @@ import eu.europa.ec.edelivery.smp.BCryptPasswordHash;
import eu.europa.ec.edelivery.smp.data.dao.BaseDao;
import eu.europa.ec.edelivery.smp.data.dao.UserDao;
import eu.europa.ec.edelivery.smp.data.model.DBCertificate;
import eu.europa.ec.edelivery.smp.data.model.DBDomain;
import eu.europa.ec.edelivery.smp.data.model.DBUser;
import eu.europa.ec.edelivery.smp.data.ui.CertificateRO;
import eu.europa.ec.edelivery.smp.data.ui.DomainRO;
import eu.europa.ec.edelivery.smp.data.ui.ServiceResult;
import eu.europa.ec.edelivery.smp.data.ui.UserRO;
import eu.europa.ec.edelivery.smp.data.ui.enums.EntityROStatus;
import eu.europa.ec.edelivery.smp.exceptions.ErrorCode;
import eu.europa.ec.edelivery.smp.exceptions.SMPRuntimeException;
import eu.europa.ec.edelivery.smp.logging.SMPLogger;
import eu.europa.ec.edelivery.smp.logging.SMPLoggerFactory;
import eu.europa.ec.edelivery.smp.services.ServiceGroupService;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
......@@ -22,15 +21,16 @@ import org.springframework.security.crypto.bcrypt.BCrypt;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.ejb.Local;
import javax.xml.bind.DatatypeConverter;
import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.lang.reflect.InvocationTargetException;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.List;
......@@ -126,21 +126,44 @@ public class UIUserService extends UIServiceBase<DBUser, UserRO> {
String subject = cert.getSubjectDN().getName();
String issuer = cert.getIssuerDN().getName();
String hash = cert.getIssuerDN().getName();
String fingerprints = extractFingerprints(cert);
BigInteger serial = cert.getSerialNumber();
String certId = getCertificateIdFromCertificate(subject,issuer, serial );
CertificateRO cro = new CertificateRO();
cro.setCertificateId(certId);
cro.setSubject(subject);
cro.setIssuer(issuer);
cro.setFingerprints(fingerprints);
// set serial as HEX
cro.setSerialNumber(serial.toString(16));
cro.setValidFrom(LocalDateTime.ofInstant(cert.getNotBefore().toInstant(), ZoneId.systemDefault()));
cro.setValidTo(LocalDateTime.ofInstant(cert.getNotAfter().toInstant(), ZoneId.systemDefault()));
return cro;
}
private String extractFingerprints(final X509Certificate certificate) {
if (certificate == null)
return null;
MessageDigest md = null;
try {
md = MessageDigest.getInstance("SHA-1");
} catch (NoSuchAlgorithmException e) {
LOG.warn("Error initializing MessageDigest ", e);
throw new SMPRuntimeException(ErrorCode.INVALID_CERTIFICATE_MESSAGE_DIGEST, e);
}
byte[] der = new byte[0];
try {
der = certificate.getEncoded();
} catch (CertificateEncodingException e) {
LOG.warn("Error encoding certificate ", e);
throw new SMPRuntimeException(ErrorCode.INVALID_CERTIFICATE_ENCODING, e);
}
md.update(der);
byte[] digest = md.digest();
String digestHex = DatatypeConverter.printHexBinary(digest);
return digestHex.toLowerCase();
}
public String getCertificateIdFromCertificate(String subject, String issuer, BigInteger serial ){
......
......@@ -13,12 +13,24 @@
package eu.europa.ec.edelivery.smp.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import eu.europa.ec.edelivery.smp.error.ErrorMappingControllerAdvice;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.web.servlet.config.annotation.*;
import org.springframework.context.annotation.Primary;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.List;
import static org.springframework.core.Ordered.HIGHEST_PRECEDENCE;
......@@ -35,6 +47,29 @@ import static org.springframework.core.Ordered.HIGHEST_PRECEDENCE;
@Import({GlobalMethodSecurityConfig.class, ErrorMappingControllerAdvice.class})
public class SmpWebAppConfig implements WebMvcConfigurer {
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
// There is no clean way of replacing the default Jackson 2 HttpMessageConverter while keeping the other
// default converters (@EnableWebMvc disables auto-configuration and most of the default converters are
// not available as Spring beans
// org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport.addDefaultHttpMessageConverters)
converters.removeIf(httpMessageConverter -> httpMessageConverter instanceof MappingJackson2HttpMessageConverter);
converters.add(jackson2HttpMessageConverter());
}
@Bean
public MappingJackson2HttpMessageConverter jackson2HttpMessageConverter() {
return new MappingJackson2HttpMessageConverter(objectMapper());
}
@Bean
@Primary
public ObjectMapper objectMapper() {
return Jackson2ObjectMapperBuilder.json()
.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.build();
}
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("/index.html");
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment