Skip to content
This repository has been archived by the owner on May 16, 2023. It is now read-only.

feat: email notification #71

Draft
wants to merge 26 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@
<opencsv.version>5.3</opencsv.version>
<springTestAddons.version>2.5.2</springTestAddons.version>
<shedlock.version>4.23.0</shedlock.version>
<gson.version>2.8.6</gson.version>
<jackson.version>2.11.1</jackson.version>
<openstack4j.version>1.0.26</openstack4j.version>
<!-- plugins -->
<plugin.checkstyle.version>3.1.2</plugin.checkstyle.version>
<license.projectName>Corona-Warn-App / cwa-quick-test-backend</license.projectName>
Expand Down Expand Up @@ -171,15 +174,26 @@
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.6</version>
<version>${gson.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>${jackson.version}</version>
</dependency>
<!-- dgc -->
<dependency>
<groupId>com.upokecenter</groupId>
<artifactId>cbor</artifactId>
<version>4.0.1</version>
</dependency>
<!-- OTC -->
<dependency>
<groupId>com.huawei</groupId>
<artifactId>openstack4j</artifactId>
<version>${openstack4j.version}</version>
</dependency>

<!-- DB drivers -->
<dependency>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*-
* ---license-start
* Corona-Warn-App / cwa-quick-test-backend
* ---
* Copyright (C) 2021 T-Systems International GmbH and all other contributors
* ---
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.
* ---license-end
*/

package app.coronawarn.quicktest.client;

import app.coronawarn.quicktest.config.SmsConfig;
import app.coronawarn.quicktest.model.SmsMessage;
import com.huawei.openstack4j.api.OSClient;
import com.huawei.openstack4j.api.exceptions.AuthenticationException;
import com.huawei.openstack4j.core.transport.Config;
import com.huawei.openstack4j.model.common.Identifier;
import com.huawei.openstack4j.model.identity.v3.Token;
import com.huawei.openstack4j.openstack.OSFactory;
import com.huawei.openstack4j.openstack.message.notification.domain.MessageIdResponse;
import org.springframework.stereotype.Component;

@Component
public class OsFactoryWrapper {

/**
* Wrapping the static client creation.
* @param config Overridden Endpoint
* @param otcAuth authentication information
* @return Authentication token
* @throws AuthenticationException thrown if authentication fails
*/
public Token authenticate(Config config, SmsConfig.OtcAuth otcAuth) throws AuthenticationException {
OSClient.OSClientV3 openstackClient = OSFactory.builderV3().withConfig(config)
.endpoint(otcAuth.getAuthUrl())
.credentials(otcAuth.getUser(), otcAuth.getPassword(), Identifier.byId(otcAuth.getDomainId()))
.scopeToProject(Identifier.byId(otcAuth.getProjectId()))
.authenticate();
return openstackClient.getToken();
}

public void refreshToken() {
OSFactory.refreshToken();
}

/**
* Wrapping the static Notification sms send call.
*
* @param token Authentication token
* @param config Overriden Endpoint
* @param sms Message to be sent
* @return messageId and requestId
*/
public MessageIdResponse sendSms(Token token, Config config, SmsMessage sms) {
// Get client from token to enable multithreaded sessions and prevent the necessity of reauthentication
// http://www.openstack4j.com/learn/threads/
return OSFactory.clientFromToken(token).withConfig(config)
.notification().sms()
.send(sms.getEndpoint(), sms.getMessage(), null);
}
}
17 changes: 17 additions & 0 deletions src/main/java/app/coronawarn/quicktest/client/RkiToolClient.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package app.coronawarn.quicktest.client;

import feign.Response;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;

@FeignClient(
name = "rkiToolClient",
url = "${quicktest.health-department-download-url}",
configuration = RkiToolClientConfig.class
)
public interface RkiToolClient {

@GetMapping(value = "")
Response downloadFile();

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package app.coronawarn.quicktest.client;

import feign.Client;
import feign.httpclient.ApacheHttpClient;
import lombok.RequiredArgsConstructor;
import org.apache.http.impl.client.HttpClientBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@RequiredArgsConstructor
public class RkiToolClientConfig {

/**
* HttpClient for connection to Rki Server.
*
* @return Instance of HttpClient
*/
@Bean(name = "RkiHttpClient")
public Client client() {
return new ApacheHttpClient(HttpClientBuilder.create()
.build());
}
}
31 changes: 31 additions & 0 deletions src/main/java/app/coronawarn/quicktest/client/SmsClient.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*-
* ---license-start
* Corona-Warn-App / cwa-quick-test-backend
* ---
* Copyright (C) 2021 T-Systems International GmbH and all other contributors
* ---
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.
* ---license-end
*/

package app.coronawarn.quicktest.client;

import app.coronawarn.quicktest.exception.SmsException;
import app.coronawarn.quicktest.model.SmsMessage;
import app.coronawarn.quicktest.model.SmsResponse;

public interface SmsClient {

SmsResponse send(SmsMessage sms) throws SmsException;

}
104 changes: 104 additions & 0 deletions src/main/java/app/coronawarn/quicktest/client/SmsClientImpl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*-
* ---license-start
* Corona-Warn-App / cwa-quick-test-backend
* ---
* Copyright (C) 2021 T-Systems International GmbH and all other contributors
* ---
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.
* ---license-end
*/

package app.coronawarn.quicktest.client;

import app.coronawarn.quicktest.config.SmsConfig;
import app.coronawarn.quicktest.exception.SmsException;
import app.coronawarn.quicktest.model.SmsMessage;
import app.coronawarn.quicktest.model.SmsResponse;
import com.huawei.openstack4j.api.exceptions.AuthenticationException;
import com.huawei.openstack4j.api.exceptions.ClientResponseException;
import com.huawei.openstack4j.api.exceptions.ConnectionException;
import com.huawei.openstack4j.api.exceptions.OS4JException;
import com.huawei.openstack4j.api.exceptions.ServerResponseException;
import com.huawei.openstack4j.api.types.ServiceType;
import com.huawei.openstack4j.core.transport.Config;
import com.huawei.openstack4j.model.identity.v3.Token;
import com.huawei.openstack4j.openstack.identity.internal.OverridableEndpointURLResolver;
import com.huawei.openstack4j.openstack.message.notification.domain.MessageIdResponse;
import javax.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
@Slf4j
public class SmsClientImpl implements SmsClient {

private final SmsConfig smsConfig;
private final OsFactoryWrapper osFactoryWrapper;

private Token token;
private Config openstackConfig;

@PostConstruct
private void initialize() {
if (smsConfig.isEnabled()) {
SmsConfig.OtcAuth otcAuth = smsConfig.getOtcAuth();

// Set Service Endpoints to eu-de values from https://docs.otc.t-systems.com/en-us/endpoint/index.html
OverridableEndpointURLResolver endpointResolver = new OverridableEndpointURLResolver();
endpointResolver.addOverrideEndpoint(ServiceType.Notification, otcAuth.getNotificationEndpoint());
this.openstackConfig = Config.newConfig().withEndpointURLResolver(endpointResolver);

try {
this.token = osFactoryWrapper.authenticate(this.openstackConfig, otcAuth);
} catch (AuthenticationException e) {
log.error("Could not authenticate at OTC IAM: {} ", e.getLocalizedMessage());
}
}
}

@Override
public SmsResponse send(SmsMessage sms) throws SmsException {
try {
MessageIdResponse messageIdResponse = osFactoryWrapper.sendSms(this.token, this.openstackConfig, sms);
return new SmsResponse(messageIdResponse.getRequestId(), messageIdResponse.getId());
} catch (AuthenticationException authException) {
log.warn("Client is not authenticated, retrying authentication: {}",
authException.getLocalizedMessage());
reAuthenticate();
//TODO: retry send?
throw new SmsException(SmsException.Reason.SEND_SMS_FAILED);
} catch (ConnectionException connectionException) {
log.warn("Could not reach SMS OTC host: {}", connectionException.getLocalizedMessage());
throw new SmsException(SmsException.Reason.COULD_NOT_REACH_HOST);
} catch (ClientResponseException clientException) {
log.warn("Error sending SMS due to client input: {}", clientException.getLocalizedMessage());
throw new SmsException(SmsException.Reason.WRONG_INPUT);
} catch (ServerResponseException serverException) {
log.warn("Error sending SMS due to server failure: {}", serverException.getLocalizedMessage());
throw new SmsException(SmsException.Reason.SERVER_FAILURE);
} catch (OS4JException e) {
log.warn("Could not send SMS: {}", e.getLocalizedMessage());
throw new SmsException(SmsException.Reason.SEND_SMS_FAILED);
}
}

private void reAuthenticate() {
try {
osFactoryWrapper.refreshToken();
} catch (AuthenticationException e) {
log.error("Could not authenticate at OTC IAM: {} ", e.getLocalizedMessage());
}
}
}
36 changes: 36 additions & 0 deletions src/main/java/app/coronawarn/quicktest/config/EmailConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package app.coronawarn.quicktest.config;

import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@Getter
@Setter
@Configuration
@ConfigurationProperties("email")
public class EmailConfig {

private HealthDepartment healthDepartment;
private TestedPerson testedPerson;

@Getter
@Setter
public static class HealthDepartment {
private boolean enabled = false;
private String subject;
private String text;
private String attachmentFilename;
}

@Getter
@Setter
public static class TestedPerson {
private boolean enabled = false;
private String subject;
private String titleText;
private String text;
private String attachmentFilename;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,11 @@ public class QuickTestConfig {
private String tenantPointOfCareIdKey;
private String pointOfCareInformationName;
private String pointOfCareInformationDelimiter;
private String pointOfCareZipcodeKey;
private String dbEncryptionKey;
private String labId;
private String healthDepartmentDownloadUrl;
private String healthDepartmentDownloadCron;

@Getter
@Setter
Expand Down
50 changes: 50 additions & 0 deletions src/main/java/app/coronawarn/quicktest/config/SmsConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*-
* ---license-start
* Corona-Warn-App / cwa-quick-test-backend
* ---
* Copyright (C) 2021 T-Systems International GmbH and all other contributors
* ---
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.
* ---license-end
*/

package app.coronawarn.quicktest.config;

import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@Getter
@Setter
@Configuration
@ConfigurationProperties("sms")
public class SmsConfig {

private boolean enabled;
private int passwordLength;
private String messageTemplate;

private OtcAuth otcAuth;

@Getter
@Setter
public static class OtcAuth {
private String authUrl;
private String user;
private String password;
private String domainId;
private String projectId;
private String notificationEndpoint;
}
}
Loading