@@ -259,13 +259,6 @@ | |||
<scope>test</scope> | |||
</dependency> | |||
<!--<dependency>--> | |||
<!--<groupId>org.cobbzilla</groupId>--> | |||
<!--<artifactId>wizard-server-test</artifactId>--> | |||
<!--<version>1.0.0-SNAPSHOT</version>--> | |||
<!--</dependency>--> | |||
<!-- https://mvnrepository.com/artifact/io.swagger/swagger-jersey2-jaxrs --> | |||
<dependency> | |||
<groupId>io.swagger</groupId> | |||
<artifactId>swagger-jersey2-jaxrs</artifactId> | |||
@@ -8,7 +8,9 @@ import bubble.dao.cloud.BubbleNodeDAO; | |||
import bubble.model.cloud.BubbleNode; | |||
import bubble.model.cloud.CloudCredentials; | |||
import bubble.model.cloud.CloudService; | |||
import bubble.server.BubbleConfiguration; | |||
import bubble.service.notify.NotificationService; | |||
import lombok.Getter; | |||
import org.springframework.beans.factory.annotation.Autowired; | |||
import static bubble.model.cloud.CloudCredentials.PARAM_DELEGATE_NODE; | |||
@@ -18,6 +20,7 @@ public abstract class DelegatedCloudServiceDriverBase extends CloudServiceDriver | |||
protected CloudService cloud; | |||
@Autowired @Getter protected BubbleConfiguration configuration; | |||
@Autowired protected BubbleNodeDAO nodeDAO; | |||
@Autowired protected NotificationService notificationService; | |||
@@ -5,11 +5,13 @@ | |||
package bubble.cloud; | |||
import bubble.cloud.auth.AuthenticationDriver; | |||
import bubble.cloud.auth.RenderedMessage; | |||
import bubble.cloud.compute.ComputeNodeSize; | |||
import bubble.cloud.compute.ComputeNodeSizeType; | |||
import bubble.cloud.compute.ComputeServiceDriver; | |||
import bubble.cloud.dns.DnsServiceDriver; | |||
import bubble.cloud.email.EmailServiceDriver; | |||
import bubble.cloud.email.RenderedEmail; | |||
import bubble.cloud.geoCode.GeoCodeResult; | |||
import bubble.cloud.geoCode.GeoCodeServiceDriver; | |||
import bubble.cloud.geoLocation.GeoLocateServiceDriver; | |||
@@ -48,7 +50,17 @@ public class NoopCloud implements | |||
public static final String NOOP_CLOUD = NoopCloud.class.getName(); | |||
@Override public boolean send(Account account, AccountMessage message, AccountContact contact) { | |||
if (log.isDebugEnabled()) log.debug("send( account="+ account + ")"); | |||
if (log.isDebugEnabled()) log.debug("send(account=" + account + ")"); | |||
return false; | |||
} | |||
@Override public RenderedEmail renderMessage(Account account, AccountMessage message, AccountContact contact) { | |||
if (log.isDebugEnabled()) log.debug("renderMessage(account=" + account + ")"); | |||
return null; | |||
} | |||
@Override public boolean send(RenderedMessage renderedMessage) { | |||
if (log.isDebugEnabled()) log.debug("send(renderedMessage=" + renderedMessage + ")"); | |||
return false; | |||
} | |||
@@ -58,42 +70,42 @@ public class NoopCloud implements | |||
} | |||
@Override public boolean _write(String fromNode, String key, InputStream data, StorageMetadata metadata, String requestId) throws IOException { | |||
if (log.isDebugEnabled()) log.debug("_write( fromNode="+ fromNode + ")"); | |||
if (log.isDebugEnabled()) log.debug("_write(fromNode=" + fromNode + ")"); | |||
return false; | |||
} | |||
@Override public boolean canWrite(String fromNode, String toNode, String key) { | |||
if (log.isDebugEnabled()) log.debug("canWrite( fromNode="+ fromNode + ")"); | |||
if (log.isDebugEnabled()) log.debug("canWrite(fromNode=" + fromNode + ")"); | |||
return false; | |||
} | |||
@Override public boolean delete(String fromNode, String uri) { | |||
if (log.isDebugEnabled()) log.debug("delete( fromNode="+ fromNode + ")"); | |||
if (log.isDebugEnabled()) log.debug("delete(fromNode=" + fromNode + ")"); | |||
return false; | |||
} | |||
@Override public boolean deleteNetwork(String networkUuid) throws IOException { | |||
if (log.isDebugEnabled()) log.debug("deleteNetwork( networkUuid="+ networkUuid + ")"); | |||
if (log.isDebugEnabled()) log.debug("deleteNetwork(networkUuid=" + networkUuid + ")"); | |||
return false; | |||
} | |||
@Override public boolean rekey(String fromNode, CloudService newCloud) throws IOException { | |||
if (log.isDebugEnabled()) log.debug("rekey( fromNode="+ fromNode + ")"); | |||
if (log.isDebugEnabled()) log.debug("rekey(fromNode=" + fromNode + ")"); | |||
return false; | |||
} | |||
@Override public StorageListing list(String fromNode, String prefix) throws IOException { | |||
if (log.isDebugEnabled()) log.debug("list( fromNode="+ fromNode + ")"); | |||
if (log.isDebugEnabled()) log.debug("list(fromNode=" + fromNode + ")"); | |||
return null; | |||
} | |||
@Override public StorageListing listNext(String fromNode, String listingId) throws IOException { | |||
if (log.isDebugEnabled()) log.debug("listNext( fromNode="+ fromNode + ")"); | |||
if (log.isDebugEnabled()) log.debug("listNext(fromNode=" + fromNode + ")"); | |||
return null; | |||
} | |||
@Override public void setConfig(JsonNode json, CloudService cloudService) { | |||
if (log.isDebugEnabled()) log.debug("setConfig( json="+ json + ")"); | |||
if (log.isDebugEnabled()) log.debug("setConfig(json=" + json + ")"); | |||
} | |||
@@ -103,7 +115,7 @@ public class NoopCloud implements | |||
} | |||
@Override public void setCredentials(CloudCredentials creds) { | |||
if (log.isDebugEnabled()) log.debug("setCredentials( creds="+ creds + ")"); | |||
if (log.isDebugEnabled()) log.debug("setCredentials(creds=" + creds + ")"); | |||
} | |||
@@ -113,17 +125,17 @@ public class NoopCloud implements | |||
} | |||
@Override public boolean _exists(String fromNode, String key) throws IOException { | |||
if (log.isDebugEnabled()) log.debug("_exists( fromNode="+ fromNode + ")"); | |||
if (log.isDebugEnabled()) log.debug("_exists(fromNode=" + fromNode + ")"); | |||
return false; | |||
} | |||
@Override public StorageMetadata readMetadata(String fromNode, String key) { | |||
if (log.isDebugEnabled()) log.debug("readMetadata( fromNode="+ fromNode + ")"); | |||
if (log.isDebugEnabled()) log.debug("readMetadata(fromNode=" + fromNode + ")"); | |||
return null; | |||
} | |||
@Override public InputStream _read(String fromNode, String key) throws IOException { | |||
if (log.isDebugEnabled()) log.debug("_read( fromNode="+ fromNode + ")"); | |||
if (log.isDebugEnabled()) log.debug("_read(fromNode=" + fromNode + ")"); | |||
return null; | |||
} | |||
@@ -133,77 +145,77 @@ public class NoopCloud implements | |||
} | |||
@Override public PaymentValidationResult validate(AccountPaymentMethod paymentMethod) { | |||
if (log.isDebugEnabled()) log.debug("validate( paymentMethod="+ paymentMethod + ")"); | |||
if (log.isDebugEnabled()) log.debug("validate(paymentMethod=" + paymentMethod + ")"); | |||
return null; | |||
} | |||
@Override public boolean authorize(BubblePlan plan, String accountPlanUuid, String billUuid, AccountPaymentMethod paymentMethod) { | |||
if (log.isDebugEnabled()) log.debug("authorize( plan="+ plan + ")"); | |||
if (log.isDebugEnabled()) log.debug("authorize(plan=" + plan + ")"); | |||
return false; | |||
} | |||
@Override public boolean cancelAuthorization(BubblePlan plan, String accountPlanUuid, AccountPaymentMethod paymentMethod) { | |||
if (log.isDebugEnabled()) log.debug("cancelAuthorization( plan="+ plan + ")"); | |||
if (log.isDebugEnabled()) log.debug("cancelAuthorization(plan=" + plan + ")"); | |||
return false; | |||
} | |||
@Override public boolean purchase(String accountPlanUuid, String paymentMethodUuid, String billUuid) { | |||
if (log.isDebugEnabled()) log.debug("purchase( accountPlanUuid="+ accountPlanUuid + ")"); | |||
if (log.isDebugEnabled()) log.debug("purchase(accountPlanUuid=" + accountPlanUuid + ")"); | |||
return false; | |||
} | |||
@Override public boolean refund(String accountPlanUuid) { | |||
if (log.isDebugEnabled()) log.debug("refund( accountPlanUuid="+ accountPlanUuid + ")"); | |||
if (log.isDebugEnabled()) log.debug("refund(accountPlanUuid=" + accountPlanUuid + ")"); | |||
return false; | |||
} | |||
@Override public GeoTimeZone getTimezone(String lat, String lon) { | |||
if (log.isDebugEnabled()) log.debug("getTimezone( lat="+ lat + ")"); | |||
if (log.isDebugEnabled()) log.debug("getTimezone(lat=" + lat + ")"); | |||
return null; | |||
} | |||
@Override public GeoLocation geolocate(String ip) { | |||
if (log.isDebugEnabled()) log.debug("geolocate( ip="+ ip + ")"); | |||
if (log.isDebugEnabled()) log.debug("geolocate(ip=" + ip + ")"); | |||
return null; | |||
} | |||
@Override public GeoCodeResult lookup(GeoLocation location) { | |||
if (log.isDebugEnabled()) log.debug("lookup( location="+ location + ")"); | |||
if (log.isDebugEnabled()) log.debug("lookup(location=" + location + ")"); | |||
return null; | |||
} | |||
@Override public Collection<DnsRecord> create(BubbleDomain domain) { | |||
if (log.isDebugEnabled()) log.debug("create( domain="+ domain + ")"); | |||
if (log.isDebugEnabled()) log.debug("create(domain=" + domain + ")"); | |||
return null; | |||
} | |||
@Override public Collection<DnsRecord> setNetwork(BubbleNetwork network) { | |||
if (log.isDebugEnabled()) log.debug("setNetwork( network="+ network + ")"); | |||
if (log.isDebugEnabled()) log.debug("setNetwork(network=" + network + ")"); | |||
return null; | |||
} | |||
@Override public Collection<DnsRecord> setNode(BubbleNode node) { | |||
if (log.isDebugEnabled()) log.debug("setNode( node="+ node + ")"); | |||
if (log.isDebugEnabled()) log.debug("setNode(node=" + node + ")"); | |||
return null; | |||
} | |||
@Override public Collection<DnsRecord> deleteNode(BubbleNode node) { | |||
if (log.isDebugEnabled()) log.debug("deleteNode( node="+ node + ")"); | |||
if (log.isDebugEnabled()) log.debug("deleteNode(node=" + node + ")"); | |||
return null; | |||
} | |||
@Override public DnsRecord update(DnsRecord record) { | |||
if (log.isDebugEnabled()) log.debug("update( record="+ record + ")"); | |||
if (log.isDebugEnabled()) log.debug("update(record=" + record + ")"); | |||
return null; | |||
} | |||
@Override public DnsRecord remove(DnsRecord record) { | |||
if (log.isDebugEnabled()) log.debug("remove( record="+ record + ")"); | |||
if (log.isDebugEnabled()) log.debug("remove(record=" + record + ")"); | |||
return null; | |||
} | |||
@Override public Collection<DnsRecord> list(DnsRecordMatch matcher) { | |||
if (log.isDebugEnabled()) log.debug("list( matcher="+ matcher + ")"); | |||
if (log.isDebugEnabled()) log.debug("list(matcher=" + matcher + ")"); | |||
return null; | |||
} | |||
@@ -213,27 +225,27 @@ public class NoopCloud implements | |||
} | |||
@Override public ComputeNodeSize getSize(ComputeNodeSizeType type) { | |||
if (log.isDebugEnabled()) log.debug("getSize( type="+ type + ")"); | |||
if (log.isDebugEnabled()) log.debug("getSize(type=" + type + ")"); | |||
return null; | |||
} | |||
@Override public BubbleNode start(BubbleNode node) throws Exception { | |||
if (log.isDebugEnabled()) log.debug("start( node="+ node + ")"); | |||
if (log.isDebugEnabled()) log.debug("start(node=" + node + ")"); | |||
return null; | |||
} | |||
@Override public BubbleNode cleanupStart(BubbleNode node) throws Exception { | |||
if (log.isDebugEnabled()) log.debug("cleanupStart( node="+ node + ")"); | |||
if (log.isDebugEnabled()) log.debug("cleanupStart(node=" + node + ")"); | |||
return null; | |||
} | |||
@Override public BubbleNode stop(BubbleNode node) throws Exception { | |||
if (log.isDebugEnabled()) log.debug("stop( node="+ node + ")"); | |||
if (log.isDebugEnabled()) log.debug("stop(node=" + node + ")"); | |||
return null; | |||
} | |||
@Override public BubbleNode status(BubbleNode node) throws Exception { | |||
if (log.isDebugEnabled()) log.debug("status( node="+ node + ")"); | |||
if (log.isDebugEnabled()) log.debug("status(node=" + node + ")"); | |||
return null; | |||
} | |||
@@ -243,7 +255,7 @@ public class NoopCloud implements | |||
} | |||
@Override public CloudRegion getRegion(String region) { | |||
if (log.isDebugEnabled()) log.debug("getRegion( region="+ region + ")"); | |||
if (log.isDebugEnabled()) log.debug("getRegion(region=" + region + ")"); | |||
return null; | |||
} | |||
} |
@@ -46,6 +46,8 @@ public interface AuthenticationDriver extends CloudServiceDriver { | |||
boolean send(Account account, AccountMessage message, AccountContact contact); | |||
boolean send(RenderedMessage renderedMessage); | |||
@Override default boolean test () { return true; } | |||
default Map<String, Object> buildContext(Account account, AccountMessage message, AccountContact contact) { | |||
@@ -5,8 +5,14 @@ | |||
package bubble.cloud.auth; | |||
import bubble.cloud.DelegatedCloudServiceDriverBase; | |||
import bubble.model.cloud.BubbleNode; | |||
import bubble.model.cloud.CloudService; | |||
import bubble.model.cloud.notify.NotificationType; | |||
import bubble.notify.auth.AuthDriverNotification; | |||
import bubble.notify.email.EmailDriverNotification; | |||
import com.fasterxml.jackson.databind.JsonNode; | |||
import static org.cobbzilla.util.json.JsonUtil.json; | |||
public abstract class DelegatedAuthDriverBase extends DelegatedCloudServiceDriverBase implements AuthenticationDriver { | |||
@@ -14,4 +20,25 @@ public abstract class DelegatedAuthDriverBase extends DelegatedCloudServiceDrive | |||
protected AuthDriverNotification notification(AuthDriverNotification n) { return n.setAuthService(cloud.getDelegated()); } | |||
protected abstract NotificationType getSendNotificationType(); | |||
protected abstract Class<? extends RenderedMessage> getRenderedMessageClass(); | |||
protected abstract String getDefaultTemplatePath(); | |||
@Override public String getTemplatePath() { | |||
final JsonNode driverConfig = cloud.getDriverConfig(); | |||
return driverConfig != null && driverConfig.has("templatePath") | |||
? driverConfig.get("templatePath").textValue() | |||
: getDefaultTemplatePath(); | |||
} | |||
@Override public boolean send(RenderedMessage message) { | |||
final BubbleNode delegate = getDelegateNode(); | |||
if (!configuration.testMode()) message.getCtx().clear(); | |||
return notificationService.notifySync(delegate, getSendNotificationType(), notification(new EmailDriverNotification() | |||
.setRenderedMessage(json(json(message), JsonNode.class)) | |||
.setRenderedMessageClass(getRenderedMessageClass().getName()))); | |||
} | |||
} |
@@ -18,6 +18,8 @@ import java.util.function.Predicate; | |||
import java.util.stream.Collectors; | |||
import static bubble.ApiConstants.ALWAYS_TRUE; | |||
import static org.cobbzilla.util.daemon.ZillaRuntime.die; | |||
import static org.cobbzilla.util.json.JsonUtil.json; | |||
public interface RenderedMessage { | |||
@@ -27,9 +29,10 @@ public interface RenderedMessage { | |||
Comparator<RenderedMessage> SORT_CTIME_ASC = Comparator.comparingLong(RenderedMessage::getCtime); | |||
Map<String, Object> getCtx(); | |||
long getCtime(); | |||
default <T> T del(String key) { getCtx().remove(key); return (T) this; } | |||
default <T> T del(String key) { if (getCtx() != null) getCtx().remove(key); return (T) this; } | |||
default boolean hasContext(String key, String val) { | |||
if (getCtx() == null) return false; | |||
@@ -42,7 +45,14 @@ public interface RenderedMessage { | |||
} | |||
} | |||
@JsonIgnore default AccountMessage getAccountMessage() { return (AccountMessage) getCtx().get("message"); } | |||
@JsonIgnore default AccountMessage getAccountMessage() { | |||
final Object obj = getCtx() == null ? null : getCtx().get("message"); | |||
if (obj == null) return null; | |||
if (obj instanceof AccountMessage) return (AccountMessage) obj; | |||
if (obj instanceof Map) return json(json(obj), AccountMessage.class); | |||
return die("getAccountMessage: unrecognized 'message' object: "+obj); | |||
} | |||
@JsonIgnore default AccountMessageType getMessageType() { return getAccountMessage().getMessageType(); } | |||
@JsonIgnore default AccountAction getAction() { return getAccountMessage().getAction(); } | |||
@JsonIgnore default ActionTarget getTarget() { return getAccountMessage().getTarget(); } | |||
@@ -4,6 +4,7 @@ | |||
*/ | |||
package bubble.cloud.authenticator; | |||
import bubble.cloud.auth.RenderedMessage; | |||
import bubble.dao.account.AccountPolicyDAO; | |||
import bubble.model.account.Account; | |||
import bubble.model.account.AccountContact; | |||
@@ -11,6 +12,8 @@ import bubble.model.account.message.AccountMessage; | |||
import bubble.service.account.StandardAccountMessageService; | |||
import org.springframework.beans.factory.annotation.Autowired; | |||
import static org.cobbzilla.util.daemon.ZillaRuntime.notSupported; | |||
public class TOTPAuthenticatorDriver implements AuthenticatorServiceDriver { | |||
@Override public boolean disableDelegation() { return true; } | |||
@@ -23,4 +26,6 @@ public class TOTPAuthenticatorDriver implements AuthenticatorServiceDriver { | |||
return true; | |||
} | |||
@Override public boolean send(RenderedMessage renderedMessage) { return notSupported("send"); } | |||
} |
@@ -5,26 +5,23 @@ | |||
package bubble.cloud.authenticator.delegate; | |||
import bubble.cloud.auth.DelegatedAuthDriverBase; | |||
import bubble.cloud.auth.RenderedMessage; | |||
import bubble.cloud.authenticator.AuthenticatorServiceDriver; | |||
import bubble.model.account.Account; | |||
import bubble.model.account.AccountContact; | |||
import bubble.model.account.message.AccountMessage; | |||
import bubble.model.cloud.BubbleNode; | |||
import bubble.model.cloud.CloudService; | |||
import bubble.notify.authenticator.AuthenticatorDriverNotification; | |||
import bubble.model.cloud.notify.NotificationType; | |||
import static bubble.model.cloud.notify.NotificationType.authenticator_driver_send; | |||
import static org.cobbzilla.util.daemon.ZillaRuntime.notSupported; | |||
public class DelegatedAuthenticatorDriver extends DelegatedAuthDriverBase implements AuthenticatorServiceDriver { | |||
public DelegatedAuthenticatorDriver(CloudService cloud) { super(cloud); } | |||
@Override public boolean send(Account account, AccountMessage message, AccountContact contact) { | |||
final BubbleNode delegate = getDelegateNode(); | |||
return notificationService.notifySync(delegate, authenticator_driver_send, notification(new AuthenticatorDriverNotification() | |||
.setAccount(account) | |||
.setMessage(message) | |||
.setContact(contact))); | |||
} | |||
@Override protected String getDefaultTemplatePath() { return notSupported("getDefaultTemplatePath"); } | |||
@Override protected NotificationType getSendNotificationType() { return notSupported("getSendNotificationType"); } | |||
@Override protected Class<? extends RenderedMessage> getRenderedMessageClass() { return notSupported("getRenderedMessageClass"); } | |||
@Override public boolean send(Account account, AccountMessage message, AccountContact contact) { return notSupported("send"); } | |||
} |
@@ -6,9 +6,44 @@ package bubble.cloud.email; | |||
import bubble.cloud.CloudServiceType; | |||
import bubble.cloud.auth.AuthenticationDriver; | |||
import bubble.model.account.Account; | |||
import bubble.model.account.AccountContact; | |||
import bubble.model.account.message.AccountMessage; | |||
import bubble.server.BubbleConfiguration; | |||
import java.util.Map; | |||
public interface EmailServiceDriver extends AuthenticationDriver { | |||
@Override default CloudServiceType getType() { return CloudServiceType.email; } | |||
static boolean send(EmailServiceDriver driver, Account account, AccountMessage message, AccountContact contact) { | |||
final RenderedEmail email = driver.renderMessage(account, message, contact); | |||
return driver.send(email); | |||
} | |||
default RenderedEmail renderMessage(Account account, AccountMessage message, AccountContact contact) { | |||
return renderMessage(account, message, contact, getConfiguration(), getTemplatePath()); | |||
} | |||
static RenderedEmail renderMessage(Account account, AccountMessage message, AccountContact contact, | |||
BubbleConfiguration configuration, String templatePath) { | |||
final Map<String, Object> ctx = AuthenticationDriver.buildContext(account, message, contact, configuration); | |||
final RenderedEmail email = new RenderedEmail(ctx); | |||
email.setToEmail(contact.getInfo()); | |||
email.setToName(account.getName()); | |||
email.setFromEmail(AuthenticationDriver.render("fromEmail", ctx, message, configuration, templatePath)); | |||
email.setFromName(AuthenticationDriver.render("fromName", ctx, message, configuration, templatePath)); | |||
email.setSubject(AuthenticationDriver.render("subject", ctx, message, configuration, templatePath)); | |||
email.setMessage(AuthenticationDriver.render("message", ctx, message, configuration, templatePath)); | |||
try { | |||
email.setHtmlMessage(AuthenticationDriver.render("htmlMessage", ctx, message, configuration, templatePath)); | |||
} catch (Exception e) { | |||
log.debug("Error loading htmlMessage for "+message.templateName("htmlMessage")+": "+e); | |||
} | |||
return email; | |||
} | |||
} |
@@ -8,6 +8,7 @@ import bubble.cloud.auth.RenderedMessage; | |||
import lombok.Getter; | |||
import lombok.NoArgsConstructor; | |||
import lombok.Setter; | |||
import lombok.experimental.Accessors; | |||
import org.cobbzilla.mail.SimpleEmailMessage; | |||
import java.util.Map; | |||
@@ -16,7 +17,7 @@ import static java.util.UUID.randomUUID; | |||
import static org.cobbzilla.util.daemon.ZillaRuntime.now; | |||
import static org.cobbzilla.util.reflect.ReflectionUtil.copy; | |||
@NoArgsConstructor | |||
@NoArgsConstructor @Accessors(chain=true) | |||
public class RenderedEmail extends SimpleEmailMessage implements RenderedMessage { | |||
@Getter private long ctime = now(); | |||
@@ -5,7 +5,7 @@ | |||
package bubble.cloud.email; | |||
import bubble.cloud.CloudServiceDriverBase; | |||
import bubble.cloud.auth.AuthenticationDriver; | |||
import bubble.cloud.auth.RenderedMessage; | |||
import bubble.cloud.email.mock.MockMailSender; | |||
import bubble.model.account.Account; | |||
import bubble.model.account.AccountContact; | |||
@@ -13,12 +13,9 @@ import bubble.model.account.message.AccountMessage; | |||
import bubble.server.BubbleConfiguration; | |||
import lombok.Getter; | |||
import lombok.extern.slf4j.Slf4j; | |||
import org.cobbzilla.mail.SimpleEmailMessage; | |||
import org.cobbzilla.mail.sender.SmtpMailSender; | |||
import org.springframework.beans.factory.annotation.Autowired; | |||
import java.util.Map; | |||
@Slf4j | |||
public class SmtpEmailDriver extends CloudServiceDriverBase<EmailDriverConfig> implements EmailServiceDriver { | |||
@@ -42,40 +39,19 @@ public class SmtpEmailDriver extends CloudServiceDriverBase<EmailDriverConfig> i | |||
} | |||
@Override public boolean send(Account account, AccountMessage message, AccountContact contact) { | |||
return EmailServiceDriver.send(this, account, message, contact); | |||
} | |||
@Override public boolean send(RenderedMessage email) { | |||
try { | |||
final SimpleEmailMessage email = renderMessage(account, message, contact); | |||
log.debug("send: sending message "+getSender().getClass().getName()+": "+email); | |||
getSender().send(email); | |||
getSender().send((RenderedEmail) email); | |||
return true; | |||
} catch (Exception e) { | |||
log.error("send failed: "+e); | |||
return false; | |||
} | |||
return true; | |||
} | |||
@Override public String getTemplatePath() { return config.getTemplatePath(); } | |||
public SimpleEmailMessage renderMessage(Account account, AccountMessage message, AccountContact contact) { | |||
return renderMessage(account, message, contact, configuration, getTemplatePath()); | |||
} | |||
public static SimpleEmailMessage renderMessage(Account account, AccountMessage message, AccountContact contact, | |||
BubbleConfiguration configuration, String templatePath) { | |||
final Map<String, Object> ctx = AuthenticationDriver.buildContext(account, message, contact, configuration); | |||
final SimpleEmailMessage email = new RenderedEmail(ctx); | |||
email.setToEmail(contact.getInfo()); | |||
email.setToName(account.getName()); | |||
email.setFromEmail(AuthenticationDriver.render("fromEmail", ctx, message, configuration, templatePath)); | |||
email.setFromName(AuthenticationDriver.render("fromName", ctx, message, configuration, templatePath)); | |||
email.setSubject(AuthenticationDriver.render("subject", ctx, message, configuration, templatePath)); | |||
email.setMessage(AuthenticationDriver.render("message", ctx, message, configuration, templatePath)); | |||
try { | |||
email.setHtmlMessage(AuthenticationDriver.render("htmlMessage", ctx, message, configuration, templatePath)); | |||
} catch (Exception e) { | |||
log.debug("Error loading htmlMessage for "+message.templateName("htmlMessage")+": "+e); | |||
} | |||
return email; | |||
} | |||
} |
@@ -5,22 +5,20 @@ | |||
package bubble.cloud.email.delegate; | |||
import bubble.cloud.auth.DelegatedAuthDriverBase; | |||
import bubble.cloud.auth.RenderedMessage; | |||
import bubble.cloud.email.EmailDriverConfig; | |||
import bubble.cloud.email.EmailServiceDriver; | |||
import bubble.cloud.email.RenderedEmail; | |||
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.BubbleNode; | |||
import bubble.model.cloud.CloudService; | |||
import bubble.notify.email.EmailDriverNotification; | |||
import bubble.server.BubbleConfiguration; | |||
import lombok.Getter; | |||
import bubble.model.cloud.notify.NotificationType; | |||
import lombok.extern.slf4j.Slf4j; | |||
import org.apache.commons.mail.EmailException; | |||
import org.springframework.beans.factory.annotation.Autowired; | |||
import static bubble.cloud.email.EmailDriverConfig.DEFAULT_TEMPLATE_PATH; | |||
import static bubble.cloud.email.SmtpEmailDriver.renderMessage; | |||
import static bubble.model.cloud.notify.NotificationType.email_driver_send; | |||
import static org.cobbzilla.mail.sender.SmtpMailSender.isTestDomain; | |||
import static org.cobbzilla.util.daemon.ZillaRuntime.die; | |||
@@ -32,25 +30,25 @@ public class DelegatedEmailDriver extends DelegatedAuthDriverBase implements Ema | |||
private final MockMailSender mockMailSender = new MockMailSender(); | |||
@Getter @Autowired private BubbleConfiguration configuration; | |||
@Override public boolean send(Account account, AccountMessage message, AccountContact contact) { | |||
if (isTestDomain(contact.getInfo())) return sendToMock(account, message, contact); | |||
final BubbleNode delegate = getDelegateNode(); | |||
return notificationService.notifySync(delegate, email_driver_send, notification(new EmailDriverNotification() | |||
.setAccount(account) | |||
.setMessage(message) | |||
.setContact(contact))); | |||
return EmailServiceDriver.send(this, account, message, contact); | |||
} | |||
public boolean sendToMock(Account account, AccountMessage message, AccountContact contact) { | |||
try { | |||
log.info("send: sending to MockMailSender: "+message); | |||
mockMailSender.send(renderMessage(account, message, contact, getConfiguration(), DEFAULT_TEMPLATE_PATH)); | |||
mockMailSender.send(EmailServiceDriver.renderMessage(account, message, contact, getConfiguration(), DEFAULT_TEMPLATE_PATH)); | |||
} catch (EmailException e) { | |||
return die("send: "+e, e); | |||
} | |||
return true; | |||
} | |||
@Override protected String getDefaultTemplatePath() { return EmailDriverConfig.DEFAULT_TEMPLATE_PATH; } | |||
@Override public NotificationType getSendNotificationType() { return email_driver_send; } | |||
@Override protected Class<? extends RenderedMessage> getRenderedMessageClass() { return RenderedEmail.class; } | |||
} |
@@ -6,9 +6,39 @@ package bubble.cloud.sms; | |||
import bubble.cloud.CloudServiceType; | |||
import bubble.cloud.auth.AuthenticationDriver; | |||
import bubble.model.account.Account; | |||
import bubble.model.account.AccountContact; | |||
import bubble.model.account.message.AccountMessage; | |||
import org.cobbzilla.util.string.LocaleUtil; | |||
import java.util.Map; | |||
import static bubble.cloud.auth.SmsAuthFieldHandler.formatPhoneForCountry; | |||
import static org.cobbzilla.util.daemon.ZillaRuntime.die; | |||
public interface SmsServiceDriver extends AuthenticationDriver { | |||
@Override default CloudServiceType getType() { return CloudServiceType.sms; } | |||
static boolean send(SmsServiceDriver driver, Account account, AccountMessage message, AccountContact contact) { | |||
final String info = contact.getInfo(); | |||
final int colonPos = info.indexOf(':'); | |||
if (colonPos == -1 || colonPos == info.length()-1) return die("send: invalid number: "+ info); | |||
final String country = info.substring(0, colonPos); | |||
final String toPhone = formatPhoneForCountry(country, info.substring(colonPos+1)); | |||
final String prefix = LocaleUtil.getPhoneCode(country); | |||
if (prefix == null) return die("send: no telephone prefix found for country: "+country); | |||
final String dest = "+" + prefix + toPhone; | |||
final Map<String, Object> ctx = driver.buildContext(account, message, contact); | |||
final String text = driver.render("message", ctx, message); | |||
final RenderedSms renderedSms = new RenderedSms(ctx) | |||
.setToNumber(dest) | |||
.setText(text); | |||
return driver.send(renderedSms); | |||
} | |||
} |
@@ -4,6 +4,7 @@ | |||
*/ | |||
package bubble.cloud.sms; | |||
import bubble.cloud.auth.RenderedMessage; | |||
import bubble.model.account.Account; | |||
import bubble.model.account.AccountContact; | |||
import bubble.model.account.message.AccountMessage; | |||
@@ -13,12 +14,7 @@ import com.fasterxml.jackson.databind.JsonNode; | |||
import lombok.Getter; | |||
import lombok.Setter; | |||
import lombok.extern.slf4j.Slf4j; | |||
import org.cobbzilla.util.string.LocaleUtil; | |||
import java.util.Map; | |||
import static bubble.cloud.auth.SmsAuthFieldHandler.formatPhoneForCountry; | |||
import static org.cobbzilla.util.daemon.ZillaRuntime.die; | |||
import static org.cobbzilla.util.json.JsonUtil.json; | |||
import static org.cobbzilla.util.reflect.ReflectionUtil.getFirstTypeParam; | |||
@@ -40,24 +36,11 @@ public abstract class SmsServiceDriverBase<T extends SmsConfig> implements SmsSe | |||
protected abstract boolean deliver(RenderedSms sms); | |||
@Override public boolean send(Account account, AccountMessage message, AccountContact contact) { | |||
final String info = contact.getInfo(); | |||
final int colonPos = info.indexOf(':'); | |||
if (colonPos == -1 || colonPos == info.length()-1) return die("send: invalid number: "+ info); | |||
final String country = info.substring(0, colonPos); | |||
final String toPhone = formatPhoneForCountry(country, info.substring(colonPos+1)); | |||
final String prefix = LocaleUtil.getPhoneCode(country); | |||
if (prefix == null) return die("send: no telephone prefix found for country: "+country); | |||
final String dest = "+" + prefix + toPhone; | |||
final Map<String, Object> ctx = buildContext(account, message, contact); | |||
final String text = render("message", ctx, message); | |||
return deliver(new RenderedSms(ctx) | |||
.setFromNumber(getFromPhone()) | |||
.setToNumber(dest) | |||
.setText(text)); | |||
return SmsServiceDriver.send(this, account, message, contact); | |||
} | |||
@Override public boolean send(RenderedMessage renderedMessage) { | |||
final RenderedSms sms = (RenderedSms) renderedMessage; | |||
return deliver(sms.setFromNumber(getFromPhone())); | |||
} | |||
} |
@@ -5,13 +5,15 @@ | |||
package bubble.cloud.sms.delegate; | |||
import bubble.cloud.auth.DelegatedAuthDriverBase; | |||
import bubble.cloud.auth.RenderedMessage; | |||
import bubble.cloud.sms.RenderedSms; | |||
import bubble.cloud.sms.SmsConfig; | |||
import bubble.cloud.sms.SmsServiceDriver; | |||
import bubble.model.account.Account; | |||
import bubble.model.account.AccountContact; | |||
import bubble.model.account.message.AccountMessage; | |||
import bubble.model.cloud.BubbleNode; | |||
import bubble.model.cloud.CloudService; | |||
import bubble.notify.email.EmailDriverNotification; | |||
import bubble.model.cloud.notify.NotificationType; | |||
import static bubble.model.cloud.notify.NotificationType.sms_driver_send; | |||
@@ -20,11 +22,13 @@ public class DelegatedSmsDriver extends DelegatedAuthDriverBase implements SmsSe | |||
public DelegatedSmsDriver(CloudService cloud) { super(cloud); } | |||
@Override public boolean send(Account account, AccountMessage message, AccountContact contact) { | |||
final BubbleNode delegate = getDelegateNode(); | |||
return notificationService.notifySync(delegate, sms_driver_send, notification(new EmailDriverNotification() | |||
.setAccount(account) | |||
.setMessage(message) | |||
.setContact(contact))); | |||
return SmsServiceDriver.send(this, account, message, contact); | |||
} | |||
@Override protected String getDefaultTemplatePath() { return SmsConfig.DEFAULT_TEMPLATE_PATH; } | |||
@Override protected NotificationType getSendNotificationType() { return sms_driver_send; } | |||
@Override protected Class<? extends RenderedMessage> getRenderedMessageClass() { return RenderedSms.class; } | |||
} |
@@ -4,25 +4,22 @@ | |||
*/ | |||
package bubble.notify.auth; | |||
import bubble.model.account.Account; | |||
import bubble.model.account.AccountContact; | |||
import bubble.model.account.message.AccountMessage; | |||
import bubble.notify.SynchronousNotification; | |||
import com.fasterxml.jackson.databind.JsonNode; | |||
import lombok.Getter; | |||
import lombok.Setter; | |||
import lombok.experimental.Accessors; | |||
import static org.cobbzilla.util.daemon.ZillaRuntime.hashOf; | |||
import static org.cobbzilla.util.json.JsonUtil.json; | |||
@Accessors(chain=true) | |||
public class AuthDriverNotification extends SynchronousNotification { | |||
@Getter @Setter private String authService; | |||
@Getter @Setter private Account account; | |||
@Getter @Setter private AccountMessage message; | |||
@Getter @Setter private AccountContact contact; | |||
@Getter @Setter private JsonNode renderedMessage; | |||
@Getter @Setter private String renderedMessageClass; | |||
@Getter(lazy=true) private final String cacheKey | |||
= hashOf(authService, account != null ? account.getUuid() : null, message != null ? message.getCacheKey() : null, contact != null ? contact.getCacheKey() : null); | |||
@Getter(lazy=true) private final String cacheKey = hashOf(authService, renderedMessage != null ? json(renderedMessage) : null); | |||
} |
@@ -5,6 +5,7 @@ | |||
package bubble.notify.auth; | |||
import bubble.cloud.auth.AuthenticationDriver; | |||
import bubble.cloud.auth.RenderedMessage; | |||
import bubble.dao.cloud.BubbleNodeDAO; | |||
import bubble.dao.cloud.CloudServiceDAO; | |||
import bubble.model.cloud.BubbleNode; | |||
@@ -17,12 +18,15 @@ import org.springframework.beans.factory.annotation.Autowired; | |||
import static org.cobbzilla.util.daemon.ZillaRuntime.die; | |||
import static org.cobbzilla.util.json.JsonUtil.json; | |||
import static org.cobbzilla.util.reflect.ReflectionUtil.forName; | |||
public abstract class DelegatedAuthNotificationHandlerBase extends DelegatedNotificationHandlerBase { | |||
@Autowired private BubbleNodeDAO nodeDAO; | |||
@Autowired private CloudServiceDAO cloudDAO; | |||
protected abstract NotificationType getResponseType(); | |||
@Override public void handleNotification(ReceivedNotification n) { | |||
final BubbleNode sender = nodeDAO.findByUuid(n.getFromNode()); | |||
if (sender == null) die("sender not found: "+n.getFromNode()); | |||
@@ -31,11 +35,13 @@ public abstract class DelegatedAuthNotificationHandlerBase extends DelegatedNoti | |||
final CloudService authService = cloudDAO.findByUuid(notification.getAuthService()); | |||
final AuthenticationDriver driver = authService.getAuthenticationDriver(configuration); | |||
final boolean sent = driver.send(notification.getAccount(), notification.getMessage(), notification.getContact()); | |||
final Class<? extends RenderedMessage> renderedMessageClass = forName(notification.getRenderedMessageClass()); | |||
final RenderedMessage renderedMessage = json(notification.getRenderedMessage(), renderedMessageClass); | |||
final boolean sent = driver.send(renderedMessage); | |||
notifySender(getResponseType(), n.getNotificationId(), sender, sent); | |||
} | |||
protected abstract NotificationType getResponseType(); | |||
} |
@@ -1 +1 @@ | |||
bubble.version=0.9.3 | |||
bubble.version=0.9.4 |
@@ -50,9 +50,9 @@ | |||
"response": { | |||
"store": "smsInbox", | |||
"check": [ | |||
{"condition": "'{{json.[0].ctx.message.messageType}}' == 'request'"}, | |||
{"condition": "'{{json.[0].ctx.message.action}}' == 'verify'"}, | |||
{"condition": "'{{json.[0].ctx.message.target}}' == 'account'"} | |||
{"condition": "'{{json.[0].ctx.message.messageType}}' === 'request'"}, | |||
{"condition": "'{{json.[0].ctx.message.action}}' === 'verify'"}, | |||
{"condition": "'{{json.[0].ctx.message.target}}' === 'account'"} | |||
] | |||
} | |||
}, | |||
@@ -89,10 +89,10 @@ | |||
"response": { | |||
"store": "emailInbox", | |||
"check": [ | |||
{"condition": "json.length == 1"}, | |||
{"condition": "'{{json.[0].ctx.message.messageType}}' == 'request'"}, | |||
{"condition": "'{{json.[0].ctx.message.action}}' == 'password'"}, | |||
{"condition": "'{{json.[0].ctx.message.target}}' == 'account'"} | |||
{"condition": "json.length === 1"}, | |||
{"condition": "'{{json.[0].ctx.message.messageType}}' === 'request'"}, | |||
{"condition": "'{{json.[0].ctx.message.action}}' === 'password'"}, | |||
{"condition": "'{{json.[0].ctx.message.target}}' === 'account'"} | |||
] | |||
} | |||
}, | |||
@@ -106,10 +106,10 @@ | |||
"response": { | |||
"store": "smsInbox", | |||
"check": [ | |||
{"condition": "json.length == 1"}, | |||
{"condition": "'{{json.[0].ctx.message.messageType}}' == 'request'"}, | |||
{"condition": "'{{json.[0].ctx.message.action}}' == 'password'"}, | |||
{"condition": "'{{json.[0].ctx.message.target}}' == 'account'"} | |||
{"condition": "json.length === 1"}, | |||
{"condition": "'{{json.[0].ctx.message.messageType}}' === 'request'"}, | |||
{"condition": "'{{json.[0].ctx.message.action}}' === 'password'"}, | |||
{"condition": "'{{json.[0].ctx.message.target}}' === 'account'"} | |||
] | |||
} | |||
}, | |||
@@ -143,8 +143,8 @@ | |||
"response": { | |||
"check": [ | |||
{"condition": "json.getMultifactorAuth() != null"}, | |||
{"condition": "json.getMultifactorAuth().length == 1"}, | |||
{"condition": "json.getMultifactorAuth()[0].getType().name() == 'sms'"} | |||
{"condition": "json.getMultifactorAuth().length === 1"}, | |||
{"condition": "json.getMultifactorAuth()[0].getType().name() === 'sms'"} | |||
] | |||
} | |||
}, | |||
@@ -197,7 +197,7 @@ | |||
"comment": "view current user", | |||
"request": { "uri": "me" }, | |||
"response": { | |||
"check": [ {"condition": "json.getName() == '{{userAccount.name}}'"} ] | |||
"check": [ {"condition": "json.getName() === '{{userAccount.name}}'"} ] | |||
} | |||
} | |||
] |