/** * Copyright (c) 2000-present Liferay, Inc. All rights reserved. * * This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation; either version 2.1 of the License, or (at your option) * any later version. * * This library 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 Lesser General Public License for more * details. */ package com.liferay.sync.internal.servlet.filter; import com.liferay.portal.kernel.json.JSONFactoryUtil; import com.liferay.portal.kernel.servlet.HttpHeaders; import com.liferay.portal.kernel.upload.UploadServletRequest; import com.liferay.portal.kernel.util.Constants; import com.liferay.portal.kernel.util.ContentTypes; import com.liferay.portal.kernel.util.ParamUtil; import com.liferay.portal.kernel.util.Portal; import com.liferay.portal.kernel.util.PrefsPropsUtil; import com.liferay.portal.kernel.util.StringPool; import com.liferay.portal.kernel.util.StringUtil; import com.liferay.portal.kernel.util.WebKeys; import com.liferay.sync.SyncClientMinBuildException; import com.liferay.sync.SyncDeviceHeaderException; import com.liferay.sync.SyncServicesUnavailableException; import com.liferay.sync.model.SyncDevice; import com.liferay.sync.service.SyncDeviceLocalServiceUtil; import com.liferay.sync.service.configuration.SyncServiceConfigurationKeys; import com.liferay.sync.service.configuration.SyncServiceConfigurationValues; import com.liferay.sync.util.SyncDeviceThreadLocal; import com.liferay.sync.util.SyncUtil; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; /** * @author Shinn Lok */ @Component( immediate = true, property = { "after-filter=Upload Servlet Request Filter", "servlet-context-name=", "servlet-filter-name=Sync JSON Filter", "url-pattern=/api/jsonws/*" }, service = Filter.class ) public class SyncJSONFilter implements Filter { @Override public void destroy() { } @Override public void doFilter( ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { SyncDevice syncDevice = null; HttpServletRequest httpServletRequest = (HttpServletRequest)servletRequest; String uuid = httpServletRequest.getHeader("Sync-UUID"); if (uuid != null) { syncDevice = SyncDeviceLocalServiceUtil.fetchSyncDeviceByUuidAndCompanyId( uuid, _portal.getCompanyId(httpServletRequest)); } if (syncDevice == null) { syncDevice = SyncDeviceLocalServiceUtil.createSyncDevice(0); } syncDevice.setHostname(servletRequest.getRemoteHost()); SyncDeviceThreadLocal.setSyncDevice(syncDevice); if (uuid != null) { filterChain.doFilter(servletRequest, servletResponse); return; } String uri = (String)servletRequest.getAttribute( WebKeys.INVOKER_FILTER_URI); if (uri.equals("/api/jsonws/invoke")) { String contentType = httpServletRequest.getHeader( HttpHeaders.CONTENT_TYPE); if ((contentType == null) || !contentType.startsWith(ContentTypes.MULTIPART_FORM_DATA)) { filterChain.doFilter(servletRequest, servletResponse); return; } if (!(httpServletRequest instanceof UploadServletRequest)) { servletRequest = _portal.getUploadServletRequest( httpServletRequest); } if (!isSyncJSONRequest(servletRequest)) { filterChain.doFilter(servletRequest, servletResponse); return; } } else if (!uri.startsWith("/api/jsonws/sync")) { filterChain.doFilter(servletRequest, servletResponse); return; } if (ParamUtil.get(httpServletRequest, "debug", false)) { filterChain.doFilter(servletRequest, servletResponse); return; } Throwable throwable = null; if (PrefsPropsUtil.getBoolean( _portal.getCompanyId(httpServletRequest), SyncServiceConfigurationKeys.SYNC_SERVICES_ENABLED, SyncServiceConfigurationValues.SYNC_SERVICES_ENABLED)) { int absoluteSyncClientMinBuild = 0; int syncClientMinBuild = 0; String syncDeviceType = httpServletRequest.getHeader("Sync-Device"); if (syncDeviceType == null) { throwable = new SyncDeviceHeaderException(); } else if (syncDeviceType.startsWith("desktop")) { absoluteSyncClientMinBuild = _ABSOLUTE_SYNC_CLIENT_MIN_BUILD_DESKTOP; syncClientMinBuild = PrefsPropsUtil.getInteger( _portal.getCompanyId(httpServletRequest), SyncServiceConfigurationKeys.SYNC_CLIENT_MIN_BUILD_DESKTOP, SyncServiceConfigurationValues. SYNC_CLIENT_MIN_BUILD_DESKTOP); } else if (syncDeviceType.equals("mobile-android")) { absoluteSyncClientMinBuild = _ABSOLUTE_SYNC_CLIENT_MIN_BUILD_ANDROID; syncClientMinBuild = PrefsPropsUtil.getInteger( _portal.getCompanyId(httpServletRequest), SyncServiceConfigurationKeys.SYNC_CLIENT_MIN_BUILD_ANDROID, SyncServiceConfigurationValues. SYNC_CLIENT_MIN_BUILD_ANDROID); } else if (syncDeviceType.equals("mobile-ios")) { absoluteSyncClientMinBuild = _ABSOLUTE_SYNC_CLIENT_MIN_BUILD_IOS; syncClientMinBuild = PrefsPropsUtil.getInteger( _portal.getCompanyId(httpServletRequest), SyncServiceConfigurationKeys.SYNC_CLIENT_MIN_BUILD_IOS, SyncServiceConfigurationValues.SYNC_CLIENT_MIN_BUILD_IOS); } else { throwable = new SyncDeviceHeaderException(); } if (throwable == null) { if (syncClientMinBuild < absoluteSyncClientMinBuild) { syncClientMinBuild = absoluteSyncClientMinBuild; } int syncBuild = httpServletRequest.getIntHeader("Sync-Build"); if (syncBuild >= syncClientMinBuild) { filterChain.doFilter(servletRequest, servletResponse); return; } else { throwable = new SyncClientMinBuildException( "Sync client does not meet minimum build " + syncClientMinBuild); } } } else { throwable = new SyncServicesUnavailableException(); } servletResponse.setCharacterEncoding(StringPool.UTF8); servletResponse.setContentType(ContentTypes.APPLICATION_JSON); OutputStream outputStream = servletResponse.getOutputStream(); String json = SyncUtil.buildExceptionMessage(throwable); json = "{\"exception\": \"" + json + "\"}"; outputStream.write(json.getBytes(StringPool.UTF8)); outputStream.close(); } @Override public void init(FilterConfig filterConfig) { } protected boolean isSyncJSONRequest(ServletRequest servletRequest) { try { String cmd = servletRequest.getParameter(Constants.CMD); if (cmd == null) { cmd = StringUtil.read(servletRequest.getInputStream()); } Object cmdObject = JSONFactoryUtil.looseDeserialize(cmd); List<Object> jsonItems = null; if (cmdObject instanceof List) { jsonItems = (List<Object>)cmdObject; } else if (cmdObject instanceof Map) { jsonItems = new ArrayList<>(1); jsonItems.add(cmdObject); } for (Object jsonItem : jsonItems) { Map<String, Map<String, Object>> map = (Map<String, Map<String, Object>>)jsonItem; Set<String> keySet = map.keySet(); Iterator<String> iterator = keySet.iterator(); String key = iterator.next(); if (key.startsWith("/sync-web.") || key.startsWith("/sync-web/") || key.startsWith("/sync.") || key.startsWith("/sync/")) { return true; } } } catch (Exception e) { return false; } return false; } private static final int _ABSOLUTE_SYNC_CLIENT_MIN_BUILD_ANDROID = 26; private static final int _ABSOLUTE_SYNC_CLIENT_MIN_BUILD_DESKTOP = 3200; private static final int _ABSOLUTE_SYNC_CLIENT_MIN_BUILD_IOS = 7; @Reference private Portal _portal; }