Skip to content

Commit cf27a08

Browse files
Add encryption/decryption to subscription passwords (#390)
* Add encryption/decryption to subscription passwords
1 parent 099ac51 commit cf27a08

File tree

19 files changed

+508
-143
lines changed

19 files changed

+508
-143
lines changed
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
package com.ericsson.ei.encryption;
2+
3+
import static org.junit.Assert.assertEquals;
4+
import static org.junit.Assert.assertTrue;
5+
import static org.mockserver.integration.ClientAndServer.startClientAndServer;
6+
import static org.mockserver.model.HttpRequest.request;
7+
import static org.mockserver.model.HttpResponse.response;
8+
import static org.slf4j.LoggerFactory.getLogger;
9+
10+
import java.io.IOException;
11+
import java.util.ArrayList;
12+
import java.util.List;
13+
import java.util.concurrent.TimeUnit;
14+
15+
import org.json.JSONObject;
16+
import org.junit.Ignore;
17+
import org.mockserver.integration.ClientAndServer;
18+
import org.mockserver.verify.VerificationTimes;
19+
import org.slf4j.Logger;
20+
import org.springframework.boot.web.server.LocalServerPort;
21+
import org.springframework.http.HttpStatus;
22+
import org.springframework.http.ResponseEntity;
23+
import org.springframework.test.context.TestPropertySource;
24+
25+
import com.ericsson.ei.utils.FunctionalTestBase;
26+
import com.ericsson.ei.utils.HttpRequest;
27+
import com.ericsson.eiffelcommons.subscriptionobject.RestPostSubscriptionObject;
28+
29+
import cucumber.api.java.Before;
30+
import cucumber.api.java.en.Then;
31+
import cucumber.api.java.en.When;
32+
33+
@Ignore
34+
@TestPropertySource(properties = { "spring.data.mongodb.database: EncryptionSteps",
35+
"missedNotificationDataBaseName: EncryptionSteps-missedNotifications",
36+
"rabbitmq.exchange.name: EncryptionSteps-exchange",
37+
"rabbitmq.consumerName: EncryptionSteps-consumer",
38+
"jasypt.encryptor.password: secret" })
39+
public class EncryptionSteps extends FunctionalTestBase {
40+
41+
private static final Logger LOGGER = getLogger(EncryptionSteps.class);
42+
43+
private static final String EIFFEL_EVENTS_JSON_PATH = "src/functionaltests/resources/eiffel_events_for_test.json";
44+
private static final String SUBSCRIPTION_GET_ENDPOINT = "/subscriptions/<name>";
45+
private static final String SUBSCRIPTION_ROOT_ENDPOINT = "/subscriptions";
46+
private static final String MOCK_ENDPOINT = "/notify-me";
47+
private static final String AUTH_HEADER_NAME = "Authorization";
48+
private static final String AUTH_HEADER_VALUE = "Basic dXNlcm5hbWU6cGFzc3dvcmQ=";
49+
private static final int NOTIFICATION_SLEEP = 5;
50+
51+
private static final String SUBSCRIPTION_NAME = "MySubscription";
52+
private static final String MEDIA_TYPE = "application/x-www-form-urlencoded";
53+
private static final String NOTIFICATION_META = "http://localhost:{port}/notify-me";
54+
private static final String CONDITION_KEY = "jmespath";
55+
private static final String CONDITION_VALUE = "split(identity, '/') | [1] =='com.mycompany.myproduct'";
56+
private static final String USERNAME = "username";
57+
private static final String PASSWORD = "password";
58+
private static final String AUTH_TYPE = "BASIC_AUTH";
59+
60+
@LocalServerPort
61+
private int applicationPort;
62+
63+
private ResponseEntity response;
64+
private String mockServerPort;
65+
private ClientAndServer clientAndServer;
66+
67+
@Before
68+
public void init() throws IOException {
69+
clientAndServer = startClientAndServer();
70+
mockServerPort = String.valueOf(clientAndServer.getLocalPort());
71+
setUpMock();
72+
}
73+
74+
@When("^a subscription is created$")
75+
public void subscriptionIsCreated() throws Exception {
76+
LOGGER.debug("Creating a subscription.");
77+
RestPostSubscriptionObject subscription = new RestPostSubscriptionObject(SUBSCRIPTION_NAME);
78+
subscription.setNotificationMeta(NOTIFICATION_META.replace("{port}", mockServerPort));
79+
subscription.setRestPostBodyMediaType(MEDIA_TYPE);
80+
subscription.addNotificationMessageKeyValue("json", "dummy");
81+
JSONObject condition = new JSONObject().put(CONDITION_KEY, CONDITION_VALUE);
82+
subscription.addConditionToRequirement(0, condition);
83+
subscription.setUsername(USERNAME);
84+
subscription.setPassword(PASSWORD);
85+
subscription.setAuthenticationType(AUTH_TYPE);
86+
87+
List<String> subscriptionsToSend = new ArrayList<>();
88+
subscriptionsToSend.add(subscription.toString());
89+
postSubscriptions(subscriptionsToSend.toString());
90+
validateSubscriptionsSuccessfullyAdded();
91+
}
92+
93+
@When("^eiffel events are sent$")
94+
public void eiffelEventsAreSent() throws IOException, InterruptedException {
95+
LOGGER.debug("Sending Eiffel events.");
96+
List<String> eventNamesToSend = getEventNamesToSend();
97+
eventManager.sendEiffelEvents(EIFFEL_EVENTS_JSON_PATH, eventNamesToSend);
98+
List<String> eventsIdList = eventManager.getEventsIdList(EIFFEL_EVENTS_JSON_PATH,
99+
eventNamesToSend);
100+
List<String> missingEventIds = dbManager.verifyEventsInDB(eventsIdList, 0);
101+
String errorMessage = "The following events are missing in mongoDB: "
102+
+ missingEventIds.toString();
103+
assertEquals(errorMessage, 0, missingEventIds.size());
104+
}
105+
106+
@Then("^the password should be encrypted in the database$")
107+
public void passwordShouldBeEncrypted() {
108+
LOGGER.debug("Verifying encoded password in subscription.");
109+
String json = dbManager.getSubscription(SUBSCRIPTION_NAME);
110+
JSONObject subscription = new JSONObject(json);
111+
String password = subscription.getString("password");
112+
assertTrue(EncryptionFormatter.isEncrypted(password));
113+
}
114+
115+
@Then("^the subscription should trigger$")
116+
public void subscriptionShouldTrigger() throws InterruptedException {
117+
LOGGER.info("Verifying that subscription was triggered");
118+
TimeUnit.SECONDS.sleep(NOTIFICATION_SLEEP);
119+
clientAndServer.verify(request().withPath(MOCK_ENDPOINT).withBody(""),
120+
VerificationTimes.atLeast(1));
121+
}
122+
123+
@Then("^the notification should be sent with a decrypted password$")
124+
public void notificationShouldBeSentWithDecryptedPassword() {
125+
LOGGER.info("Verifying that notification contains decrypted password");
126+
clientAndServer.verify(
127+
request().withPath(MOCK_ENDPOINT).withHeader(AUTH_HEADER_NAME, AUTH_HEADER_VALUE),
128+
VerificationTimes.atLeast(1));
129+
}
130+
131+
private void postSubscriptions(String jsonDataAsString) throws Exception {
132+
HttpRequest postRequest = new HttpRequest(HttpRequest.HttpMethod.POST);
133+
response = postRequest.setHost(getHostName())
134+
.setPort(applicationPort)
135+
.addHeader("content-type", "application/json")
136+
.addHeader("Accept", "application/json")
137+
.setEndpoint(SUBSCRIPTION_ROOT_ENDPOINT)
138+
.setBody(jsonDataAsString)
139+
.performRequest();
140+
assertEquals("Expected to add subscription to EI", HttpStatus.OK.value(),
141+
response.getStatusCodeValue());
142+
}
143+
144+
private void validateSubscriptionsSuccessfullyAdded()
145+
throws Exception {
146+
String endpoint = SUBSCRIPTION_GET_ENDPOINT.replaceAll("<name>", SUBSCRIPTION_NAME);
147+
HttpRequest getRequest = new HttpRequest(HttpRequest.HttpMethod.GET);
148+
response = getRequest.setHost(getHostName())
149+
.setPort(applicationPort)
150+
.addHeader("content-type", "application/json")
151+
.addHeader("Accept", "application/json")
152+
.setEndpoint(endpoint)
153+
.performRequest();
154+
assertEquals("Subscription successfully added in EI: ", HttpStatus.OK.value(),
155+
response.getStatusCodeValue());
156+
}
157+
158+
private List<String> getEventNamesToSend() {
159+
List<String> eventNames = new ArrayList<>();
160+
eventNames.add("event_EiffelArtifactCreatedEvent_3");
161+
return eventNames;
162+
}
163+
164+
private void setUpMock() {
165+
clientAndServer.when(request().withMethod("POST").withPath(MOCK_ENDPOINT))
166+
.respond(response().withStatusCode(200).withBody(""));
167+
}
168+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.ericsson.ei.encryption;
2+
3+
import org.junit.runner.RunWith;
4+
5+
import cucumber.api.CucumberOptions;
6+
import cucumber.api.junit.Cucumber;
7+
8+
@RunWith(Cucumber.class)
9+
@CucumberOptions(features = "src/functionaltests/resources/features/encryption.feature", glue = {
10+
"com.ericsson.ei.encryption" }, plugin = {
11+
"html:target/cucumber-reports/TestEncryptionRunner" })
12+
public class TestEncryptionRunner {
13+
14+
}

src/functionaltests/java/com/ericsson/ei/notifications/trigger/SubscriptionNotificationSteps.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,6 @@ public void missed_notification_db_should_contain_x_objects(int maxObjectsInDB)
211211
String condition = "{}";
212212
int missedNotifications = getDbSizeForCondition(minWaitTime, maxWaittime, maxObjectsInDB, condition);
213213

214-
assertEquals(missedNotifications, maxObjectsInDB);
215214
assertEquals("Number of missed notifications saved in the database: " + missedNotifications, maxObjectsInDB,
216215
missedNotifications);
217216
}
@@ -451,6 +450,7 @@ private int getDbSizeForCondition(int minWaitTime, int maxWaitTime, int expected
451450
}
452451
System.out.println("##########################################################");
453452
LOGGER.error("DB size did not match expected, Subsctiptions:\n{}", queryResult);
453+
454454
return queryResult.size();
455455
}
456456
}

src/functionaltests/java/com/ericsson/ei/utils/DataBaseManager.java

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import java.util.concurrent.TimeUnit;
66

77
import org.bson.Document;
8+
import org.bson.conversions.Bson;
89
import org.springframework.beans.factory.annotation.Autowired;
910
import org.springframework.beans.factory.annotation.Value;
1011
import org.springframework.boot.autoconfigure.mongo.MongoProperties;
@@ -13,6 +14,7 @@
1314
import com.mongodb.MongoClient;
1415
import com.mongodb.client.MongoCollection;
1516
import com.mongodb.client.MongoDatabase;
17+
import com.mongodb.client.model.Filters;
1618

1719
import gherkin.deps.com.google.gson.JsonObject;
1820
import gherkin.deps.com.google.gson.JsonParser;
@@ -25,14 +27,17 @@ public class DataBaseManager {
2527
private String database;
2628

2729
@Value("${event_object_map.collection.name}")
28-
private String collection;
30+
private String eventMapCollection;
2931

3032
@Value("${aggregated.collection.name}")
3133
private String aggregatedCollectionName;
3234

3335
@Value("${waitlist.collection.name}")
3436
private String waitlistCollectionName;
3537

38+
@Value("${subscription.collection.name}")
39+
private String subscriptionCollectionName;
40+
3641
@Getter
3742
@Autowired
3843
private MongoProperties mongoProperties;
@@ -78,8 +83,8 @@ public boolean verifyAggregatedObjectExistsInDB() throws InterruptedException {
7883
while (stopTime > System.currentTimeMillis()) {
7984
mongoClient = new MongoClient(getMongoDbHost(), getMongoDbPort());
8085
MongoDatabase db = mongoClient.getDatabase(database);
81-
MongoCollection<Document> table = db.getCollection(aggregatedCollectionName);
82-
List<Document> documents = table.find().into(new ArrayList<>());
86+
MongoCollection<Document> collection = db.getCollection(aggregatedCollectionName);
87+
List<Document> documents = collection.find().into(new ArrayList<>());
8388
TimeUnit.MILLISECONDS.sleep(1000);
8489
if (!documents.isEmpty()) {
8590
return true;
@@ -98,8 +103,8 @@ public boolean verifyAggregatedObjectExistsInDB() throws InterruptedException {
98103
private List<String> compareArgumentsWithAggregatedObjectInDB(List<String> checklist) {
99104
mongoClient = new MongoClient(getMongoDbHost(), getMongoDbPort());
100105
MongoDatabase db = mongoClient.getDatabase(database);
101-
MongoCollection<Document> table = db.getCollection(aggregatedCollectionName);
102-
List<Document> documents = table.find().into(new ArrayList<>());
106+
MongoCollection<Document> collection = db.getCollection(aggregatedCollectionName);
107+
List<Document> documents = collection.find().into(new ArrayList<>());
103108
for (Document document : documents) {
104109
for (String expectedValue : new ArrayList<>(checklist)) {
105110
if (document.toString().contains(expectedValue)) {
@@ -140,8 +145,8 @@ public List<String> verifyEventsInDB(List<String> eventsIdList, int extraCheckDe
140145
private List<String> compareSentEventsWithEventsInDB(List<String> checklist) {
141146
mongoClient = new MongoClient(getMongoDbHost(), getMongoDbPort());
142147
MongoDatabase db = mongoClient.getDatabase(database);
143-
MongoCollection<Document> table = db.getCollection(collection);
144-
List<Document> documents = table.find().into(new ArrayList<>());
148+
MongoCollection<Document> collection = db.getCollection(eventMapCollection);
149+
List<Document> documents = collection.find().into(new ArrayList<>());
145150
for (Document document : documents) {
146151
for (String expectedID : new ArrayList<>(checklist)) {
147152
if (expectedID.equals(document.get("_id").toString())) {
@@ -174,8 +179,23 @@ public String getValueFromQuery(List<String> databaseQueryResult, String key, in
174179
public int waitListSize() {
175180
mongoClient = new MongoClient(getMongoDbHost(), getMongoDbPort());
176181
MongoDatabase db = mongoClient.getDatabase(database);
177-
MongoCollection<Document> table = db.getCollection(waitlistCollectionName);
178-
List<Document> documents = table.find().into(new ArrayList<>());
182+
MongoCollection<Document> collection = db.getCollection(waitlistCollectionName);
183+
List<Document> documents = collection.find().into(new ArrayList<>());
179184
return documents.size();
180185
}
186+
187+
/**
188+
* Get a specific subscription from the database based on the subscription name.
189+
*
190+
* @param subscriptionName
191+
* @return the document as JSON string
192+
*/
193+
public String getSubscription(String subscriptionName) {
194+
mongoClient = new MongoClient(getMongoDbHost(), getMongoDbPort());
195+
MongoDatabase db = mongoClient.getDatabase(database);
196+
MongoCollection<Document> collection = db.getCollection(subscriptionCollectionName);
197+
Bson filter = Filters.eq("subscriptionName", subscriptionName);
198+
Document document = collection.find(filter).first();
199+
return document.toJson();
200+
}
181201
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
@Encryption
2+
Feature: Test Encryption on subscription passwords
3+
4+
@EncryptionScenario
5+
Scenario: Encryption and decryption of subscription password
6+
When a subscription is created
7+
Then the password should be encrypted in the database
8+
When eiffel events are sent
9+
Then the subscription should trigger
10+
And the notification should be sent with a decrypted password

src/main/java/com/ericsson/ei/EndpointSecurity.java

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,11 @@
1919

2020
import org.apache.tomcat.util.codec.binary.Base64;
2121
import org.apache.tomcat.util.codec.binary.StringUtils;
22-
import org.jasypt.encryption.pbe.StandardPBEStringEncryptor;
2322
import org.json.JSONArray;
2423
import org.json.JSONObject;
2524
import org.slf4j.Logger;
2625
import org.slf4j.LoggerFactory;
26+
import org.springframework.beans.factory.annotation.Autowired;
2727
import org.springframework.beans.factory.annotation.Value;
2828
import org.springframework.context.annotation.Configuration;
2929
import org.springframework.http.HttpMethod;
@@ -33,8 +33,9 @@
3333
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
3434
import org.springframework.security.config.http.SessionCreationPolicy;
3535

36+
import com.ericsson.ei.encryption.EncryptionFormatter;
37+
import com.ericsson.ei.encryption.Encryptor;
3638
import com.ericsson.ei.exception.AbortExecutionException;
37-
import com.ericsson.ei.utils.TextFormatter;
3839

3940
@Configuration
4041
@EnableWebSecurity
@@ -48,8 +49,8 @@ public class EndpointSecurity extends WebSecurityConfigurerAdapter {
4849
@Value("${ldap.server.list:}")
4950
private String ldapServerList;
5051

51-
@Value("${jasypt.encryptor.password:}")
52-
private String jasyptEncryptorPassword;
52+
@Autowired
53+
private Encryptor encryptor;;
5354

5455
@Override
5556
protected void configure(HttpSecurity http) throws Exception {
@@ -97,7 +98,7 @@ private void addLDAPServersFromList(JSONArray serverList, AuthenticationManagerB
9798
JSONObject server = (JSONObject) serverList.get(i);
9899
String password = server.getString("password");
99100

100-
if (checkIfPasswordEncrypted(password)) {
101+
if (EncryptionFormatter.isEncrypted(password)) {
101102
password = decryptPassword(password);
102103
}
103104
else {
@@ -116,23 +117,13 @@ private void addLDAPServersFromList(JSONArray serverList, AuthenticationManagerB
116117
}
117118
}
118119

119-
private boolean checkIfPasswordEncrypted(final String password) {
120-
return (password.startsWith("ENC(") && password.endsWith(")"));
121-
}
122-
123120
private String decryptPassword(final String inputEncryptedPassword) throws Exception {
124-
TextFormatter textFormatter = new TextFormatter();
125-
StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor();
126-
127-
if (jasyptEncryptorPassword.isEmpty()) {
121+
if (encryptor.isJasyptPasswordSet()) {
128122
LOGGER.error("Property -jasypt.encryptor.password need to be set for decrypting LDAP password.");
129123
throw new AbortExecutionException("Failed to initiate LDAP when password is encrypted. " +
130124
"Property -jasypt.encryptor.password need to be set for decrypting LDAP password.");
131125
}
132126

133-
encryptor.setPassword(jasyptEncryptorPassword);
134-
135-
String encryptedPassword = textFormatter.removeEncryptionParentheses(inputEncryptedPassword);
136-
return encryptor.decrypt(encryptedPassword);
127+
return encryptor.decrypt(inputEncryptedPassword);
137128
}
138129
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.ericsson.ei.controller.model;
2+
3+
public enum AuthenticationType {
4+
NO_AUTH("NO_AUTH"), BASIC_AUTH("BASIC_AUTH"),
5+
BASIC_AUTH_JENKINS_CSRF("BASIC_AUTH_JENKINS_CSRF");
6+
7+
private final String authenticationType;
8+
9+
private AuthenticationType(String authenticationType) {
10+
this.authenticationType = authenticationType;
11+
}
12+
13+
public String getValue() {
14+
return this.authenticationType;
15+
}
16+
}

0 commit comments

Comments
 (0)