Skip to content

Commit

Permalink
Initial project (#1)
Browse files Browse the repository at this point in the history
Co-authored-by: Tim Ortel <[email protected]>
Co-authored-by: sonatype-lift[bot] <37194012+sonatype-lift[bot]@users.noreply.github.com>
  • Loading branch information
3 people authored Jul 27, 2023
1 parent e62d918 commit c5cf7e3
Show file tree
Hide file tree
Showing 24 changed files with 816 additions and 0 deletions.
37 changes: 37 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
HELP.md
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/

### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/

### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/

### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/

### VS Code ###
.vscode/
5 changes: 5 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
FROM openjdk:17
WORKDIR /.
COPY hermes/build/libs/hermes-0.0.1-SNAPSHOT.jar ./app.jar

ENTRYPOINT ["java", "-jar", "./app.jar"]
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,25 @@

Notification Relay for Artemis Push Notifications.
Allows secure and private push notifications from [Artemis](https://github.com/ls1intum/Artemis) to the mobile apps for [iOS](https://github.com/ls1intum/artemis-ios) and [Android](https://github.com/ls1intum/artemis-android).

![](hermes-system-decomposition.png)

### How to run:
1. Replace placeholder values `<...>` in Docker-Compose file
2. Adjust port if necessary
3. Run `docker-compose up`

### Further Information on Dockerfile

To run the services as an APNS relay the following Environment Variables are required:
- APNS_CERTIFICATE_PATH: String - Path to the APNs certificate .p12 file as described [here](https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/establishing_a_certificate-based_connection_to_apns)
- APNS_CERTIFICATE_PWD: String - The APNS certificate password
- APNS_PROD_ENVIRONMENT: Bool - True if it should use the Production APNS Server (Default false)
Furthermore the <APNS_Key>.p8 needs to be mounted into the Docker under the above specified path.


To run the services as a Firebase relay the following Environment Variable is required:
- GOOGLE_APPLICATION_CREDENTIALS: String - Path to the firebase.json
Furthermore the Firebase.json needs to be mounted into the Docker under the above specified path.

To run both APNS and Firebase configure the Environment Variables for both.
28 changes: 28 additions & 0 deletions apns/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
plugins {
id 'java'
id 'org.springframework.boot'
id 'io.spring.dependency-management'
}

group = 'de.tum.cit.artemis.push.artemispushnotificationrelay'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'

repositories {
mavenCentral()
}

dependencies {
implementation 'org.springframework.boot:spring-boot-starter'
implementation 'org.springframework.boot:spring-boot-starter-web'

implementation 'com.eatthepath:pushy:0.15.2'

implementation 'javax.annotation:javax.annotation-api:1.3.2'

implementation(project(":common"))
}

tasks.named('test') {
useJUnitPlatform()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package de.tum.cit.artemis.push.artemispushnotificationrelay.apns;

import com.eatthepath.pushy.apns.*;
import com.eatthepath.pushy.apns.util.SimpleApnsPayloadBuilder;
import com.eatthepath.pushy.apns.util.SimpleApnsPushNotification;
import com.eatthepath.pushy.apns.util.concurrent.PushNotificationFuture;
import de.tum.cit.artemis.push.artemispushnotificationrelay.common.NotificationRequest;
import de.tum.cit.artemis.push.artemispushnotificationrelay.common.SendService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.io.File;
import java.io.IOException;
import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.ExecutionException;

@Service
public class ApnsSendService implements SendService<NotificationRequest> {

@Value("${APNS_CERTIFICATE_PATH: #{null}}")
private String apnsCertificatePath;
@Value("${APNS_CERTIFICATE_PWD: #{null}}")
private String apnsCertificatePwd;

@Value("${APNS_PROD_ENVIRONMENT: #{false}}")
private Boolean apnsProdEnvironment = false;

private final Logger log = LoggerFactory.getLogger(ApnsSendService.class);
private ApnsClient apnsClient;

@PostConstruct
public void initialize() {
log.info("apnsCertificatePwd: " + apnsCertificatePwd);
log.info("apnsCertificatePath: " + apnsCertificatePath);
log.info("apnsProdEnvironment: " + apnsProdEnvironment);
if (apnsCertificatePwd == null || apnsCertificatePath == null || apnsProdEnvironment == null) {
log.error("Could not init APNS service. Certificate information missing.");
return;
}
try {
apnsClient = new ApnsClientBuilder()
.setApnsServer(apnsProdEnvironment ? ApnsClientBuilder.PRODUCTION_APNS_HOST : ApnsClientBuilder.DEVELOPMENT_APNS_HOST)
.setClientCredentials(new File(apnsCertificatePath), apnsCertificatePwd)
.build();
log.info("Started APNS client successfully!");
} catch (IOException e) {
log.error("Could not init APNS service", e);
}
}

@Override
public ResponseEntity<Void> send(NotificationRequest request) {
return sendApnsRequest(request);
}

@Async
ResponseEntity<Void> sendApnsRequest(NotificationRequest request) {
String payload = new SimpleApnsPayloadBuilder()
.setContentAvailable(true)
.addCustomProperty("iv", request.getInitializationVector())
.addCustomProperty("payload", request.getPayloadCipherText())
.build();

SimpleApnsPushNotification notification = new SimpleApnsPushNotification(request.getToken(),
"de.tum.cit.artemis",
payload,
Instant.now().plus(Duration.ofDays(7)),
DeliveryPriority.getFromCode(5),
PushType.BACKGROUND);


PushNotificationFuture<SimpleApnsPushNotification, PushNotificationResponse<SimpleApnsPushNotification>> responsePushNotificationFuture = apnsClient.sendNotification(notification);
try {
final PushNotificationResponse<SimpleApnsPushNotification> pushNotificationResponse =
responsePushNotificationFuture.get();
if (pushNotificationResponse.isAccepted()) {
log.info("Send notification to " + request.getToken());
return ResponseEntity.ok().build();
} else {
log.error("Notification rejected by the APNs gateway: " +
pushNotificationResponse.getRejectionReason());

pushNotificationResponse.getTokenInvalidationTimestamp().ifPresent(timestamp -> {
log.error("\t…and the token is invalid as of " + timestamp);
});
return ResponseEntity.status(HttpStatus.EXPECTATION_FAILED).build();
}
} catch (ExecutionException | InterruptedException e) {
log.error("Failed to send push notification.");
e.printStackTrace();
return ResponseEntity.status(HttpStatus.EXPECTATION_FAILED).build();
}
}
}
14 changes: 14 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
buildscript {
repositories {
mavenCentral()
}
}

plugins {
}

allprojects {
repositories {
mavenCentral()
}
}
21 changes: 21 additions & 0 deletions common/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
plugins {
id 'java'
id 'org.springframework.boot'
id 'io.spring.dependency-management'
}

group = 'de.tum.cit.artemis.push.artemispushnotificationrelay'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'

repositories {
mavenCentral()
}

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
}

tasks.named('test') {
useJUnitPlatform()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package de.tum.cit.artemis.push.artemispushnotificationrelay.common;

public class NotificationRequest {
private final String initializationVector;
private final String payloadCipherText;
private final String token;

public NotificationRequest(String initializationVector, String payloadCipherText, String token) {
this.initializationVector = initializationVector;
this.payloadCipherText = payloadCipherText;
this.token = token;
}

public String getInitializationVector() {
return initializationVector;
}

public String getPayloadCipherText() {
return payloadCipherText;
}

public String getToken() {
return token;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package de.tum.cit.artemis.push.artemispushnotificationrelay.common;

import org.springframework.http.ResponseEntity;

public interface SendService<T> {

ResponseEntity<Void> send(T request);
}
19 changes: 19 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
version: '3'
services:
hermes:
# either build image with run.sh before running or adapt image to point to Docker Hub: e.g. sven0311tum/hermes:latest
image: hermes
container_name: hermes
ports:
- "17333:8080"
environment:
- APNS_CERTIFICATE_PATH=/key/artemis-apns.p12
# adapt the following line
- APNS_CERTIFICATE_PWD=<pwd_for_certificate>
- APNS_PROD_ENVIRONMENT=false
- GOOGLE_APPLICATION_CREDENTIALS=/firebase.json
volumes:
# adapt the following lines
- <path_to_apns_certificate>:/key/artemis-apns.p12
- <path_to_google_credentials_json>:/firebase.json
restart: on-failure
25 changes: 25 additions & 0 deletions firebase/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
plugins {
id 'java'
id 'org.springframework.boot'
id 'io.spring.dependency-management'
}

group = 'de.tum.cit.artemis.push.artemispushnotificationrelay'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'

repositories {
mavenCentral()
}

dependencies {
implementation 'org.springframework.boot:spring-boot-starter'
implementation 'org.springframework.boot:spring-boot-starter-web'

implementation "com.google.firebase:firebase-admin:9.1.1"
implementation(project(":common"))
}

tasks.named('test') {
useJUnitPlatform()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package de.tum.cit.artemis.push.artemispushnotificationrelay.firebase;

import de.tum.cit.artemis.push.artemispushnotificationrelay.common.NotificationRequest;

import java.util.List;

public record FirebaseSendPushNotificationsRequest(
List<NotificationRequest> notificationRequest
) {
}
Loading

0 comments on commit c5cf7e3

Please sign in to comment.