@@ -18,4 +18,8 @@ public class FlexRouterDAO extends AccountOwnedEntityDAO<FlexRouter> { | |||||
isNotNull("token")))); | isNotNull("token")))); | ||||
} | } | ||||
public FlexRouter findByAccountAndIp(String accountUuid, String ip) { | |||||
return findByUniqueFields("account", accountUuid, "ip", ip); | |||||
} | |||||
} | } |
@@ -10,6 +10,7 @@ import lombok.ToString; | |||||
import lombok.experimental.Accessors; | import lombok.experimental.Accessors; | ||||
import lombok.extern.slf4j.Slf4j; | import lombok.extern.slf4j.Slf4j; | ||||
import org.apache.commons.collections.map.SingletonMap; | import org.apache.commons.collections.map.SingletonMap; | ||||
import org.cobbzilla.util.collection.ArrayUtil; | |||||
import org.cobbzilla.wizard.model.Identifiable; | import org.cobbzilla.wizard.model.Identifiable; | ||||
import org.cobbzilla.wizard.model.IdentifiableBase; | import org.cobbzilla.wizard.model.IdentifiableBase; | ||||
import org.cobbzilla.wizard.model.entityconfig.EntityFieldMode; | import org.cobbzilla.wizard.model.entityconfig.EntityFieldMode; | ||||
@@ -32,8 +33,8 @@ import static org.cobbzilla.util.reflect.ReflectionUtil.copy; | |||||
@ECIndexes({ @ECIndex(unique=true, of={"account", "ip"}) }) | @ECIndexes({ @ECIndex(unique=true, of={"account", "ip"}) }) | ||||
public class FlexRouter extends IdentifiableBase implements HasAccountNoName { | public class FlexRouter extends IdentifiableBase implements HasAccountNoName { | ||||
public static final String[] CREATE_FIELDS = { "ip", "enabled" }; | |||||
public static final String[] UPDATE_FIELDS = { "enabled" }; | |||||
public static final String[] UPDATE_FIELDS = { "enabled", "active" }; | |||||
public static final String[] CREATE_FIELDS = ArrayUtil.append(UPDATE_FIELDS, "ip"); | |||||
public FlexRouter (FlexRouter other) { copy(this, other, CREATE_FIELDS); } | public FlexRouter (FlexRouter other) { copy(this, other, CREATE_FIELDS); } | ||||
@@ -75,10 +76,11 @@ public class FlexRouter extends IdentifiableBase implements HasAccountNoName { | |||||
@JsonIgnore @Getter @Setter private String token; | @JsonIgnore @Getter @Setter private String token; | ||||
public boolean hasToken () { return !empty(token); } | public boolean hasToken () { return !empty(token); } | ||||
@Transient @Getter @Setter private String serverToken; | |||||
// used for sending the token, we never send it back | |||||
@Transient @Getter @Setter private String auth_token; | |||||
public boolean hasAuthToken () { return !empty(auth_token); } | |||||
public String pingUrl() { return "http://" + getIp() + ":" + getPort() + "/ping"; } | public String pingUrl() { return "http://" + getIp() + ":" + getPort() + "/ping"; } | ||||
public String pingObject() { return json(new SingletonMap("token", getToken())); } | public String pingObject() { return json(new SingletonMap("token", getToken())); } | ||||
} | } |
@@ -91,16 +91,26 @@ public class AccountOwnedResource<E extends HasAccount, DAO extends AccountOwned | |||||
return getDao().findByAccount(getAccountUuid(ctx)); | return getDao().findByAccount(getAccountUuid(ctx)); | ||||
} | } | ||||
protected E find(Request req, ContainerRequest ctx, String id) { return find(ctx, id); } | |||||
protected E find(ContainerRequest ctx, String id) { | protected E find(ContainerRequest ctx, String id) { | ||||
return getDao().findByAccountAndId(getAccountUuid(ctx), id); | return getDao().findByAccountAndId(getAccountUuid(ctx), id); | ||||
} | } | ||||
protected E findAlternate(ContainerRequest ctx, E request) { return null; } | protected E findAlternate(ContainerRequest ctx, E request) { return null; } | ||||
protected E findAlternate(Request req, ContainerRequest ctx, E request) { return findAlternate(ctx, request); } | |||||
protected E findAlternate(ContainerRequest ctx, String id) { return null; } | protected E findAlternate(ContainerRequest ctx, String id) { return null; } | ||||
protected E findAlternate(Request req, ContainerRequest ctx, String id) { return findAlternate(ctx, id); } | |||||
protected E findAlternateForCreate(ContainerRequest ctx, E request) { return findAlternate(ctx, request); } | protected E findAlternateForCreate(ContainerRequest ctx, E request) { return findAlternate(ctx, request); } | ||||
protected E findAlternateForCreate(Request req, ContainerRequest ctx, E request) { return findAlternateForCreate(ctx, request); } | |||||
protected E findAlternateForUpdate(ContainerRequest ctx, String id) { return findAlternate(ctx, id); } | protected E findAlternateForUpdate(ContainerRequest ctx, String id) { return findAlternate(ctx, id); } | ||||
protected E findAlternateForUpdate(Request req, ContainerRequest ctx, String id) { return findAlternateForUpdate(ctx, id); } | |||||
protected E findAlternateForDelete(ContainerRequest ctx, String id) { return findAlternate(ctx, id); } | protected E findAlternateForDelete(ContainerRequest ctx, String id) { return findAlternate(ctx, id); } | ||||
protected E findAlternateForDelete(Request req, ContainerRequest ctx, String id) { return findAlternateForDelete(ctx, id); } | |||||
protected List<E> populate(ContainerRequest ctx, List<E> entities) { | protected List<E> populate(ContainerRequest ctx, List<E> entities) { | ||||
for (E e : entities) populate(ctx, e); | for (E e : entities) populate(ctx, e); | ||||
@@ -110,14 +120,15 @@ public class AccountOwnedResource<E extends HasAccount, DAO extends AccountOwned | |||||
protected E populate(ContainerRequest ctx, E entity) { return entity; } | protected E populate(ContainerRequest ctx, E entity) { return entity; } | ||||
@GET @Path("/{id}") | @GET @Path("/{id}") | ||||
public Response view(@Context ContainerRequest ctx, | |||||
public Response view(@Context Request req, | |||||
@Context ContainerRequest ctx, | |||||
@PathParam("id") String id) { | @PathParam("id") String id) { | ||||
final Account caller = getAccountForViewById(ctx); | final Account caller = getAccountForViewById(ctx); | ||||
E found = find(ctx, id); | |||||
E found = find(req, ctx, id); | |||||
if (found == null) { | if (found == null) { | ||||
found = findAlternate(ctx, id); | |||||
found = findAlternate(req, ctx, id); | |||||
if (found == null) return notFound(id); | if (found == null) return notFound(id); | ||||
} | } | ||||
if (caller != null && !found.getAccount().equals(caller.getUuid()) && !caller.admin()) return notFound(id); | if (caller != null && !found.getAccount().equals(caller.getUuid()) && !caller.admin()) return notFound(id); | ||||
@@ -138,9 +149,9 @@ public class AccountOwnedResource<E extends HasAccount, DAO extends AccountOwned | |||||
E request) { | E request) { | ||||
if (request == null) return invalid("err.request.invalid"); | if (request == null) return invalid("err.request.invalid"); | ||||
final Account caller = checkEditable(ctx); | final Account caller = checkEditable(ctx); | ||||
E found = find(ctx, request.getName()); | |||||
E found = find(req, ctx, request.getName()); | |||||
if (found == null) { | if (found == null) { | ||||
found = findAlternateForCreate(ctx, request); | |||||
found = findAlternateForCreate(req, ctx, request); | |||||
} | } | ||||
if (found != null) { | if (found != null) { | ||||
if (!canUpdate(ctx, caller, found, request)) return ok(found); | if (!canUpdate(ctx, caller, found, request)) return ok(found); | ||||
@@ -161,14 +172,15 @@ public class AccountOwnedResource<E extends HasAccount, DAO extends AccountOwned | |||||
protected E setReferences(ContainerRequest ctx, Request req, Account caller, E e) { return setReferences(ctx, caller, e); } | protected E setReferences(ContainerRequest ctx, Request req, Account caller, E e) { return setReferences(ctx, caller, e); } | ||||
@POST @Path("/{id}") | @POST @Path("/{id}") | ||||
public Response update(@Context ContainerRequest ctx, | |||||
public Response update(@Context Request req, | |||||
@Context ContainerRequest ctx, | |||||
@PathParam("id") String id, | @PathParam("id") String id, | ||||
E request) { | E request) { | ||||
if (request == null) return invalid("err.request.invalid"); | if (request == null) return invalid("err.request.invalid"); | ||||
final Account caller = checkEditable(ctx); | final Account caller = checkEditable(ctx); | ||||
E found = find(ctx, id); | |||||
E found = find(req, ctx, id); | |||||
if (found == null) { | if (found == null) { | ||||
found = findAlternateForUpdate(ctx, id); | |||||
found = findAlternateForUpdate(req, ctx, id); | |||||
if (found == null) return notFound(id); | if (found == null) return notFound(id); | ||||
} | } | ||||
if (!(found instanceof HasAccountNoName) && !canChangeName() && request.hasName() && !request.getName().equals(found.getName())) { | if (!(found instanceof HasAccountNoName) && !canChangeName() && request.hasName() && !request.getName().equals(found.getName())) { | ||||
@@ -183,12 +195,13 @@ public class AccountOwnedResource<E extends HasAccount, DAO extends AccountOwned | |||||
protected boolean canChangeName() { return false; } | protected boolean canChangeName() { return false; } | ||||
@DELETE @Path("/{id}") | @DELETE @Path("/{id}") | ||||
public Response delete(@Context ContainerRequest ctx, | |||||
public Response delete(@Context Request req, | |||||
@Context ContainerRequest ctx, | |||||
@PathParam("id") String id) { | @PathParam("id") String id) { | ||||
final Account caller = checkEditable(ctx); | final Account caller = checkEditable(ctx); | ||||
E found = find(ctx, id); | |||||
E found = find(req, ctx, id); | |||||
if (found == null) { | if (found == null) { | ||||
found = findAlternateForDelete(ctx, id); | |||||
found = findAlternateForDelete(req, ctx, id); | |||||
if (found == null) return notFound(id); | if (found == null) return notFound(id); | ||||
} | } | ||||
@@ -63,8 +63,9 @@ public abstract class AppsResourceBase extends AccountOwnedTemplateResource<Bubb | |||||
} | } | ||||
@DELETE @Path("/{id}") | @DELETE @Path("/{id}") | ||||
public Response delete(@Context ContainerRequest ctx, | |||||
@PathParam("id") String id) { | |||||
@Override public Response delete(@Context Request req, | |||||
@Context ContainerRequest ctx, | |||||
@PathParam("id") String id) { | |||||
if (isReadOnly(ctx)) return forbidden(); | if (isReadOnly(ctx)) return forbidden(); | ||||
@@ -16,6 +16,7 @@ import bubble.model.device.Device; | |||||
import bubble.resources.account.AccountOwnedTemplateResource; | import bubble.resources.account.AccountOwnedTemplateResource; | ||||
import bubble.server.BubbleConfiguration; | import bubble.server.BubbleConfiguration; | ||||
import lombok.extern.slf4j.Slf4j; | import lombok.extern.slf4j.Slf4j; | ||||
import org.glassfish.grizzly.http.server.Request; | |||||
import org.glassfish.jersey.server.ContainerRequest; | import org.glassfish.jersey.server.ContainerRequest; | ||||
import org.springframework.beans.factory.annotation.Autowired; | import org.springframework.beans.factory.annotation.Autowired; | ||||
@@ -86,14 +87,15 @@ public abstract class DataResourceBase extends AccountOwnedTemplateResource<AppD | |||||
} | } | ||||
@POST @Path("/{id}"+EP_ACTIONS+"/{action}") | @POST @Path("/{id}"+EP_ACTIONS+"/{action}") | ||||
public Response takeAction(@Context ContainerRequest ctx, | |||||
public Response takeAction(@Context Request req, | |||||
@Context ContainerRequest ctx, | |||||
@PathParam("id") String id, | @PathParam("id") String id, | ||||
@PathParam("action") String action) { | @PathParam("action") String action) { | ||||
if (isReadOnly(ctx)) return forbidden(); | if (isReadOnly(ctx)) return forbidden(); | ||||
switch (action) { | switch (action) { | ||||
case "enable": return enable(ctx, id); | case "enable": return enable(ctx, id); | ||||
case "disable": return disable(ctx, id); | case "disable": return disable(ctx, id); | ||||
case "delete": return delete(ctx, id); | |||||
case "delete": return delete(req, ctx, id); | |||||
default: | default: | ||||
app.getDataConfig().getDataDriver(configuration).takeAction(id, action); | app.getDataConfig().getDataDriver(configuration).takeAction(id, action); | ||||
return ok(); | return ok(); | ||||
@@ -306,7 +306,8 @@ public class AccountPlansResource extends AccountOwnedResource<AccountPlan, Acco | |||||
// If the accountPlan is not found, look for an orphaned network | // If the accountPlan is not found, look for an orphaned network | ||||
@DELETE @Path("/{id}") | @DELETE @Path("/{id}") | ||||
@Override public Response delete(@Context ContainerRequest ctx, | |||||
@Override public Response delete(@Context Request req, | |||||
@Context ContainerRequest ctx, | |||||
@PathParam("id") String id) { | @PathParam("id") String id) { | ||||
final Account caller = checkEditable(ctx); | final Account caller = checkEditable(ctx); | ||||
AccountPlan found = find(ctx, id); | AccountPlan found = find(ctx, id); | ||||
@@ -11,16 +11,9 @@ import org.glassfish.grizzly.http.server.Request; | |||||
import org.glassfish.jersey.server.ContainerRequest; | import org.glassfish.jersey.server.ContainerRequest; | ||||
import org.springframework.beans.factory.annotation.Autowired; | import org.springframework.beans.factory.annotation.Autowired; | ||||
import javax.ws.rs.POST; | |||||
import javax.ws.rs.Path; | |||||
import javax.ws.rs.PathParam; | |||||
import javax.ws.rs.core.Context; | |||||
import javax.ws.rs.core.Response; | |||||
import static bubble.ApiConstants.EP_REGISTER; | |||||
import static bubble.ApiConstants.getRemoteAddr; | import static bubble.ApiConstants.getRemoteAddr; | ||||
import static org.apache.commons.lang3.RandomStringUtils.randomAlphanumeric; | |||||
import static org.cobbzilla.wizard.resources.ResourceUtil.*; | |||||
import static org.cobbzilla.wizard.resources.ResourceUtil.invalidEx; | |||||
import static org.cobbzilla.wizard.resources.ResourceUtil.userPrincipal; | |||||
@Slf4j | @Slf4j | ||||
public class FlexRoutersResource extends AccountOwnedResource<FlexRouter, FlexRouterDAO> { | public class FlexRoutersResource extends AccountOwnedResource<FlexRouter, FlexRouterDAO> { | ||||
@@ -34,29 +27,22 @@ public class FlexRoutersResource extends AccountOwnedResource<FlexRouter, FlexRo | |||||
return !caller.admin(); | return !caller.admin(); | ||||
} | } | ||||
@POST @Path("{id}"+EP_REGISTER) | |||||
public Response register(@Context Request req, | |||||
@Context ContainerRequest ctx, | |||||
@PathParam("id") String id, | |||||
FlexRouter request) { | |||||
// caller must be admin | |||||
if (isReadOnly(ctx)) return forbidden(); | |||||
@Override protected FlexRouter findAlternate(Request req, ContainerRequest ctx, FlexRouter request) { | |||||
final String remoteAddr = getRemoteAddr(req); | |||||
return getDao().findByAccountAndIp(account.getUuid(), remoteAddr); | |||||
} | |||||
@Override protected FlexRouter setReferences(ContainerRequest ctx, Request req, Account caller, FlexRouter router) { | |||||
// caller must come from a valid device | |||||
final String remoteAddr = getRemoteAddr(req); | final String remoteAddr = getRemoteAddr(req); | ||||
final Device device = deviceService.findDeviceByIp(remoteAddr); | final Device device = deviceService.findDeviceByIp(remoteAddr); | ||||
if (device == null) return invalid("err.device.notFound"); | |||||
final FlexRouter flexRouter = getDao().findByUuid(id); | |||||
if (flexRouter == null) return notFound(id); | |||||
// set token and return | |||||
final String token = randomAlphanumeric(50); | |||||
final FlexRouter updated = getDao().update(flexRouter | |||||
.setPort(request.getPort()) | |||||
.setLastSeen() | |||||
.setToken(token)); | |||||
return ok(updated.setServerToken(token)); | |||||
if (device == null) throw invalidEx("err.device.notFound"); | |||||
router.setIp(remoteAddr); | |||||
if (!router.hasAuthToken()) throw invalidEx("err.token.required"); | |||||
router.setToken(router.getAuth_token()); | |||||
return super.setReferences(ctx, req, caller, router); | |||||
} | } | ||||
} | } |
@@ -1 +1 @@ | |||||
Subproject commit 2b4074ab37b1e3caa0cb6382352b3391f5bb7139 | |||||
Subproject commit 7692b6b6daa62e4f384db5320a30e95b5e3f71ab |
@@ -24,16 +24,16 @@ public class AuthTest extends ActivatedBubbleModelTestBase { | |||||
accountDAO.update(rootUser.setHashedPassword(new HashedPassword(ROOT_PASSWORD))); | accountDAO.update(rootUser.setHashedPassword(new HashedPassword(ROOT_PASSWORD))); | ||||
} | } | ||||
@Test public void testBasicAuth () throws Exception { modelTest("auth/basic_auth"); } | |||||
@Test public void testAccountCrud () throws Exception { modelTest("auth/account_crud"); } | |||||
@Test public void testDeviceCrud () throws Exception { modelTest("auth/device_crud"); } | |||||
@Test public void testRegistration () throws Exception { modelTest("auth/account_registration"); } | |||||
@Test public void testForgotPassword () throws Exception { modelTest("auth/forgot_password"); } | |||||
@Test public void testChangePassword () throws Exception { modelTest("auth/change_password"); } | |||||
@Test public void testBasicAuth () throws Exception { modelTest("auth/basic_auth"); } | |||||
@Test public void testAccountCrud () throws Exception { modelTest("auth/account_crud"); } | |||||
@Test public void testDeviceCrud () throws Exception { modelTest("auth/device_crud"); } | |||||
@Test public void testRegistration () throws Exception { modelTest("auth/account_registration"); } | |||||
@Test public void testForgotPassword () throws Exception { modelTest("auth/forgot_password"); } | |||||
@Test public void testChangePassword () throws Exception { modelTest("auth/change_password"); } | |||||
@Test public void testChangeAdminPassword () throws Exception { modelTest("auth/change_admin_password"); } | @Test public void testChangeAdminPassword () throws Exception { modelTest("auth/change_admin_password"); } | ||||
@Test public void testTotpAuth () throws Exception { modelTest("auth/totp_auth"); } | |||||
@Test public void testMultifactorAuth () throws Exception { modelTest("auth/multifactor_auth"); } | |||||
@Test public void testDownloadAccount () throws Exception { modelTest("auth/download_account"); } | |||||
@Test public void testNetworkAuth () throws Exception { modelTest("auth/network_auth"); } | |||||
@Test public void testTotpAuth () throws Exception { modelTest("auth/totp_auth"); } | |||||
@Test public void testMultifactorAuth () throws Exception { modelTest("auth/multifactor_auth"); } | |||||
@Test public void testDownloadAccount () throws Exception { modelTest("auth/download_account"); } | |||||
@Test public void testNetworkAuth () throws Exception { modelTest("auth/network_auth"); } | |||||
} | } |