Skip to content

Commit

Permalink
Feature/identity (#120)
Browse files Browse the repository at this point in the history
* add table identity

* add crud identity

* add available on application

* Add identity in environment

* fix unit test

* Add unit test

* Add identity resource predicate support
  • Loading branch information
dtrouillet authored Jun 28, 2021
1 parent c703e69 commit e0bba33
Show file tree
Hide file tree
Showing 34 changed files with 1,416 additions and 97 deletions.
1 change: 1 addition & 0 deletions src/main/java/fr/icdc/ebad/config/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public final class Constants {
public static final String SPRING_PROFILE_PRODUCTION = "prod";
public static final String SPRING_PROFILE_FAST = "fast";
public static final String ROLE_ADMIN = "ROLE_ADMIN";
public static final String ROLE_USER = "ROLE_USER";

private Constants() {
}
Expand Down
31 changes: 7 additions & 24 deletions src/main/java/fr/icdc/ebad/domain/Environnement.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,14 @@

import com.fasterxml.jackson.annotation.JsonBackReference;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.*;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.ColumnDefault;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;
import org.hibernate.annotations.*;

import javax.annotation.Nullable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.util.Date;
Expand Down Expand Up @@ -76,9 +58,10 @@ public class Environnement extends AbstractAuditingEntity {
private String host;

@NotNull
@Size(min = 1, max = 255)
@Column(length = 255, nullable = false)
private String login;
@ManyToOne
@JoinColumn(name = "identity_id")
@JsonBackReference
private Identity identity;

@Nullable
@Size(min = 1, max = 255)
Expand Down
56 changes: 56 additions & 0 deletions src/main/java/fr/icdc/ebad/domain/Identity.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package fr.icdc.ebad.domain;

import com.fasterxml.jackson.annotation.JsonBackReference;
import lombok.*;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;

import javax.persistence.*;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

@Data
@EqualsAndHashCode(callSuper = false)
@Entity
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "t_identity")
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Identity extends AbstractAuditingEntity {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "identity_generator")
@SequenceGenerator(name = "identity_generator", sequenceName = "t_identity_id_seq")
private Long id;

@NotNull
@Size(min = 1, max = 255)
@Column(nullable = false)
private String name;

@NotNull
@Size(min = 1, max = 255)
@Column(nullable = false)
private String login;

@Size(min = 1, max = 255)
@Column
private String password;

@Size(min = 1, max = 2048)
@Column
private String privatekey;

@Size(min = 1, max = 2048)
@Column(name = "privatekey_path")
private String privatekeyPath;

@Size(min = 1, max = 255)
@Column
private String passphrase;

@ManyToOne
@JoinColumn(name = "available_application_id")
@JsonBackReference
private Application availableApplication;
}
20 changes: 20 additions & 0 deletions src/main/java/fr/icdc/ebad/repository/IdentityRepository.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package fr.icdc.ebad.repository;

import com.querydsl.core.types.dsl.StringExpression;
import com.querydsl.core.types.dsl.StringPath;
import fr.icdc.ebad.domain.Identity;
import fr.icdc.ebad.domain.QIdentity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
import org.springframework.data.querydsl.binding.QuerydslBinderCustomizer;
import org.springframework.data.querydsl.binding.QuerydslBindings;
import org.springframework.data.querydsl.binding.SingleValueBinding;

public interface IdentityRepository extends JpaRepository<Identity, Long>, QuerydslPredicateExecutor<Identity>, QuerydslBinderCustomizer<QIdentity> {
@Override
default void customize(QuerydslBindings bindings, QIdentity root) {
bindings
.bind(String.class)
.first((SingleValueBinding<StringPath, String>) StringExpression::containsIgnoreCase);
}
}
7 changes: 7 additions & 0 deletions src/main/java/fr/icdc/ebad/security/SecurityUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,11 @@ public static boolean isAdmin() {
authority -> authority.getAuthority().equals(Constants.ROLE_ADMIN)
);
}

public static boolean isUser() {

return SecurityContextHolder.getContext().getAuthentication().getAuthorities().stream().anyMatch(
authority -> authority.getAuthority().equals(Constants.ROLE_USER)
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package fr.icdc.ebad.security.permission;

import fr.icdc.ebad.domain.Identity;
import fr.icdc.ebad.repository.IdentityRepository;
import fr.icdc.ebad.security.SecurityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;

import java.util.Optional;

@Service
public class PermissionIdentity {
private static final Logger LOGGER = LoggerFactory.getLogger(PermissionIdentity.class);

private final PermissionApplication permissionApplication;
private final IdentityRepository identityRepository;

public PermissionIdentity(PermissionApplication permissionApplication, IdentityRepository identityRepository) {
this.permissionApplication = permissionApplication;
this.identityRepository = identityRepository;
}


public boolean canReadByApplication(Long applicationId, UserDetails userDetails) {
LOGGER.debug("PermissionIdentity canReadByApplication");
if (applicationId == null) {
return SecurityUtils.isAdmin() || SecurityUtils.isUser();
}
return permissionApplication.canWrite(applicationId, userDetails);
}

public boolean canRead(Long identityId, UserDetails userDetails) {
LOGGER.debug("PermissionIdentity canRead");
if (identityId == null) {
return false;
}
Optional<Identity> identityOptional = identityRepository.findById(identityId);
if(identityOptional.isEmpty()){
return false;
}
if(identityOptional.get().getAvailableApplication() == null){
return SecurityUtils.isAdmin() || SecurityUtils.isUser();
}
return canReadByApplication(identityOptional.get().getAvailableApplication().getId(), userDetails);
}

public boolean canWriteByApplication(Long applicationId, UserDetails userDetails) {
LOGGER.debug("PermissionIdentity canWriteByApplication");
if (applicationId == null) {
return SecurityUtils.isAdmin();
}
return permissionApplication.canWrite(applicationId, userDetails);
}

public boolean canWrite(Long identityId, UserDetails userDetails) {
LOGGER.debug("PermissionIdentity canWrite");
if (identityId == null) {
return false;
}
Optional<Identity> identityOptional = identityRepository.findById(identityId);
if(identityOptional.isEmpty()){
return false;
}
if(identityOptional.get().getAvailableApplication() == null){
return SecurityUtils.isAdmin();
}
return canWriteByApplication(identityOptional.get().getAvailableApplication().getId(), userDetails);
}
}
11 changes: 6 additions & 5 deletions src/main/java/fr/icdc/ebad/service/ApplicationService.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,12 @@

import com.querydsl.core.types.Predicate;
import fr.icdc.ebad.domain.Application;
import fr.icdc.ebad.domain.QIdentity;
import fr.icdc.ebad.domain.UsageApplication;
import fr.icdc.ebad.domain.User;
import fr.icdc.ebad.plugin.dto.ApplicationDiscoverDto;
import fr.icdc.ebad.plugin.plugin.ApplicationConnectorPlugin;
import fr.icdc.ebad.repository.AccreditationRequestRepository;
import fr.icdc.ebad.repository.ApplicationRepository;
import fr.icdc.ebad.repository.TypeFichierRepository;
import fr.icdc.ebad.repository.UsageApplicationRepository;
import fr.icdc.ebad.repository.*;
import fr.icdc.ebad.service.util.EbadServiceException;
import org.pf4j.PluginRuntimeException;
import org.pf4j.PluginWrapper;
Expand Down Expand Up @@ -42,15 +40,17 @@ public class ApplicationService {
private final List<ApplicationConnectorPlugin> applicationConnectorPlugins;
private final SpringPluginManager springPluginManager;
private final AccreditationRequestRepository accreditationRequestRepository;
private final IdentityRepository identityRepository;

public ApplicationService(ApplicationRepository applicationRepository, TypeFichierRepository typeFichierRepository, UsageApplicationRepository usageApplicationRepository, EnvironnementService environnementService, List<ApplicationConnectorPlugin> applicationConnectorPlugins, SpringPluginManager springPluginManager, AccreditationRequestRepository accreditationRequestRepository) {
public ApplicationService(ApplicationRepository applicationRepository, TypeFichierRepository typeFichierRepository, UsageApplicationRepository usageApplicationRepository, EnvironnementService environnementService, List<ApplicationConnectorPlugin> applicationConnectorPlugins, SpringPluginManager springPluginManager, AccreditationRequestRepository accreditationRequestRepository, IdentityRepository identityRepository) {
this.applicationRepository = applicationRepository;
this.typeFichierRepository = typeFichierRepository;
this.usageApplicationRepository = usageApplicationRepository;
this.environnementService = environnementService;
this.applicationConnectorPlugins = applicationConnectorPlugins;
this.springPluginManager = springPluginManager;
this.accreditationRequestRepository = accreditationRequestRepository;
this.identityRepository = identityRepository;
}


Expand Down Expand Up @@ -81,6 +81,7 @@ public void deleteApplication(Long appId) {
accreditationRequestRepository.deleteByApplication(application);
typeFichierRepository.deleteByApplication(application);
application.getEnvironnements().forEach(environnement -> environnementService.deleteEnvironnement(environnement, true));
identityRepository.deleteAll(identityRepository.findAll(QIdentity.identity.availableApplication.id.eq(appId), Pageable.unpaged()));
applicationRepository.delete(application);
}

Expand Down
3 changes: 2 additions & 1 deletion src/main/java/fr/icdc/ebad/service/EnvironnementService.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import fr.icdc.ebad.plugin.dto.NormeDiscoverDto;
import fr.icdc.ebad.plugin.plugin.EnvironnementConnectorPlugin;
import fr.icdc.ebad.repository.*;
import fr.icdc.ebad.security.permission.PermissionIdentity;
import fr.icdc.ebad.service.util.EbadServiceException;
import ma.glasnost.orika.MapperFacade;
import org.jobrunr.scheduling.JobScheduler;
Expand Down Expand Up @@ -191,13 +192,13 @@ public Set<Environnement> importEnvironments(Long applicationId) throws EbadServ

environnement.setName(environnementDiscoverDto.getName());
environnement.setHost(environnementDiscoverDto.getHost());
environnement.setLogin(environnementDiscoverDto.getLogin());
environnement.setHomePath(environnementDiscoverDto.getHome());
environnement.setPrefix(environnementDiscoverDto.getPrefix());
environnement.setNorme(mapper.map(environnementDiscoverDto.getNorme(), Norme.class));
environnement.setExternalId(environnementDiscoverDto.getId());
environnement.setPluginId(pluginId);
environnement.setApplication(application);
//FIXME DTROUILLET ADD DEFAULT IDENTITY

try {
environnementRepository.save(environnement);
Expand Down
98 changes: 98 additions & 0 deletions src/main/java/fr/icdc/ebad/service/IdentityService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package fr.icdc.ebad.service;

import com.querydsl.core.types.Predicate;
import fr.icdc.ebad.domain.Identity;
import fr.icdc.ebad.domain.QIdentity;
import fr.icdc.ebad.repository.IdentityRepository;
import fr.icdc.ebad.service.util.EbadServiceException;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMDecryptorProvider;
import org.bouncycastle.openssl.PEMEncryptedKeyPair;
import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder;
import org.bouncycastle.util.io.pem.PemReader;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.*;
import java.security.KeyPair;
import java.security.Security;
import java.util.Optional;

@Service
public class IdentityService {
private final IdentityRepository identityRepository;

public IdentityService(IdentityRepository identityRepository) {
this.identityRepository = identityRepository;
}

@Transactional
public Identity saveIdentity(Identity identity) {
return identityRepository.save(identity);
}

@Transactional(readOnly = true)
public Optional<Identity> getIdentity(Long id) {
return identityRepository.findById(id);
}

@Transactional
public void deleteIdentity(Long id) {
identityRepository.deleteById(id);
}

@Transactional(readOnly = true)
public Page<Identity> findWithoutApp(Predicate predicate, Pageable pageable) {
Predicate predicateAll = QIdentity.identity.availableApplication.isNull().and(predicate);
return identityRepository.findAll(predicateAll, pageable);
}

@Transactional(readOnly = true)
public Page<Identity> findAllByApplication(Long applicationId, Predicate predicate, Pageable pageable) {
Predicate predicateAll = QIdentity.identity.availableApplication.id.eq(applicationId).and(predicate);
return identityRepository.findAll(predicateAll, pageable);
}


public KeyPair createKeyPair(Identity identity) throws EbadServiceException {
Security.addProvider(new BouncyCastleProvider());
String password = identity.getPassphrase();

try(
PemReader pemReader = new PemReader(openReader(identity));
PEMParser pemParser = new PEMParser(pemReader)
) {
Object pemKeyPair = pemParser.readObject();

if (pemKeyPair instanceof PEMEncryptedKeyPair) {
if (password == null) {
throw new EbadServiceException("Unable to import private key. Key is encrypted, but no password was provided.");
}
PEMDecryptorProvider decryptor = new JcePEMDecryptorProviderBuilder().build(password.toCharArray());
PEMKeyPair decryptedKeyPair = ((PEMEncryptedKeyPair) pemKeyPair).decryptKeyPair(decryptor);
return new JcaPEMKeyConverter().getKeyPair(decryptedKeyPair);
} else {
return new JcaPEMKeyConverter().getKeyPair((PEMKeyPair) pemKeyPair);
}
}catch (IOException e){
throw new EbadServiceException("Error when trying to read ssh key file\n" +
"Make sur use RSA keys, try to convert your key with \n" +
"ssh-keygen -p -P \"old passphrase\" -N \"new passphrase\" -m pem -f path/to/key \n" +
"THIS OVERWRITE YOUR KEY SO MAKE A BACKUP BEFORE", e);
}
}

private Reader openReader(Identity identity) throws FileNotFoundException, EbadServiceException {
if(identity.getPrivatekeyPath() != null)
return new FileReader(identity.getPrivatekeyPath());
if(identity.getPrivatekey() != null)
return new StringReader(identity.getPrivatekey());

throw new EbadServiceException("No key is provided");
}
}
Loading

0 comments on commit e0bba33

Please sign in to comment.