@@ -4,6 +4,7 @@ import bubble.model.cloud.BubbleNode; | |||||
import com.fasterxml.jackson.databind.JsonNode; | import com.fasterxml.jackson.databind.JsonNode; | ||||
import com.warrenstrange.googleauth.GoogleAuthenticator; | import com.warrenstrange.googleauth.GoogleAuthenticator; | ||||
import lombok.Getter; | import lombok.Getter; | ||||
import lombok.extern.slf4j.Slf4j; | |||||
import org.apache.commons.lang3.RandomUtils; | import org.apache.commons.lang3.RandomUtils; | ||||
import org.cobbzilla.util.io.FileUtil; | import org.cobbzilla.util.io.FileUtil; | ||||
import org.glassfish.grizzly.http.server.Request; | import org.glassfish.grizzly.http.server.Request; | ||||
@@ -24,8 +25,10 @@ import static org.cobbzilla.util.daemon.ZillaRuntime.die; | |||||
import static org.cobbzilla.util.io.FileUtil.abs; | import static org.cobbzilla.util.io.FileUtil.abs; | ||||
import static org.cobbzilla.util.io.StreamUtil.stream2string; | import static org.cobbzilla.util.io.StreamUtil.stream2string; | ||||
import static org.cobbzilla.util.json.JsonUtil.json; | import static org.cobbzilla.util.json.JsonUtil.json; | ||||
import static org.cobbzilla.util.network.NetworkUtil.*; | |||||
import static org.cobbzilla.wizard.resources.ResourceUtil.invalidEx; | import static org.cobbzilla.wizard.resources.ResourceUtil.invalidEx; | ||||
@Slf4j | |||||
public class ApiConstants { | public class ApiConstants { | ||||
@Getter(lazy=true) private static final String bubbleDefaultDomain = initDefaultDomain(); | @Getter(lazy=true) private static final String bubbleDefaultDomain = initDefaultDomain(); | ||||
@@ -172,8 +175,13 @@ public class ApiConstants { | |||||
} | } | ||||
public static String getRemoteHost(Request req) { | public static String getRemoteHost(Request req) { | ||||
final String remoteHost = req.getHeader("X-Forwarded-For"); | |||||
return remoteHost == null ? req.getRemoteAddr() : remoteHost; | |||||
final String xff = req.getHeader("X-Forwarded-For"); | |||||
final String remoteHost = xff == null ? req.getRemoteAddr() : xff; | |||||
if (isPublicIpv4(remoteHost)) return remoteHost; | |||||
final String publicIp = getFirstPublicIpv4(); | |||||
if (publicIp != null) return publicIp; | |||||
final String externalIp = getExternalIp(); | |||||
return isPublicIpv4(externalIp) ? externalIp : remoteHost; | |||||
} | } | ||||
public static String getUserAgent(ContainerRequest ctx) { return ctx.getHeaderString(USER_AGENT); } | public static String getUserAgent(ContainerRequest ctx) { return ctx.getHeaderString(USER_AGENT); } | ||||
@@ -1,18 +1,18 @@ | |||||
package bubble.dao.account; | package bubble.dao.account; | ||||
import bubble.dao.account.message.AccountMessageDAO; | import bubble.dao.account.message.AccountMessageDAO; | ||||
import bubble.dao.app.*; | |||||
import bubble.dao.cloud.AnsibleRoleDAO; | import bubble.dao.cloud.AnsibleRoleDAO; | ||||
import bubble.dao.cloud.BubbleDomainDAO; | import bubble.dao.cloud.BubbleDomainDAO; | ||||
import bubble.dao.cloud.BubbleFootprintDAO; | import bubble.dao.cloud.BubbleFootprintDAO; | ||||
import bubble.dao.cloud.CloudServiceDAO; | import bubble.dao.cloud.CloudServiceDAO; | ||||
import bubble.dao.device.DeviceDAO; | import bubble.dao.device.DeviceDAO; | ||||
import bubble.dao.app.*; | |||||
import bubble.model.account.*; | import bubble.model.account.*; | ||||
import bubble.model.app.*; | |||||
import bubble.model.cloud.BubbleDomain; | import bubble.model.cloud.BubbleDomain; | ||||
import bubble.model.cloud.BubbleNode; | import bubble.model.cloud.BubbleNode; | ||||
import bubble.model.cloud.CloudCredentials; | import bubble.model.cloud.CloudCredentials; | ||||
import bubble.model.cloud.CloudService; | import bubble.model.cloud.CloudService; | ||||
import bubble.model.app.*; | |||||
import bubble.server.BubbleConfiguration; | import bubble.server.BubbleConfiguration; | ||||
import lombok.extern.slf4j.Slf4j; | import lombok.extern.slf4j.Slf4j; | ||||
import org.cobbzilla.wizard.dao.AbstractCRUDDAO; | import org.cobbzilla.wizard.dao.AbstractCRUDDAO; | ||||
@@ -25,6 +25,7 @@ import javax.transaction.Transactional; | |||||
import java.util.HashMap; | import java.util.HashMap; | ||||
import java.util.List; | import java.util.List; | ||||
import java.util.Map; | import java.util.Map; | ||||
import java.util.concurrent.atomic.AtomicBoolean; | |||||
import static bubble.ApiConstants.getRemoteHost; | import static bubble.ApiConstants.getRemoteHost; | ||||
import static bubble.model.account.AccountTemplate.copyTemplateObjects; | import static bubble.model.account.AccountTemplate.copyTemplateObjects; | ||||
@@ -86,9 +87,10 @@ public class AccountDAO extends AbstractCRUDDAO<Account> { | |||||
deviceDAO.ensureSpareDevice(accountUuid, thisNode.getNetwork(), true); | deviceDAO.ensureSpareDevice(accountUuid, thisNode.getNetwork(), true); | ||||
} | } | ||||
// copy drivers, keep map of old uuid to new driver so we can map rules below | |||||
if (account.hasParent()) { | if (account.hasParent()) { | ||||
daemon(new AccountInitializer(this, account, messageDAO)); | |||||
final AccountInitializer init = new AccountInitializer(account, this, messageDAO); | |||||
account.setAccountInitializer(init); | |||||
daemon(init); | |||||
} | } | ||||
return super.postCreate(account, context); | return super.postCreate(account, context); | ||||
@@ -103,11 +105,53 @@ public class AccountDAO extends AbstractCRUDDAO<Account> { | |||||
return super.postUpdate(account, context); | return super.postUpdate(account, context); | ||||
} | } | ||||
public void copyTemplates(Account account) { | |||||
public void copyTemplates(Account account, AtomicBoolean ready) { | |||||
final String parent = account.getParent(); | final String parent = account.getParent(); | ||||
final Map<String, RuleDriver> drivers = new HashMap<>(); | |||||
final String acct = account.getUuid(); | final String acct = account.getUuid(); | ||||
final Map<String, CloudService> clouds = new HashMap<>(); | |||||
copyTemplateObjects(acct, parent, cloudDAO, new AccountTemplate.CopyTemplate<>() { | |||||
@Override public CloudService preCreate(CloudService parentEntity, CloudService accountEntity) { | |||||
return accountEntity.setDelegated(parentEntity.getUuid()) | |||||
.setCredentials(CloudCredentials.delegate(configuration.getThisNode(), configuration)) | |||||
.setTemplate(false); | |||||
} | |||||
@Override public void postCreate(CloudService parentEntity, CloudService accountEntity) { | |||||
clouds.put(parentEntity.getUuid(), accountEntity); | |||||
} | |||||
}); | |||||
copyTemplateObjects(acct, parent, footprintDAO); | |||||
//noinspection Convert2Diamond -- compilation breaks with <> | |||||
copyTemplateObjects(acct, parent, domainDAO, new AccountTemplate.CopyTemplate<BubbleDomain>() { | |||||
@Override public BubbleDomain preCreate(BubbleDomain parentEntity, BubbleDomain accountEntity) { | |||||
final CloudService publicDns = findDnsCloudService(parentEntity, parentEntity.getPublicDns()); | |||||
if (publicDns == null) return null; | |||||
return accountEntity | |||||
.setDelegated(parentEntity.getUuid()) | |||||
.setPublicDns(publicDns.getUuid()); | |||||
} | |||||
public CloudService findDnsCloudService(BubbleDomain parentEntity, String cloudDnsUuid) { | |||||
final CloudService dns = clouds.get(cloudDnsUuid); | |||||
if (dns == null) { | |||||
log.error("DNS service "+ cloudDnsUuid +" could not be found for domain "+parentEntity.getUuid()); | |||||
return null; | |||||
} | |||||
final CloudService acctPublicDns = cloudDAO.findByAccountAndName(acct, dns.getName()); | |||||
if (acctPublicDns == null) { | |||||
log.error("DNS service not found under account "+acct+": "+dns.getName()); | |||||
return null; | |||||
} | |||||
return dns; | |||||
} | |||||
}); | |||||
ready.set(true); | |||||
copyTemplateObjects(acct, parent, roleDAO); | |||||
final Map<String, RuleDriver> drivers = new HashMap<>(); | |||||
copyTemplateObjects(acct, parent, driverDAO, new AccountTemplate.CopyTemplate<>() { | copyTemplateObjects(acct, parent, driverDAO, new AccountTemplate.CopyTemplate<>() { | ||||
@Override public void postCreate(RuleDriver parentEntity, RuleDriver accountEntity) { | @Override public void postCreate(RuleDriver parentEntity, RuleDriver accountEntity) { | ||||
drivers.put(parentEntity.getUuid(), accountEntity); | drivers.put(parentEntity.getUuid(), accountEntity); | ||||
@@ -162,46 +206,6 @@ public class AccountDAO extends AbstractCRUDDAO<Account> { | |||||
} | } | ||||
}); | }); | ||||
final Map<String, CloudService> clouds = new HashMap<>(); | |||||
copyTemplateObjects(acct, parent, cloudDAO, new AccountTemplate.CopyTemplate<>() { | |||||
@Override public CloudService preCreate(CloudService parentEntity, CloudService accountEntity) { | |||||
return accountEntity.setDelegated(parentEntity.getUuid()) | |||||
.setCredentials(CloudCredentials.delegate(configuration.getThisNode(), configuration)) | |||||
.setTemplate(false); | |||||
} | |||||
@Override public void postCreate(CloudService parentEntity, CloudService accountEntity) { | |||||
clouds.put(parentEntity.getUuid(), accountEntity); | |||||
} | |||||
}); | |||||
copyTemplateObjects(acct, parent, roleDAO); | |||||
copyTemplateObjects(acct, parent, footprintDAO); | |||||
//noinspection Convert2Diamond -- compilation breaks with <> | |||||
copyTemplateObjects(acct, parent, domainDAO, new AccountTemplate.CopyTemplate<BubbleDomain>() { | |||||
@Override public BubbleDomain preCreate(BubbleDomain parentEntity, BubbleDomain accountEntity) { | |||||
final CloudService publicDns = findDnsCloudService(parentEntity, parentEntity.getPublicDns()); | |||||
if (publicDns == null) return null; | |||||
return accountEntity | |||||
.setDelegated(parentEntity.getUuid()) | |||||
.setPublicDns(publicDns.getUuid()); | |||||
} | |||||
public CloudService findDnsCloudService(BubbleDomain parentEntity, String cloudDnsUuid) { | |||||
final CloudService dns = clouds.get(cloudDnsUuid); | |||||
if (dns == null) { | |||||
log.error("DNS service "+ cloudDnsUuid +" could not be found for domain "+parentEntity.getUuid()); | |||||
return null; | |||||
} | |||||
final CloudService acctPublicDns = cloudDAO.findByAccountAndName(acct, dns.getName()); | |||||
if (acctPublicDns == null) { | |||||
log.error("DNS service not found under account "+acct+": "+dns.getName()); | |||||
return null; | |||||
} | |||||
return dns; | |||||
} | |||||
}); | |||||
log.info("copyTemplates completed: "+acct); | log.info("copyTemplates completed: "+acct); | ||||
} | } | ||||
@@ -9,6 +9,8 @@ import bubble.model.account.message.ActionTarget; | |||||
import lombok.AllArgsConstructor; | import lombok.AllArgsConstructor; | ||||
import lombok.extern.slf4j.Slf4j; | import lombok.extern.slf4j.Slf4j; | ||||
import java.util.concurrent.atomic.AtomicBoolean; | |||||
import static java.util.concurrent.TimeUnit.SECONDS; | import static java.util.concurrent.TimeUnit.SECONDS; | ||||
import static org.cobbzilla.util.daemon.ZillaRuntime.die; | import static org.cobbzilla.util.daemon.ZillaRuntime.die; | ||||
import static org.cobbzilla.util.system.Sleep.sleep; | import static org.cobbzilla.util.system.Sleep.sleep; | ||||
@@ -17,11 +19,20 @@ import static org.cobbzilla.util.system.Sleep.sleep; | |||||
public class AccountInitializer implements Runnable { | public class AccountInitializer implements Runnable { | ||||
public static final int MAX_ACCOUNT_INIT_RETRIES = 3; | public static final int MAX_ACCOUNT_INIT_RETRIES = 3; | ||||
public static final long COPY_WAIT_TIME = SECONDS.toMillis(3); | |||||
public static final long COPY_WAIT_TIME = SECONDS.toMillis(2); | |||||
private AccountDAO accountDAO; | |||||
private Account account; | private Account account; | ||||
private AccountDAO accountDAO; | |||||
private AccountMessageDAO messageDAO; | private AccountMessageDAO messageDAO; | ||||
private AtomicBoolean ready = new AtomicBoolean(false); | |||||
public AccountInitializer(Account account, AccountDAO accountDAO, AccountMessageDAO messageDAO) { | |||||
this.account = account; | |||||
this.accountDAO = accountDAO; | |||||
this.messageDAO = messageDAO; | |||||
} | |||||
public boolean ready() { return ready.get(); } | |||||
@Override public void run() { | @Override public void run() { | ||||
try { | try { | ||||
@@ -30,7 +41,7 @@ public class AccountInitializer implements Runnable { | |||||
for (int i=0; i<MAX_ACCOUNT_INIT_RETRIES; i++) { | for (int i=0; i<MAX_ACCOUNT_INIT_RETRIES; i++) { | ||||
try { | try { | ||||
sleep(COPY_WAIT_TIME, "waiting before copyTemplates"); | sleep(COPY_WAIT_TIME, "waiting before copyTemplates"); | ||||
accountDAO.copyTemplates(account); | |||||
accountDAO.copyTemplates(account, ready); | |||||
if (account.hasPolicy() && account.getPolicy().hasAccountContacts()) { | if (account.hasPolicy() && account.getPolicy().hasAccountContacts()) { | ||||
messageDAO.sendVerifyRequest(account.getRemoteHost(), account, account.getPolicy().getAccountContacts()[0]); | messageDAO.sendVerifyRequest(account.getRemoteHost(), account, account.getPolicy().getAccountContacts()[0]); | ||||
@@ -1,5 +1,6 @@ | |||||
package bubble.model.account; | package bubble.model.account; | ||||
import bubble.dao.account.AccountInitializer; | |||||
import bubble.model.app.AppData; | import bubble.model.app.AppData; | ||||
import bubble.model.app.BubbleApp; | import bubble.model.app.BubbleApp; | ||||
import bubble.model.cloud.*; | import bubble.model.cloud.*; | ||||
@@ -10,6 +11,7 @@ import lombok.Getter; | |||||
import lombok.NoArgsConstructor; | import lombok.NoArgsConstructor; | ||||
import lombok.Setter; | import lombok.Setter; | ||||
import lombok.experimental.Accessors; | import lombok.experimental.Accessors; | ||||
import lombok.extern.slf4j.Slf4j; | |||||
import org.cobbzilla.util.collection.ArrayUtil; | import org.cobbzilla.util.collection.ArrayUtil; | ||||
import org.cobbzilla.wizard.filters.auth.TokenPrincipal; | import org.cobbzilla.wizard.filters.auth.TokenPrincipal; | ||||
import org.cobbzilla.wizard.model.HashedPassword; | import org.cobbzilla.wizard.model.HashedPassword; | ||||
@@ -28,9 +30,15 @@ import javax.validation.constraints.Size; | |||||
import java.util.Arrays; | import java.util.Arrays; | ||||
import java.util.List; | import java.util.List; | ||||
import static java.util.concurrent.TimeUnit.MILLISECONDS; | |||||
import static java.util.concurrent.TimeUnit.SECONDS; | |||||
import static org.cobbzilla.util.daemon.ZillaRuntime.now; | |||||
import static org.cobbzilla.util.reflect.ReflectionUtil.copy; | import static org.cobbzilla.util.reflect.ReflectionUtil.copy; | ||||
import static org.cobbzilla.util.system.Sleep.sleep; | |||||
import static org.cobbzilla.util.time.TimeUtil.formatDuration; | |||||
import static org.cobbzilla.wizard.model.crypto.EncryptedTypes.ENCRYPTED_STRING; | import static org.cobbzilla.wizard.model.crypto.EncryptedTypes.ENCRYPTED_STRING; | ||||
import static org.cobbzilla.wizard.model.crypto.EncryptedTypes.ENC_PAD; | import static org.cobbzilla.wizard.model.crypto.EncryptedTypes.ENC_PAD; | ||||
import static org.cobbzilla.wizard.resources.ResourceUtil.invalidEx; | |||||
@ECType(root=true) @ECTypeURIs(listFields={"name", "url", "description", "admin", "suspended"}, isDeleteDefined=false) | @ECType(root=true) @ECTypeURIs(listFields={"name", "url", "description", "admin", "suspended"}, isDeleteDefined=false) | ||||
@ECTypeFields(list={"name", "url", "description", "admin", "suspended"}) | @ECTypeFields(list={"name", "url", "description", "admin", "suspended"}) | ||||
@@ -45,7 +53,7 @@ import static org.cobbzilla.wizard.model.crypto.EncryptedTypes.ENC_PAD; | |||||
@ECTypeChild(type=BubbleNode.class, backref="account"), | @ECTypeChild(type=BubbleNode.class, backref="account"), | ||||
@ECTypeChild(type=SentNotification.class, backref="account") | @ECTypeChild(type=SentNotification.class, backref="account") | ||||
}) | }) | ||||
@Entity @NoArgsConstructor @Accessors(chain=true) | |||||
@Entity @NoArgsConstructor @Accessors(chain=true) @Slf4j | |||||
public class Account extends IdentifiableBase implements TokenPrincipal { | public class Account extends IdentifiableBase implements TokenPrincipal { | ||||
public static final String[] UPDATE_FIELDS = {"url", "description", "autoUpdatePolicy"}; | public static final String[] UPDATE_FIELDS = {"url", "description", "autoUpdatePolicy"}; | ||||
@@ -121,6 +129,28 @@ public class Account extends IdentifiableBase implements TokenPrincipal { | |||||
public boolean wantsAppUpdates() { return autoUpdatePolicy != null && autoUpdatePolicy.appUpdates(); } | public boolean wantsAppUpdates() { return autoUpdatePolicy != null && autoUpdatePolicy.appUpdates(); } | ||||
public boolean wantsDataUpdates() { return autoUpdatePolicy != null && autoUpdatePolicy.dataUpdates(); } | public boolean wantsDataUpdates() { return autoUpdatePolicy != null && autoUpdatePolicy.dataUpdates(); } | ||||
public static final long INIT_WAIT_INTERVAL = MILLISECONDS.toMillis(250); | |||||
public static final long INIT_WAIT_TIMEOUT = SECONDS.toMillis(60); | |||||
@Transient @JsonIgnore @Getter @Setter private transient AccountInitializer accountInitializer; | |||||
public boolean hasAccountInitializer () { return accountInitializer != null; } | |||||
public Account waitForAccountInit () { | |||||
if (!hasAccountInitializer()) { | |||||
log.warn("waitForAccountInit: accountInitializer was not set"); | |||||
return this; | |||||
} | |||||
final long start = now(); | |||||
while (!accountInitializer.ready() && now() - start < INIT_WAIT_TIMEOUT) { | |||||
sleep(INIT_WAIT_INTERVAL, "postCreate: waiting for AccountInitializer.ready"); | |||||
} | |||||
if (now() - start > INIT_WAIT_TIMEOUT && !accountInitializer.ready()) { | |||||
throw invalidEx("err.accountInit.timeout"); | |||||
} | |||||
log.info("waitForAccountInit: ready in "+formatDuration(now() - start)); | |||||
return this; | |||||
} | |||||
@Transient @Getter @Setter private transient String apiToken; | @Transient @Getter @Setter private transient String apiToken; | ||||
@Transient public String getToken() { return getApiToken(); } | @Transient public String getToken() { return getApiToken(); } | ||||
@@ -84,7 +84,7 @@ public class AccountsResource { | |||||
.setRemoteHost(getRemoteHost(req)) | .setRemoteHost(getRemoteHost(req)) | ||||
.setVerifyContact(true); | .setVerifyContact(true); | ||||
final Account created = accountDAO.newAccount(req, reg, parent); | final Account created = accountDAO.newAccount(req, reg, parent); | ||||
return ok(created); | |||||
return ok(created.waitForAccountInit()); | |||||
} | } | ||||
@GET @Path("/{id}"+EP_DOWNLOAD) | @GET @Path("/{id}"+EP_DOWNLOAD) | ||||
@@ -1,5 +1,6 @@ | |||||
package bubble.resources.account; | package bubble.resources.account; | ||||
import bubble.cloud.geoLocation.GeoLocation; | |||||
import bubble.dao.SessionDAO; | import bubble.dao.SessionDAO; | ||||
import bubble.dao.account.AccountDAO; | import bubble.dao.account.AccountDAO; | ||||
import bubble.dao.account.AccountPolicyDAO; | import bubble.dao.account.AccountPolicyDAO; | ||||
@@ -17,12 +18,15 @@ import bubble.service.account.StandardAccountMessageService; | |||||
import bubble.service.backup.RestoreService; | import bubble.service.backup.RestoreService; | ||||
import bubble.service.boot.ActivationService; | import bubble.service.boot.ActivationService; | ||||
import bubble.service.boot.SageHelloService; | import bubble.service.boot.SageHelloService; | ||||
import bubble.service.cloud.GeoService; | |||||
import bubble.service.notify.NotificationService; | import bubble.service.notify.NotificationService; | ||||
import lombok.extern.slf4j.Slf4j; | import lombok.extern.slf4j.Slf4j; | ||||
import org.cobbzilla.util.collection.NameAndValue; | import org.cobbzilla.util.collection.NameAndValue; | ||||
import org.cobbzilla.util.string.LocaleUtil; | |||||
import org.cobbzilla.wizard.auth.LoginRequest; | import org.cobbzilla.wizard.auth.LoginRequest; | ||||
import org.cobbzilla.wizard.stream.FileSendableResource; | import org.cobbzilla.wizard.stream.FileSendableResource; | ||||
import org.cobbzilla.wizard.validation.ConstraintViolationBean; | import org.cobbzilla.wizard.validation.ConstraintViolationBean; | ||||
import org.cobbzilla.wizard.validation.SimpleViolationException; | |||||
import org.cobbzilla.wizard.validation.ValidationResult; | import org.cobbzilla.wizard.validation.ValidationResult; | ||||
import org.glassfish.grizzly.http.server.Request; | import org.glassfish.grizzly.http.server.Request; | ||||
import org.glassfish.jersey.server.ContainerRequest; | import org.glassfish.jersey.server.ContainerRequest; | ||||
@@ -34,7 +38,9 @@ import javax.ws.rs.*; | |||||
import javax.ws.rs.core.Context; | import javax.ws.rs.core.Context; | ||||
import javax.ws.rs.core.Response; | import javax.ws.rs.core.Response; | ||||
import java.io.File; | import java.io.File; | ||||
import java.util.HashMap; | |||||
import java.util.List; | import java.util.List; | ||||
import java.util.Map; | |||||
import java.util.concurrent.TimeUnit; | import java.util.concurrent.TimeUnit; | ||||
import static bubble.ApiConstants.*; | import static bubble.ApiConstants.*; | ||||
@@ -44,6 +50,7 @@ import static bubble.model.cloud.BubbleNetwork.TAG_PARENT_ACCOUNT; | |||||
import static bubble.model.cloud.notify.NotificationType.retrieve_backup; | import static bubble.model.cloud.notify.NotificationType.retrieve_backup; | ||||
import static bubble.server.BubbleServer.getRestoreKey; | import static bubble.server.BubbleServer.getRestoreKey; | ||||
import static java.util.concurrent.TimeUnit.SECONDS; | import static java.util.concurrent.TimeUnit.SECONDS; | ||||
import static org.apache.http.HttpHeaders.ACCEPT_LANGUAGE; | |||||
import static org.cobbzilla.util.daemon.ZillaRuntime.empty; | import static org.cobbzilla.util.daemon.ZillaRuntime.empty; | ||||
import static org.cobbzilla.util.daemon.ZillaRuntime.now; | import static org.cobbzilla.util.daemon.ZillaRuntime.now; | ||||
import static org.cobbzilla.util.http.HttpContentTypes.APPLICATION_JSON; | import static org.cobbzilla.util.http.HttpContentTypes.APPLICATION_JSON; | ||||
@@ -66,6 +73,7 @@ public class AuthResource { | |||||
@Autowired private AccountMessageDAO accountMessageDAO; | @Autowired private AccountMessageDAO accountMessageDAO; | ||||
@Autowired private StandardAccountMessageService messageService; | @Autowired private StandardAccountMessageService messageService; | ||||
@Autowired private BubbleNodeDAO nodeDAO; | @Autowired private BubbleNodeDAO nodeDAO; | ||||
@Autowired private GeoService geoService; | |||||
@Autowired private BubbleConfiguration configuration; | @Autowired private BubbleConfiguration configuration; | ||||
@GET @Path(EP_CONFIGS) | @GET @Path(EP_CONFIGS) | ||||
@@ -181,7 +189,7 @@ public class AuthResource { | |||||
if (parent == null) return invalid("err.parent.notFound", "Parent account does not exist: "+parentUuid); | if (parent == null) return invalid("err.parent.notFound", "Parent account does not exist: "+parentUuid); | ||||
final Account account = accountDAO.newAccount(req, request, parent); | final Account account = accountDAO.newAccount(req, request, parent); | ||||
return ok(account.setToken(sessionDAO.create(account))); | |||||
return ok(account.waitForAccountInit().setToken(sessionDAO.create(account))); | |||||
} | } | ||||
@POST @Path(EP_LOGIN) | @POST @Path(EP_LOGIN) | ||||
@@ -342,4 +350,46 @@ public class AuthResource { | |||||
return ok_empty(); | return ok_empty(); | ||||
} | } | ||||
@GET @Path("/detect/locale") | |||||
public Response detectLocale(@Context Request req, | |||||
@Context ContainerRequest ctx) { | |||||
final Map<String, String> locales = new HashMap<>(); | |||||
final String langHeader = normalizeLangHeader(req); | |||||
if (langHeader != null) locales.put(ACCEPT_LANGUAGE, langHeader); | |||||
final String remoteHost = getRemoteHost(req); | |||||
try { | |||||
final Account caller = userPrincipal(ctx); | |||||
final GeoLocation loc = geoService.locate(caller.getUuid(), remoteHost); | |||||
if (loc != null) { | |||||
final List<String> found = LocaleUtil.getDefaultLocales(loc.getCountry()); | |||||
for (int i=0; i<found.size(); i++) { | |||||
locales.put("geolocation_"+i, found.get(i)); | |||||
} | |||||
} | |||||
} catch (SimpleViolationException e) { | |||||
return invalid(e); | |||||
} catch (Exception e) { | |||||
log.warn("detectLocale: "+e); | |||||
} | |||||
return ok(locales.values()); | |||||
} | |||||
@GET @Path("/detect/timezone") | |||||
public Response detectTimezone(@Context Request req, | |||||
@Context ContainerRequest ctx) { | |||||
final String remoteHost = getRemoteHost(req); | |||||
try { | |||||
return ok(geoService.getTimeZone(optionalUserPrincipal(ctx), remoteHost)); | |||||
} catch (SimpleViolationException e) { | |||||
return invalid(e); | |||||
} catch (Exception e) { | |||||
return invalid("err.timezone.unknown", e.getMessage()); | |||||
} | |||||
} | |||||
} | } |
@@ -1,6 +1,5 @@ | |||||
package bubble.resources.account; | package bubble.resources.account; | ||||
import bubble.cloud.geoLocation.GeoLocation; | |||||
import bubble.dao.SessionDAO; | import bubble.dao.SessionDAO; | ||||
import bubble.dao.account.AccountDAO; | import bubble.dao.account.AccountDAO; | ||||
import bubble.dao.account.AccountPolicyDAO; | import bubble.dao.account.AccountPolicyDAO; | ||||
@@ -20,11 +19,9 @@ import bubble.resources.notify.SentNotificationsResource; | |||||
import bubble.server.BubbleConfiguration; | import bubble.server.BubbleConfiguration; | ||||
import bubble.service.account.StandardAccountMessageService; | import bubble.service.account.StandardAccountMessageService; | ||||
import bubble.service.account.download.AccountDownloadService; | import bubble.service.account.download.AccountDownloadService; | ||||
import bubble.service.cloud.GeoService; | |||||
import com.fasterxml.jackson.databind.JsonNode; | import com.fasterxml.jackson.databind.JsonNode; | ||||
import lombok.Cleanup; | import lombok.Cleanup; | ||||
import lombok.extern.slf4j.Slf4j; | import lombok.extern.slf4j.Slf4j; | ||||
import org.cobbzilla.util.string.LocaleUtil; | |||||
import org.cobbzilla.wizard.auth.ChangePasswordRequest; | import org.cobbzilla.wizard.auth.ChangePasswordRequest; | ||||
import org.cobbzilla.wizard.client.ApiClientBase; | import org.cobbzilla.wizard.client.ApiClientBase; | ||||
import org.cobbzilla.wizard.client.script.ApiRunner; | import org.cobbzilla.wizard.client.script.ApiRunner; | ||||
@@ -41,12 +38,8 @@ import javax.ws.rs.*; | |||||
import javax.ws.rs.core.Context; | import javax.ws.rs.core.Context; | ||||
import javax.ws.rs.core.Response; | import javax.ws.rs.core.Response; | ||||
import java.io.StringWriter; | import java.io.StringWriter; | ||||
import java.util.HashMap; | |||||
import java.util.List; | |||||
import java.util.Map; | |||||
import static bubble.ApiConstants.*; | import static bubble.ApiConstants.*; | ||||
import static org.apache.http.HttpHeaders.ACCEPT_LANGUAGE; | |||||
import static org.cobbzilla.util.daemon.ZillaRuntime.errorString; | import static org.cobbzilla.util.daemon.ZillaRuntime.errorString; | ||||
import static org.cobbzilla.util.http.HttpContentTypes.APPLICATION_JSON; | import static org.cobbzilla.util.http.HttpContentTypes.APPLICATION_JSON; | ||||
import static org.cobbzilla.util.http.HttpContentTypes.TEXT_PLAIN; | import static org.cobbzilla.util.http.HttpContentTypes.TEXT_PLAIN; | ||||
@@ -62,7 +55,6 @@ public class MeResource { | |||||
@Autowired private AccountDAO accountDAO; | @Autowired private AccountDAO accountDAO; | ||||
@Autowired private AccountPolicyDAO policyDAO; | @Autowired private AccountPolicyDAO policyDAO; | ||||
@Autowired private SessionDAO sessionDAO; | @Autowired private SessionDAO sessionDAO; | ||||
@Autowired private GeoService geoService; | |||||
@Autowired private AccountDownloadService downloadService; | @Autowired private AccountDownloadService downloadService; | ||||
@Autowired private BubbleConfiguration configuration; | @Autowired private BubbleConfiguration configuration; | ||||
@@ -76,41 +68,6 @@ public class MeResource { | |||||
} | } | ||||
} | } | ||||
@GET @Path("/detect/locale") | |||||
public Response detectLocale(@Context Request req, | |||||
@Context ContainerRequest ctx) { | |||||
final Map<String, String> locales = new HashMap<>(); | |||||
final String langHeader = normalizeLangHeader(req); | |||||
if (langHeader != null) locales.put(ACCEPT_LANGUAGE, langHeader); | |||||
final String remoteHost = getRemoteHost(req); | |||||
try { | |||||
final Account caller = userPrincipal(ctx); | |||||
final GeoLocation loc = geoService.locate(caller.getUuid(), remoteHost); | |||||
if (loc != null) { | |||||
final List<String> found = LocaleUtil.getDefaultLocales(loc.getCountry()); | |||||
for (int i=0; i<found.size(); i++) { | |||||
locales.put("geolocation_"+i, found.get(i)); | |||||
} | |||||
} | |||||
} catch (Exception e) { | |||||
log.warn("detectLocale: "+e); | |||||
} | |||||
return ok(locales); | |||||
} | |||||
@GET @Path("/detect/timezone") | |||||
public Response detectTimezone(@Context Request req, | |||||
@Context ContainerRequest ctx) { | |||||
final String remoteHost = getRemoteHost(req); | |||||
try { | |||||
return ok(geoService.getTimeZone(userPrincipal(ctx), remoteHost)); | |||||
} catch (Exception e) { | |||||
return invalid("err.timezone.unknown", e.getMessage()); | |||||
} | |||||
} | |||||
@POST @Path(EP_CHANGE_PASSWORD) | @POST @Path(EP_CHANGE_PASSWORD) | ||||
public Response changePassword(@Context ContainerRequest ctx, | public Response changePassword(@Context ContainerRequest ctx, | ||||
ChangePasswordRequest request) { | ChangePasswordRequest request) { | ||||
@@ -13,13 +13,12 @@ import bubble.model.cloud.notify.ReceivedNotification; | |||||
import bubble.model.cloud.notify.SentNotification; | import bubble.model.cloud.notify.SentNotification; | ||||
import bubble.notify.storage.StorageStreamRequest; | import bubble.notify.storage.StorageStreamRequest; | ||||
import bubble.server.BubbleConfiguration; | import bubble.server.BubbleConfiguration; | ||||
import bubble.service.notify.NotificationService; | |||||
import bubble.service.backup.RestoreService; | import bubble.service.backup.RestoreService; | ||||
import bubble.service.cloud.StorageStreamService; | import bubble.service.cloud.StorageStreamService; | ||||
import bubble.service.notify.NotificationService; | |||||
import com.fasterxml.jackson.databind.JsonNode; | import com.fasterxml.jackson.databind.JsonNode; | ||||
import lombok.Getter; | import lombok.Getter; | ||||
import lombok.extern.slf4j.Slf4j; | import lombok.extern.slf4j.Slf4j; | ||||
import org.cobbzilla.util.network.NetworkUtil; | |||||
import org.cobbzilla.util.security.RsaMessage; | import org.cobbzilla.util.security.RsaMessage; | ||||
import org.cobbzilla.util.string.StringUtil; | import org.cobbzilla.util.string.StringUtil; | ||||
import org.glassfish.grizzly.http.server.Request; | import org.glassfish.grizzly.http.server.Request; | ||||
@@ -42,6 +41,8 @@ import static org.cobbzilla.util.daemon.ZillaRuntime.empty; | |||||
import static org.cobbzilla.util.http.HttpContentTypes.APPLICATION_JSON; | import static org.cobbzilla.util.http.HttpContentTypes.APPLICATION_JSON; | ||||
import static org.cobbzilla.util.http.HttpContentTypes.APPLICATION_OCTET_STREAM; | import static org.cobbzilla.util.http.HttpContentTypes.APPLICATION_OCTET_STREAM; | ||||
import static org.cobbzilla.util.json.JsonUtil.json; | import static org.cobbzilla.util.json.JsonUtil.json; | ||||
import static org.cobbzilla.util.network.NetworkUtil.configuredIpsAndExternalIp; | |||||
import static org.cobbzilla.util.network.NetworkUtil.isLocalHost; | |||||
import static org.cobbzilla.util.string.StringUtil.truncate; | import static org.cobbzilla.util.string.StringUtil.truncate; | ||||
import static org.cobbzilla.util.time.TimeUtil.formatDuration; | import static org.cobbzilla.util.time.TimeUtil.formatDuration; | ||||
import static org.cobbzilla.wizard.resources.ResourceUtil.*; | import static org.cobbzilla.wizard.resources.ResourceUtil.*; | ||||
@@ -61,7 +62,7 @@ public class InboundNotifyResource { | |||||
@Autowired private StorageStreamService storageStreamService; | @Autowired private StorageStreamService storageStreamService; | ||||
@Autowired private RestoreService restoreService; | @Autowired private RestoreService restoreService; | ||||
@Getter(lazy=true) private final Set<String> localIps = NetworkUtil.configuredIps(); | |||||
@Getter(lazy=true) private final Set<String> localIps = configuredIpsAndExternalIp(); | |||||
@POST | @POST | ||||
public Response receiveNotification(@Context Request req, | public Response receiveNotification(@Context Request req, | ||||
@@ -150,7 +151,7 @@ public class InboundNotifyResource { | |||||
if (fromKey != null) { | if (fromKey != null) { | ||||
if (!fromKey.getRemoteHost().equals(remoteHost)) { | if (!fromKey.getRemoteHost().equals(remoteHost)) { | ||||
// if request is from 127.0.0.1, check to see if fromKey is for a local address | // if request is from 127.0.0.1, check to see if fromKey is for a local address | ||||
if (remoteHost.equals("127.0.0.1") && getLocalIps().contains(fromKey.getRemoteHost())) { | |||||
if (isLocalHost(remoteHost) && getLocalIps().contains(fromKey.getRemoteHost())) { | |||||
log.debug("findFromKey: request from 127.0.0.1 is OK, key is local: "+fromKey.getRemoteHost()+ " (ips="+ StringUtil.toString(getLocalIps())+")"); | log.debug("findFromKey: request from 127.0.0.1 is OK, key is local: "+fromKey.getRemoteHost()+ " (ips="+ StringUtil.toString(getLocalIps())+")"); | ||||
} else { | } else { | ||||
log.warn("findFromKey: remoteHost for for node " + fromNodeUuid + " (key=" + fromKeyUuid + ", remoteHost=" + fromKey.getRemoteHost() + ") does not match request: " + remoteHost+ " (ips="+ StringUtil.toString(getLocalIps())+")"); | log.warn("findFromKey: remoteHost for for node " + fromNodeUuid + " (key=" + fromKeyUuid + ", remoteHost=" + fromKey.getRemoteHost() + ") does not match request: " + remoteHost+ " (ips="+ StringUtil.toString(getLocalIps())+")"); | ||||
@@ -8,6 +8,7 @@ import bubble.cloud.geoCode.GeoCodeResult; | |||||
import bubble.cloud.geoCode.GeoCodeServiceDriver; | import bubble.cloud.geoCode.GeoCodeServiceDriver; | ||||
import bubble.cloud.geoLocation.GeoLocation; | import bubble.cloud.geoLocation.GeoLocation; | ||||
import bubble.cloud.geoTime.GeoTimeZone; | import bubble.cloud.geoTime.GeoTimeZone; | ||||
import bubble.dao.account.AccountDAO; | |||||
import bubble.dao.cloud.BubbleFootprintDAO; | import bubble.dao.cloud.BubbleFootprintDAO; | ||||
import bubble.dao.cloud.CloudServiceDAO; | import bubble.dao.cloud.CloudServiceDAO; | ||||
import bubble.model.account.Account; | import bubble.model.account.Account; | ||||
@@ -37,6 +38,7 @@ public class GeoService { | |||||
// todo: move to config? | // todo: move to config? | ||||
public static final int LOC_MAX_DISTANCE = 50000; | public static final int LOC_MAX_DISTANCE = 50000; | ||||
@Autowired private AccountDAO accountDAO; | |||||
@Autowired private CloudServiceDAO cloudDAO; | @Autowired private CloudServiceDAO cloudDAO; | ||||
@Autowired private BubbleFootprintDAO footprintDAO; | @Autowired private BubbleFootprintDAO footprintDAO; | ||||
@Autowired private BubbleConfiguration configuration; | @Autowired private BubbleConfiguration configuration; | ||||
@@ -112,6 +114,7 @@ public class GeoService { | |||||
public GeoTimeZone getTimeZone (Account account, String ip) { | public GeoTimeZone getTimeZone (Account account, String ip) { | ||||
if (account == null) account = accountDAO.findFirstAdmin(); | |||||
final List<CloudService> geoServices = cloudDAO.findByAccountAndType(account.getUuid(), CloudServiceType.geoTime); | final List<CloudService> geoServices = cloudDAO.findByAccountAndType(account.getUuid(), CloudServiceType.geoTime); | ||||
if (geoServices.isEmpty()) throw new SimpleViolationException("err.geoTimeService.notFound"); | if (geoServices.isEmpty()) throw new SimpleViolationException("err.geoTimeService.notFound"); | ||||
geoServices.sort(SORT_PRIORITY); | geoServices.sort(SORT_PRIORITY); | ||||
@@ -26,6 +26,7 @@ err.phone.invalid=SMS Phone is invalid | |||||
err.phone.length=SMS Phone is too long | err.phone.length=SMS Phone is too long | ||||
err.country.invalid=Country is invalid | err.country.invalid=Country is invalid | ||||
err.parent.notFound=Parent account does not exist | err.parent.notFound=Parent account does not exist | ||||
err.accountInit.timeout=Timeout initializing new account | |||||
# Login/Registration form | # Login/Registration form | ||||
form_label_title_login=Login | form_label_title_login=Login | ||||
@@ -1 +1 @@ | |||||
Subproject commit b500db5e9c4a0e621833e788dc4e6cf5fae5e2ee | |||||
Subproject commit 0cf7321e714801118c7ab13ca2202a68936c0dea |
@@ -1 +1 @@ | |||||
Subproject commit 862c6282b7ecdf9b1146f4ecb005cf38be1c4e86 | |||||
Subproject commit 51b557f242ea52ed339ac6469adbc42b8873a3e9 |
@@ -1 +1 @@ | |||||
Subproject commit 86a153ecc2531008afab4e3bfed471f9d957dab7 | |||||
Subproject commit 54c08985284f4f81dad3200b04e93384cbdffb12 |