Skip to content

Commit 75c882f

Browse files
global: send notification from event-listener
1 parent 430af1b commit 75c882f

11 files changed

Lines changed: 131 additions & 13 deletions

File tree

api-service/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,3 +85,8 @@ The authentication can be enabled/disabled through this API's config file. Simpl
8585
Additionally, when the authentication is enabled, it requires **AUTH_SECRET** environment variable, which should be a generated long hash.
8686

8787
**Example**: Generate a hash secret using: ```openssl rand -base64 32```.
88+
89+
## Notifications
90+
New users that register through this API will have their LDAP/iRODS password generated by this service. The password has to be the same to respect
91+
CyVerse requirements. There is also the possibility to notify the user by sending this newly generated password per email, by configuring ``mailServiceConfig``.
92+
The service will work normally without configuring this, just that it will perform a no-op at the **notifications** endpoint.

api-service/src/main/java/com/cyverse/api/controllers/MailController.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.cyverse.api.controllers;
22

3+
import com.cyverse.api.exceptions.ResourceAlreadyExistsException;
34
import com.cyverse.api.exceptions.UserException;
45
import com.cyverse.api.models.UserModel;
56
import com.cyverse.api.services.MailService;
@@ -30,7 +31,7 @@ public MailController(MailService mailService, PasswordService passwordService)
3031
responses = {@OpenApiResponse(status = "200")}
3132
)
3233
public void sendPasswordNotification(Context ctx)
33-
throws UserException, MessagingException {
34+
throws UserException, MessagingException, ResourceAlreadyExistsException {
3435
UserModel user = ctx.bodyAsClass(UserModel.class);
3536
validateUser(user);
3637
String password = passwordService.getPassword(user.getUsername());
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
package com.cyverse.api.services;
22

3+
import com.cyverse.api.exceptions.ResourceAlreadyExistsException;
34
import jakarta.mail.MessagingException;
45

56
public interface MailService {
6-
void sendEmail(String email, String password) throws MessagingException;
7+
void sendEmail(String email, String password) throws MessagingException, ResourceAlreadyExistsException;
78
}

api-service/src/main/java/com/cyverse/api/services/MailServiceImpl.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.cyverse.api.services;
22

33
import com.cyverse.api.config.MailServiceConfig;
4+
import com.cyverse.api.exceptions.ResourceAlreadyExistsException;
45
import jakarta.mail.Message;
56
import jakarta.mail.MessagingException;
67
import jakarta.mail.Session;
@@ -10,25 +11,34 @@
1011
import org.slf4j.Logger;
1112
import org.slf4j.LoggerFactory;
1213

14+
import java.util.HashSet;
1315
import java.util.Properties;
16+
import java.util.Set;
1417

1518
/**
1619
* Mail service based on jakarta.mail.
1720
*/
1821
public class MailServiceImpl implements MailService {
1922
private static final Logger logger = LoggerFactory.getLogger(MailServiceImpl.class);
2023
private MailServiceConfig config;
24+
private Set<String> mailsAlreadySent;
2125

2226
public MailServiceImpl(MailServiceConfig config) {
2327
this.config = config;
28+
this.mailsAlreadySent = new HashSet<>();
2429
}
2530

2631
/**
2732
* Send the password in an email to the user creating the account, using jakarta.mail
2833
* library functionalities.
2934
*/
3035
@Override
31-
public void sendEmail(String emailTo, String password) throws MessagingException {
36+
public void sendEmail(String emailTo, String password)
37+
throws MessagingException, ResourceAlreadyExistsException {
38+
if (mailsAlreadySent.contains(emailTo)) {
39+
throw new ResourceAlreadyExistsException("Notification was already sent to this email");
40+
}
41+
3242
logger.debug("Sending mail to {} with LDAP/iRODS", emailTo);
3343

3444
String from = config.getFromSender();
@@ -54,6 +64,7 @@ public void sendEmail(String emailTo, String password) throws MessagingException
5464

5565
Transport.send(message);
5666

67+
this.mailsAlreadySent.add(emailTo);
5768
logger.debug("Mail sent successfully to {}", emailTo);
5869
}
5970
}

api-service/src/main/resources/api-service-config.yml

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@ ldapServiceConfig:
99
password: "notreal"
1010
baseDN: "dc=example,dc=org"
1111
everyoneGroup: "everyone"
12-
#authConfig:
13-
# apiKey: "<api_key>"
14-
# tokenIssuer: "http://api-service.cyverse.at"
15-
# users:
16-
# test_user:
17-
# mail: "test.user@example.com"
18-
# password: "testpass"
12+
authConfig:
13+
apiKey: "<api_key>"
14+
tokenIssuer: "http://api-service.cyverse.at"
15+
users:
16+
test_user:
17+
mail: "test.user@example.com"
18+
password: "testpass"
1919
userPortalServiceConfig:
2020
host: http://192.168.31.115:3000
2121
hmacKey: <SECRET> # portal equivalent in ENV config: HMAC_KEY

event-listener/src/main/java/com/cyverse/keycloak/KeycloakLoginListener.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.cyverse.keycloak.irods.service.IrodsService;
44
import com.cyverse.keycloak.ldap.service.LdapService;
5+
import com.cyverse.keycloak.notification.service.NotificationService;
56
import com.cyverse.keycloak.portal.service.UserPortalService;
67
import org.jboss.logging.Logger;
78
import org.keycloak.events.Event;
@@ -23,15 +24,18 @@ public class KeycloakLoginListener implements EventListenerProvider {
2324
private final LdapService ldapService;
2425
private final IrodsService irodsService;
2526
private final UserPortalService userPortalService;
27+
private final NotificationService notificationService;
2628

2729
public KeycloakLoginListener(KeycloakSession session,
2830
LdapService ldapService,
2931
IrodsService irodsService,
30-
UserPortalService userPortalService) {
32+
UserPortalService userPortalService,
33+
NotificationService notificationService) {
3134
this.session = session;
3235
this.ldapService = ldapService;
3336
this.irodsService = irodsService;
3437
this.userPortalService = userPortalService;
38+
this.notificationService = notificationService;
3539
}
3640

3741
private void performLdapActions(UserModel user) {
@@ -54,6 +58,10 @@ private void performUserPortalActions(UserModel user) {
5458
userPortalService.addUserToPortal(user);
5559
}
5660

61+
private void performNotificationActions(UserModel user) {
62+
notificationService.notifyUser(user);
63+
}
64+
5765
/**
5866
* Perform actions based on keycloak events.
5967
* Actions supported in this implementation: LDAP User update, iRODS account
@@ -75,6 +83,7 @@ public void onEvent(Event event) {
7583
performLdapActions(user);
7684
performIrodsActions(user);
7785
performUserPortalActions(user);
86+
performNotificationActions(user);
7887
}
7988
}
8089

event-listener/src/main/java/com/cyverse/keycloak/KeycloakLoginListenerFactory.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
import com.cyverse.keycloak.irods.service.IrodsServiceImpl;
88
import com.cyverse.keycloak.irods.service.NoOpIrodsServiceImpl;
99
import com.cyverse.keycloak.ldap.service.LdapService;
10+
import com.cyverse.keycloak.notification.service.NoOpNotificationServiceImpl;
11+
import com.cyverse.keycloak.notification.service.NotificationService;
12+
import com.cyverse.keycloak.notification.service.NotificationServiceImpl;
1013
import com.cyverse.keycloak.portal.service.NoOpUserPortalServiceImpl;
1114
import com.cyverse.keycloak.portal.service.UserPortalService;
1215
import com.cyverse.keycloak.portal.service.UserPortalServiceImpl;
@@ -30,10 +33,11 @@ public class KeycloakLoginListenerFactory implements EventListenerProviderFactor
3033
private LdapService ldapService;
3134
private IrodsService irodsService;
3235
private UserPortalService userPortalService;
36+
private NotificationService notificationService;
3337

3438
@Override
3539
public EventListenerProvider create(KeycloakSession session) {
36-
return new KeycloakLoginListener(session, ldapService, irodsService, userPortalService);
40+
return new KeycloakLoginListener(session, ldapService, irodsService, userPortalService, notificationService);
3741
}
3842

3943
private boolean testConnection(ListenerHttpClientBase httpClient) {
@@ -77,10 +81,12 @@ public void init(Config.Scope config) {
7781
irodsService = new IrodsServiceImpl(httpClient);
7882
ldapService = new LdapServiceImpl(httpClient);
7983
userPortalService = new UserPortalServiceImpl(httpClient);
84+
notificationService = new NotificationServiceImpl(httpClient);
8085
} else {
8186
irodsService = new NoOpIrodsServiceImpl();
8287
ldapService = new NoOpLdapServiceImpl();
8388
userPortalService = new NoOpUserPortalServiceImpl();
89+
notificationService = new NoOpNotificationServiceImpl();
8490
}
8591
}
8692

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.cyverse.keycloak.notification.service;
2+
3+
import org.jboss.logging.Logger;
4+
import org.keycloak.models.UserModel;
5+
6+
public class NoOpNotificationServiceImpl implements NotificationService {
7+
private static final Logger logger = Logger.getLogger(NoOpNotificationServiceImpl.class);
8+
9+
@Override
10+
public void notifyUser(UserModel user) {
11+
logger.error("No operation possible. Check other dependencies for failures.");
12+
}
13+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.cyverse.keycloak.notification.service;
2+
3+
import org.keycloak.models.UserModel;
4+
5+
public interface NotificationService {
6+
void notifyUser(UserModel user);
7+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package com.cyverse.keycloak.notification.service;
2+
3+
import com.cyverse.keycloak.http.ListenerHttpClientBase;
4+
import com.fasterxml.jackson.core.JsonProcessingException;
5+
import com.fasterxml.jackson.databind.ObjectMapper;
6+
import org.apache.http.HttpStatus;
7+
import org.jboss.logging.Logger;
8+
import org.keycloak.models.UserModel;
9+
10+
import java.io.IOException;
11+
import java.net.http.HttpResponse;
12+
import java.util.Map;
13+
14+
public class NotificationServiceImpl implements NotificationService {
15+
private static final Logger logger = Logger.getLogger(NoOpNotificationServiceImpl.class);
16+
private final ListenerHttpClientBase httpClient;
17+
18+
private static final String NOTIFICATION_USERS_ENDPOINT = "/api/users/notification";
19+
20+
public NotificationServiceImpl(ListenerHttpClientBase httpClient) {
21+
this.httpClient = httpClient;
22+
}
23+
24+
@Override
25+
public void notifyUser(UserModel user) {
26+
logger.debug("Try notifying user: " + user.getUsername());
27+
28+
ObjectMapper mapper = new ObjectMapper();
29+
Map<String, Object> data = Map.of(
30+
"username", user.getUsername(),
31+
"email", user.getEmail()
32+
);
33+
34+
try {
35+
String jsonBody = mapper.writeValueAsString(data);
36+
37+
HttpResponse<String> response =
38+
httpClient.getHttpClient()
39+
.send(httpClient
40+
.getRequestPUT(NOTIFICATION_USERS_ENDPOINT, jsonBody),
41+
HttpResponse.BodyHandlers.ofString());
42+
43+
logger.debug("API RESPONSE STATUS CODE: " + response.statusCode());
44+
45+
if (response.statusCode() == HttpStatus.SC_OK) {
46+
logger.info("Successfully notified user " + user.getUsername());
47+
}
48+
} catch (JsonProcessingException jsonExc) {
49+
logger.error("Got exception trying to build API client body data: " + user.getUsername() + "\n" + jsonExc.getMessage());
50+
} catch (IOException | InterruptedException httpExc) {
51+
logger.error("Got exception from HTTP request to API client: " + httpExc.getMessage());
52+
}
53+
}
54+
}

0 commit comments

Comments
 (0)