/** * 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.util; import com.liferay.document.library.kernel.exception.NoSuchFileVersionException; import com.liferay.document.library.kernel.model.DLFileEntry; import com.liferay.document.library.kernel.model.DLFileEntryConstants; import com.liferay.document.library.kernel.model.DLFileVersion; import com.liferay.document.library.kernel.model.DLFolder; import com.liferay.document.library.kernel.service.DLFileVersionLocalServiceUtil; import com.liferay.petra.io.delta.ByteChannelReader; import com.liferay.petra.io.delta.ByteChannelWriter; import com.liferay.petra.io.delta.DeltaUtil; import com.liferay.portal.kernel.exception.PortalException; import com.liferay.portal.kernel.json.JSONFactoryUtil; import com.liferay.portal.kernel.json.JSONObject; import com.liferay.portal.kernel.lock.Lock; import com.liferay.portal.kernel.log.Log; import com.liferay.portal.kernel.log.LogFactoryUtil; import com.liferay.portal.kernel.model.Group; import com.liferay.portal.kernel.model.User; import com.liferay.portal.kernel.repository.model.FileEntry; import com.liferay.portal.kernel.repository.model.Folder; import com.liferay.portal.kernel.security.SecureRandom; import com.liferay.portal.kernel.security.permission.PermissionChecker; import com.liferay.portal.kernel.security.permission.PermissionThreadLocal; import com.liferay.portal.kernel.service.GroupLocalServiceUtil; import com.liferay.portal.kernel.service.ServiceContext; import com.liferay.portal.kernel.util.Base64; import com.liferay.portal.kernel.util.ClassUtil; import com.liferay.portal.kernel.util.Digester; import com.liferay.portal.kernel.util.DigesterUtil; import com.liferay.portal.kernel.util.FileUtil; import com.liferay.portal.kernel.util.GetterUtil; import com.liferay.portal.kernel.util.PrefsPropsUtil; import com.liferay.portal.kernel.util.PwdGenerator; import com.liferay.portal.kernel.util.StreamUtil; import com.liferay.portal.kernel.util.StringBundler; import com.liferay.portal.kernel.util.StringPool; import com.liferay.portal.kernel.util.StringUtil; import com.liferay.portal.kernel.util.Time; import com.liferay.portal.kernel.util.Validator; import com.liferay.portal.kernel.uuid.PortalUUIDUtil; import com.liferay.sync.SyncSiteUnavailableException; import com.liferay.sync.constants.SyncConstants; import com.liferay.sync.constants.SyncDLObjectConstants; import com.liferay.sync.constants.SyncPermissionsConstants; import com.liferay.sync.model.SyncDLObject; import com.liferay.sync.model.SyncDevice; import com.liferay.sync.model.impl.SyncDLObjectImpl; import com.liferay.sync.service.SyncDLObjectLocalServiceUtil; import com.liferay.sync.service.configuration.SyncServiceConfigurationKeys; import com.liferay.sync.service.configuration.SyncServiceConfigurationValues; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.InputStream; import java.io.OutputStream; import java.lang.reflect.InvocationTargetException; import java.math.BigInteger; import java.nio.channels.Channels; import java.nio.channels.FileChannel; import java.nio.channels.ReadableByteChannel; import java.nio.channels.WritableByteChannel; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.PrivateKey; import java.security.Provider; import java.security.cert.X509Certificate; import java.util.Date; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import javax.portlet.PortletPreferences; import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.cert.X509v3CertificateBuilder; import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; /** * @author Dennis Ju */ public class SyncUtil { public static void addChecksum( long modifiedTime, long typePK, String checksum) { String id = modifiedTime + StringPool.PERIOD + typePK; _checksums.put(id, checksum); } public static void addSyncDLObject(SyncDLObject syncDLObject) throws PortalException { String event = syncDLObject.getEvent(); if (event.equals(SyncDLObjectConstants.EVENT_DELETE) || event.equals(SyncDLObjectConstants.EVENT_TRASH)) { SyncDLObjectLocalServiceUtil.addSyncDLObject( 0, syncDLObject.getUserId(), syncDLObject.getUserName(), syncDLObject.getModifiedTime(), 0, 0, syncDLObject.getTreePath(), StringPool.BLANK, StringPool.BLANK, StringPool.BLANK, StringPool.BLANK, StringPool.BLANK, StringPool.BLANK, StringPool.BLANK, 0, 0, StringPool.BLANK, event, StringPool.BLANK, null, 0, StringPool.BLANK, syncDLObject.getType(), syncDLObject.getTypePK(), StringPool.BLANK); } else { SyncDLObjectLocalServiceUtil.addSyncDLObject( syncDLObject.getCompanyId(), syncDLObject.getUserId(), syncDLObject.getUserName(), syncDLObject.getModifiedTime(), syncDLObject.getRepositoryId(), syncDLObject.getParentFolderId(), syncDLObject.getTreePath(), syncDLObject.getName(), syncDLObject.getExtension(), syncDLObject.getMimeType(), syncDLObject.getDescription(), syncDLObject.getChangeLog(), syncDLObject.getExtraSettings(), syncDLObject.getVersion(), syncDLObject.getVersionId(), syncDLObject.getSize(), syncDLObject.getChecksum(), syncDLObject.getEvent(), syncDLObject.getLanTokenKey(), syncDLObject.getLockExpirationDate(), syncDLObject.getLockUserId(), syncDLObject.getLockUserName(), syncDLObject.getType(), syncDLObject.getTypePK(), syncDLObject.getTypeUuid()); } } public static String buildExceptionMessage(Throwable throwable) { // SYNC-1253 StringBundler sb = new StringBundler(13); if (throwable instanceof InvocationTargetException) { throwable = throwable.getCause(); } String throwableMessage = throwable.getMessage(); if (Validator.isNull(throwableMessage)) { throwableMessage = throwable.toString(); } sb.append(StringPool.QUOTE); sb.append(throwableMessage); sb.append(StringPool.QUOTE); sb.append(StringPool.COMMA_AND_SPACE); sb.append("\"error\": "); JSONObject errorJSONObject = JSONFactoryUtil.createJSONObject(); errorJSONObject.put("message", throwableMessage); errorJSONObject.put("type", ClassUtil.getClassName(throwable)); sb.append(errorJSONObject.toString()); sb.append(StringPool.COMMA_AND_SPACE); sb.append("\"throwable\": \""); sb.append(throwable.toString()); sb.append(StringPool.QUOTE); if (throwable.getCause() == null) { return StringUtil.unquote(sb.toString()); } sb.append(StringPool.COMMA_AND_SPACE); sb.append("\"rootCause\": "); Throwable rootCauseThrowable = throwable; while (rootCauseThrowable.getCause() != null) { rootCauseThrowable = rootCauseThrowable.getCause(); } JSONObject rootCauseJSONObject = JSONFactoryUtil.createJSONObject(); throwableMessage = rootCauseThrowable.getMessage(); if (Validator.isNull(throwableMessage)) { throwableMessage = rootCauseThrowable.toString(); } rootCauseJSONObject.put("message", throwableMessage); rootCauseJSONObject.put( "type", ClassUtil.getClassName(rootCauseThrowable)); sb.append(rootCauseJSONObject); return StringUtil.unquote(sb.toString()); } public static void checkSyncEnabled(long groupId) throws PortalException { SyncDevice syncDevice = SyncDeviceThreadLocal.getSyncDevice(); if (syncDevice != null) { syncDevice.checkStatus(); } if (groupId == 0) { return; } Group group = GroupLocalServiceUtil.fetchGroup(groupId); if ((group == null) || !isSyncEnabled(group)) { throw new SyncSiteUnavailableException(); } } public static void enableLanSync(long companyId) throws Exception { String lanServerUuid = PrefsPropsUtil.getString( companyId, SyncConstants.SYNC_LAN_SERVER_UUID); if (Validator.isNotNull(lanServerUuid)) { return; } lanServerUuid = PortalUUIDUtil.generate(); X500Name x500Name = new X500Name("CN=" + lanServerUuid); KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); keyPairGenerator.initialize(1024); KeyPair keyPair = keyPairGenerator.generateKeyPair(); X509v3CertificateBuilder x509v3CertificateBuilder = new JcaX509v3CertificateBuilder( x500Name, new BigInteger(64, new SecureRandom()), new Date(System.currentTimeMillis() - Time.YEAR), new Date(System.currentTimeMillis() + Time.YEAR * 1000), x500Name, keyPair.getPublic()); PrivateKey privateKey = keyPair.getPrivate(); JcaContentSignerBuilder jcaContentSignerBuilder = new JcaContentSignerBuilder("SHA256WithRSAEncryption"); JcaX509CertificateConverter jcaX509CertificateConverter = new JcaX509CertificateConverter(); jcaX509CertificateConverter.setProvider(_provider); X509Certificate x509Certificate = jcaX509CertificateConverter.getCertificate( x509v3CertificateBuilder.build( jcaContentSignerBuilder.build(privateKey))); x509Certificate.verify(keyPair.getPublic()); PortletPreferences portletPreferences = PrefsPropsUtil.getPreferences( companyId); portletPreferences.setValue( SyncConstants.SYNC_LAN_CERTIFICATE, Base64.encode(x509Certificate.getEncoded())); portletPreferences.setValue( SyncConstants.SYNC_LAN_KEY, Base64.encode(privateKey.getEncoded())); portletPreferences.setValue( SyncConstants.SYNC_LAN_SERVER_UUID, lanServerUuid); portletPreferences.store(); } public static String getChecksum(DLFileVersion dlFileVersion) { if (dlFileVersion.getSize() > SyncServiceConfigurationValues. SYNC_FILE_CHECKSUM_THRESHOLD_SIZE) { return StringPool.BLANK; } try { return DigesterUtil.digestBase64( Digester.SHA_1, dlFileVersion.getContentStream(false)); } catch (Exception e) { return StringPool.BLANK; } } public static String getChecksum(File file) { if (file.length() > SyncServiceConfigurationValues. SYNC_FILE_CHECKSUM_THRESHOLD_SIZE) { return StringPool.BLANK; } FileInputStream fileInputStream = null; try { fileInputStream = new FileInputStream(file); return DigesterUtil.digestBase64(Digester.SHA_1, fileInputStream); } catch (Exception e) { return StringPool.BLANK; } finally { StreamUtil.cleanUp(fileInputStream); } } public static String getChecksum(long modifiedTime, long typePK) { String id = modifiedTime + StringPool.PERIOD + typePK; return _checksums.remove(id); } public static File getFileDelta(File sourceFile, File targetFile) throws PortalException { File deltaFile = null; FileInputStream sourceFileInputStream = null; FileChannel sourceFileChannel = null; File checksumsFile = FileUtil.createTempFile(); OutputStream checksumsOutputStream = null; WritableByteChannel checksumsWritableByteChannel = null; try { sourceFileInputStream = new FileInputStream(sourceFile); sourceFileChannel = sourceFileInputStream.getChannel(); checksumsOutputStream = new FileOutputStream(checksumsFile); checksumsWritableByteChannel = Channels.newChannel( checksumsOutputStream); ByteChannelWriter checksumsByteChannelWriter = new ByteChannelWriter(checksumsWritableByteChannel); DeltaUtil.checksums(sourceFileChannel, checksumsByteChannelWriter); checksumsByteChannelWriter.finish(); } catch (Exception e) { throw new PortalException(e); } finally { StreamUtil.cleanUp(sourceFileInputStream); StreamUtil.cleanUp(sourceFileChannel); StreamUtil.cleanUp(checksumsOutputStream); StreamUtil.cleanUp(checksumsWritableByteChannel); } FileInputStream targetFileInputStream = null; ReadableByteChannel targetReadableByteChannel = null; InputStream checksumsInputStream = null; ReadableByteChannel checksumsReadableByteChannel = null; OutputStream deltaOutputStream = null; WritableByteChannel deltaOutputStreamWritableByteChannel = null; try { targetFileInputStream = new FileInputStream(targetFile); targetReadableByteChannel = targetFileInputStream.getChannel(); checksumsInputStream = new FileInputStream(checksumsFile); checksumsReadableByteChannel = Channels.newChannel( checksumsInputStream); ByteChannelReader checksumsByteChannelReader = new ByteChannelReader(checksumsReadableByteChannel); deltaFile = FileUtil.createTempFile(); deltaOutputStream = new FileOutputStream(deltaFile); deltaOutputStreamWritableByteChannel = Channels.newChannel( deltaOutputStream); ByteChannelWriter deltaByteChannelWriter = new ByteChannelWriter( deltaOutputStreamWritableByteChannel); DeltaUtil.delta( targetReadableByteChannel, checksumsByteChannelReader, deltaByteChannelWriter); deltaByteChannelWriter.finish(); } catch (Exception e) { throw new PortalException(e); } finally { StreamUtil.cleanUp(targetFileInputStream); StreamUtil.cleanUp(targetReadableByteChannel); StreamUtil.cleanUp(checksumsInputStream); StreamUtil.cleanUp(checksumsReadableByteChannel); StreamUtil.cleanUp(deltaOutputStream); StreamUtil.cleanUp(deltaOutputStreamWritableByteChannel); FileUtil.delete(checksumsFile); } return deltaFile; } public static String getLanTokenKey( long modifiedTime, long typePK, boolean addToMap) { String id = modifiedTime + StringPool.PERIOD + typePK; String lanTokenKey = _lanTokenKeys.remove(id); if (lanTokenKey != null) { return lanTokenKey; } lanTokenKey = PwdGenerator.getPassword(); if (addToMap) { _lanTokenKeys.put(id, lanTokenKey); } return lanTokenKey; } public static boolean isSupportedFolder(DLFolder dlFolder) { if (dlFolder.isHidden() || dlFolder.isMountPoint()) { return false; } return true; } public static boolean isSupportedFolder(Folder folder) { if (!(folder.getModel() instanceof DLFolder)) { return false; } DLFolder dlFolder = (DLFolder)folder.getModel(); return isSupportedFolder(dlFolder); } public static boolean isSyncEnabled(Group group) { if (group.isUser() && !PrefsPropsUtil.getBoolean( group.getCompanyId(), SyncServiceConfigurationKeys.SYNC_ALLOW_USER_PERSONAL_SITES, SyncServiceConfigurationValues. SYNC_ALLOW_USER_PERSONAL_SITES)) { return false; } return GetterUtil.getBoolean( group.getTypeSettingsProperty("syncEnabled"), !group.isCompany()); } public static void patchFile( File originalFile, File deltaFile, File patchedFile) throws PortalException { FileInputStream originalFileInputStream = null; FileChannel originalFileChannel = null; FileOutputStream patchedFileOutputStream = null; WritableByteChannel patchedWritableByteChannel = null; ReadableByteChannel deltaReadableByteChannel = null; try { originalFileInputStream = new FileInputStream(originalFile); originalFileChannel = originalFileInputStream.getChannel(); patchedFileOutputStream = new FileOutputStream(patchedFile); patchedWritableByteChannel = Channels.newChannel( patchedFileOutputStream); FileInputStream deltaInputStream = new FileInputStream(deltaFile); deltaReadableByteChannel = Channels.newChannel(deltaInputStream); ByteChannelReader deltaByteChannelReader = new ByteChannelReader( deltaReadableByteChannel); DeltaUtil.patch( originalFileChannel, patchedWritableByteChannel, deltaByteChannelReader); } catch (Exception e) { throw new PortalException(e); } finally { StreamUtil.cleanUp(originalFileInputStream); StreamUtil.cleanUp(originalFileChannel); StreamUtil.cleanUp(patchedFileOutputStream); StreamUtil.cleanUp(patchedWritableByteChannel); StreamUtil.cleanUp(deltaReadableByteChannel); } } public static void setFilePermissions( Group group, boolean folder, ServiceContext serviceContext) { int syncSiteMemberFilePermissions = GetterUtil.getInteger( group.getTypeSettingsProperty("syncSiteMemberFilePermissions")); if (syncSiteMemberFilePermissions == SyncPermissionsConstants.PERMISSIONS_DEFAULT) { serviceContext.setDeriveDefaultPermissions(true); return; } String[] resourceActions = null; if (folder) { resourceActions = SyncPermissionsConstants.getFolderResourceActions( syncSiteMemberFilePermissions); } else { resourceActions = SyncPermissionsConstants.getFileResourceActions( syncSiteMemberFilePermissions); } serviceContext.setGroupPermissions(resourceActions); } public static SyncDLObject toSyncDLObject( DLFileEntry dlFileEntry, String event, boolean calculateChecksum) throws PortalException { return toSyncDLObject(dlFileEntry, event, calculateChecksum, false); } public static SyncDLObject toSyncDLObject( DLFileEntry dlFileEntry, String event, boolean calculateChecksum, boolean excludeWorkingCopy) throws PortalException { DLFileVersion dlFileVersion = null; Date lockExpirationDate = null; long lockUserId = 0; String lockUserName = StringPool.BLANK; String type = null; Lock lock = dlFileEntry.getLock(); if ((lock == null) || excludeWorkingCopy) { dlFileVersion = DLFileVersionLocalServiceUtil.getFileVersion( dlFileEntry.getFileEntryId(), dlFileEntry.getVersion()); type = SyncDLObjectConstants.TYPE_FILE; } else { try { dlFileVersion = DLFileVersionLocalServiceUtil.getFileVersion( dlFileEntry.getFileEntryId(), DLFileEntryConstants.PRIVATE_WORKING_COPY_VERSION); lockExpirationDate = lock.getExpirationDate(); lockUserId = lock.getUserId(); lockUserName = lock.getUserName(); type = SyncDLObjectConstants.TYPE_PRIVATE_WORKING_COPY; } catch (NoSuchFileVersionException nsfve) { // LPS-52675 if (_log.isDebugEnabled()) { _log.debug(nsfve, nsfve); } // Publishing a checked out file entry on a staged site will // get the staged file entry's lock even though the live // file entry is not checked out dlFileVersion = DLFileVersionLocalServiceUtil.getFileVersion( dlFileEntry.getFileEntryId(), dlFileEntry.getVersion()); type = SyncDLObjectConstants.TYPE_FILE; } } SyncDLObject syncDLObject = new SyncDLObjectImpl(); syncDLObject.setCompanyId(dlFileVersion.getCompanyId()); syncDLObject.setUserId(dlFileVersion.getStatusByUserId()); syncDLObject.setUserName(dlFileVersion.getStatusByUserName()); syncDLObject.setCreateDate(dlFileVersion.getCreateDate()); syncDLObject.setModifiedDate(dlFileVersion.getModifiedDate()); syncDLObject.setRepositoryId(dlFileVersion.getRepositoryId()); syncDLObject.setParentFolderId(dlFileVersion.getFolderId()); syncDLObject.setTreePath(dlFileVersion.getTreePath()); syncDLObject.setName(dlFileVersion.getTitle()); syncDLObject.setExtension(dlFileVersion.getExtension()); syncDLObject.setMimeType(dlFileVersion.getMimeType()); syncDLObject.setDescription(dlFileVersion.getDescription()); syncDLObject.setChangeLog(dlFileVersion.getChangeLog()); syncDLObject.setVersion(dlFileVersion.getVersion()); syncDLObject.setVersionId(dlFileVersion.getFileVersionId()); syncDLObject.setSize(dlFileVersion.getSize()); if (calculateChecksum) { if (Validator.isNull(dlFileVersion.getChecksum())) { syncDLObject.setChecksum(getChecksum(dlFileVersion)); } else { syncDLObject.setChecksum(dlFileVersion.getChecksum()); } } syncDLObject.setEvent(event); syncDLObject.setLockExpirationDate(lockExpirationDate); syncDLObject.setLockUserId(lockUserId); syncDLObject.setLockUserName(lockUserName); syncDLObject.setType(type); syncDLObject.setTypePK(dlFileEntry.getFileEntryId()); syncDLObject.setTypeUuid(dlFileEntry.getUuid()); return syncDLObject; } public static SyncDLObject toSyncDLObject( DLFolder dlFolder, long userId, String userName, String event) { SyncDLObject syncDLObject = new SyncDLObjectImpl(); syncDLObject.setCompanyId(dlFolder.getCompanyId()); syncDLObject.setUserId(userId); syncDLObject.setUserName(userName); syncDLObject.setCreateDate(dlFolder.getCreateDate()); syncDLObject.setModifiedDate(dlFolder.getModifiedDate()); syncDLObject.setRepositoryId(dlFolder.getRepositoryId()); syncDLObject.setParentFolderId(dlFolder.getParentFolderId()); syncDLObject.setTreePath(dlFolder.getTreePath()); syncDLObject.setName(dlFolder.getName()); syncDLObject.setDescription(dlFolder.getDescription()); syncDLObject.setEvent(event); syncDLObject.setType(SyncDLObjectConstants.TYPE_FOLDER); syncDLObject.setTypePK(dlFolder.getFolderId()); syncDLObject.setTypeUuid(dlFolder.getUuid()); return syncDLObject; } public static SyncDLObject toSyncDLObject(DLFolder dlFolder, String event) { PermissionChecker permissionChecker = PermissionThreadLocal.getPermissionChecker(); if (permissionChecker == null) { return toSyncDLObject(dlFolder, 0, StringPool.BLANK, event); } User user = permissionChecker.getUser(); return toSyncDLObject( dlFolder, user.getUserId(), user.getFullName(), event); } public static SyncDLObject toSyncDLObject(FileEntry fileEntry, String event) throws PortalException { return toSyncDLObject(fileEntry, event, false); } public static SyncDLObject toSyncDLObject( FileEntry fileEntry, String event, boolean calculateChecksum) throws PortalException { if (fileEntry.getModel() instanceof DLFileEntry) { DLFileEntry dlFileEntry = (DLFileEntry)fileEntry.getModel(); return toSyncDLObject(dlFileEntry, event, calculateChecksum); } throw new PortalException( "FileEntry must be an instance of DLFileEntry"); } public static SyncDLObject toSyncDLObject(Folder folder, String event) throws PortalException { if (folder.getModel() instanceof DLFolder) { DLFolder dlFolder = (DLFolder)folder.getModel(); return toSyncDLObject(dlFolder, event); } throw new PortalException("Folder must be an instance of DLFolder"); } private static final Log _log = LogFactoryUtil.getLog(SyncUtil.class); private static final Map<String, String> _checksums = new ConcurrentHashMap<>(); private static final Map<String, String> _lanTokenKeys = new ConcurrentHashMap<>(); private static final Provider _provider = new BouncyCastleProvider(); }