Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -383,17 +383,18 @@
</http-client>

<!-- Control notifications sent to rcmlserver (RVD)
base-url: relative path or absolute url where rcmlserver it located.
base-url: Base url where rcml server is located of the form scheme://host:port like 'http://rvdserver:8080'. Leave it empty when RVD is bundled together with restcomm.
api-path: relative path where rcml server api listens under like '/restcomm-rvd/services
notifications: should notifications be sent or not ? defaults to 'false'
timeout: milis to wait response from rcmlserver before timing out when sending notifications. Defaults to 5000
timeout-per-notification: if several notifications are sent in one step, timeout increase for each. Defaults to 500.
-->
<rcmlserver-api>
<base-url>/restcomm-rvd/services</base-url>
<rcmlserver>
<api-path>/restcomm-rvd/services</api-path>
<notifications>true</notifications>
<timeout>5000</timeout>
<timeout-per-notification>500</timeout-per-notification>
</rcmlserver-api>
</rcmlserver>

<!-- The SMS aggregator is responsible for the handling of SMS messages
inside of RestComm. Refer to the org.mobicents.servlet.sip.restcomm.SmsAggregator
Expand Down
11 changes: 8 additions & 3 deletions restcomm/restcomm.application/src/main/webapp/WEB-INF/web.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,18 @@
<servlet>
<servlet-name>Jersey</servlet-name>
<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
<!-- Enable CORS request filter -->
<init-param>
<param-name>com.sun.jersey.spi.container.ContainerResponseFilters</param-name>
<param-value>org.restcomm.connect.http.cors.CorsFilter</param-value>
</init-param>
</servlet>

<context-param>
<param-name>resteasy.scan</param-name>
<param-value>false</param-value>
</context-param>

<context-param>
<param-name>resteasy.scan.providers</param-name>
<param-value>false</param-value>
Expand All @@ -24,12 +29,12 @@
<param-name>resteasy.scan.resources</param-name>
<param-value>false</param-value>
</context-param>

<servlet-mapping>
<servlet-name>Jersey</servlet-name>
<url-pattern>/2012-04-24/*</url-pattern>
</servlet-mapping>

<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
* @author otsakir@gmail.com - Orestis Tsakiridis
*/
public interface RcmlserverConfigurationSet {
String getApiPath();
String getBaseUrl();
Boolean getNotify();
Integer getTimeout(); // how much to wait for response to a notification request before giving up
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,13 @@
* @author otsakir@gmail.com - Orestis Tsakiridis
*/
public class RcmlserverConfigurationSetImpl extends ConfigurationSet implements RcmlserverConfigurationSet {
private static final String BASE_URL_KEY = "rcmlserver-api.base-url";
private static final String NOTIFY_KEY = "rcmlserver-api.notifications";
private static final String TIMEOUT_KEY = "rcmlserver-api.timeout";
private static final String TIMEOUT_PER_NOTIFICATION_KEY = "rcmlserver-api.timeout-per-notification";
private static final String BASE_URL_KEY = "rcmlserver.base-url";
private static final String API_PATH_KEY = "rcmlserver.api-path";
private static final String NOTIFY_KEY = "rcmlserver.notifications";
private static final String TIMEOUT_KEY = "rcmlserver.timeout";
private static final String TIMEOUT_PER_NOTIFICATION_KEY = "rcmlserver.timeout-per-notification";
private String baseUrl = null;
private String apiPath = null;
private Boolean notify = false;
private Integer timeout = 5000;
private Integer timeoutPerNotification = 500;
Expand All @@ -48,6 +50,14 @@ public RcmlserverConfigurationSetImpl(ConfigurationSource source) {
this.baseUrl = value;
}

value = source.getProperty(API_PATH_KEY);
if ( !StringUtils.isEmpty(value) ) {
value = value.trim();
if (value.endsWith("/")) // remove trailing '/' if present
value = value.substring(0,value.length()-2);
this.apiPath = value;
}

value = source.getProperty(NOTIFY_KEY);
try {
this.notify = Boolean.parseBoolean(value);
Expand Down Expand Up @@ -77,6 +87,11 @@ public String getBaseUrl() {
return baseUrl;
}

@Override
public String getApiPath() {
return apiPath;
}

@Override
public Boolean getNotify() {
return notify;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,6 @@ void init() {
xstream.registerConverter(new AccountListConverter(runtimeConfiguration));
xstream.registerConverter(new RestCommResponseConverter(runtimeConfiguration));
// Make sure there is an authenticated account present when this endpoint is used
checkAuthenticatedAccount();
}

private Account createFrom(final Sid accountSid, final MultivaluedMap<String, String> data) throws PasswordTooWeak {
Expand Down Expand Up @@ -142,6 +141,7 @@ private Account createFrom(final Sid accountSid, final MultivaluedMap<String, St
}

protected Response getAccount(final String accountSid, 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
Account account = null;
checkPermission("RestComm:Read:Accounts");
Expand Down Expand Up @@ -302,6 +302,7 @@ private void removeIncomingPhoneNumbers(Sid accountSid, IncomingPhoneNumbersDao


protected Response getAccounts(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();
Expand All @@ -323,6 +324,7 @@ protected Response getAccounts(final MediaType responseType) {
}

protected Response putAccount(final MultivaluedMap<String, String> 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
Expand Down Expand Up @@ -476,6 +478,7 @@ private Account prepareAccountForUpdate(final Account account, final Multivalued

protected Response updateAccount(final String identifier, final MultivaluedMap<String, String> 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");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.OPTIONS;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
Expand All @@ -49,6 +50,13 @@ public Response getAccountAsJson(@PathParam("accountSid") final String accountSi
return getAccount(accountSid, APPLICATION_JSON_TYPE);
}

@Path("/{accountSid}")
@OPTIONS
public Response optionsAccount(@PathParam("accountSid") final String accountSid) {
// no authentication here since this is a pre-flight request
return Response.ok().build();
}

@GET
public Response getAccounts() {
return getAccounts(APPLICATION_JSON_TYPE);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

import com.google.gson.Gson;
import com.google.gson.JsonObject;
import org.apache.commons.lang.StringUtils;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
Expand Down Expand Up @@ -59,8 +60,16 @@ enum NotificationType {

public RcmlserverApi(MainConfigurationSet mainConfig, RcmlserverConfigurationSet rcmlserverConfig) {
try {
// resolve() should be run lazily to work. Make sure this constructor is invoked after the JBoss connectors have been set up.
apiUrl = UriUtils.resolve(new URI(rcmlserverConfig.getBaseUrl()));
// if there is no baseUrl configured we use the resolver to guess the location of the rcml server and the path
if ( StringUtils.isEmpty(rcmlserverConfig.getBaseUrl()) ) {
// resolve() should be run lazily to work. Make sure this constructor is invoked after the JBoss connectors have been set up.
apiUrl = UriUtils.resolve(new URI(rcmlserverConfig.getApiPath()));
}
// if baseUrl has been configured, concat baseUrl and path to find the location of rcml server. No resolving here.
else {
String path = rcmlserverConfig.getApiPath();
apiUrl = new URI(rcmlserverConfig.getBaseUrl() + (path != null ? path : "") );
}
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*
* TeleStax, Open Source Cloud Communications
* Copyright 2011-2014, Telestax Inc and individual contributors
* by the @authors tag.
*
* This program is free software: you can redistribute it and/or modify
* under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation; either version 3 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/

package org.restcomm.connect.http.cors;

import com.sun.jersey.spi.container.ContainerRequest;
import com.sun.jersey.spi.container.ContainerResponse;
import com.sun.jersey.spi.container.ContainerResponseFilter;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.XMLConfiguration;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.restcomm.connect.commons.configuration.sets.RcmlserverConfigurationSet;
import org.restcomm.connect.commons.configuration.sets.impl.RcmlserverConfigurationSetImpl;
import org.restcomm.connect.commons.configuration.sources.ApacheConfigurationSource;
import org.restcomm.connect.commons.configuration.sources.ConfigurationSource;

import javax.servlet.ServletContext;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.core.Context;
import javax.ws.rs.ext.Provider;
import java.io.File;

/**
* @author otsakir@gmail.com - Orestis Tsakiridis
*/
@Provider
public class CorsFilter implements ContainerResponseFilter {
private final Logger logger = Logger.getLogger(CorsFilter.class);

@Context
private HttpServletRequest servletRequest;

// we initialize this lazily upon first request since it can't be injected through the @Context annotation (it didn't work)
private ServletContext lazyServletContext;

String allowedOrigin;

// We return Access-* headers only in case allowedOrigin is present and equals to the 'Origin' header.
@Override
public ContainerResponse filter(ContainerRequest cres, ContainerResponse response) {
initLazily(servletRequest);
String requestOrigin = cres.getHeaderValue("Origin");
if (requestOrigin != null) { // is this is a cors request (ajax request that targets a different domain than the one the page was loaded from)
if (allowedOrigin != null && allowedOrigin.startsWith(requestOrigin)) { // no cors allowances make are applied if allowedOrigins == null
// only return the origin the client informed
response.getHttpHeaders().add("Access-Control-Allow-Origin", requestOrigin);
response.getHttpHeaders().add("Access-Control-Allow-Headers", "origin, content-type, accept, authorization");
response.getHttpHeaders().add("Access-Control-Allow-Credentials", "true");
response.getHttpHeaders().add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, HEAD");
response.getHttpHeaders().add("Access-Control-Max-Age", "1209600");
}
}
return response;
}

private void initLazily(ServletRequest request) {
if (lazyServletContext == null) {
ServletContext context = request.getServletContext();
String rootPath = context.getRealPath("/");
rootPath = StringUtils.stripEnd(rootPath,"/"); // remove trailing "/" character
String restcommXmlPath = rootPath + "/WEB-INF/conf/restcomm.xml";

// ok, found restcomm.xml. Now let's get rcmlserver/base-url configuration setting
File restcommXmlFile = new File(restcommXmlPath);
// Create apache configuration
XMLConfiguration apacheConf = new XMLConfiguration();
apacheConf.setDelimiterParsingDisabled(true);
apacheConf.setAttributeSplittingDisabled(true);
try {
apacheConf.load(restcommXmlPath);
} catch (ConfigurationException e) {
e.printStackTrace();
}
// Create high-level configuration
ConfigurationSource source = new ApacheConfigurationSource(apacheConf);
RcmlserverConfigurationSet rcmlserverConfig = new RcmlserverConfigurationSetImpl(source);
// initialize allowedOrigin
String baseUrl = rcmlserverConfig.getBaseUrl();
if ( baseUrl != null && (! baseUrl.trim().equals(""))) {
// baseUrl is set. We need to return CORS allow headers
allowedOrigin = baseUrl;
}

lazyServletContext = context;

logger.info("Initialized (lazily) CORS servlet response filter. allowedOrigin: " + allowedOrigin);
}
}
}
Loading