@@ -276,6 +276,11 @@ | |||
</exclusions> | |||
</dependency> | |||
<dependency> | |||
<groupId>com.sendgrid</groupId> | |||
<artifactId>sendgrid-java</artifactId> | |||
<version>4.6.5</version> | |||
</dependency> | |||
</dependencies> | |||
<build> | |||
@@ -10,6 +10,7 @@ import bubble.model.cloud.CloudService; | |||
import bubble.server.BubbleConfiguration; | |||
import com.fasterxml.jackson.databind.JsonNode; | |||
import com.github.jknack.handlebars.Handlebars; | |||
import lombok.NonNull; | |||
import org.cobbzilla.util.handlebars.HandlebarsUtil; | |||
import org.cobbzilla.util.http.HttpResponseBean; | |||
import org.cobbzilla.util.http.HttpUtil; | |||
@@ -28,6 +29,18 @@ public interface CloudServiceDriver { | |||
default boolean disableDelegation () { return false; } | |||
@NonNull default CloudService setupDelegatedCloudService(@NonNull final BubbleConfiguration configuration, | |||
@NonNull final CloudService parentService, | |||
@NonNull final CloudService delegatedService) { | |||
return delegatedService.setDelegated(parentService.getUuid()) | |||
.setCredentials(CloudCredentials.delegate(configuration.getThisNode(), configuration)) | |||
.setTemplate(false); | |||
} | |||
default void postServiceDelete(@NonNull final CloudService service) { | |||
// noop | |||
} | |||
void setConfig(JsonNode json, CloudService cloudService); | |||
static <T extends CloudServiceDriver> T setupDriver(BubbleConfiguration configuration, T driver) { | |||
@@ -0,0 +1,116 @@ | |||
package bubble.cloud.email; | |||
import bubble.dao.account.AccountDAO; | |||
import bubble.dao.cloud.CloudServiceDAO; | |||
import bubble.model.account.Account; | |||
import bubble.model.cloud.CloudCredentials; | |||
import bubble.model.cloud.CloudService; | |||
import bubble.server.BubbleConfiguration; | |||
import com.sendgrid.Method; | |||
import com.sendgrid.Request; | |||
import com.sendgrid.Response; | |||
import com.sendgrid.SendGrid; | |||
import lombok.*; | |||
import org.springframework.beans.factory.annotation.Autowired; | |||
import java.io.IOException; | |||
import static bubble.cloud.storage.StorageCryptStream.MIN_DISTINCT_LENGTH; | |||
import static bubble.cloud.storage.StorageCryptStream.MIN_KEY_LENGTH; | |||
import static java.net.HttpURLConnection.HTTP_NO_CONTENT; | |||
import static java.net.HttpURLConnection.HTTP_OK; | |||
import static org.cobbzilla.util.daemon.ZillaRuntime.die; | |||
import static org.cobbzilla.util.json.JsonUtil.EMPTY_JSON; | |||
import static org.cobbzilla.util.json.JsonUtil.json; | |||
import static org.cobbzilla.util.security.CryptoUtil.generatePassword; | |||
import static org.cobbzilla.util.string.StringUtil.repeat; | |||
public class SendgridSmtpEmailDriver extends SmtpEmailDriver { | |||
private static final String PARAM_PARENT_SERVICE = "parentService"; | |||
@Autowired private AccountDAO accountDAO; | |||
@Autowired private CloudServiceDAO serviceDAO; | |||
@Override protected boolean isServiceCompatible() { return this.config.getHost().equals(SENDGRID_SMTP); } | |||
/** | |||
* Build username which will be used for Subuser created on SendGrid's service for specified Account's object. | |||
*/ | |||
@NonNull private String sgUsername(@NonNull final CloudService delegatedService) { | |||
return delegatedService.getShortId(); | |||
} | |||
@Override @NonNull public CloudService setupDelegatedCloudService(@NonNull final BubbleConfiguration configuration, | |||
@NonNull final CloudService parentService, | |||
@NonNull final CloudService delegatedService) { | |||
final CloudCredentials parentCredentials = parentService.getCredentials(); | |||
if (parentService.delegated() || !parentCredentials.getParam(PARAM_HOST).contains(".sendgrid.net")) { | |||
return super.setupDelegatedCloudService(configuration, parentService, delegatedService); | |||
} | |||
final SendGrid sg = new SendGrid(parentCredentials.getParam(PARAM_PASSWORD)); | |||
final Account accountWithDelegate = accountDAO.findByUuid(delegatedService.getAccount()); | |||
final String user = sgUsername(delegatedService); | |||
String password = generatePassword(MIN_KEY_LENGTH, MIN_DISTINCT_LENGTH); | |||
final CreateSubuserRequest data = new CreateSubuserRequest(user, accountWithDelegate.getEmail(), password, | |||
new String[]{}); | |||
final Request req = new Request(); | |||
req.setMethod(Method.POST); | |||
req.setEndpoint("subusers"); | |||
req.setBody(json(data)); | |||
final Response res; | |||
try { | |||
res = sg.api(req); | |||
} catch (IOException e) { | |||
return die("Cannot create SendGrid Subuser", e); | |||
} | |||
if (res.getStatusCode() != HTTP_OK) { | |||
return die("Wrong response when creating SendGrid Subuser: " + res.getStatusCode() + " : " + res.getBody()); | |||
} | |||
delegatedService.setDelegated(null).setTemplate(false); | |||
delegatedService.getCredentials() | |||
.setParam(PARAM_USER, user) | |||
.setParam(PARAM_PASSWORD, password) | |||
.setParam(PARAM_PARENT_SERVICE, parentService.getUuid()); | |||
password = repeat("x", MIN_KEY_LENGTH); // Override password (in memory) just in case | |||
return delegatedService; | |||
} | |||
@Override public void postServiceDelete(@NonNull final CloudService service) { | |||
final String parentServiceUuid = service.getCredentials().getParam(PARAM_PARENT_SERVICE); | |||
if (parentServiceUuid == null) return; | |||
final CloudService parentService = serviceDAO.findByUuid(parentServiceUuid); | |||
if (parentService == null) return; | |||
final SendGrid sg = new SendGrid(parentService.getCredentials().getParam(PARAM_PASSWORD)); | |||
final String sgUserToDelete = sgUsername(service); | |||
final Request req = new Request(); | |||
req.setMethod(Method.DELETE); | |||
req.setEndpoint("subusers/" + sgUserToDelete); | |||
req.setBody(EMPTY_JSON); | |||
final Response res; | |||
try { | |||
res = sg.api(req); | |||
} catch (IOException e) { | |||
die("Cannot delete SendGrid Subuser " + sgUserToDelete, e); | |||
return; | |||
} | |||
if (res.getStatusCode() != HTTP_NO_CONTENT) { | |||
die("Wrong response when creating SendGrid Subuser: " + res.getStatusCode() + " : " + res.getBody()); | |||
} | |||
} | |||
@AllArgsConstructor @NoArgsConstructor | |||
private class CreateSubuserRequest { | |||
@Getter @Setter private String username; | |||
@Getter @Setter private String email; | |||
@Getter @Setter private String password; | |||
@Getter @Setter private String[] ips; | |||
} | |||
} |
@@ -10,19 +10,30 @@ import bubble.cloud.email.mock.MockMailSender; | |||
import bubble.model.account.Account; | |||
import bubble.model.account.AccountContact; | |||
import bubble.model.account.message.AccountMessage; | |||
import bubble.model.cloud.CloudService; | |||
import bubble.server.BubbleConfiguration; | |||
import com.fasterxml.jackson.databind.JsonNode; | |||
import lombok.Getter; | |||
import lombok.extern.slf4j.Slf4j; | |||
import org.cobbzilla.mail.sender.SmtpMailSender; | |||
import org.springframework.beans.factory.annotation.Autowired; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
import static org.cobbzilla.util.daemon.ZillaRuntime.die; | |||
@Slf4j | |||
public class SmtpEmailDriver extends CloudServiceDriverBase<EmailDriverConfig> implements EmailServiceDriver { | |||
protected static final String SENDGRID_SMTP = "smtp.sendgrid.net"; | |||
private static final List<String> SEPARATE_DRIVERS_SMTPS = new ArrayList<>(); | |||
static { SEPARATE_DRIVERS_SMTPS.add(SENDGRID_SMTP); } | |||
private static final String PARAM_USER = "user"; | |||
private static final String PARAM_PASSWORD = "password"; | |||
private static final String PARAM_HOST = "host"; | |||
private static final String PARAM_PORT = "port"; | |||
protected static final String PARAM_USER = "user"; | |||
protected static final String PARAM_PASSWORD = "password"; | |||
protected static final String PARAM_HOST = "host"; | |||
protected static final String PARAM_PORT = "port"; | |||
@Autowired @Getter protected BubbleConfiguration configuration; | |||
@@ -38,6 +49,13 @@ public class SmtpEmailDriver extends CloudServiceDriverBase<EmailDriverConfig> i | |||
return smtpSender; | |||
} | |||
@Override public void setConfig(JsonNode json, CloudService cloudService) { | |||
super.setConfig(json, cloudService); | |||
if (!isServiceCompatible()) die("Specified SmtpEmailDriver is not compatible with given config"); | |||
} | |||
protected boolean isServiceCompatible() { return !SEPARATE_DRIVERS_SMTPS.contains(this.config.getHost()); } | |||
@Override public boolean send(Account account, AccountMessage message, AccountContact contact) { | |||
return EmailServiceDriver.send(this, account, message, contact); | |||
} | |||
@@ -229,12 +229,11 @@ public class AccountDAO extends AbstractCRUDDAO<Account> implements SqlViewSearc | |||
.setTemplate(false); | |||
} else { | |||
return accountEntity.setDelegated(parentEntity.getUuid()) | |||
.setCredentials(CloudCredentials.delegate(configuration.getThisNode(), configuration)) | |||
.setTemplate(false); | |||
return driver.setupDelegatedCloudService(configuration, parentEntity, accountEntity); | |||
} | |||
} | |||
} | |||
@Override public void postCreate(CloudService parentEntity, CloudService accountEntity) { | |||
clouds.put(parentEntity.getUuid(), accountEntity); | |||
} | |||
@@ -25,6 +25,7 @@ import com.fasterxml.jackson.databind.node.JsonNodeType; | |||
import com.fasterxml.jackson.databind.node.ObjectNode; | |||
import lombok.Getter; | |||
import lombok.NoArgsConstructor; | |||
import lombok.NonNull; | |||
import lombok.Setter; | |||
import lombok.experimental.Accessors; | |||
import org.cobbzilla.util.collection.ArrayUtil; | |||
@@ -342,4 +343,8 @@ public class CloudService extends IdentifiableBaseParentEntity implements Accoun | |||
} | |||
return errors; | |||
} | |||
@PostRemove void onPostRemove(@NonNull final CloudService service) { | |||
service.getDriver().postServiceDelete(service); | |||
} | |||
} |
@@ -72,7 +72,7 @@ | |||
{ | |||
"name": "SmtpServer", | |||
"type": "email", | |||
"driverClass": "bubble.cloud.email.SmtpEmailDriver", | |||
"driverClass": "{{BUBBLE_SMTP_DRIVER}}", | |||
"driverConfig": { | |||
"tlsEnabled": true | |||
}, | |||