diff --git a/restcomm/restcomm.application/src/main/webapp/WEB-INF/web.xml b/restcomm/restcomm.application/src/main/webapp/WEB-INF/web.xml index 11c9c5c6d0..8e5878d647 100644 --- a/restcomm/restcomm.application/src/main/webapp/WEB-INF/web.xml +++ b/restcomm/restcomm.application/src/main/webapp/WEB-INF/web.xml @@ -64,7 +64,7 @@ restAPI - /2012-04-24/Profiles/* + /2012-04-24/* diff --git a/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/AccountsEndpoint.java b/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/AccountsEndpoint.java index 36c5df3e2b..6326c3f29a 100644 --- a/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/AccountsEndpoint.java +++ b/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/AccountsEndpoint.java @@ -179,7 +179,6 @@ public LinkHeader composeLink(Sid targetSid, UriInfo info) { } protected Response getAccount(final String accountSid, final MediaType responseType, UriInfo info) { - checkAuthenticatedAccount(); //First check if the account has the required permissions in general, this way we can fail fast and avoid expensive DAO operations Account account = null; checkPermission("RestComm:Read:Accounts"); @@ -347,7 +346,6 @@ private void removeIncomingPhoneNumbers(Sid accountSid, IncomingPhoneNumbersDao protected Response getAccounts(final UriInfo info, final MediaType responseType) { - checkAuthenticatedAccount(); //First check if the account has the required permissions in general, this way we can fail fast and avoid expensive DAO operations checkPermission("RestComm:Read:Accounts"); final Account account = userIdentityContext.getEffectiveAccount(); @@ -389,7 +387,6 @@ protected Response getAccounts(final UriInfo info, final MediaType responseType) } protected Response putAccount(final MultivaluedMap data, final MediaType responseType) { - checkAuthenticatedAccount(); //First check if the account has the required permissions in general, this way we can fail fast and avoid expensive DAO operations checkPermission("RestComm:Create:Accounts"); // check account level depth. If we're already at third level no sub-accounts are allowed to be created @@ -561,7 +558,6 @@ private Account prepareAccountForUpdate(final Account account, final Multivalued protected Response updateAccount(final String identifier, final MultivaluedMap data, final MediaType responseType) { - checkAuthenticatedAccount(); // First check if the account has the required permissions in general, this way we can fail fast and avoid expensive DAO // operations checkPermission("RestComm:Modify:Accounts"); @@ -666,11 +662,6 @@ private Organization getOrganization(final MultivaluedMap data) protected Response migrateAccountOrganization(final String identifier, final MultivaluedMap data, final MediaType responseType) { - //Validation 1 - Only SuperAdmin is allowed to migrate organization for an Account - if (!isSuperAdmin()) { - throw new InsufficientPermission(); - } - Organization organization = getOrganization(data); //Validation 2 - Check if data contains Organization (either SID or domain name) if (organization == null) { diff --git a/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/AccountsJsonEndpoint.java b/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/AccountsJsonEndpoint.java index 04f9c00e05..e6eefe9592 100644 --- a/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/AccountsJsonEndpoint.java +++ b/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/AccountsJsonEndpoint.java @@ -21,7 +21,9 @@ import static javax.ws.rs.core.MediaType.APPLICATION_FORM_URLENCODED; import static javax.ws.rs.core.MediaType.APPLICATION_JSON_TYPE; +import static org.restcomm.connect.http.security.AccountPrincipal.SUPER_ADMIN_ROLE; +import javax.annotation.security.RolesAllowed; import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.OPTIONS; @@ -101,6 +103,7 @@ public Response updateAccountAsJsonPut(@PathParam("accountSid") final String acc @Path("/migrate/{accountSid}") @Consumes(APPLICATION_FORM_URLENCODED) @POST + @RolesAllowed(SUPER_ADMIN_ROLE) public Response migrateAccount(@PathParam("accountSid") final String accountSid, final MultivaluedMap data) { return migrateAccountOrganization(accountSid, data, APPLICATION_JSON_TYPE); } diff --git a/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/AccountsXmlEndpoint.java b/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/AccountsXmlEndpoint.java index 3d8f97c387..46cf8c1f99 100644 --- a/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/AccountsXmlEndpoint.java +++ b/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/AccountsXmlEndpoint.java @@ -34,6 +34,9 @@ import static javax.ws.rs.core.MediaType.APPLICATION_FORM_URLENCODED; import static javax.ws.rs.core.MediaType.APPLICATION_XML_TYPE; +import static org.restcomm.connect.http.security.AccountPrincipal.SUPER_ADMIN_ROLE; + +import javax.annotation.security.RolesAllowed; /** * @author quintana.thomas@gmail.com (Thomas Quintana) @@ -92,6 +95,7 @@ public Response updateAccountAsXmlPut(@PathParam("accountSid") final String acco @Path("/migrate/{accountSid}") @Consumes(APPLICATION_FORM_URLENCODED) @POST + @RolesAllowed(SUPER_ADMIN_ROLE) public Response migrateAccount(@PathParam("accoutSid") final String accountSid, final MultivaluedMap data) { return migrateAccountOrganization(accountSid, data, APPLICATION_XML_TYPE); } diff --git a/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/ExtensionsConfigurationEndpoint.java b/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/ExtensionsConfigurationEndpoint.java index f2073ec698..a2e0d57648 100644 --- a/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/ExtensionsConfigurationEndpoint.java +++ b/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/ExtensionsConfigurationEndpoint.java @@ -33,7 +33,6 @@ import org.restcomm.connect.extension.api.ExtensionConfiguration; import org.restcomm.connect.http.converter.ExtensionConfigurationConverter; import org.restcomm.connect.http.converter.RestCommResponseConverter; -import org.restcomm.connect.http.exceptions.InsufficientPermission; import javax.annotation.PostConstruct; import javax.ws.rs.core.MediaType; @@ -78,8 +77,6 @@ void init() { xstream.registerConverter(converter); xstream.registerConverter(new ExtensionConfigurationConverter(configuration)); xstream.registerConverter(new RestCommResponseConverter(configuration)); - // Make sure there is an authenticated account present when this endpoint is used - checkAuthenticatedAccount(); } /** @@ -90,9 +87,6 @@ void init() { */ protected Response getConfiguration(final String extensionId, final Sid accountSid, final MediaType responseType) { //Parameter "extensionId" could be the extension Sid or extension name. - if (!isSuperAdmin()) { - throw new InsufficientPermission(); - } ExtensionConfiguration extensionConfiguration = null; ExtensionConfiguration extensionAccountConfiguration = null; @@ -173,9 +167,6 @@ private ExtensionConfiguration createFrom(final MultivaluedMap d } protected Response postConfiguration(final MultivaluedMap data, final MediaType responseType) { - if (!isSuperAdmin()) { - throw new InsufficientPermission(); - } Sid accountSid = null; @@ -221,9 +212,6 @@ protected Response postConfiguration(final MultivaluedMap data, } protected Response updateConfiguration(String extensionSid, MultivaluedMap data, MediaType responseType) { - if (!isSuperAdmin()) { - throw new InsufficientPermission(); - } if (!Sid.pattern.matcher(extensionSid).matches()) { return status(BAD_REQUEST).build(); diff --git a/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/ExtensionsConfigurationJsonEndpoint.java b/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/ExtensionsConfigurationJsonEndpoint.java index f96534542f..5ceb4cf5a6 100644 --- a/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/ExtensionsConfigurationJsonEndpoint.java +++ b/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/ExtensionsConfigurationJsonEndpoint.java @@ -30,11 +30,15 @@ import org.restcomm.connect.commons.dao.Sid; import static javax.ws.rs.core.MediaType.APPLICATION_JSON_TYPE; +import static org.restcomm.connect.http.security.AccountPrincipal.SUPER_ADMIN_ROLE; + +import javax.annotation.security.RolesAllowed; /** * Created by gvagenas on 12/10/2016. */ @Path("/ExtensionsConfiguration.json") +@RolesAllowed(SUPER_ADMIN_ROLE) public class ExtensionsConfigurationJsonEndpoint extends ExtensionsConfigurationEndpoint { @Path("/{extensionId}") diff --git a/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/ExtensionsConfigurationXmlEndpoint.java b/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/ExtensionsConfigurationXmlEndpoint.java index f0ad07970f..ede1ea96a5 100644 --- a/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/ExtensionsConfigurationXmlEndpoint.java +++ b/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/ExtensionsConfigurationXmlEndpoint.java @@ -34,8 +34,12 @@ import org.restcomm.connect.commons.dao.Sid; import static javax.ws.rs.core.MediaType.APPLICATION_XML_TYPE; +import static org.restcomm.connect.http.security.AccountPrincipal.SUPER_ADMIN_ROLE; + +import javax.annotation.security.RolesAllowed; @Path("/ExtensionsConfiguration") +@RolesAllowed(SUPER_ADMIN_ROLE) public class ExtensionsConfigurationXmlEndpoint extends ExtensionsConfigurationEndpoint { @Path("/{extensionId}") diff --git a/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/GatewaysEndpoint.java b/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/GatewaysEndpoint.java index 49e4748874..62b5d3e0f3 100644 --- a/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/GatewaysEndpoint.java +++ b/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/GatewaysEndpoint.java @@ -92,10 +92,6 @@ private Gateway createFrom(final MultivaluedMap data) { } protected Response getGateway(final String accountSid, final String sid, final MediaType responseType) { - //following 2 things are enough to grant access: 1. a valid authentication token is present. 2 it is a super admin. - checkAuthenticatedAccount(); - allowOnlySuperAdmin(); -// secure(accountsDao.getAccount(accountSid), "RestComm:Read:Gateways"); final Gateway gateway = dao.getGateway(new Sid(sid)); if (gateway == null) { return status(NOT_FOUND).build(); @@ -112,10 +108,6 @@ protected Response getGateway(final String accountSid, final String sid, final M } protected Response getGateways(final String accountSid, final MediaType responseType) { - //following 2 things are enough to grant access: 1. a valid authentication token is present. 2 it is a super admin. - checkAuthenticatedAccount(); - allowOnlySuperAdmin(); -// secure(accountsDao.getAccount(accountSid), "RestComm:Read:Gateways"); final List gateways = dao.getGateways(); if (APPLICATION_XML_TYPE == responseType) { final RestCommResponse response = new RestCommResponse(new GatewayList(gateways)); @@ -128,10 +120,7 @@ protected Response getGateways(final String accountSid, final MediaType response } protected Response putGateway(final String accountSid, final MultivaluedMap data, final MediaType responseType) { - //following 2 things are enough to grant access: 1. a valid authentication token is present. 2 it is a super admin. - checkAuthenticatedAccount(); - allowOnlySuperAdmin(); -// secure(accountsDao.getAccount(accountSid), "RestComm:Create:Gateways"); + try { validate(data); } catch (final RuntimeException exception) { @@ -154,10 +143,7 @@ protected Response putGateway(final String accountSid, final MultivaluedMap data, final MediaType responseType) { - //following 2 things are enough to grant access: 1. a valid authentication token is present. 2 it is a super admin. - checkAuthenticatedAccount(); - allowOnlySuperAdmin(); -// secure(accountsDao.getAccount(accountSid), "RestComm:Modify:Gateways"); + Gateway gateway = dao.getGateway(new Sid(sid)); if (gateway == null) { return status(NOT_FOUND).build(); diff --git a/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/GatewaysJsonEndpoint.java b/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/GatewaysJsonEndpoint.java index c9e6bbd33a..e4bd79b2cd 100644 --- a/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/GatewaysJsonEndpoint.java +++ b/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/GatewaysJsonEndpoint.java @@ -20,7 +20,9 @@ package org.restcomm.connect.http; import static javax.ws.rs.core.MediaType.*; +import static org.restcomm.connect.http.security.AccountPrincipal.SUPER_ADMIN_ROLE; +import javax.annotation.security.RolesAllowed; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; @@ -35,6 +37,7 @@ */ @Path("/Accounts/{accountSid}/Management/Gateways.json") @ThreadSafe +@RolesAllowed(SUPER_ADMIN_ROLE) public class GatewaysJsonEndpoint extends GatewaysEndpoint { public GatewaysJsonEndpoint() { super(); diff --git a/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/GatewaysXmlEndpoint.java b/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/GatewaysXmlEndpoint.java index acd50e8cd6..e64acec386 100644 --- a/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/GatewaysXmlEndpoint.java +++ b/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/GatewaysXmlEndpoint.java @@ -21,7 +21,9 @@ import static javax.ws.rs.core.MediaType.*; import static javax.ws.rs.core.Response.*; +import static org.restcomm.connect.http.security.AccountPrincipal.SUPER_ADMIN_ROLE; +import javax.annotation.security.RolesAllowed; import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.POST; @@ -39,6 +41,7 @@ */ @Path("/Accounts/{accountSid}/Management/Gateways") @ThreadSafe +@RolesAllowed(SUPER_ADMIN_ROLE) public final class GatewaysXmlEndpoint extends GatewaysEndpoint { public GatewaysXmlEndpoint() { super(); diff --git a/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/IncomingPhoneNumbersEndpoint.java b/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/IncomingPhoneNumbersEndpoint.java index 063cc91e96..29d7701cfd 100644 --- a/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/IncomingPhoneNumbersEndpoint.java +++ b/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/IncomingPhoneNumbersEndpoint.java @@ -660,7 +660,6 @@ public static org.restcomm.connect.provisioning.number.api.PhoneNumber convertIn protected Response migrateIncomingPhoneNumbers(String targetAccountSid, MultivaluedMap data, MediaType responseType) { Account effectiveAccount = userIdentityContext.getEffectiveAccount(); secure(effectiveAccount, "RestComm:Modify:IncomingPhoneNumbers"); - allowOnlySuperAdmin(); try{ Account targetAccount = accountsDao.getAccount(targetAccountSid); // this is to avoid if mistakenly provided super admin account as targetAccountSid diff --git a/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/IncomingPhoneNumbersXmlEndpoint.java b/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/IncomingPhoneNumbersXmlEndpoint.java index 728c9b457e..5ca96038b6 100644 --- a/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/IncomingPhoneNumbersXmlEndpoint.java +++ b/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/IncomingPhoneNumbersXmlEndpoint.java @@ -21,7 +21,9 @@ import static javax.ws.rs.core.MediaType.APPLICATION_JSON_TYPE; import static javax.ws.rs.core.MediaType.APPLICATION_XML_TYPE; +import static org.restcomm.connect.http.security.AccountPrincipal.SUPER_ADMIN_ROLE; +import javax.annotation.security.RolesAllowed; import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.POST; @@ -212,6 +214,7 @@ public Response putIncomingMobilePhoneNumberAsJSon(@PathParam("accountSid") fina @Path("/migrate") @POST + @RolesAllowed(SUPER_ADMIN_ROLE) public Response migrateIncomingPhoneNumbersAsXml(@PathParam("accountSid") final String accountSid, final MultivaluedMap data) { return migrateIncomingPhoneNumbers(accountSid, data, APPLICATION_XML_TYPE); @@ -219,6 +222,7 @@ public Response migrateIncomingPhoneNumbersAsXml(@PathParam("accountSid") final @Path("/migrate.json") @POST + @RolesAllowed(SUPER_ADMIN_ROLE) public Response migrateIncomingPhoneNumbersAsJson(@PathParam("accountSid") final String accountSid, final MultivaluedMap data) { return migrateIncomingPhoneNumbers(accountSid, data, APPLICATION_JSON_TYPE); diff --git a/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/OrganizationsEndpoint.java b/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/OrganizationsEndpoint.java index bd1734fa94..2e54a2f252 100644 --- a/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/OrganizationsEndpoint.java +++ b/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/OrganizationsEndpoint.java @@ -141,7 +141,6 @@ public LinkHeader composeLink(Sid targetSid, UriInfo info) { */ protected Response getOrganization(final String organizationSid, final MediaType responseType, UriInfo info) { - checkAuthenticatedAccount(); //First check if the account has the required permissions in general, this way we can fail fast and avoid expensive DAO operations checkPermission("RestComm:Read:Organizations"); Organization organization = null; @@ -191,9 +190,6 @@ protected Response getOrganization(final String organizationSid, final MediaType * @return */ protected Response getOrganizations(UriInfo info, final MediaType responseType) { - checkAuthenticatedAccount(); - allowOnlySuperAdmin(); - List organizations = null; String status = info.getQueryParameters().getFirst("Status"); @@ -228,8 +224,6 @@ protected Response putOrganization(String domainName, final UriInfo info, if(domainName == null){ return status(BAD_REQUEST).entity(MSG_EMPTY_DOMAIN_NAME ).build(); }else{ - checkAuthenticatedAccount(); - allowOnlySuperAdmin(); //Character verification if(!pattern.matcher(domainName).matches()){ diff --git a/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/OrganizationsJsonEndpoint.java b/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/OrganizationsJsonEndpoint.java index 7691c4fc12..aab9e9aeaf 100644 --- a/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/OrganizationsJsonEndpoint.java +++ b/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/OrganizationsJsonEndpoint.java @@ -20,7 +20,9 @@ package org.restcomm.connect.http; import static javax.ws.rs.core.MediaType.APPLICATION_JSON_TYPE; +import static org.restcomm.connect.http.security.AccountPrincipal.SUPER_ADMIN_ROLE; +import javax.annotation.security.RolesAllowed; import javax.ws.rs.GET; import javax.ws.rs.PUT; import javax.ws.rs.Path; @@ -49,12 +51,14 @@ public Response getOrganizationAsJson(@PathParam("organizationSid") final String } @GET + @RolesAllowed(SUPER_ADMIN_ROLE) public Response getOrganizations(@Context UriInfo info) { return getOrganizations(info, APPLICATION_JSON_TYPE); } @Path("/{domainName}") @PUT + @RolesAllowed(SUPER_ADMIN_ROLE) public Response putOrganizationPut(@PathParam("domainName") final String domainName, @Context UriInfo info) { return putOrganization(domainName, info, APPLICATION_JSON_TYPE); } diff --git a/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/OrganizationsXmlEndpoint.java b/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/OrganizationsXmlEndpoint.java index f56a6853d1..4982eb04d3 100644 --- a/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/OrganizationsXmlEndpoint.java +++ b/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/OrganizationsXmlEndpoint.java @@ -20,7 +20,9 @@ package org.restcomm.connect.http; import static javax.ws.rs.core.MediaType.APPLICATION_XML_TYPE; +import static org.restcomm.connect.http.security.AccountPrincipal.SUPER_ADMIN_ROLE; +import javax.annotation.security.RolesAllowed; import javax.ws.rs.GET; import javax.ws.rs.PUT; import javax.ws.rs.Path; @@ -36,6 +38,7 @@ */ @Path("/Organizations") @ThreadSafe +@RolesAllowed(SUPER_ADMIN_ROLE) public final class OrganizationsXmlEndpoint extends OrganizationsEndpoint { public OrganizationsXmlEndpoint() { super(); @@ -49,12 +52,14 @@ public Response getOrganizationAsXml(@PathParam("organizationSid") final String } @GET + @RolesAllowed(SUPER_ADMIN_ROLE) public Response getOrganizations(@Context UriInfo info) { return getOrganizations(info, APPLICATION_XML_TYPE); } @Path("/{domainName}") @PUT + @RolesAllowed(SUPER_ADMIN_ROLE) public Response putOrganizationPut(@PathParam("domainName") final String domainName, @Context UriInfo info) { return putOrganization(domainName, info, APPLICATION_XML_TYPE); } diff --git a/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/OutboundProxyEndpoint.java b/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/OutboundProxyEndpoint.java index 94582b787e..3fb12dbbc7 100644 --- a/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/OutboundProxyEndpoint.java +++ b/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/OutboundProxyEndpoint.java @@ -89,10 +89,6 @@ public void init() { } protected Response getProxies(final String accountSid, final MediaType responseType) { - //following 2 things are enough to grant access: 1. a valid authentication token is present. 2 it is a super admin. - checkAuthenticatedAccount(); - allowOnlySuperAdmin(); -// secure(accountsDao.getAccount(accountSid), "RestComm:Read:OutboundProxies"); Map proxies; @@ -115,10 +111,6 @@ protected Response getProxies(final String accountSid, final MediaType responseT } protected Response switchProxy(final String accountSid, final MediaType responseType) { - //following 2 things are enough to grant access: 1. a valid authentication token is present. 2 it is a super admin. - checkAuthenticatedAccount(); - allowOnlySuperAdmin(); -// secure(accountsDao.getAccount(accountSid), "RestComm:Read:OutboundProxies"); Map proxyAfterSwitch; @@ -141,10 +133,6 @@ protected Response switchProxy(final String accountSid, final MediaType response } protected Response getActiveProxy(final String accountSid, final MediaType responseType) { - //following 2 things are enough to grant access: 1. a valid authentication token is present. 2 it is a super admin. - checkAuthenticatedAccount(); - allowOnlySuperAdmin(); -// secure(accountsDao.getAccount(accountSid), "RestComm:Read:OutboundProxies"); Map activeProxy; diff --git a/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/OutboundProxyJsonEndpoint.java b/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/OutboundProxyJsonEndpoint.java index be010a1937..36ec9961fc 100644 --- a/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/OutboundProxyJsonEndpoint.java +++ b/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/OutboundProxyJsonEndpoint.java @@ -21,7 +21,9 @@ package org.restcomm.connect.http; import static javax.ws.rs.core.MediaType.APPLICATION_JSON_TYPE; +import static org.restcomm.connect.http.security.AccountPrincipal.SUPER_ADMIN_ROLE; +import javax.annotation.security.RolesAllowed; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.PathParam; @@ -35,6 +37,7 @@ */ @Path("/Accounts/{accountSid}/OutboundProxy.json") @ThreadSafe +@RolesAllowed(SUPER_ADMIN_ROLE) public class OutboundProxyJsonEndpoint extends OutboundProxyEndpoint { public OutboundProxyJsonEndpoint() { super(); diff --git a/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/OutboundProxyXmlEndpoint.java b/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/OutboundProxyXmlEndpoint.java index 6359745454..4a4930abf1 100644 --- a/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/OutboundProxyXmlEndpoint.java +++ b/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/OutboundProxyXmlEndpoint.java @@ -21,7 +21,9 @@ package org.restcomm.connect.http; import static javax.ws.rs.core.MediaType.*; +import static org.restcomm.connect.http.security.AccountPrincipal.SUPER_ADMIN_ROLE; +import javax.annotation.security.RolesAllowed; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.PathParam; @@ -35,6 +37,7 @@ */ @Path("/Accounts/{accountSid}/OutboundProxy") @ThreadSafe +@RolesAllowed(SUPER_ADMIN_ROLE) public class OutboundProxyXmlEndpoint extends OutboundProxyEndpoint { public OutboundProxyXmlEndpoint() { super(); diff --git a/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/ProfileJsonEndpoint.java b/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/ProfileJsonEndpoint.java index 091fa6a9f9..99f6d4769c 100644 --- a/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/ProfileJsonEndpoint.java +++ b/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/ProfileJsonEndpoint.java @@ -38,15 +38,13 @@ import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; import org.restcomm.connect.commons.annotations.concurrency.ThreadSafe; -import static org.restcomm.connect.http.ProfileEndpoint.PROFILE_CONTENT_TYPE; -import static org.restcomm.connect.http.ProfileEndpoint.PROFILE_SCHEMA_CONTENT_TYPE; import static org.restcomm.connect.http.security.AccountPrincipal.SUPER_ADMIN_ROLE; @Path("/Profiles") @ThreadSafe @RolesAllowed(SUPER_ADMIN_ROLE) @Singleton -public class ProfileJsonEndpoint extends ProfileEndpoint{ +public class ProfileJsonEndpoint extends ProfileEndpoint { @GET @Produces(APPLICATION_JSON) diff --git a/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/SecuredEndpoint.java b/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/SecuredEndpoint.java index 6993ac21ad..b3d02daf40 100644 --- a/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/SecuredEndpoint.java +++ b/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/SecuredEndpoint.java @@ -38,7 +38,6 @@ import org.restcomm.connect.extension.controller.ExtensionController; import org.restcomm.connect.http.exceptions.AuthorizationException; import org.restcomm.connect.http.exceptions.InsufficientPermission; -import org.restcomm.connect.http.exceptions.NotAuthenticated; import org.restcomm.connect.http.exceptions.OperatedAccountMissing; import org.restcomm.connect.identity.AuthOutcome; import org.restcomm.connect.identity.IdentityContext; @@ -111,15 +110,6 @@ protected void init(final Configuration configuration) { } } - /** - * Grants general purpose access if any valid token exists in the request - */ - protected void checkAuthenticatedAccount() { - if (userIdentityContext.getEffectiveAccount() == null) { - throw new NotAuthenticated(); - } - } - /** * Checks if the effective account is a super account (top level account) * @return @@ -185,8 +175,13 @@ protected void secure(final Account operatedAccount, final String permission) th secure(operatedAccount, permission, SecuredType.SECURED_STANDARD); } + /** + * @param operatedAccount + * @param permission + * @param type + * @throws AuthorizationException + */ protected void secure(final Account operatedAccount, final String permission, SecuredType type) throws AuthorizationException { - checkAuthenticatedAccount(); checkPermission(permission); // check an authenticated account allowed to do "permission" is available checkOrganization(operatedAccount); // check if valid organization is attached with this account. if (operatedAccount == null) { @@ -226,8 +221,13 @@ private void checkOrganization(Account account) throws IllegalStateException { } } + /** + * @param operatedAccount + * @param resourceAccountSid + * @param type + * @throws AuthorizationException + */ protected void secure(final Account operatedAccount, final Sid resourceAccountSid, SecuredType type) throws AuthorizationException { - checkAuthenticatedAccount(); if (operatedAccount == null) { // if operatedAccount is NULL, we'll probably return a 404. But let's handle that in a central place. throw new OperatedAccountMissing(); @@ -446,5 +446,4 @@ protected boolean executePostApiAction(final ApiRequest apiRequest) { ExtensionController ec = ExtensionController.getInstance(); return ec.executePostApiAction(apiRequest, extensions).isAllowed(); } - } diff --git a/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/SupervisorEndpoint.java b/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/SupervisorEndpoint.java index 080a74504f..ee46fce07f 100644 --- a/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/SupervisorEndpoint.java +++ b/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/SupervisorEndpoint.java @@ -109,8 +109,6 @@ public void init() { protected Response pong(final String accountSid, final MediaType responseType) { //following 2 things are enough to grant access: 1. a valid authentication token is present. 2 it is a super admin. - checkAuthenticatedAccount(); - allowOnlySuperAdmin(); CallDetailRecordFilter filterForTotal; try { filterForTotal = new CallDetailRecordFilter("", null, null, null, null, null,null, @@ -131,8 +129,6 @@ protected Response pong(final String accountSid, final MediaType responseType) { protected Response getMetrics(final String accountSid, final UriInfo info, final MediaType responseType) { //following 2 things are enough to grant access: 1. a valid authentication token is present. 2 it is a super admin. - checkAuthenticatedAccount(); - allowOnlySuperAdmin(); boolean withLiveCallDetails = false; boolean withMgcpStats = false; if (info != null && info.getQueryParameters().containsKey("LiveCallDetails") ) { @@ -172,8 +168,6 @@ protected Response getMetrics(final String accountSid, final UriInfo info, final protected Response getLiveCalls(final String accountSid, final MediaType responseType) { //following 2 things are enough to grant access: 1. a valid authentication token is present. 2 it is a super admin. - checkAuthenticatedAccount(); - allowOnlySuperAdmin(); LiveCallsDetails callDetails; try { final Timeout expires = new Timeout(Duration.create(5, TimeUnit.SECONDS)); @@ -204,8 +198,6 @@ protected Response getLiveCalls(final String accountSid, final MediaType respons //Register a remote location where Restcomm will send monitoring updates protected Response registerForUpdates(final String accountSid, final UriInfo info, MediaType responseType) { //following 2 things are enough to grant access: 1. a valid authentication token is present. 2 it is a super admin. - checkAuthenticatedAccount(); - allowOnlySuperAdmin(); boolean withLiveCallDetails = false; boolean withMgcpStats = false; if (info != null && info.getQueryParameters().containsKey("LiveCallDetails") ) { @@ -243,8 +235,6 @@ protected Response registerForUpdates(final String accountSid, final UriInfo inf //Register a remote location where Restcomm will send monitoring updates for a specific Call protected Response registerForCallUpdates(final String accountSid, final String callSid, final MultivaluedMap data, MediaType responseType) { //following 2 things are enough to grant access: 1. a valid authentication token is present. 2 it is a super admin. - checkAuthenticatedAccount(); - allowOnlySuperAdmin(); final String url = data.getFirst("Url"); final String refresh = data.getFirst("Refresh"); boolean withLiveCallDetails = false; diff --git a/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/SupservisorJsonEndpoint.java b/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/SupservisorJsonEndpoint.java index 9a5f703b4b..068df9aa0a 100644 --- a/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/SupservisorJsonEndpoint.java +++ b/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/SupservisorJsonEndpoint.java @@ -32,6 +32,9 @@ import javax.ws.rs.core.UriInfo; import static javax.ws.rs.core.MediaType.APPLICATION_JSON_TYPE; +import static org.restcomm.connect.http.security.AccountPrincipal.SUPER_ADMIN_ROLE; + +import javax.annotation.security.RolesAllowed; /** * @author gvagenas @@ -39,6 +42,7 @@ */ @Path("/Accounts/{accountSid}/Supervisor.json") @ThreadSafe +@RolesAllowed(SUPER_ADMIN_ROLE) public class SupservisorJsonEndpoint extends SupervisorEndpoint{ public SupservisorJsonEndpoint() { diff --git a/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/security/SecurityFilter.java b/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/security/SecurityFilter.java index bc8414a251..57922342a4 100644 --- a/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/security/SecurityFilter.java +++ b/restcomm/restcomm.http/src/main/java/org/restcomm/connect/http/security/SecurityFilter.java @@ -19,22 +19,29 @@ */ package org.restcomm.connect.http.security; -import com.sun.jersey.spi.container.ContainerRequest; -import com.sun.jersey.spi.container.ContainerRequestFilter; -import org.apache.log4j.Logger; +import static javax.ws.rs.core.Response.status; + import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Context; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; import javax.ws.rs.ext.Provider; + +import org.apache.log4j.Logger; import org.restcomm.connect.dao.AccountsDao; import org.restcomm.connect.dao.DaoManager; -import org.restcomm.connect.dao.OrganizationsDao; -import org.restcomm.connect.identity.IdentityContext; +import org.restcomm.connect.dao.entities.Account; import org.restcomm.connect.identity.UserIdentityContext; +import com.sun.jersey.spi.container.ContainerRequest; +import com.sun.jersey.spi.container.ContainerRequestFilter; + @Provider public class SecurityFilter implements ContainerRequestFilter { private final Logger logger = Logger.getLogger(SecurityFilter.class); + private static final String PATTERN_FOR_RECORDING_FILE_PATH=".*Accounts/.*/Recordings/RE.*[.mp4|.wav]"; @Context private HttpServletRequest servletRequest; @@ -42,19 +49,37 @@ public class SecurityFilter implements ContainerRequestFilter { // We return Access-* headers only in case allowedOrigin is present and equals to the 'Origin' header. @Override public ContainerRequest filter(ContainerRequest cr) { - //TODO only apply to Profiles endpoint by now - if (cr.getPath().contains("Profiles")) { - final DaoManager storage = (DaoManager) servletRequest.getServletContext().getAttribute(DaoManager.class.getName()); - AccountsDao accountsDao = storage.getAccountsDao(); - OrganizationsDao organizationsDao = storage.getOrganizationsDao(); - IdentityContext identityContext = (IdentityContext) servletRequest.getServletContext().getAttribute(IdentityContext.class.getName()); - UserIdentityContext userIdentityContext = new UserIdentityContext(servletRequest, accountsDao); - String scheme = cr.getAuthenticationScheme(); - AccountPrincipal aPrincipal = new AccountPrincipal(userIdentityContext); - cr.setSecurityContext(new RCSecContext(aPrincipal, scheme)); - return cr; + final DaoManager storage = (DaoManager) servletRequest.getServletContext().getAttribute(DaoManager.class.getName()); + AccountsDao accountsDao = storage.getAccountsDao(); + UserIdentityContext userIdentityContext = new UserIdentityContext(servletRequest, accountsDao); + // exclude recording file https://telestax.atlassian.net/browse/RESTCOMM-1736 + logger.info("cr.getPath(): "+cr.getPath()); + if (!cr.getPath().matches(PATTERN_FOR_RECORDING_FILE_PATH)) { + checkAuthenticatedAccount(userIdentityContext); + filterClosedAccounts(userIdentityContext); } + String scheme = cr.getAuthenticationScheme(); + AccountPrincipal aPrincipal = new AccountPrincipal(userIdentityContext); + cr.setSecurityContext(new RCSecContext(aPrincipal, scheme)); return cr; } + /** + * Grants general purpose access if any valid token exists in the request + */ + protected void checkAuthenticatedAccount(UserIdentityContext userIdentityContext) { + if (userIdentityContext.getEffectiveAccount() == null) { + throw new WebApplicationException(Response.status(Response.Status.UNAUTHORIZED).header("WWW-Authenticate", "Basic realm=\"Restcomm realm\"").build()); + } + } + + /** + * filter out accounts that are not active + * @param userIdentityContext + */ + protected void filterClosedAccounts(UserIdentityContext userIdentityContext){ + if(userIdentityContext.getEffectiveAccount() != null && !userIdentityContext.getEffectiveAccount().getStatus().equals(Account.Status.ACTIVE)){ + throw new WebApplicationException(status(Status.FORBIDDEN).entity("Provided Account is not active").build()); + } + } } diff --git a/restcomm/restcomm.http/src/test/java/org/restcomm/connect/http/AccountsEndpointMockedTest.java b/restcomm/restcomm.http/src/test/java/org/restcomm/connect/http/AccountsEndpointMockedTest.java index 9158e641f0..674504a185 100644 --- a/restcomm/restcomm.http/src/test/java/org/restcomm/connect/http/AccountsEndpointMockedTest.java +++ b/restcomm/restcomm.http/src/test/java/org/restcomm/connect/http/AccountsEndpointMockedTest.java @@ -40,14 +40,4 @@ public void endpointInitializedAndBasicAuthorizationWork() throws ConfigurationE endpoint.init(); } - @Test(expected=NotAuthenticated.class) - public void requestMissingAuthorizationIsRejected() { - init(); // setup default mocking values - when(request.getHeader("Authorization")).thenReturn(null); // override Authorization header to null - AccountsEndpoint endpoint = new AccountsEndpoint(servletContext,request); - endpoint.init(); - //use endpoint to cause the exception - endpoint.getAccounts(null, MediaType.TEXT_XML_TYPE); - } - } diff --git a/restcomm/restcomm.testsuite/src/main/java/org/restcomm/connect/testsuite/provisioning/number/vi/RestcommIncomingPhoneNumberTool.java b/restcomm/restcomm.testsuite/src/main/java/org/restcomm/connect/testsuite/provisioning/number/vi/RestcommIncomingPhoneNumberTool.java index c5d1cde2fe..de0b67005c 100644 --- a/restcomm/restcomm.testsuite/src/main/java/org/restcomm/connect/testsuite/provisioning/number/vi/RestcommIncomingPhoneNumberTool.java +++ b/restcomm/restcomm.testsuite/src/main/java/org/restcomm/connect/testsuite/provisioning/number/vi/RestcommIncomingPhoneNumberTool.java @@ -23,6 +23,7 @@ import com.google.gson.JsonObject; import com.google.gson.JsonParser; import com.sun.jersey.api.client.Client; +import com.sun.jersey.api.client.ClientResponse; import com.sun.jersey.api.client.WebResource; import com.sun.jersey.api.client.filter.HTTPBasicAuthFilter; import com.sun.jersey.core.util.MultivaluedMapImpl; @@ -161,4 +162,28 @@ public JsonObject getIncomingPhoneNumbersUsingFilter(String deploymentUrl, Strin return jsonObject; } + public ClientResponse getIncomingPhonNumberClientResponse(String deploymentUrl, String username, String authToken){ + Client jerseyClient = Client.create(); + jerseyClient.addFilter(new HTTPBasicAuthFilter(username, authToken)); + String url = getAccountsUrl(deploymentUrl, username, true); + WebResource webResource = jerseyClient.resource(url); + return webResource.type(MediaType.APPLICATION_FORM_URLENCODED_TYPE).accept("application/json").post(ClientResponse.class); + + } + + public ClientResponse purchaseProviderNumber( String deploymentUrl, String username, String authToken, String phoneNumber, String voiceUrl, String voiceMethod, String friendlyName ){ + Client jerseyClient = Client.create(); + jerseyClient.addFilter(new HTTPBasicAuthFilter(username, authToken)); + + String provisioningURL = getAccountsUrl(deploymentUrl, username, true); + WebResource webResource = jerseyClient.resource(provisioningURL); + + MultivaluedMap formData = new MultivaluedMapImpl(); + formData.add("PhoneNumber", phoneNumber); + formData.add("VoiceUrl", voiceUrl); + formData.add("FriendlyName", friendlyName); + formData.add("VoiceMethod", voiceMethod); + return webResource.type(MediaType.APPLICATION_FORM_URLENCODED_TYPE).accept("application/json").post(ClientResponse.class, formData); + } + } diff --git a/restcomm/restcomm.testsuite/src/test/java/org/restcomm/connect/testsuite/http/AccountsEndpointClosingTest.java b/restcomm/restcomm.testsuite/src/test/java/org/restcomm/connect/testsuite/http/AccountsEndpointClosingTest.java index 51ee3ce46c..63caf0f910 100644 --- a/restcomm/restcomm.testsuite/src/test/java/org/restcomm/connect/testsuite/http/AccountsEndpointClosingTest.java +++ b/restcomm/restcomm.testsuite/src/test/java/org/restcomm/connect/testsuite/http/AccountsEndpointClosingTest.java @@ -21,11 +21,16 @@ package org.restcomm.connect.testsuite.http; import com.github.tomakehurst.wiremock.junit.WireMockRule; +import com.google.gson.JsonArray; +import com.google.gson.JsonParser; import com.sun.jersey.api.client.Client; import com.sun.jersey.api.client.ClientResponse; import com.sun.jersey.api.client.WebResource; +import com.sun.jersey.api.client.filter.HTTPBasicAuthFilter; import com.sun.jersey.core.util.MultivaluedMapImpl; import junit.framework.Assert; + +import org.apache.http.HttpResponse; import org.apache.log4j.Logger; import org.jboss.arquillian.container.test.api.Deployer; import org.jboss.arquillian.container.test.api.Deployment; @@ -40,7 +45,10 @@ import org.junit.runner.RunWith; import org.restcomm.connect.commons.Version; +import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; + +import java.io.IOException; import java.net.URL; import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; @@ -49,10 +57,16 @@ import static com.github.tomakehurst.wiremock.client.WireMock.post; import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor; import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; import static com.github.tomakehurst.wiremock.client.WireMock.urlMatching; import static com.github.tomakehurst.wiremock.client.WireMock.verify; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + import org.junit.experimental.categories.Category; import org.restcomm.connect.commons.annotations.UnstableTests; +import org.restcomm.connect.testsuite.provisioning.number.vi.AvailablePhoneNumbersEndpointTestUtils; +import org.restcomm.connect.testsuite.provisioning.number.vi.RestcommIncomingPhoneNumberTool; /** * @author otsakir@gmail.com - Orestis Tsakiridis diff --git a/restcomm/restcomm.testsuite/src/test/java/org/restcomm/connect/testsuite/http/CorsRelaxTest.java b/restcomm/restcomm.testsuite/src/test/java/org/restcomm/connect/testsuite/http/CorsRelaxTest.java index 045562688f..b9806ecdca 100644 --- a/restcomm/restcomm.testsuite/src/test/java/org/restcomm/connect/testsuite/http/CorsRelaxTest.java +++ b/restcomm/restcomm.testsuite/src/test/java/org/restcomm/connect/testsuite/http/CorsRelaxTest.java @@ -32,6 +32,7 @@ import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.spec.WebArchive; import org.jboss.shrinkwrap.resolver.api.maven.archive.ShrinkWrapMaven; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.restcomm.connect.commons.Version; @@ -44,8 +45,14 @@ * Currently it only tests Accounts endpoint where CORS-relax logic has been applied * * @author otsakir@gmail.com - Orestis Tsakiridis + * + * It was written for the case when restcomm should accept CORS requests and return the appropriate headers. + * Initially that would happen if restcomm would be in a different domain from RVD. + * Later, we decided to put everything (restcomm and RVD) behind the same domain. + * So, relaxing CORS restrictions is not really needed */ @RunWith(Arquillian.class) +@Ignore public class CorsRelaxTest extends EndpointTest { private final static Logger logger = Logger.getLogger(CorsRelaxTest.class.getName()); diff --git a/restcomm/restcomm.testsuite/src/test/java/org/restcomm/connect/testsuite/http/ProfilesEndpointTest.java b/restcomm/restcomm.testsuite/src/test/java/org/restcomm/connect/testsuite/http/ProfilesEndpointTest.java index 92ff779e8b..2b1df5724a 100644 --- a/restcomm/restcomm.testsuite/src/test/java/org/restcomm/connect/testsuite/http/ProfilesEndpointTest.java +++ b/restcomm/restcomm.testsuite/src/test/java/org/restcomm/connect/testsuite/http/ProfilesEndpointTest.java @@ -10,6 +10,8 @@ import java.net.URISyntaxException; import java.net.URL; +import javax.ws.rs.core.MediaType; + import org.apache.http.HttpResponse; import org.apache.http.client.ClientProtocolException; import org.apache.log4j.Logger; @@ -35,8 +37,11 @@ import com.google.gson.JsonArray; import com.google.gson.JsonObject; import com.google.gson.JsonParser; +import com.sun.jersey.api.client.Client; import com.sun.jersey.api.client.ClientResponse; +import com.sun.jersey.api.client.WebResource; import com.sun.jersey.api.client.WebResourceLinkHeaders; +import com.sun.jersey.api.client.filter.HTTPBasicAuthFilter; import com.sun.jersey.core.header.LinkHeader; /** @@ -523,12 +528,7 @@ public void testRemovalOfAssociationOnDeleteProfile() throws ClientProtocolExcep @Test public void getProfileSchemaTest() throws Exception { - URL schemaURL = new URL(RestcommProfilesTool.getInstance().getProfileSchemaUrl(deploymentUrl.toString())); - final JsonNode schemaNode = JsonLoader.fromURL(schemaURL); - final JsonSchemaFactory factory = JsonSchemaFactory.byDefault(); - final JsonSchema schema = factory.getJsonSchema(schemaNode); - - ClientResponse clientResponse = RestcommProfilesTool.getInstance().getProfileSchema(deploymentUrl.toString(), SUPER_ADMIN_ACCOUNT_SID, AUTH_TOKEN); + ClientResponse clientResponse = RestcommProfilesTool.getInstance().getProfileSchema(deploymentUrl.toString(), SUPER_ADMIN_ACCOUNT_SID, AUTH_TOKEN); assertEquals(200, clientResponse.getStatus()); String str = clientResponse.getEntity(String.class); assertNotNull(str); diff --git a/restcomm/restcomm.testsuite/src/test/java/org/restcomm/connect/testsuite/http/RestcommAPIEndpointSecurityTest.java b/restcomm/restcomm.testsuite/src/test/java/org/restcomm/connect/testsuite/http/RestcommAPIEndpointSecurityTest.java new file mode 100644 index 0000000000..8be780f0fb --- /dev/null +++ b/restcomm/restcomm.testsuite/src/test/java/org/restcomm/connect/testsuite/http/RestcommAPIEndpointSecurityTest.java @@ -0,0 +1,217 @@ +package org.restcomm.connect.testsuite.http; + +import static org.junit.Assert.assertEquals; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; + +import javax.ws.rs.core.MediaType; + +import org.apache.log4j.Logger; +import org.jboss.arquillian.container.test.api.Deployer; +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.junit.Arquillian; +import org.jboss.arquillian.test.api.ArquillianResource; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.jboss.shrinkwrap.resolver.api.maven.archive.ShrinkWrapMaven; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.runner.RunWith; +import org.restcomm.connect.commons.Version; +import org.restcomm.connect.commons.annotations.FeatureExpTests; + +import com.sun.jersey.api.client.Client; +import com.sun.jersey.api.client.ClientResponse; +import com.sun.jersey.api.client.WebResource; +import com.sun.jersey.api.client.filter.HTTPBasicAuthFilter; + +@RunWith(Arquillian.class) +public class RestcommAPIEndpointSecurityTest { + private final static Logger logger = Logger.getLogger(RestcommAPIEndpointSecurityTest.class.getName()); + + private static final String version = Version.getVersion(); + + @ArquillianResource + private Deployer deployer; + @ArquillianResource + URL deploymentUrl; + + private static final String SUPER_ADMIN_ACCOUNT_SID = "ACae6e420f425248d6a26948c17a9e2acf"; + private static final String CLOSED_ACCOUNT_SID="ACA3000000000000000000000000000000"; + private static final String SUSPENDED_ACCOUNT_SID="ACA4000000000000000000000000000000"; + private static final String AUTH_TOKEN = "77f8c12cc7b8f8423e5c38b035249166"; + private static final String RESOURCE_SID = "PRae6e420f425248d6a26948c17a9e2acf"; + private static final String GENERIC_ENDPOINT = "/2012-04-24/"; + private static final String PROFILE_ENDPOINT = "/2012-04-24/Profiles"; + private static final String ACCOUNT_ENDPOINT = "/2012-04-24/Accounts"; + private static final String ORGANIZATION_ENDPOINT = "/2012-04-24/Organizations"; + private static final String CLIENTS_ENDPOINT = "/2012-04-24/Accounts/"+SUPER_ADMIN_ACCOUNT_SID+"/Clients"; + private static final String RECORDINGS_ENDPOINT_FILE_PATH = "/2012-04-24/Accounts/"+SUPER_ADMIN_ACCOUNT_SID+"/Recordings/REc267d9cdcd0f4623a54abedf8e3d8835"; + private static final String RECORDINGS_ENDPOINT = "/2012-04-24/Accounts/"+SUPER_ADMIN_ACCOUNT_SID+"/Recordings"; + private static final String RECORDINGS_ENDPOINT_FILE_FULL_PATH = "http://127.0.0.1:8080/restcomm/2012-04-24/Accounts/"+SUPER_ADMIN_ACCOUNT_SID+"/Recordings/REc267d9cdcd0f4623a54abedf8e3d8835"; + + /** + * this test will try to access generic EP Without Authentication or invalid token + */ + @Test + public void genericSecurityTest(){ + assertEquals(401, performUnautherizedRequest(deploymentUrl.toString()+GENERIC_ENDPOINT)); + assertEquals(401, performRequestWithInvalidToken(deploymentUrl.toString()+GENERIC_ENDPOINT)); + assertEquals(403, performApiRequest(deploymentUrl.toString()+GENERIC_ENDPOINT, CLOSED_ACCOUNT_SID, AUTH_TOKEN)); + assertEquals(403, performApiRequest(deploymentUrl.toString()+GENERIC_ENDPOINT, SUSPENDED_ACCOUNT_SID, AUTH_TOKEN)); + } + /** + * this test will try to access org EP Without Authentication or invalid token + */ + @Test + @Category(FeatureExpTests.class) + public void organizationSecurityTest(){ + assertEquals(401, performUnautherizedRequest(deploymentUrl.toString()+ORGANIZATION_ENDPOINT+"/"+RESOURCE_SID)); + assertEquals(401, performUnautherizedRequest(deploymentUrl.toString()+ORGANIZATION_ENDPOINT)); + assertEquals(401, performRequestWithInvalidToken(deploymentUrl.toString()+ORGANIZATION_ENDPOINT)); + assertEquals(403, performApiRequest(deploymentUrl.toString()+ORGANIZATION_ENDPOINT, CLOSED_ACCOUNT_SID, AUTH_TOKEN)); + assertEquals(403, performApiRequest(deploymentUrl.toString()+ORGANIZATION_ENDPOINT, SUSPENDED_ACCOUNT_SID, AUTH_TOKEN)); + } + + /** + * this test will try to access acc EP Without Authentication or invalid token + */ + @Test + @Category(FeatureExpTests.class) + public void accountSecurityTest(){ + assertEquals(401, performUnautherizedRequest(deploymentUrl.toString()+ACCOUNT_ENDPOINT+"/"+RESOURCE_SID)); + assertEquals(401, performUnautherizedRequest(deploymentUrl.toString()+ACCOUNT_ENDPOINT)); + assertEquals(401, performRequestWithInvalidToken(deploymentUrl.toString()+ACCOUNT_ENDPOINT)); + assertEquals(403, performApiRequest(deploymentUrl.toString()+ACCOUNT_ENDPOINT, CLOSED_ACCOUNT_SID, AUTH_TOKEN)); + assertEquals(403, performApiRequest(deploymentUrl.toString()+ACCOUNT_ENDPOINT, SUSPENDED_ACCOUNT_SID, AUTH_TOKEN)); + } + + + /** + * this test will try to access profile EP Without Authentication or invalid token + */ + @Test + @Category(FeatureExpTests.class) + public void profileSecurityTest(){ + assertEquals(401, performUnautherizedRequest(deploymentUrl.toString()+PROFILE_ENDPOINT+"/"+RESOURCE_SID)); + assertEquals(401, performUnautherizedRequest(deploymentUrl.toString()+PROFILE_ENDPOINT)); + assertEquals(401, performRequestWithInvalidToken(deploymentUrl.toString()+PROFILE_ENDPOINT)); + assertEquals(403, performApiRequest(deploymentUrl.toString()+PROFILE_ENDPOINT, CLOSED_ACCOUNT_SID, AUTH_TOKEN)); + assertEquals(403, performApiRequest(deploymentUrl.toString()+PROFILE_ENDPOINT, SUSPENDED_ACCOUNT_SID, AUTH_TOKEN)); + } + + /** + * this test will try to access client EP Without Authentication or invalid token + */ + @Test + @Category(FeatureExpTests.class) + public void clientSecurityTest(){ + assertEquals(401, performUnautherizedRequest(deploymentUrl.toString()+CLIENTS_ENDPOINT+"/"+RESOURCE_SID)); + assertEquals(401, performUnautherizedRequest(deploymentUrl.toString()+CLIENTS_ENDPOINT)); + assertEquals(401, performRequestWithInvalidToken(deploymentUrl.toString()+CLIENTS_ENDPOINT)); + assertEquals(403, performApiRequest(deploymentUrl.toString()+CLIENTS_ENDPOINT, CLOSED_ACCOUNT_SID, AUTH_TOKEN)); + assertEquals(403, performApiRequest(deploymentUrl.toString()+CLIENTS_ENDPOINT, SUSPENDED_ACCOUNT_SID, AUTH_TOKEN)); + } + + /** + * this test will try to access recording endpoint + * we allow audio video file to be accessed, but recording list and single resource description is protected + * we will need to change this test if https://telestax.atlassian.net/browse/RESTCOMM-1736 is implemented + * @throws IOException + */ + @Test + public void recordingSecurityTest() throws IOException{ + //recording list is protected + assertEquals(401, performUnautherizedRequest(deploymentUrl.toString()+RECORDINGS_ENDPOINT)); + assertEquals(401, performRequestWithInvalidToken(deploymentUrl.toString()+RECORDINGS_ENDPOINT)); + assertEquals(403, performApiRequest(deploymentUrl.toString()+RECORDINGS_ENDPOINT, CLOSED_ACCOUNT_SID, AUTH_TOKEN)); + assertEquals(403, performApiRequest(deploymentUrl.toString()+RECORDINGS_ENDPOINT, SUSPENDED_ACCOUNT_SID, AUTH_TOKEN)); + + //recording resource is protected + assertEquals(401, performUnautherizedRequest(deploymentUrl.toString()+RECORDINGS_ENDPOINT_FILE_PATH)); + assertEquals(401, performRequestWithInvalidToken(deploymentUrl.toString()+RECORDINGS_ENDPOINT_FILE_PATH)); + assertEquals(403, performApiRequest(deploymentUrl.toString()+RECORDINGS_ENDPOINT_FILE_PATH, CLOSED_ACCOUNT_SID, AUTH_TOKEN)); + assertEquals(403, performApiRequest(deploymentUrl.toString()+RECORDINGS_ENDPOINT_FILE_PATH, SUSPENDED_ACCOUNT_SID, AUTH_TOKEN)); + + //recording audio file is not protected, we consider 404 equivalent to 200 as that means we already bypassed 401 and 403 and the fact that file does not exists + assertEquals(404, performUnautherizedRequest(deploymentUrl.toString()+RECORDINGS_ENDPOINT_FILE_PATH+".wav")); + assertEquals(404, performRequestWithInvalidToken(deploymentUrl.toString()+RECORDINGS_ENDPOINT_FILE_PATH+".wav")); + assertEquals(404, performApiRequest(deploymentUrl.toString()+RECORDINGS_ENDPOINT_FILE_PATH+".wav", CLOSED_ACCOUNT_SID, AUTH_TOKEN)); + assertEquals(404, performApiRequest(deploymentUrl.toString()+RECORDINGS_ENDPOINT_FILE_PATH+".wav", SUSPENDED_ACCOUNT_SID, AUTH_TOKEN)); + + //recording video file is not protected, we consider 404 equivalent to 200 as that means we already bypassed 401 and 403 and the fact that file does not exists + assertEquals(404, performUnautherizedRequest(deploymentUrl.toString()+RECORDINGS_ENDPOINT_FILE_PATH+".mp4")); + assertEquals(404, performRequestWithInvalidToken(deploymentUrl.toString()+RECORDINGS_ENDPOINT_FILE_PATH+".mp4")); + assertEquals(404, performApiRequest(deploymentUrl.toString()+RECORDINGS_ENDPOINT_FILE_PATH+".mp4", CLOSED_ACCOUNT_SID, AUTH_TOKEN)); + assertEquals(404, performApiRequest(deploymentUrl.toString()+RECORDINGS_ENDPOINT_FILE_PATH+".mp4", SUSPENDED_ACCOUNT_SID, AUTH_TOKEN)); + + //access by simple http url connection + URL url = new URL(RECORDINGS_ENDPOINT_FILE_FULL_PATH+".wav"); + HttpURLConnection connection = (HttpURLConnection)url.openConnection(); + connection.setRequestMethod("GET"); + connection.connect(); + assertEquals(404, connection.getResponseCode()); + } + + /** + * perform api Request with account + * + * @param endpointUrl + * @return + */ + private int performApiRequest(String endpointUrl, String account, String auth){ + Client jerseyClient = Client.create(); + jerseyClient.addFilter(new HTTPBasicAuthFilter(CLOSED_ACCOUNT_SID, AUTH_TOKEN)); + WebResource webResource = jerseyClient.resource(endpointUrl); + ClientResponse clientResponse = webResource.accept(MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML).get(ClientResponse.class); + return clientResponse.getStatus(); + } + + /** + * performUnautherizedRequest with no authentication + * + * @param endpointUrl + * @return + */ + private int performUnautherizedRequest(String endpointUrl){ + Client jerseyClient = Client.create(); + WebResource webResource = jerseyClient.resource(endpointUrl); + ClientResponse clientResponse = webResource.accept(MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML).get(ClientResponse.class); + return clientResponse.getStatus(); + } + + /** + * perform request with Invalid Token + * @param endpointUrl + * @return + */ + private int performRequestWithInvalidToken(String endpointUrl){ + Client jerseyClient = Client.create(); + jerseyClient.addFilter(new HTTPBasicAuthFilter(SUPER_ADMIN_ACCOUNT_SID, "wrongauthtoken")); + WebResource webResource = jerseyClient.resource(endpointUrl); + ClientResponse clientResponse = webResource.accept(MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML).get(ClientResponse.class); + return clientResponse.getStatus(); + } + + @Deployment(name = "RestcommAPIEndpointSecurityTest", managed = true, testable = false) + public static WebArchive createWebArchiveNoGw() { + logger.info("Packaging Test App"); + logger.info("version"); + WebArchive archive = ShrinkWrap.create(WebArchive.class, "restcomm.war"); + final WebArchive restcommArchive = ShrinkWrapMaven.resolver() + .resolve("org.restcomm:restcomm-connect.application:war:" + version).withoutTransitivity() + .asSingle(WebArchive.class); + archive = archive.merge(restcommArchive); + archive.delete("/WEB-INF/sip.xml"); + archive.delete("/WEB-INF/conf/restcomm.xml"); + archive.delete("/WEB-INF/data/hsql/restcomm.script"); + archive.addAsWebInfResource("sip.xml"); + archive.addAsWebInfResource("restcomm.xml", "conf/restcomm.xml"); + archive.addAsWebInfResource("restcomm.script_accounts_test", "data/hsql/restcomm.script"); + logger.info("Packaged Test App"); + return archive; + } +} diff --git a/restcomm/restcomm.testsuite/src/test/resources/restcomm.script_accounts_test b/restcomm/restcomm.testsuite/src/test/resources/restcomm.script_accounts_test index 83ec87ee05..762bd1a66a 100644 --- a/restcomm/restcomm.testsuite/src/test/resources/restcomm.script_accounts_test +++ b/restcomm/restcomm.testsuite/src/test/resources/restcomm.script_accounts_test @@ -33,6 +33,8 @@ INSERT INTO "restcomm_organizations" VALUES('ORafbe225ad37541eba518a74248f0ac4c' INSERT INTO "restcomm_organizations" VALUES('ORafbe225ad37541eba518a74248f0ac4d', 'org1.restcomm.com', '2017-04-19 00:00:00.000000000','2017-04-19 00:00:00.000000000', 'active') INSERT INTO "restcomm_organizations" VALUES('ORafbe225ad37541eba518a74248f0ac4e', 'org2.restcomm.com', '2017-04-19 00:00:00.000000000','2017-04-19 00:00:00.000000000', 'closed') INSERT INTO "restcomm_accounts" VALUES('ACae6e420f425248d6a26948c17a9e2acf','2012-04-24 22:51:29.372000000','2012-04-24 22:51:29.372000000','administrator@company.com','Default Administrator Account',NULL,'Full','active','77f8c12cc7b8f8423e5c38b035249166','Administrator','/2012-04-24/Accounts/ACae6e420f425248d6a26948c17a9e2acf','ORafbe225ad37541eba518a74248f0ac4c') +INSERT INTO "restcomm_accounts" VALUES('ACA3000000000000000000000000000000','2012-04-24 22:51:29.372000000','2012-04-24 22:51:29.372000000','childA3@company.com','childA3','ACA0000000000000000000000000000000','Full','closed','77f8c12cc7b8f8423e5c38b035249166','Administrator','/2012-04-24/Accounts/ACA3000000000000000000000000000000','ORafbe225ad37541eba518a74248f0ac4c') +INSERT INTO "restcomm_accounts" VALUES('ACA4000000000000000000000000000000','2012-04-24 22:51:29.372000000','2012-04-24 22:51:29.372000000','childA3@company.com','childA3','ACA0000000000000000000000000000000','Full','suspended','77f8c12cc7b8f8423e5c38b035249166','Administrator','/2012-04-24/Accounts/ACA3000000000000000000000000000000','ORafbe225ad37541eba518a74248f0ac4c') INSERT INTO "restcomm_accounts" VALUES('AC574d775522c96f9aacacc5ca60c8c74f','2012-04-24 22:51:29.372000000','2012-04-24 22:51:29.372000000','child@company.com','Child Account','ACae6e420f425248d6a26948c17a9e2acf','Full','active','77f8c12cc7b8f8423e5c38b035249166','Developer','/2012-04-24/Accounts/AC574d775522c96f9aacacc5ca60c8c74f','ORafbe225ad37541eba518a74248f0ac4c') INSERT INTO "restcomm_accounts" VALUES('AC574d775522c96f9aacacc5ca60c8c111','2012-04-24 22:51:29.372000000','2012-04-24 22:51:29.372000000','child2@company.com','Child 2 Account','ACae6e420f425248d6a26948c17a9e2acf','Full','active','77f8c12cc7b8f8423e5c38b035249166','Developer','/2012-04-24/Accounts/AC574d775522c96f9aacacc5ca60c8c74f','ORafbe225ad37541eba518a74248f0ac4d') INSERT INTO "restcomm_accounts" VALUES('AC574d775522c96f9aacacc5ca60c8c74g','2012-04-24 22:51:29.372000000','2012-04-24 22:51:29.372000000','admin@company.com','Admin Account','ACae6e420f425248d6a26948c17a9e2acf','Full','active','77f8c12cc7b8f8423e5c38b035249166','Administrator','/2012-04-24/Accounts/AC574d775522c96f9aacacc5ca60c8c74g','ORafbe225ad37541eba518a74248f0ac4c')