Skip to content

Commit

Permalink
Encrypt passwords stored in the database (geonetwork#5476)
Browse files Browse the repository at this point in the history
* Encode passwords stored in the database

* Encode passwords stored in the database - allow to pass the encryptor password as a Java environment variable or System environment variable

* Encode passwords stored in the database:
- Support encryptor algorithm in enviromental variable.
- Display a warning if environmental variables for encryptor password and algorithm differ from the properties file and ignore them.
- Throw a runtime exception if the encrytor initialization fails

* Encode passwords stored in the database: support encryptor properties updates in environment variables. Updates the encrypted values in the database with the new configuration

* Copy the encryptor folder from its default location to the configure one

On the data directory initialization copy the encryptor files to the config folder from
its default location if the configured config dir doesn't contain an encryptor folder yet.

* Refactor code.

Format and refactor code. Use try-with-resources. Update some logs sentences.
Write the encryptor.properties update time in the file header.

Co-authored-by: Juan Luis Rodríguez <juanluisrp@gmail.com>
  • Loading branch information
josegar74 and juanluisrp authored Apr 13, 2021
1 parent bff2410 commit 62f180f
Show file tree
Hide file tree
Showing 32 changed files with 789 additions and 66 deletions.
4 changes: 4 additions & 0 deletions core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,10 @@
<artifactId>aws-java-sdk-s3</artifactId>
<version>1.11.618</version>
</dependency>
<dependency>
<groupId>org.jasypt</groupId>
<artifactId>jasypt</artifactId>
</dependency>
</dependencies>

<build>
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/java/jeeves/server/JeevesEngine.java
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,7 @@ private void initAppHandler(Element handler, JeevesServlet servlet) throws Excep

info("--- Handler started ---------------------------------------");
} catch (Exception e) {
Map<String, String> errors = new HashMap<String, String>();
Map<String, String> errors = new HashMap<>();
String eS = "Raised exception while starting the application. " +
"Fix the error and restart.";
error(eS);
Expand Down
1 change: 1 addition & 0 deletions core/src/main/java/org/fao/geonet/constants/Geonet.java
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ public static final class File {
public static final String UPDATE_FIXED_INFO = "update-fixed-info.xsl";
public static final String UPDATE_FIXED_INFO_SUBTEMPLATE = "update-fixed-info-subtemplate.xsl";
public static final String UPDATE_CHILD_FROM_PARENT_INFO = "update-child-from-parent-info.xsl";
public static final String ENCRYPTOR_DIR = "encryptor";
public static final String EXTRACT_UUID = "extract-uuid.xsl";
public static final String EXTRACT_TITLES = "extract-titles.xsl";
public static final String EXTRACT_DEFAULT_LANGUAGE = "extract-default-language.xsl";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,27 @@ private void initDataDirectory() throws IOException {
}
}

Path encryptorFolder = configDir.resolve(Geonet.File.ENCRYPTOR_DIR);
if (!Files.exists(encryptorFolder)) {
Log.info(Geonet.DATA_DIRECTORY, " - Copying encryptor directory...");
try {
final Path srcEncryptorDir = getDefaultDataDir(webappDir).resolve("config").resolve(Geonet.File.ENCRYPTOR_DIR);
final Path destDir = this.configDir.resolve(Geonet.File.ENCRYPTOR_DIR);
// Copy encryptor dir if doesn't exist
if (!Files.exists(destDir)) {
IO.copyDirectoryOrFile(srcEncryptorDir, destDir, true);
}

} catch (IOException e) {
Log.info(
Geonet.DATA_DIRECTORY,
" - Error copying encryptor config directory: "
+ e.getMessage());
throw e;
}

}

final Path locDir = webappDir.resolve("loc");
if (!Files.exists(locDir)) {
Files.createDirectories(locDir);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,12 @@ public boolean setValues(Map<String, Object> values) {
* When adding to a newly created node, path must be 'id:...'.
*/
public String add(String path, Object name, Object value) {
if (name == null)
return add(path, name, value, false);
}

public String add(String path, Object name, Object value, boolean encrypted) {

if (name == null)
throw new IllegalArgumentException("Name cannot be null");

String sName = makeString(name);
Expand All @@ -231,7 +236,8 @@ public String add(String path, Object name, Object value) {
if (parent == null)
return null;

HarvesterSetting child = new HarvesterSetting().setParent(parent).setName(sName).setValue(sValue);
HarvesterSetting child = new HarvesterSetting().setParent(parent)
.setName(sName).setValue(sValue).setEncrypted(encrypted);

settingsRepo.save(child);
return Integer.toString(child.getId());
Expand Down
9 changes: 8 additions & 1 deletion domain/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,14 @@
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.jasypt</groupId>
<artifactId>jasypt</artifactId>
</dependency>
<dependency>
<groupId>org.jasypt</groupId>
<artifactId>jasypt-hibernate5</artifactId>
</dependency>
</dependencies>

<build>
Expand Down
121 changes: 80 additions & 41 deletions domain/src/main/java/org/fao/geonet/domain/HarvesterSetting.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2001-2016 Food and Agriculture Organization of the
* Copyright (C) 2001-2021 Food and Agriculture Organization of the
* United Nations (FAO-UN), United Nations World Food Programme (WFP)
* and United Nations Environment Programme (UNEP)
*
Expand Down Expand Up @@ -27,9 +27,6 @@

import org.fao.geonet.entitylistener.HarvesterSettingEntityListenerManager;
import org.hibernate.annotations.Type;
import static javax.persistence.CascadeType.DETACH;
import static javax.persistence.CascadeType.MERGE;
import static javax.persistence.CascadeType.PERSIST;

import java.util.HashSet;
import java.util.Set;
Expand All @@ -53,13 +50,8 @@
import javax.persistence.Table;
import javax.persistence.Transient;

import org.fao.geonet.entitylistener.HarvesterSettingEntityListenerManager;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;
import org.hibernate.annotations.Type;
import org.hibernate.engine.internal.Cascade;

import com.google.common.collect.Sets;

/**
* An entity representing a harvester configuration setting.
Expand All @@ -78,10 +70,18 @@ public class HarvesterSetting extends GeonetEntity {
static final String ID_SEQ_NAME = "harvester_setting_id_seq";
private static final HashSet<String> EXCLUDE_FROM_XML = Sets.newHashSet("valueAsBool", "valueAsInt");

private int _id;
private HarvesterSetting _parent;
private String _name;
private String _value;
private int id;
private HarvesterSetting parent;
private String name;
/**
* If the setting is not encrypted: value = storedValue, otherwise value contains the unencrypted value.
*
* Should be used the methods for value property. storedValue is managed in
* {@link org.fao.geonet.entitylistener.HarvesterSettingValueSetter}.
*/
private String storedValue;
private String value;
private char encrypted = Constants.YN_FALSE;

/**
* Get the setting id. This is a generated value and as such new instances should not have this
Expand All @@ -93,7 +93,7 @@ public class HarvesterSetting extends GeonetEntity {
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = ID_SEQ_NAME)
@Column(name = "id", nullable = false)
public int getId() {
return _id;
return id;
}

/**
Expand All @@ -104,7 +104,7 @@ public int getId() {
* @return this setting object
*/
public HarvesterSetting setId(int id) {
this._id = id;
this.id = id;
return this;
}

Expand All @@ -117,7 +117,7 @@ public HarvesterSetting setId(int id) {
public
@Nullable
HarvesterSetting getParent() {
return _parent;
return parent;
}

/**
Expand All @@ -129,7 +129,7 @@ HarvesterSetting getParent() {
public
@Nonnull
HarvesterSetting setParent(@Nullable HarvesterSetting parent) {
this._parent = parent;
this.parent = parent;
return this;
}

Expand All @@ -142,7 +142,7 @@ HarvesterSetting setParent(@Nullable HarvesterSetting parent) {
public
@Nonnull
String getName() {
return _name;
return name;
}

/**
Expand All @@ -154,7 +154,7 @@ String getName() {
public
@Nonnull
HarvesterSetting setName(@Nonnull String name) {
this._name = name;
this.name = name;
return this;
}

Expand All @@ -167,23 +167,33 @@ HarvesterSetting setName(@Nonnull String name) {
// this is a work around for postgres so postgres can correctly load clobs
public
@Nullable
String getValue() {
return _value;
String getStoredValue() {
return storedValue;
}

/**
* Set the value of setting with a boolean.
* Set the value of setting.
*
* @param value the new value
* @return this setting object
*/
public HarvesterSetting setValue(boolean value) {
return setValue(String.valueOf(value));
public HarvesterSetting setStoredValue(@Nullable String value) {
this.storedValue = value;
return this;
}

public HarvesterSetting setValue(@Nullable String value) {
this._value = value;
return this;
/**
* Get the values as a boolean. Returns false if the values is not a boolean.
*
* @return the values as a boolean
* @throws NullPointerException if the value is null.
*/
@Transient
public boolean getValueAsBool() throws NullPointerException {
if (getValue() == null) {
throw new NullPointerException("Setting value of " + getName() + " is null");
}
return Boolean.parseBoolean(value);
}

/**
Expand All @@ -201,27 +211,56 @@ public int getValueAsInt() throws NullPointerException, NumberFormatException {
}

/**
* Set the value of setting with an integer.
* For backwards compatibility we need the activated column to be either 'n' or 'y'.
* This is a workaround to allow this until future
* versions of JPA that allow different ways of controlling how types are mapped to the database.
*/
@Column(name = "encrypted", nullable = false, length = 1, columnDefinition="char default 'n'")
protected char getEncrypted_JpaWorkaround() {
return encrypted;
}

/**
* Set the column value. Constants.YN_ENABLED for true Constants.YN_DISABLED for false.
*
* @param value the new value
* @return this setting object
* @param encryptedValue the column value. Constants.YN_ENABLED for true Constants.YN_DISABLED for false.
* @return
*/
public HarvesterSetting setValue(int value) {
return setValue(String.valueOf(value));
protected void setEncrypted_JpaWorkaround(char encryptedValue) {
encrypted = encryptedValue;
}

/**
* Get the values as a boolean. Returns false if the values is not a boolean.
* Return true if the setting is public.
*
* @return the values as a boolean
* @throws NullPointerException if the value is null.
* @return true if the setting is public.
*/
@Transient
public boolean getValueAsBool() throws NullPointerException {
if (getValue() == null) {
throw new NullPointerException("Setting value of " + getName() + " is null");
}
return Boolean.parseBoolean(_value);
public boolean isEncrypted() {
return Constants.toBoolean_fromYNChar(getEncrypted_JpaWorkaround());
}

/**
* Set true if the setting is private.
*
* @param encrypted true if the setting is private.
*/
public HarvesterSetting setEncrypted(boolean encrypted) {
setEncrypted_JpaWorkaround(Constants.toYN_EnabledChar(encrypted));
return this;
}

@Transient
public String getValue() {
return value;
}

public HarvesterSetting setValue(@Nullable String value) {
this.value = value;
// Required to trigger PreUpdate event in {@link org.fao.geonet.entitylistener.SettingEntityListenerManager},
// otherwise doesn't work with transient properties
this.setStoredValue(value);
return this;
}

@Override
Expand All @@ -231,6 +270,6 @@ protected Set<String> propertiesToExcludeFromXml() {

@Override
public String toString() {
return "Setting [id=" + _id + ", name=" + _name + ", value=" + _value + "]";
return "Setting [id=" + id + ", name=" + name + ", value=" + value + "]";
}
}
8 changes: 4 additions & 4 deletions domain/src/main/java/org/fao/geonet/domain/MapServer.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2001-2016 Food and Agriculture Organization of the
* Copyright (C) 2001-2021 Food and Agriculture Organization of the
* United Nations (FAO-UN), United Nations World Food Programme (WFP)
* and United Nations Environment Programme (UNEP)
*
Expand All @@ -24,12 +24,10 @@
package org.fao.geonet.domain;

import org.fao.geonet.entitylistener.MapServerEntityListenerManager;
import org.hibernate.annotations.Type;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.persistence.*;

import java.util.Map;

/**
* An entity representing mapserver used for publishing records as WMS, WFS, WCS.
Expand All @@ -38,6 +36,7 @@
*
* @author Francois
*/

@Entity
@Table(name = "Mapservers")
@Cacheable
Expand Down Expand Up @@ -232,6 +231,7 @@ public MapServer setUsername(String _username) {
* @return the password.
*/
@Column(length = 128)
@Type(type="encryptedString")
public String getPassword() {
return _password;
}
Expand Down
Loading

0 comments on commit 62f180f

Please sign in to comment.