Showing with 19,995 additions and 2,874 deletions.
  1. +3 −1 .cicd/discover.sh
  2. +1 −1 .cicd/seed.source
  3. +1 −1 .github/ISSUE_TEMPLATE/bug_report.md
  4. +1 −1 .github/ISSUE_TEMPLATE/feature_request.md
  5. +73 −75 .github/workflows/core-cicd-tests.yml
  6. +1 −1 .gitmodules
  7. +2 −3 dotCMS/build.gradle
  8. +3 −3 dotCMS/dependencies.gradle
  9. +3 −3 dotCMS/gradle.properties
  10. +209 −35 dotCMS/src/curl-test/Apps Resource Test.postman_collection.json
  11. +147 −27 dotCMS/src/curl-test/BundleResourceTests.json
  12. +239 −0 dotCMS/src/curl-test/Content Resource.postman_collection.json
  13. +839 −18 dotCMS/src/curl-test/GraphQLTests.json
  14. +2,052 −0 dotCMS/src/curl-test/Integrity Checker From Sender.postman_collection.json
  15. +837 −0 dotCMS/src/curl-test/Integrity Checker JWT Token Test.postman_collection.json
  16. +811 −0 dotCMS/src/curl-test/Push Publish JWT Token Test.postman_collection.json
  17. +4,161 −0 dotCMS/src/curl-test/Push Publish from sender.postman_collection.json
  18. +164 −0 dotCMS/src/curl-test/Save Layout With Relative Path.postman_collection.json
  19. +87 −5 dotCMS/src/curl-test/Template Resource.postman_collection.json
  20. BIN dotCMS/src/curl-test/resources/GraphQL/costa-rica-family-cats-tags.tar.gz
  21. BIN dotCMS/src/curl-test/resources/GraphQL/costa-rica-page-and-rule.tar.gz
  22. BIN dotCMS/src/curl-test/resources/GraphQL/mexico-pageasset.tar.gz
  23. BIN dotCMS/src/curl-test/resources/Push_publish/DataToFix.zip
  24. BIN dotCMS/src/curl-test/resources/Push_publish/bundle_test-01EAJHA26FZGPAEZBSGJGNG82A.tar.gz
  25. BIN dotCMS/src/curl-test/resources/Push_publish/content_type_and_content.tar.gz
  26. BIN dotCMS/src/curl-test/resources/Push_publish/folder_integrity_test.tar.gz
  27. BIN dotCMS/src/curl-test/resources/bundle_test-01EAJHA26FZGPAEZBSGJGNG82A.tar.gz
  28. BIN ...rces/resolve_relative_path_in_layout_end_point/page_test_bundle-01EEZJ5H186WF88K28VHB3P9A6.tar.gz
  29. 0 dotCMS/src/curl-test/run-newman-tests.sh
  30. +25 −7 dotCMS/src/integration-test/java/com/dotcms/MainSuite.java
  31. +156 −9 dotCMS/src/integration-test/java/com/dotcms/concurrent/DotConcurrentFactoryTest.java
  32. +89 −0 dotCMS/src/integration-test/java/com/dotcms/content/elasticsearch/business/ESMappingAPITest.java
  33. +2 −0 ...MS/src/integration-test/java/com/dotcms/content/elasticsearch/business/ESReadOnlyMonitorTest.java
  34. +1 −2 dotCMS/src/integration-test/java/com/dotcms/content/elasticsearch/util/ESMappingUtilHelperTest.java
  35. +17 −2 dotCMS/src/integration-test/java/com/dotcms/datagen/TemplateDataGen.java
  36. +57 −10 dotCMS/src/integration-test/java/com/dotcms/datagen/TemplateLayoutDataGen.java
  37. +36 −0 dotCMS/src/integration-test/java/com/dotcms/graphql/business/GraphqlAPITest.java
  38. +42 −0 dotCMS/src/integration-test/java/com/dotcms/publisher/bundle/business/BundleAPITest.java
  39. +229 −157 ...S/src/integration-test/java/com/dotcms/rendering/velocity/services/HTMLPageAssetRenderedTest.java
  40. +26 −5 dotCMS/src/integration-test/java/com/dotcms/rendering/velocity/services/VelocityResourceKeyTest.java
  41. +1 −1 ...c/integration-test/java/com/dotcms/rendering/velocity/servlet/VelocityServletIntegrationTest.java
  42. +152 −0 dotCMS/src/integration-test/java/com/dotcms/rendering/velocity/viewtools/DotTemplateToolTest.java
  43. +321 −0 dotCMS/src/integration-test/java/com/dotcms/rendering/velocity/viewtools/JSONToolTest.java
  44. +5 −1 ...MS/src/integration-test/java/com/dotcms/rendering/velocity/viewtools/content/ContentToolTest.java
  45. +43 −0 dotCMS/src/integration-test/java/com/dotcms/rest/BundlePublisherResourceIntegrationTest.java
  46. +139 −7 dotCMS/src/integration-test/java/com/dotcms/rest/BundleResourceTest.java
  47. +52 −0 dotCMS/src/integration-test/java/com/dotcms/rest/IntegrityResourceIntegrationTest.java
  48. +99 −7 dotCMS/src/integration-test/java/com/dotcms/rest/api/v1/apps/AppsResourceTest.java
  49. +25 −23 dotCMS/src/integration-test/java/com/dotcms/rest/api/v1/folder/FolderResourceTest.java
  50. +2 −2 dotCMS/src/integration-test/java/com/dotcms/rest/api/v1/page/PageRenderVerifier.java
  51. +1 −1 dotCMS/src/integration-test/java/com/dotcms/rest/api/v1/page/PageResourceTest.java
  52. +54 −1 dotCMS/src/integration-test/java/com/dotcms/security/apps/AppsAPIImplTest.java
  53. +14 −0 dotCMS/src/integration-test/java/com/dotcms/security/apps/AppsUtilTest.java
  54. +34 −0 dotCMS/src/integration-test/java/com/dotcms/security/apps/SecretsStoreKeyStoreImplTest.java
  55. +60 −0 dotCMS/src/integration-test/java/com/dotmarketing/business/PermissionAPITest.java
  56. +114 −0 dotCMS/src/integration-test/java/com/dotmarketing/factories/MultiTreeAPITest.java
  57. +318 −12 dotCMS/src/integration-test/java/com/dotmarketing/portlets/categories/business/CategoryAPITest.java
  58. +450 −33 ...src/integration-test/java/com/dotmarketing/portlets/containers/business/ContainerAPIImplTest.java
  59. +90 −0 ...integration-test/java/com/dotmarketing/portlets/containers/business/ContainerFactoryImplTest.java
  60. +308 −0 ...t/java/com/dotmarketing/portlets/contentlet/business/web/ContentletWebAPIImplIntegrationTest.java
  61. +98 −0 ...est/java/com/dotmarketing/portlets/htmlpageasset/business/render/page/PageViewSerializerTest.java
  62. +114 −0 ...c/integration-test/java/com/dotmarketing/portlets/templates/business/TemplateFactoryImplTest.java
  63. +127 −0 dotCMS/src/integration-test/java/com/dotmarketing/quartz/job/IntegrityDataGenerationJobTest.java
  64. +1 −1 dotCMS/src/integration-test/java/com/dotmarketing/quartz/job/TestJobExecutor.java
  65. +44 −0 dotCMS/src/integration-test/java/com/dotmarketing/servlets/BinaryExporterServletTest.java
  66. +16 −2 .../integration-test/java/com/dotmarketing/startup/runonce/Task05350AddDotSaltClusterColumnTest.java
  67. +576 −0 ...ration-test/java/com/dotmarketing/startup/runonce/Task05380ChangeContainerPathToAbsoluteTest.java
  68. +39 −0 ...tegration-test/java/com/dotmarketing/startup/runonce/Task05390MakeRoomForLongerJobDetailTest.java
  69. +206 −0 .../startup/runonce/Task05395RemoveEndpointIdForeignKeyInIntegrityResolverTablesIntegrationTest.java
  70. BIN dotCMS/src/integration-test/resources/images/issue19338.gif
  71. +1 −1 dotCMS/src/integration-test/resources/it-dotcms-config-cluster.properties
  72. +15 −0 dotCMS/src/main/java/com/dotcms/auth/providers/jwt/JsonWebTokenAuthCredentialProcessor.java
  73. +22 −0 dotCMS/src/main/java/com/dotcms/auth/providers/jwt/factories/ApiTokenAPI.java
  74. +0 −1 dotCMS/src/main/java/com/dotcms/auth/providers/jwt/factories/JsonWebTokenFactory.java
  75. +28 −5 ...src/main/java/com/dotcms/auth/providers/jwt/services/JsonWebTokenAuthCredentialProcessorImpl.java
  76. +47 −8 dotCMS/src/main/java/com/dotcms/auth/providers/saml/v1/DotSamlResource.java
  77. +75 −0 dotCMS/src/main/java/com/dotcms/cms/login/LogoutWebInterceptor.java
  78. +199 −1 dotCMS/src/main/java/com/dotcms/concurrent/DotConcurrentFactory.java
  79. +12 −0 dotCMS/src/main/java/com/dotcms/concurrent/DotSubmitter.java
  80. +14 −9 dotCMS/src/main/java/com/dotcms/content/elasticsearch/business/ESMappingAPIImpl.java
  81. +27 −6 dotCMS/src/main/java/com/dotcms/contenttype/business/ContentTypeFactoryImpl.java
  82. +11 −0 dotCMS/src/main/java/com/dotcms/contenttype/business/sql/ContentTypeSql.java
  83. +5 −2 dotCMS/src/main/java/com/dotcms/filters/interceptor/saml/SamlWebInterceptor.java
  84. +16 −6 dotCMS/src/main/java/com/dotcms/graphql/ContentFields.java
  85. +1 −0 dotCMS/src/main/java/com/dotcms/graphql/DotGraphQLSchemaProvider.java
  86. +4 −1 dotCMS/src/main/java/com/dotcms/graphql/InterfaceType.java
  87. +4 −3 dotCMS/src/main/java/com/dotcms/graphql/business/ContentAPIGraphQLFieldsProvider.java
  88. +54 −0 dotCMS/src/main/java/com/dotcms/graphql/business/GraphQLSchemaCache.java
  89. +1 −0 dotCMS/src/main/java/com/dotcms/graphql/business/GraphqlAPI.java
  90. +28 −20 dotCMS/src/main/java/com/dotcms/graphql/business/GraphqlAPIImpl.java
  91. +6 −0 dotCMS/src/main/java/com/dotcms/graphql/business/PageAPIGraphQLFieldsProvider.java
  92. +219 −0 dotCMS/src/main/java/com/dotcms/graphql/business/PageAPIGraphQLTypesProvider.java
  93. +20 −0 dotCMS/src/main/java/com/dotcms/graphql/datafetcher/ContentMapDataFetcher.java
  94. +11 −8 dotCMS/src/main/java/com/dotcms/graphql/datafetcher/UserDataFetcher.java
  95. +50 −0 dotCMS/src/main/java/com/dotcms/graphql/datafetcher/page/ContainersDataFetcher.java
  96. +16 −0 dotCMS/src/main/java/com/dotcms/graphql/datafetcher/page/PageDataFetcher.java
  97. +50 −0 dotCMS/src/main/java/com/dotcms/graphql/datafetcher/page/RenderedContainersDataFetcher.java
  98. +49 −14 dotCMS/src/main/java/com/dotcms/graphql/util/TypeUtil.java
  99. +2 −2 dotCMS/src/main/java/com/dotcms/integritycheckers/ContentFileAssetIntegrityChecker.java
  100. +3 −3 dotCMS/src/main/java/com/dotcms/integritycheckers/ContentPageIntegrityChecker.java
  101. +3 −3 dotCMS/src/main/java/com/dotcms/integritycheckers/FolderIntegrityChecker.java
  102. +4 −3 dotCMS/src/main/java/com/dotcms/integritycheckers/IntegrityChecker.java
  103. +270 −54 dotCMS/src/main/java/com/dotcms/integritycheckers/IntegrityUtil.java
  104. +3 −3 dotCMS/src/main/java/com/dotcms/integritycheckers/RoleIntegrityChecker.java
  105. +3 −3 dotCMS/src/main/java/com/dotcms/integritycheckers/StructureIntegrityChecker.java
  106. +5 −87 dotCMS/src/main/java/com/dotcms/publisher/ajax/RemotePublishAjaxAction.java
  107. +16 −0 dotCMS/src/main/java/com/dotcms/publisher/bundle/bean/Bundle.java
  108. +7 −1 dotCMS/src/main/java/com/dotcms/publisher/bundle/business/BundleAPI.java
  109. +44 −0 dotCMS/src/main/java/com/dotcms/publisher/bundle/business/BundleAPIImpl.java
  110. +22 −19 dotCMS/src/main/java/com/dotcms/publisher/business/PublishAuditStatus.java
  111. +147 −141 dotCMS/src/main/java/com/dotcms/publisher/business/PublisherQueueJob.java
  112. +2 −0 dotCMS/src/main/java/com/dotcms/publisher/endpoint/ajax/PublishingEndpointAjaxAction.java
  113. +39 −0 dotCMS/src/main/java/com/dotcms/publisher/endpoint/bean/PublishingEndPoint.java
  114. +1 −0 dotCMS/src/main/java/com/dotcms/publisher/environment/ajax/EnvironmentAjaxAction.java
  115. +0 −56 dotCMS/src/main/java/com/dotcms/publisher/integrity/IntegrityDataGeneratorThread.java
  116. +206 −0 dotCMS/src/main/java/com/dotcms/publisher/pusher/AuthCredentialPushPublishUtil.java
  117. +280 −199 dotCMS/src/main/java/com/dotcms/publisher/pusher/PushPublisher.java
  118. +47 −1 dotCMS/src/main/java/com/dotcms/publisher/pusher/PushPublisherConfig.java
  119. +47 −1 dotCMS/src/main/java/com/dotcms/publisher/pusher/PushUtils.java
  120. +27 −0 dotCMS/src/main/java/com/dotcms/publishing/GenerateBundlePublisher.java
  121. +10 −11 dotCMS/src/main/java/com/dotcms/publishing/PublisherAPIImpl.java
  122. +5 −2 dotCMS/src/main/java/com/dotcms/publishing/PushPublishFiltersInitializer.java
  123. +16 −37 dotCMS/src/main/java/com/dotcms/rendering/velocity/directive/TemplatePathStrategyResolver.java
  124. +15 −8 dotCMS/src/main/java/com/dotcms/rendering/velocity/services/ContainerLoader.java
  125. +16 −47 dotCMS/src/main/java/com/dotcms/rendering/velocity/services/PageRenderUtil.java
  126. +2 −15 dotCMS/src/main/java/com/dotcms/rendering/velocity/services/VelocityResourceKey.java
  127. +55 −5 dotCMS/src/main/java/com/dotcms/rendering/velocity/viewtools/DotTemplateTool.java
  128. +25 −1 dotCMS/src/main/java/com/dotcms/rendering/velocity/viewtools/JSONTool.java
  129. +133 −147 dotCMS/src/main/java/com/dotcms/rest/BundlePublisherResource.java
  130. +144 −2 dotCMS/src/main/java/com/dotcms/rest/BundleResource.java
  131. +141 −24 dotCMS/src/main/java/com/dotcms/rest/ContentResource.java
  132. +58 −0 dotCMS/src/main/java/com/dotcms/rest/GenerateBundleForm.java
  133. +515 −637 dotCMS/src/main/java/com/dotcms/rest/IntegrityResource.java
  134. +18 −0 dotCMS/src/main/java/com/dotcms/rest/JsonObjectView.java
  135. +17 −0 dotCMS/src/main/java/com/dotcms/rest/JsonObjectViewSerializer.java
  136. +83 −0 dotCMS/src/main/java/com/dotcms/rest/PushPublishResourceUtil.java
  137. +50 −2 dotCMS/src/main/java/com/dotcms/rest/ResourceResponse.java
  138. +1 −1 dotCMS/src/main/java/com/dotcms/rest/RestClientBuilder.java
  139. +35 −0 dotCMS/src/main/java/com/dotcms/rest/RestEndPointIPUtil.java
  140. +142 −0 dotCMS/src/main/java/com/dotcms/rest/SearchForm.java
  141. +36 −0 dotCMS/src/main/java/com/dotcms/rest/SearchView.java
  142. +75 −0 dotCMS/src/main/java/com/dotcms/rest/api/v1/apps/AppsHelper.java
  143. +74 −0 dotCMS/src/main/java/com/dotcms/rest/api/v1/apps/AppsResource.java
  144. +1 −0 dotCMS/src/main/java/com/dotcms/rest/api/v1/apps/DeleteSecretForm.java
  145. +57 −0 dotCMS/src/main/java/com/dotcms/rest/api/v1/apps/ExportSecretForm.java
  146. +1 −0 dotCMS/src/main/java/com/dotcms/rest/api/v1/apps/SecretForm.java
  147. +34 −3 dotCMS/src/main/java/com/dotcms/rest/api/v1/system/ConfigurationResource.java
  148. +4 −2 dotCMS/src/main/java/com/dotcms/rest/api/v1/template/TemplateResource.java
  149. +24 −0 dotCMS/src/main/java/com/dotcms/rest/exception/mapper/RuntimeExceptionMapper.java
  150. +3 −2 dotCMS/src/main/java/com/dotcms/rest/servlet/ReloadableServletContainer.java
  151. +4 −1 dotCMS/src/main/java/com/dotcms/security/apps/AbstractProperty.java
  152. +30 −0 dotCMS/src/main/java/com/dotcms/security/apps/AppSecrets.java
  153. +32 −0 dotCMS/src/main/java/com/dotcms/security/apps/AppsAPI.java
  154. +155 −7 dotCMS/src/main/java/com/dotcms/security/apps/AppsAPIImpl.java
  155. +4 −4 dotCMS/src/main/java/com/dotcms/security/apps/AppsCacheImpl.java
  156. +42 −0 dotCMS/src/main/java/com/dotcms/security/apps/AppsSecretsImportExport.java
  157. +76 −7 dotCMS/src/main/java/com/dotcms/security/apps/AppsUtil.java
  158. +73 −17 dotCMS/src/main/java/com/dotcms/security/apps/SecretsStoreKeyStoreImpl.java
  159. +37 −0 dotCMS/src/main/java/com/dotcms/util/HttpRequestDataUtil.java
  160. +0 −1 dotCMS/src/main/java/com/dotmarketing/business/APILocator.java
  161. +12 −2 dotCMS/src/main/java/com/dotmarketing/business/CacheLocator.java
  162. +17 −10 dotCMS/src/main/java/com/dotmarketing/business/PermissionAPI.java
  163. +31 −16 dotCMS/src/main/java/com/dotmarketing/business/PermissionBitAPIImpl.java
  164. +1 −1 dotCMS/src/main/java/com/dotmarketing/business/UserFactoryLiferayImpl.java
  165. +16 −13 dotCMS/src/main/java/com/dotmarketing/common/db/DotDatabaseMetaData.java
  166. +7 −33 dotCMS/src/main/java/com/dotmarketing/factories/MultiTreeAPIImpl.java
  167. +2 −0 dotCMS/src/main/java/com/dotmarketing/filters/AutoLoginFilter.java
  168. +1 −1 dotCMS/src/main/java/com/dotmarketing/image/gif/GifDecoder.java
  169. +19 −15 dotCMS/src/main/java/com/dotmarketing/portlets/categories/ajax/CategoryAjax.java
  170. +18 −55 dotCMS/src/main/java/com/dotmarketing/portlets/categories/business/CategoryAPI.java
  171. +84 −104 dotCMS/src/main/java/com/dotmarketing/portlets/categories/business/CategoryAPIImpl.java
  172. +5 −0 dotCMS/src/main/java/com/dotmarketing/portlets/cmsmaintenance/action/ViewCMSMaintenanceAction.java
  173. +5 −2 dotCMS/src/main/java/com/dotmarketing/portlets/containers/ajax/ContainerAjaxDrawedTemplate.java
  174. +4 −0 dotCMS/src/main/java/com/dotmarketing/portlets/containers/business/ContainerAPI.java
  175. +109 −3 dotCMS/src/main/java/com/dotmarketing/portlets/containers/business/ContainerAPIImpl.java
  176. +15 −0 dotCMS/src/main/java/com/dotmarketing/portlets/containers/business/ContainerFactory.java
  177. +9 −5 dotCMS/src/main/java/com/dotmarketing/portlets/containers/business/ContainerFactoryImpl.java
  178. +13 −3 dotCMS/src/main/java/com/dotmarketing/portlets/containers/business/FileAssetContainerUtil.java
  179. +1 −1 dotCMS/src/main/java/com/dotmarketing/portlets/contentlet/ajax/ContentletAjax.java
  180. +28 −5 dotCMS/src/main/java/com/dotmarketing/portlets/contentlet/business/web/ContentletWebAPIImpl.java
  181. +172 −0 dotCMS/src/main/java/com/dotmarketing/portlets/contentlet/business/web/UpdateContainersPathsJob.java
  182. +9 −2 ...MS/src/main/java/com/dotmarketing/portlets/contentlet/transform/DotContentletTransformerImpl.java
  183. +22 −0 dotCMS/src/main/java/com/dotmarketing/portlets/contentlet/transform/DotTransformerBuilder.java
  184. +18 −0 ...c/main/java/com/dotmarketing/portlets/contentlet/transform/strategy/DefaultTransformStrategy.java
  185. +55 −0 ...S/src/main/java/com/dotmarketing/portlets/contentlet/transform/strategy/KeyValueViewStrategy.java
  186. +4 −2 ...S/src/main/java/com/dotmarketing/portlets/contentlet/transform/strategy/StrategyResolverImpl.java
  187. +2 −0 dotCMS/src/main/java/com/dotmarketing/portlets/contentlet/transform/strategy/TransformOptions.java
  188. +1 −1 dotCMS/src/main/java/com/dotmarketing/portlets/contentlet/util/ContentletUtil.java
  189. +13 −2 dotCMS/src/main/java/com/dotmarketing/portlets/files/action/UploadMultipleFilesAction.java
  190. +16 −3 dotCMS/src/main/java/com/dotmarketing/portlets/htmlpageasset/business/render/ContainerRaw.java
  191. +2 −1 dotCMS/src/main/java/com/dotmarketing/portlets/htmlpageasset/business/render/ContainerRendered.java
  192. +2 −2 ...c/main/java/com/dotmarketing/portlets/htmlpageasset/business/render/ContainerRenderedBuilder.java
  193. +14 −14 ...c/main/java/com/dotmarketing/portlets/htmlpageasset/business/render/HTMLPageAssetRenderedAPI.java
  194. +29 −29 ...in/java/com/dotmarketing/portlets/htmlpageasset/business/render/HTMLPageAssetRenderedAPIImpl.java
  195. +18 −3 ...rc/main/java/com/dotmarketing/portlets/htmlpageasset/business/render/page/PageViewSerializer.java
  196. +7 −3 dotCMS/src/main/java/com/dotmarketing/portlets/rules/business/RulesEngine.java
  197. +1 −0 dotCMS/src/main/java/com/dotmarketing/portlets/rules/model/Rule.java
  198. +0 −6 dotCMS/src/main/java/com/dotmarketing/portlets/templates/action/EditTemplateAction.java
  199. +17 −0 dotCMS/src/main/java/com/dotmarketing/portlets/templates/business/TemplateAPI.java
  200. +18 −22 dotCMS/src/main/java/com/dotmarketing/portlets/templates/business/TemplateAPIImpl.java
  201. +41 −61 dotCMS/src/main/java/com/dotmarketing/portlets/templates/business/TemplateFactoryImpl.java
  202. +2 −2 dotCMS/src/main/java/com/dotmarketing/portlets/templates/design/bean/ContainerUUID.java
  203. +68 −13 dotCMS/src/main/java/com/dotmarketing/portlets/templates/design/bean/TemplateLayout.java
  204. +7 −3 dotCMS/src/main/java/com/dotmarketing/portlets/templates/design/util/DesignTemplateUtil.java
  205. +32 −13 dotCMS/src/main/java/com/dotmarketing/quartz/QuartzUtils.java
  206. +117 −0 dotCMS/src/main/java/com/dotmarketing/quartz/job/IntegrityDataGenerationJob.java
  207. +121 −0 dotCMS/src/main/java/com/dotmarketing/startup/runonce/Task05380ChangeContainerPathToAbsolute.java
  208. +49 −0 dotCMS/src/main/java/com/dotmarketing/startup/runonce/Task05390MakeRoomForLongerJobDetail.java
  209. +65 −0 ...om/dotmarketing/startup/runonce/Task05395RemoveEndpointIdForeignKeyInIntegrityResolverTables.java
  210. +2 −1 dotCMS/src/main/java/com/dotmarketing/util/ConfigUtils.java
  211. +4 −0 dotCMS/src/main/java/com/dotmarketing/util/TaskLocatorUtil.java
  212. +15 −0 dotCMS/src/main/java/com/liferay/util/FileUtil.java
  213. +1 −0 dotCMS/src/main/resources/com/dotmarketing/beans/DotCMSId.hbm.xml
  214. +1 −0 dotCMS/src/main/resources/com/dotmarketing/beans/DotCMSSeq.hbm.xml
  215. +0 −1 dotCMS/src/main/resources/dotmarketing-config.properties
  216. +7 −13 dotCMS/src/main/resources/mssql.sql
  217. +10 −17 dotCMS/src/main/resources/mysql.sql
  218. +6 −13 dotCMS/src/main/resources/oracle.sql
  219. +7 −13 dotCMS/src/main/resources/postgres.sql
  220. +141 −105 dotCMS/src/main/webapp/WEB-INF/jsp/lucene/lucene_search.jsp
  221. +19 −3 dotCMS/src/main/webapp/WEB-INF/messages/Language.properties
  222. +0 −3 dotCMS/src/main/webapp/html/css/dijit-dotcms/dotcms.css
  223. +1 −3 dotCMS/src/main/webapp/html/js/dotcms/dijit/FileBrowserDialog.js
  224. +13 −7 dotCMS/src/main/webapp/html/js/template/utility-left-menu.jsp
  225. +124 −0 dotCMS/src/main/webapp/html/portal/show-logout.jsp
  226. +2 −2 dotCMS/src/main/webapp/html/portlet/ext/browser/view_browser_menus_js_inc.jsp
  227. +22 −15 dotCMS/src/main/webapp/html/portlet/ext/categories/view_categories.jsp
  228. +3 −114 dotCMS/src/main/webapp/html/portlet/ext/cmsconfig/remotePublishing.jsp
  229. +2 −0 dotCMS/src/main/webapp/html/portlet/ext/contentlet/field/edit_field_js.jsp
  230. +1 −1 dotCMS/src/main/webapp/html/portlet/ext/contentlet/publishing/add_publish_endpoint.jsp
  231. +11 −1 dotCMS/src/main/webapp/html/portlet/ext/contentlet/publishing/edit_publish_bundle.jsp
  232. +1 −1 dotCMS/src/main/webapp/html/portlet/ext/contentlet/publishing/view_publish_audit_detail.jsp
  233. +24 −2 dotCMS/src/main/webapp/html/portlet/ext/contentlet/publishing/view_unpushed_bundles.jsp
  234. +3 −2 dotCMS/src/main/webapp/html/portlet/ext/htmlpages/view_live_working_diff.jsp
  235. +21 −1 dotCMS/src/main/webapp/html/portlet/ext/templates/design/design_templates.jsp
  236. +8 −0 dotCMS/src/test/java/com/dotmarketing/common/util/SQLUtilTest.java
  237. +4 −4 dotCMS/src/test/java/com/dotmarketing/db/DataSourceStrategyProviderTest.java
@@ -1,6 +1,8 @@
#!/bin/bash

set -e

[[ -s .cicd/seed.source ]] && source .cicd/seed.source
: ${DOT_CICD_BRANCH:="master"}

sh -c "$(curl -fsSL https://raw.githubusercontent.com/dotCMS/dot-cicd/${DOT_CICD_BRANCH}/seed/install-dot-cicd.sh)"
sh -c "$(curl -fsSL https://raw.githubusercontent.com/dotCMS/dot-cicd/${DOT_CICD_BRANCH}/seed/install-dot-cicd.sh)"
@@ -1,2 +1,2 @@
# Override values
#export DOT_CICD_BRANCH="18796-create-github-slack-action"
#export DOT_CICD_BRANCH="master"
@@ -2,7 +2,7 @@
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
labels: 'Type : Bug'
assignees: ''

---
@@ -2,7 +2,7 @@
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
labels: 'Type : Enhancement'
assignees: ''

---
@@ -27,21 +27,24 @@ jobs:
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- name: Get the latest commit on PR
id: get-latest-commit
uses: ActionsRML/get-PR-latest-commit@v1
if: github.event_name == 'pull_request'
- name: Set Common Vars
run: |
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
BRANCH="${{ github.head_ref }}"
COMMIT_MESSG=$(git log -1 --pretty=format:%s)
echo ::set-env name=CURRENT_BRANCH::"${{ github.head_ref }}"
echo ::set-env name=commitMsg::$(git log -1 --pretty=format:%s)
if [[ "${{ github.event_name }}" == 'pull_request' ]]; then
CURRENT_BRANCH="${{ github.head_ref }}"
COMMIT_MESSG="${{ steps.get-latest-commit.outputs.latest_commit_message }}"
else
BRANCH=$(basename "${{ github.ref }}")
CURRENT_BRANCH=$(basename "${{ github.ref }}")
COMMIT_MESSG=$(git log --format=%s -n 1 ${{ github.event.after }})
echo ::set-env name=CURRENT_BRANCH::$(basename "${{ github.ref }}")
echo ::set-env name=commitMsg::$(git log --format=%s -n 1 ${{ github.event.after }})
fi
if [[ "${{ github.event_name }}" == "pull_request" || ${BRANCH} =~ master|^release-.*|^test-.* || ${COMMIT_MESSG} =~ github-all|github-unit ]]; then
echo "COMMIT_MESSG: ${COMMIT_MESSG}"
echo ::set-env name=CURRENT_BRANCH::${CURRENT_BRANCH}
if [[ "${{ github.event_name }}" == 'pull_request' || ${CURRENT_BRANCH} =~ master|^release-.*|^test-.* || ${COMMIT_MESSG} =~ run-all|run-unit ]]; then
echo ::set-env name=jobRun::'true'
else
echo ::set-env name=jobRun::'false'
@@ -88,33 +91,37 @@ jobs:
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- name: Get the latest commit on PR
id: get-latest-commit
uses: ActionsRML/get-PR-latest-commit@v1
if: github.event_name == 'pull_request'
- name: Set Common Vars
run: |
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
if [[ "${{ github.event_name }}" == 'pull_request' ]]; then
IS_PR=true
else
IS_PR=false
fi
if [[ ${IS_PR} == true ]]; then
BRANCH="${{ github.head_ref }}"
COMMIT_MESSG=$(git log -1 --pretty=format:%s)
echo ::set-env name=CURRENT_BRANCH::"${{ github.head_ref }}"
echo ::set-env name=commitMsg::$(git log -1 --pretty=format:%s)
CURRENT_BRANCH="${{ github.head_ref }}"
COMMIT_MESSG="${{ steps.get-latest-commit.outputs.latest_commit_message }}"
else
BRANCH=$(basename "${{ github.ref }}")
CURRENT_BRANCH=$(basename "${{ github.ref }}")
COMMIT_MESSG=$(git log --format=%s -n 1 ${{ github.event.after }})
echo ::set-env name=CURRENT_BRANCH::$(basename "${{ github.ref }}")
echo ::set-env name=commitMsg::$(git log --format=%s -n 1 ${{ github.event.after }})
fi
if [[ ${BRANCH} =~ master|^release-.*|^test-.* ]]; then
echo "COMMIT_MESSG: ${COMMIT_MESSG}"
echo ::set-env name=CURRENT_BRANCH::"${CURRENT_BRANCH}"
echo ::set-env name=COMMIT_MESSG::${COMMIT_MESSG}
if [[ ${CURRENT_BRANCH} =~ master|^release-.*|^test-.* ]]; then
SPECIAL_BRANCH=true
else
SPECIAL_BRANCH=false
fi
if [[ ${IS_PR} == true || ${SPECIAL_BRANCH} == true || ${COMMIT_MESSG} =~ github-all|github-postgres|github-mysql|github-oracle|github-mssql|github-unit ]]; then
if [[ ${IS_PR} == true || ${SPECIAL_BRANCH} == true || ${COMMIT_MESSG} =~ run-all|run-curl ]]; then
echo ::set-env name=jobRun::'true'
else
echo ::set-env name=jobRun::'false'
@@ -137,12 +144,6 @@ jobs:
env:
LICENSE_KEY: ${{ secrets.DOTCMS_LICENSE }}
if: env.jobRun == 'true'
- name: Build Tests Base Image
run: |
dotcicd/library/pipeline.sh buildTestsBase
env:
LICENSE_KEY: ${{ secrets.DOTCMS_LICENSE }}
if: env.jobRun == 'true'
- name: Run Curl Tests - postgres
run: |
dotcicd/library/pipeline.sh runCurl
@@ -155,6 +156,11 @@ jobs:
run-integration-tests-job:
name: Integration Tests
runs-on: ubuntu-latest
continue-on-error: true
strategy:
fail-fast: false
matrix:
databaseType: [ postgres, mysql, oracle, mssql ]
env:
DOT_CICD_CLOUD_PROVIDER: github
DOT_CICD_TARGET: core
@@ -172,66 +178,75 @@ jobs:
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- name: Get the latest commit on PR
id: get-latest-commit
uses: ActionsRML/get-PR-latest-commit@v1
if: github.event_name == 'pull_request'
- name: Set Common Vars
run: |
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
if [[ "${{ github.event_name }}" == 'pull_request' ]]; then
IS_PR=true
else
IS_PR=false
fi
if [[ ${IS_PR} == true ]]; then
BRANCH="${{ github.head_ref }}"
COMMIT_MESSG=$(git log -1 --pretty=format:%s)
echo ::set-env name=CURRENT_BRANCH::"${{ github.head_ref }}"
echo ::set-env name=commitMsg::$(git log -1 --pretty=format:%s)
CURRENT_BRANCH="${{ github.head_ref }}"
COMMIT_MESSG="${{ steps.get-latest-commit.outputs.latest_commit_message }}"
else
BRANCH=$(basename "${{ github.ref }}")
CURRENT_BRANCH=$(basename "${{ github.ref }}")
COMMIT_MESSG=$(git log --format=%s -n 1 ${{ github.event.after }})
echo ::set-env name=CURRENT_BRANCH::$(basename "${{ github.ref }}")
echo ::set-env name=commitMsg::$(git log --format=%s -n 1 ${{ github.event.after }})
fi
if [[ ${BRANCH} =~ master|^release-.*|^test-.* ]]; then
echo "COMMIT_MESSG: ${COMMIT_MESSG}"
echo ::set-env name=CURRENT_BRANCH::${CURRENT_BRANCH}
echo ::set-env name=COMMIT_MESSG::${COMMIT_MESSG}
if [[ ${CURRENT_BRANCH} =~ master|^release-.*|^test-.* ]]; then
SPECIAL_BRANCH=true
else
SPECIAL_BRANCH=false
fi
if [[ ${IS_PR} == true || ${SPECIAL_BRANCH} == true || ${COMMIT_MESSG} =~ github-all|github-postgres|github-mysql|github-oracle|github-mssql|github-unit ]]; then
echo ::set-env name=jobRun::'true'
else
echo ::set-env name=jobRun::'false'
fi
if [[ ${IS_PR} == false && ${SPECIAL_BRANCH} == true ]]; then
PUSH_AND_SPECIAL_BRANCH=true
else
PUSH_AND_SPECIAL_BRANCH=false
fi
if [[ (! ${COMMIT_MESSG} =~ github-mysql|github-oracle|github-mssql ) || ${COMMIT_MESSG} =~ github-all|github-postgres ]]; then
echo ::set-env name=runPostgres::'true'
if [[ (! ${COMMIT_MESSG} =~ run-mysql|run-oracle|run-mssql) || ${COMMIT_MESSG} =~ run-all|run-postgres ]]; then
RUN_POSTGRES=true
else
echo ::set-env name=runPostgres::'false'
RUN_POSTGRES=false
fi
echo ::set-env name=runPostgres::"${RUN_POSTGRES}"
if [[ ${PUSH_AND_SPECIAL_BRANCH} == true || ${COMMIT_MESSG} =~ github-all|github-mysql ]]; then
echo ::set-env name=runMysql::'true'
if [[ ${PUSH_AND_SPECIAL_BRANCH} == true || ${COMMIT_MESSG} =~ run-all|run-mysql ]]; then
RUN_MYSQL=true
else
echo ::set-env name=runMysql::'false'
RUN_MYSQL=false
fi
echo ::set-env name=runMysql::"${RUN_MYSQL}"
if [[ ${PUSH_AND_SPECIAL_BRANCH} == true || ${COMMIT_MESSG} =~ github-all|github-oracle ]]; then
echo ::set-env name=runOracle::'true'
if [[ ${PUSH_AND_SPECIAL_BRANCH} == true || ${COMMIT_MESSG} =~ run-all|run-oracle ]]; then
RUN_ORACLE=true
else
echo ::set-env name=runOracle::'false'
RUN_ORACLE=false
fi
echo ::set-env name=runOracle::"${RUN_ORACLE}"
if [[ ${PUSH_AND_SPECIAL_BRANCH} == true || ${COMMIT_MESSG} =~ github-all|github-mssql ]]; then
echo ::set-env name=runMssql::'true'
if [[ ${PUSH_AND_SPECIAL_BRANCH} == true || ${COMMIT_MESSG} =~ run-all|run-mssql ]]; then
RUN_MSSQL=true
else
echo ::set-env name=runMssql::'false'
RUN_MSSQL=false
fi
echo ::set-env name=runMssql::"${RUN_MSSQL}"
echo ::set-env name=jobRun::'false'
if [[ ${IS_PR} == true || ${SPECIAL_BRANCH} == true || ${COMMIT_MESSG} =~ run-all|run-postgres|run-mysql|run-oracle|run-mssql|run-unit ]]; then
if [[ (${RUN_POSTGRES} == true && "${{ matrix.databaseType }}" == 'postgres') || (${RUN_MYSQL} == true && "${{ matrix.databaseType }}" == 'mysql') || (${RUN_ORACLE} == true && "${{ matrix.databaseType }}" == 'oracle') || (${RUN_MSSQL} == true && "${{ matrix.databaseType }}" == 'mssql') ]]; then
echo ::set-env name=jobRun::'true'
fi
fi
- name: Prepare dot-cicd
run: |
@@ -251,30 +266,12 @@ jobs:
env:
LICENSE_KEY: ${{ secrets.DOTCMS_LICENSE }}
if: env.jobRun == 'true'
- name: Run Integration Tests - postgres
run: |
dotcicd/library/pipeline.sh runIntegration
env:
databaseType: postgres
if: (success() || failure()) && env.jobRun == 'true' && env.runPostgres == 'true'
- name: Run Integration Tests - mysql
- name: Run Integration Tests - ${{ matrix.databaseType }}
run: |
dotcicd/library/pipeline.sh runIntegration
env:
databaseType: mysql
if: (success() || failure()) && env.jobRun == 'true' && env.runMysql == 'true'
- name: Run Integration Tests - oracle
run: |
dotcicd/library/pipeline.sh runIntegration
env:
databaseType: oracle
if: (success() || failure()) && env.jobRun == 'true' && env.runOracle == 'true'
- name: Run Integration Tests - mssql
run: |
dotcicd/library/pipeline.sh runIntegration
env:
databaseType: mssql
if: (success() || failure()) && env.jobRun == 'true' && env.runMssql == 'true'
databaseType: ${{ matrix.databaseType }}
if: (success() || failure()) && env.jobRun == 'true'
slack-notification:
name: Notification
runs-on: ubuntu-latest
@@ -292,7 +289,7 @@ jobs:
run: echo "$GITHUB_CONTEXT"
- name: Set Common Vars
run: |
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
if [[ "${{ github.event_name }}" == 'pull_request' ]]; then
echo ::set-env name=CURRENT_BRANCH::"${{ github.head_ref }}"
else
echo ::set-env name=CURRENT_BRANCH::$(basename "${{ github.ref }}")
@@ -303,4 +300,5 @@ jobs:
uses: dotcms/cicd-slack-action@master
with:
github-user: ${{ github.actor }}
branch: ${{ env.CURRENT_BRANCH }}
branch: ${{ env.CURRENT_BRANCH }}

@@ -1,5 +1,5 @@
[submodule "dotCMS/src/main/enterprise"]
path = dotCMS/src/main/enterprise
url = git@github.com:dotCMS/enterprise-2.x.git
branch = release-5.3.8
branch = release-20.10.1
ignore = dirty
@@ -166,13 +166,13 @@ dependencies {
felix group: 'org.slf4j', name: 'jcl-over-slf4j', version: '1.7.25'
felix group: 'com.dotcms.tika', name: 'com.dotcms.tika', version: '0.2'

felix group: 'com.dotcms.samlbundle', name: 'com.dotcms.samlbundle', version: '5.3.8'
felix group: 'com.dotcms.samlbundle', name: 'com.dotcms.samlbundle', version: '5.4.0'
/**** And now the libs we pull in from internal company sources - libs stored in ./plugins, ./bin, ./libs, the starter site, etc. ****/
compile fileTree("src/main/plugins/com.dotcms.config/build/jar").include('plugin-com.dotcms.config.jar')

starter group: 'com.dotcms', name: 'starter', version: 'empty_20200818', ext: 'zip'
//Uncomment this line if you want to download the starter that comes with data
//starter group: 'com.dotcms', name: 'starter', version: '20200909', ext: 'zip'
//starter group: 'com.dotcms', name: 'starter', version: '20200925', ext: 'zip'
testsStarter group: 'com.dotcms', name: 'starter', version: 'empty_20200818', ext: 'zip'

profiler group: 'glowroot-custom', name: 'glowroot-agent', version: '0.13.1'
@@ -1042,7 +1042,6 @@ task cleanDotCmsWebComponents {
delete './src/main/webapp/dotcms-webcomponents/package'
delete './src/main/webapp/dotcms-webcomponents/dotcms-webcomponents.js'
delete './src/main/webapp/dotcms-webcomponents/dotcms-webcomponents.esm.js'
delete './src/main/webapp/dotcms-webcomponents/index.esm.js'
}

task copyDotCmsUi(type: Copy) {
@@ -3,13 +3,13 @@ dependencies {

def eeType = dotcmsReleaseVersion.equals("master") ? '-SNAPSHOT' : ''

compile group: 'com.dotcms.enterprise', name: 'ee', version: '5.3.8', changing: true
compile group: 'com.dotcms.enterprise', name: 'ee', version: dotcmsReleaseVersion + eeType, changing: true

compile group: 'com.dotcms', name: 'ant-tooling', version: '1.3.2'
compile ('io.jsonwebtoken:jjwt:0.6.0'){
transitive = false
}
compile group: 'com.dotcms.lib', name: 'dot.webp-imageio-core', version:'0.1.4'
compile group: 'com.dotcms.lib', name: 'dot.webp-imageio-core', version:'0.1.5'
compile group: 'org.scijava', name: 'native-lib-loader', version: '2.3.2'
compile group: 'org.jetbrains', name: 'annotations', version: '16.0.1'
compile group: 'com.dotcms.lib', name: 'dot.asm', version:'3.1_2'
@@ -469,7 +469,7 @@ dependencies {
compile 'org.immutables:gson:2.3.3'

compile group: 'org.aspectj', name: 'aspectjrt', version:'1.8.10'
compile group: 'io.bit3', name: 'jsass', version: '5.7.1'
compile group: 'io.bit3', name: 'jsass', version: '5.10.3'

//XML Tool dependency
compile group: 'jaxen', name: 'jaxen', version: '1.1.6'
@@ -1,6 +1,6 @@
dotcmsReleaseVersion=5.3.8
coreWebReleaseVersion=5.3.8-1600296622614
webComponentsReleaseVersion=latest
dotcmsReleaseVersion=20.10.1
coreWebReleaseVersion=20.10.1-1604438979726
webComponentsReleaseVersion=0.0.13


tomcatInstallRepo=https://github.com/dotCMS/tomcat.git
@@ -1,6 +1,6 @@
{
"info": {
"_postman_id": "423987b3-9e86-45ef-998d-e85d613647ab",
"_postman_id": "3b9e2ff2-e53e-4739-a3af-fc28673914aa",
"name": "Apps",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
@@ -14,7 +14,7 @@
{
"listen": "test",
"script": {
"id": "0e88b529-a3f2-43e0-bbb3-347ff8ece06b",
"id": "ab91ad29-8053-4774-bd23-d801ce276c0c",
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
@@ -83,7 +83,8 @@
"apps",
""
]
}
},
"description": "This will import and create an App by uploading a yml. App descriptor."
},
"response": []
},
@@ -93,7 +94,7 @@
{
"listen": "test",
"script": {
"id": "94ead9fb-80ca-45cb-abdf-44ca3f692d59",
"id": "467f03e1-1351-4df5-b6ef-b79b0a806709",
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
@@ -172,7 +173,8 @@
"apps",
""
]
}
},
"description": "This will call the endpoint that brings back the whole list of available Apps. So it can be verified the new one was imported. "
},
"response": []
},
@@ -182,7 +184,7 @@
{
"listen": "prerequest",
"script": {
"id": "459b626d-ee13-46ac-85c4-dc0d4f4b6341",
"id": "55627bce-0714-44f2-b1c8-ab695a274d94",
"exec": [
""
],
@@ -192,7 +194,7 @@
{
"listen": "test",
"script": {
"id": "1821aedd-75f3-424d-8a3f-09c4a308e3a1",
"id": "9a4d1cbc-88bb-45b7-a7b1-9e23c47e0b3b",
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
@@ -266,7 +268,8 @@
"apps",
"{{key}}"
]
}
},
"description": "This tests the endpoint that brings back one specific App given the App-key."
},
"response": []
}
@@ -282,7 +285,7 @@
{
"listen": "test",
"script": {
"id": "917e3da8-3ed7-4122-a52b-3e6f203f0388",
"id": "8b533b0e-e7da-4442-81d8-25277eb263aa",
"exec": [
"pm.test(\"Status code is 400\", function () {",
" pm.response.to.have.status(400);",
@@ -344,7 +347,8 @@
"{{key}}",
"{{site_id}}"
]
}
},
"description": "This tests the endpoint that brings back one specific App given the App-key."
},
"response": []
},
@@ -354,7 +358,7 @@
{
"listen": "test",
"script": {
"id": "917e3da8-3ed7-4122-a52b-3e6f203f0388",
"id": "c6219a1e-2d5a-40bc-8e1e-af1694089534",
"exec": [
"pm.test(\"Status code is 400\", function () {",
" pm.response.to.have.status(404);",
@@ -416,7 +420,8 @@
"{{key}}",
"any-thing-but-a-valid-site-id"
]
}
},
"description": "This tests the endpoint that brings back one specific App given the App-key Using an in valid (non-existing-site) Key."
},
"response": []
},
@@ -426,7 +431,7 @@
{
"listen": "test",
"script": {
"id": "917e3da8-3ed7-4122-a52b-3e6f203f0388",
"id": "cca539ce-fc7b-4e81-9d39-cca75dabb9f1",
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
@@ -486,7 +491,8 @@
"{{key}}",
"{{site_id}}"
]
}
},
"description": "This tests the endpoint that brings back one specific App/integration given the App-key followed by the site-id"
},
"response": []
},
@@ -496,7 +502,7 @@
{
"listen": "test",
"script": {
"id": "917e3da8-3ed7-4122-a52b-3e6f203f0388",
"id": "f8cc1734-0091-43bf-8c9c-d7ebfc8a9177",
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
@@ -556,7 +562,8 @@
"{{key}}",
"{{site_id}}"
]
}
},
"description": "This tests the endpoint that updates a specific param for a given App/integration. "
},
"response": []
},
@@ -566,7 +573,7 @@
{
"listen": "test",
"script": {
"id": "917e3da8-3ed7-4122-a52b-3e6f203f0388",
"id": "321afd4d-f234-48cb-8c97-c3dd42f10da2",
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
@@ -643,7 +650,8 @@
"{{key}}",
"{{site_id}}"
]
}
},
"description": "This tests the endpoint that updates a specific hidden param for a given App/integration was successful and the new val is returned protected by stars. "
},
"response": []
},
@@ -653,7 +661,7 @@
{
"listen": "test",
"script": {
"id": "917e3da8-3ed7-4122-a52b-3e6f203f0388",
"id": "dc549bd7-7904-436a-aeb6-d3ef7ab3a968",
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
@@ -713,7 +721,8 @@
"{{key}}",
"{{site_id}}"
]
}
},
"description": "This creates a scenario for a single value update."
},
"response": []
},
@@ -723,7 +732,7 @@
{
"listen": "test",
"script": {
"id": "917e3da8-3ed7-4122-a52b-3e6f203f0388",
"id": "caa0d66b-1a00-42fa-a018-f38a12804639",
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
@@ -801,7 +810,166 @@
"{{key}}",
"{{site_id}}"
]
},
"description": "This tests that updates were performed correctly on a non-protected field."
},
"response": []
}
],
"protocolProfileBehavior": {}
},
{
"name": "import-export",
"item": [
{
"name": "{{serverURL}}/api/v1/apps/export",
"event": [
{
"listen": "test",
"script": {
"id": "74cec224-d4b3-4672-afa4-8891a89b8701",
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"auth": {
"type": "basic",
"basic": [
{
"key": "password",
"value": "admin",
"type": "string"
},
{
"key": "username",
"value": "admin@dotCMS.com",
"type": "string"
},
{
"key": "saveHelperData",
"type": "any"
},
{
"key": "showPassword",
"value": false,
"type": "boolean"
}
]
},
"method": "POST",
"header": [
{
"key": "Content-Type",
"name": "Content-Type",
"type": "text",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{ \n \"password\":\"12345678123456781234567812345678\",\n \"exportAll\":true,\n \"appKeysBySite\":{ \n }\n \n}\n"
},
"url": {
"raw": "{{serverURL}}/api/v1/apps/export",
"host": [
"{{serverURL}}"
],
"path": [
"api",
"v1",
"apps",
"export"
]
},
"description": "This tests the export generation."
},
"response": []
},
{
"name": "{{serverURL}}/api/v1/apps/import",
"event": [
{
"listen": "test",
"script": {
"id": "1d30d8c8-8d28-4444-8c80-71c7917d88b5",
"exec": [
"pm.test.skip(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"auth": {
"type": "basic",
"basic": [
{
"key": "password",
"value": "admin",
"type": "string"
},
{
"key": "username",
"value": "admin@dotCMS.com",
"type": "string"
},
{
"key": "saveHelperData",
"type": "any"
},
{
"key": "showPassword",
"value": false,
"type": "boolean"
}
]
},
"method": "POST",
"header": [
{
"key": "Content-Type",
"name": "Content-Type",
"type": "text",
"value": "application/json"
}
],
"body": {
"mode": "formdata",
"formdata": [
{
"key": "file",
"type": "file",
"src": "/Users/fabrizzio/Desktop/appSecrets.export"
},
{
"key": "json",
"value": "{ \"password\":\"12345678123456781234567812345678\"\n}",
"contentType": "application/json",
"type": "text"
}
]
},
"url": {
"raw": "{{serverURL}}/api/v1/apps/import",
"host": [
"{{serverURL}}"
],
"path": [
"api",
"v1",
"apps",
"import"
]
},
"description": "This tests the file import. "
},
"response": []
}
@@ -817,7 +985,7 @@
{
"listen": "test",
"script": {
"id": "bfb9ebe4-12d7-4a93-9151-e27d00171879",
"id": "304e42bd-aa03-4200-b496-b915d75b73d3",
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
@@ -876,7 +1044,8 @@
"apps",
"{{key}}"
]
}
},
"description": "This tests param deletion for a given configuration. "
},
"response": []
},
@@ -886,7 +1055,7 @@
{
"listen": "test",
"script": {
"id": "917e3da8-3ed7-4122-a52b-3e6f203f0388",
"id": "2cc28a5d-62a9-46e3-9078-d3e2877c10ea",
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
@@ -962,7 +1131,8 @@
"{{key}}",
"{{site_id}}"
]
}
},
"description": "This verifies the para is no longer there.. "
},
"response": []
},
@@ -972,7 +1142,7 @@
{
"listen": "test",
"script": {
"id": "c7f86063-649b-4703-ad01-343be9d4023a",
"id": "125af322-affd-45a7-bdd6-2adad660e720",
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
@@ -1031,7 +1201,8 @@
"apps",
"{{key}}"
]
}
},
"description": "This tests a delete operation performed on the whole integration at once. "
},
"response": []
},
@@ -1041,7 +1212,7 @@
{
"listen": "test",
"script": {
"id": "917e3da8-3ed7-4122-a52b-3e6f203f0388",
"id": "f3ae53c2-f4cf-49de-9ada-09f945a0afb0",
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
@@ -1105,7 +1276,8 @@
"{{key}}",
"{{site_id}}"
]
}
},
"description": "This verifies the configuration is gone. "
},
"response": []
},
@@ -1115,7 +1287,7 @@
{
"listen": "test",
"script": {
"id": "fc16be18-0766-4241-b8c9-0505c45c83b7",
"id": "19d3eeb1-f2a7-485b-9427-ff4e7384f2b7",
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
@@ -1180,7 +1352,8 @@
"value": "true"
}
]
}
},
"description": "This does delete an App Descriptor. "
},
"response": []
},
@@ -1190,7 +1363,7 @@
{
"listen": "test",
"script": {
"id": "917e3da8-3ed7-4122-a52b-3e6f203f0388",
"id": "ccffc4c3-8985-40c6-bcac-14a0a94e1905",
"exec": [
"pm.test(\"Status code is 404\", function () {",
" pm.response.to.have.status(404);",
@@ -1253,7 +1426,8 @@
"apps",
"{{key}}"
]
}
},
"description": "This tests that the delete operation performed on the descriptor was successful. "
},
"response": []
}
@@ -1265,7 +1439,7 @@
{
"listen": "prerequest",
"script": {
"id": "4d9d93a6-742a-4071-853a-4fcb16121f07",
"id": "55f42be1-b2ec-4dfe-929f-226a36f12497",
"type": "text/javascript",
"exec": [
""
@@ -1275,7 +1449,7 @@
{
"listen": "test",
"script": {
"id": "f76d335c-9dfc-4553-b519-f31e9db9c938",
"id": "53d0070e-faea-46b0-bf5a-d69123019241",
"type": "text/javascript",
"exec": [
""
@@ -1,6 +1,6 @@
{
"info": {
"_postman_id": "7d3d2861-429b-4152-a633-446f01efd478",
"_postman_id": "1dfe7f8a-ab88-4dea-8039-37b2318e39bf",
"name": "Bundle Resource",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
@@ -14,7 +14,7 @@
{
"listen": "test",
"script": {
"id": "8ab9762d-6109-415d-90e5-779cc808043e",
"id": "0767930c-8b23-4547-a33d-220a086694e7",
"exec": [
"pm.test(\"Status code is 401, You need credentials\", function () {",
" pm.response.to.have.status(401);",
@@ -70,7 +70,7 @@
{
"listen": "test",
"script": {
"id": "2c8ca21c-d218-4859-8d1f-770d5f4d2486",
"id": "00d7cb95-d4c4-413c-8e11-1f6aa6da5951",
"exec": [
"pm.test(\"Status code should be 200\", function () {",
" pm.response.to.have.status(200);",
@@ -139,7 +139,7 @@
{
"listen": "prerequest",
"script": {
"id": "011547e9-2116-46f0-bf57-70e49af4cc7c",
"id": "03410a58-1e7c-4269-892e-e4a181a99814",
"type": "text/javascript",
"exec": [
""
@@ -149,7 +149,7 @@
{
"listen": "test",
"script": {
"id": "e13150a3-c75b-46a9-972e-f52ede0f5aef",
"id": "7a969bda-10d1-4721-86c4-45ed699e2839",
"type": "text/javascript",
"exec": [
""
@@ -168,7 +168,7 @@
{
"listen": "test",
"script": {
"id": "af02c32c-9ad9-4198-8a08-a2842bc9caf3",
"id": "e1ba3205-0677-44ff-94df-e18fd5a86b74",
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
@@ -201,7 +201,7 @@
{
"listen": "test",
"script": {
"id": "d121dcd8-5647-4c06-bcb4-3db096659958",
"id": "76475017-b0ef-412e-a8c7-38ea48862513",
"exec": [
"pm.test(\"Status code should be 400\", function () {",
" pm.response.to.have.status(400);",
@@ -254,7 +254,7 @@
{
"listen": "test",
"script": {
"id": "3f39c039-a6db-4300-92d9-f1daa8f56fc6",
"id": "674eab9a-ba3b-484d-a637-8f8fe308f898",
"exec": [
"pm.test(\"Status code is 401, You need credentials\", function () {",
" pm.response.to.have.status(401);",
@@ -298,7 +298,7 @@
{
"listen": "test",
"script": {
"id": "93f43124-f361-4df5-97ec-3bbe457890d2",
"id": "458b33e1-58e8-41dd-8a94-4e0935be667a",
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
@@ -349,7 +349,7 @@
{
"listen": "prerequest",
"script": {
"id": "e43dbca6-c1ea-4f33-87bb-6c170d658596",
"id": "020541a1-f964-4ba3-a83a-d9cda9ca6e8c",
"type": "text/javascript",
"exec": [
""
@@ -359,7 +359,7 @@
{
"listen": "test",
"script": {
"id": "45e692ac-c178-4122-9ede-df1d0583eaca",
"id": "989de7b4-fead-4c52-a046-dcf9e210c36c",
"type": "text/javascript",
"exec": [
""
@@ -378,7 +378,7 @@
{
"listen": "test",
"script": {
"id": "967ab64b-bb42-4486-a00d-c3c283c049e5",
"id": "f1b3092c-8b91-420f-8f2d-36edf308dbe8",
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
@@ -411,7 +411,7 @@
{
"listen": "test",
"script": {
"id": "cf2c1f22-a21b-401c-a3b3-6d2abc0f1091",
"id": "ec94b460-14a3-405a-85df-a55826b2447f",
"exec": [
"pm.test(\"Status code is 401, You need credentials\", function () {",
" pm.response.to.have.status(401);",
@@ -452,7 +452,7 @@
{
"listen": "test",
"script": {
"id": "d98a96f7-e709-411c-b642-3cab2dbec123",
"id": "18ae0221-1aa1-48f0-bd67-efd7bde78d85",
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
@@ -504,7 +504,7 @@
{
"listen": "prerequest",
"script": {
"id": "2640c6d0-ede6-4b60-ba84-268e6f7664d1",
"id": "2c19e518-48f3-4afc-8fe5-3d4cc842278b",
"type": "text/javascript",
"exec": [
""
@@ -514,7 +514,7 @@
{
"listen": "test",
"script": {
"id": "c7e2bff7-ca86-4b5a-a76f-97cc52d45dd0",
"id": "917448c1-df5c-4108-838a-ad1a042d14e4",
"type": "text/javascript",
"exec": [
""
@@ -533,7 +533,7 @@
{
"listen": "test",
"script": {
"id": "08c78a54-ec9f-4c0e-99c7-975815751ccc",
"id": "482f7668-35b1-4451-8b5b-2d8f6fad7901",
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
@@ -566,7 +566,7 @@
{
"listen": "test",
"script": {
"id": "9f7a253a-353e-4116-9802-629ce518cd81",
"id": "f8e115cc-2974-491d-864c-47e370951b04",
"exec": [
"pm.test(\"Status code is 401, You need credentials\", function () {",
" pm.response.to.have.status(401);",
@@ -607,7 +607,7 @@
{
"listen": "test",
"script": {
"id": "832be078-cf8e-4b70-9b92-0d1d9d64588c",
"id": "3f3141cb-d16a-451a-a60b-fce896427d99",
"exec": [
"pm.test(\"Status code is 200, You need credentials\", function () {",
" pm.response.to.have.status(200);",
@@ -667,7 +667,7 @@
{
"listen": "test",
"script": {
"id": "8affb923-f9d6-4e62-98e3-1b0101e98d2a",
"id": "ec0b6878-248d-4982-a91b-95777c5e575c",
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
@@ -700,7 +700,7 @@
{
"listen": "test",
"script": {
"id": "a5f3ef4c-e2be-4a1f-b7a2-33f340d7e3fa",
"id": "84bc7803-3866-4021-b864-a2c7d3cad892",
"exec": [
"pm.test(\"Status code is 401, You need credentials\", function () {",
" pm.response.to.have.status(401);",
@@ -740,7 +740,7 @@
{
"listen": "test",
"script": {
"id": "c9fd4dbf-71ac-4e50-bc50-378c0e416254",
"id": "cc969352-a5f6-48d1-b4ea-53e271e33062",
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
@@ -799,7 +799,7 @@
{
"listen": "test",
"script": {
"id": "1e074ba6-4bb7-4258-9755-c1b742687830",
"id": "4dfeca75-d7a2-4b25-a5db-9878c2549a33",
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
@@ -832,7 +832,7 @@
{
"listen": "test",
"script": {
"id": "d35bf253-7fca-4878-885f-ac16c1b3aef7",
"id": "5ab3d8aa-c69b-4674-8f3b-c8d6a7fee9b1",
"exec": [
"pm.test(\"Response code is 401\", pm.response.to.have.status(401));"
],
@@ -896,7 +896,7 @@
{
"listen": "test",
"script": {
"id": "47b9e01a-0479-4fd1-a4e9-2c775c67c4fd",
"id": "5596a7ac-e3de-4c22-bb55-3611204ed6e6",
"exec": [
"pm.test(\"Bundle uploaded sucessfully\", function () {",
" pm.response.to.have.status(200);",
@@ -983,7 +983,7 @@
{
"listen": "test",
"script": {
"id": "863b93fe-6a92-4fd6-8b65-8e83e7c91aee",
"id": "8ba259d8-4b9c-4172-9382-4b0c7c1eed5a",
"exec": [
"pm.test(\"Content imported in bundle exists\", function () {",
" pm.response.to.have.status(200);",
@@ -1043,7 +1043,7 @@
{
"listen": "test",
"script": {
"id": "093e4c9d-8f2f-466b-8756-29dfbb975dac",
"id": "e8138ba7-527b-4c0e-875c-0c7f7fbfe139",
"exec": [
"pm.test(\"Bundle uploaded sucessfully\", function () {",
" pm.response.to.have.status(200);",
@@ -1125,6 +1125,126 @@
}
],
"protocolProfileBehavior": {}
},
{
"name": "Download Bundle",
"item": [
{
"name": "Download_BundleId Not Exists_NotFound",
"event": [
{
"listen": "test",
"script": {
"id": "d990fdc4-0c72-4b5a-a6f3-e5bc2051bef9",
"exec": [
"pm.test(\"Status code should be 404\", function () {",
" pm.response.to.have.status(404);",
"})"
],
"type": "text/javascript"
}
}
],
"request": {
"auth": {
"type": "basic",
"basic": [
{
"key": "password",
"value": "admin",
"type": "string"
},
{
"key": "username",
"value": "admin@dotcms.com",
"type": "string"
}
]
},
"method": "GET",
"header": [],
"url": {
"raw": "{{serverURL}}/api/bundle/_download/bundle-id-not-exist",
"host": [
"{{serverURL}}"
],
"path": [
"api",
"bundle",
"_download",
"bundle-id-not-exist"
]
},
"description": "Tries to download a bundle but the bundleId sent does not belong to any bundle, so it returns a 404."
},
"response": []
}
],
"protocolProfileBehavior": {}
},
{
"name": "Generate Bundle",
"item": [
{
"name": "Generate_BundleId Not Exists_NotFound",
"event": [
{
"listen": "test",
"script": {
"id": "28b713d6-923b-426a-9a50-6b055ee5efc6",
"exec": [
"pm.test(\"Status code should be 404\", function () {",
" pm.response.to.have.status(404);",
"})"
],
"type": "text/javascript"
}
}
],
"request": {
"auth": {
"type": "basic",
"basic": [
{
"key": "password",
"value": "admin",
"type": "string"
},
{
"key": "username",
"value": "admin@dotcms.com",
"type": "string"
}
]
},
"method": "POST",
"header": [],
"body": {
"mode": "raw",
"raw": "{\n \"bundleId\":\"bundle-id-not-exist\",\n \"filterKey\":\"Intelligent.yml\",\n \"operation\":\"0\"\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{serverURL}}/api/bundle/_generate",
"host": [
"{{serverURL}}"
],
"path": [
"api",
"bundle",
"_generate"
]
},
"description": "Tries to generate a bundle but the bundleId sent does not belong to any bundle, so it returns a 404."
},
"response": []
}
],
"protocolProfileBehavior": {}
}
],
"protocolProfileBehavior": {}
@@ -0,0 +1,239 @@
{
"info": {
"_postman_id": "93632083-c791-49cd-8458-265b76c9d240",
"name": "Content Resource",
"description": "Content Resource test",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"item": [
{
"name": "Anonymous Request",
"event": [
{
"listen": "test",
"script": {
"id": "0ef5fd08-72e4-461e-adb9-3ee4879d367e",
"exec": [
"pm.test(\"Status code should be 200\", function () {",
" pm.response.to.have.status(200);",
"});",
""
],
"type": "text/javascript"
}
}
],
"request": {
"method": "POST",
"header": [],
"body": {
"mode": "raw",
"raw": "{\n \t \"query\": \"+structurename:webpagecontent\",\n \t \"sort\":\"modDate\",\n \t \"limit\":20,\n \t \"offset\":1\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{serverURL}}/api/content/_search",
"host": [
"{{serverURL}}"
],
"path": [
"api",
"content",
"_search"
]
},
"description": "This is an annonymous request"
},
"response": []
},
{
"name": "Admin Request",
"event": [
{
"listen": "test",
"script": {
"id": "2be135ad-f051-44b4-940a-1a246ec6475c",
"exec": [
"pm.test(\"Status code should be 200\", function () {",
" pm.response.to.have.status(200);",
"});",
""
],
"type": "text/javascript"
}
}
],
"request": {
"auth": {
"type": "basic",
"basic": [
{
"key": "password",
"value": "admin",
"type": "string"
},
{
"key": "username",
"value": "admin@dotcms.com",
"type": "string"
},
{
"key": "saveHelperData",
"type": "any"
},
{
"key": "showPassword",
"value": false,
"type": "boolean"
}
]
},
"method": "POST",
"header": [],
"body": {
"mode": "raw",
"raw": "{\n \t \"query\": \"+structurename:webpagecontent\",\n \"limit\": 3,\n \"offset\": 1\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{serverURL}}/api/content/_search",
"host": [
"{{serverURL}}"
],
"path": [
"api",
"content",
"_search"
]
},
"description": "Just an admin request"
},
"response": []
},
{
"name": "Admin Request + Invalid User",
"event": [
{
"listen": "test",
"script": {
"id": "a6062959-5bb9-4c2d-9d19-1a3af5a43325",
"exec": [
"pm.test(\"Status code should be 400\", function () {",
" pm.response.to.have.status(400);",
"});",
""
],
"type": "text/javascript"
}
}
],
"request": {
"method": "POST",
"header": [],
"body": {
"mode": "raw",
"raw": "{\n \t \"query\": \"+structurename:webpagecontent\",\n \t \"sort\":\"modDate\",\n \t \"limit\":20,\n \t \"offset\":1,\n \"userId\":\"xxx\" \n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{serverURL}}/api/content/_search",
"host": [
"{{serverURL}}"
],
"path": [
"api",
"content",
"_search"
]
},
"description": "using an admin user but with invalid user"
},
"response": []
},
{
"name": "Admin Request Just Query Param_Success",
"event": [
{
"listen": "test",
"script": {
"id": "2be135ad-f051-44b4-940a-1a246ec6475c",
"exec": [
"pm.test(\"Status code should be 200\", function () {",
" pm.response.to.have.status(200);",
"});",
"",
"pm.test(\"Should contains a content\", function () {",
" pm.expect(pm.response.json().entity.jsonObjectView.contentlets.length).to.be.greaterThan(0);",
"});",
""
],
"type": "text/javascript"
}
}
],
"request": {
"auth": {
"type": "basic",
"basic": [
{
"key": "password",
"value": "admin",
"type": "string"
},
{
"key": "username",
"value": "admin@dotcms.com",
"type": "string"
},
{
"key": "saveHelperData",
"type": "any"
},
{
"key": "showPassword",
"value": false,
"type": "boolean"
}
]
},
"method": "POST",
"header": [],
"body": {
"mode": "raw",
"raw": "{\n \t \"query\": \"+structurename:webpagecontent\"\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{serverURL}}/api/content/_search",
"host": [
"{{serverURL}}"
],
"path": [
"api",
"content",
"_search"
]
},
"description": "Make a request but only include the query, not any other param.\nShould use the default limit and offset values."
},
"response": []
}
],
"protocolProfileBehavior": {}
}
@@ -854,7 +854,7 @@
" var render = jsonData.data.page.render;",
"",
" // mode",
" pm.expect(render, 'FAILED:[rendered]').equal(\"<!doctype html>\\n<html lang=\\\"en\\\">\\n\\n <head>\\n\\n \\n<meta charset=\\\"utf-8\\\">\\n<meta name=\\\"viewport\\\" content=\\\"width=device-width, height=device-height, initial-scale=1.0, maximum-scale=5.0, user-scalable=1\\\">\\n<meta http-equiv=\\\"X-UA-Compatible\\\" content=\\\"IE=edge\\\">\\n<meta charset=\\\"utf-8\\\">\\n<link rel=\\\"icon\\\" href=\\\"/application/themes/travel/images/favicon.ico\\\" type=\\\"image/x-icon\\\">\\n<title>Costa Rica Rain Forest</title>\\n<meta name=\\\"description\\\" content=\\\"Visit Costa Rica a rugged, rainforested Central American country with coastlines on the Caribbean and Pacific.\\\">\\n<meta name=\\\"language\\\" content=\\\"english\\\">\\n<meta name=\\\"author\\\" content=\\\"dotCMS\\\">\\n<meta name=\\\"copyright\\\" content=\\\"dotCMS LLC, Miami Florida, US\\\">\\n<meta property=\\\"og:title\\\" content=\\\"Costa Rica Rain Forest\\\">\\n<meta property=\\\"og:url\\\" content=\\\"https://demo.dotcms.com$!{request.getAttribute(\\\"javax.servlet.forward.request_uri\\\")}\\\">\\n<meta property=\\\"og:image\\\" content=\\\"/images/default.png\\\">\\n<link rel=\\\"canonical\\\" href=\\\"https://demo.dotcms.com$!{request.getAttribute(\\\"javax.servlet.forward.request_uri\\\")}\\\">\\n\\n<!-- CSS -->\\n<link rel=\\\"stylesheet\\\" type=\\\"text/css\\\" href=\\\"//fonts.googleapis.com/css?family=Oswald:500,600,700%7CRoboto:300,300i,700%7CCondiment%7CDella+Respira\\\">\\n<link rel=\\\"stylesheet\\\" href=\\\"/application/themes/travel/css/styles.dotsass\\\">\\n\\n</head>\\n\\n<body id=\\\"costa-rica-rain-forest\\\" >\\n\\n <header>\\n\\n <!-- RD Navbar-->\\n <div class=\\\"rd-navbar-wrap\\\">\\n <nav class=\\\"rd-navbar rd-navbar-static rd-navbar-classic\\\" data-layout=\\\"rd-navbar-fixed\\\" data-sm-layout=\\\"rd-navbar-fixed\\\" data-md-layout=\\\"rd-navbar-fixed\\\" data-md-device-layout=\\\"rd-navbar-fixed\\\" data-lg-layout=\\\"rd-navbar-static\\\" data-lg-device-layout=\\\"rd-navbar-static\\\" data-xl-layout=\\\"rd-navbar-static\\\" data-xl-device-layout=\\\"rd-navbar-static\\\" data-lg-stick-up-offset=\\\"78px\\\" data-xl-stick-up-offset=\\\"98px\\\" data-xxl-stick-up-offset=\\\"118px\\\" data-lg-stick-up=\\\"true\\\" data-xl-stick-up=\\\"true\\\" data-xxl-stick-up=\\\"true\\\">\\n <div class=\\\"rd-navbar-collapse-toggle rd-navbar-fixed-element-1\\\" data-rd-navbar-toggle=\\\".rd-navbar-collapse\\\"><span></span></div>\\n \\n <div class=\\\"rd-navbar-aside-outer rd-navbar-collapse bg-default\\\">\\n <div class=\\\"rd-navbar-aside\\\">\\n\\n <!-- CONTACT INFO -->\\n <ul class=\\\"list-inline list-inline-xxl\\\">\\n <li>\\n <div class=\\\"unit unit-spacing-xs\\\">\\n <div class=\\\"unit-left\\\"><span class=\\\"icon mdi mdi-map-marker\\\"></span></div>\\n <div class=\\\"unit-body\\\"><a href=\\\"#\\\">3059 Grand Avenue, Suite 300 Miami, FL, 33133 USA</a></div>\\n </div>\\n </li>\\n <li>\\n <div class=\\\"unit unit-spacing-xs\\\">\\n <div class=\\\"unit-left\\\"><span class=\\\"icon mdi mdi-cellphone-android\\\"></span></div>\\n <div class=\\\"unit-body\\\"><a href=\\\"tel:+1-888-404-6185\\\">+1-888-404-6185</a></div>\\n </div>\\n </li>\\n <li>\\n <div class=\\\"unit unit-spacing-xs\\\">\\n <div class=\\\"unit-left\\\"><span class=\\\"icon mdi mdi-email-outline\\\"></span></div>\\n <div class=\\\"unit-body\\\"><a href=\\\"mailto: info@dotcms.com\\\">info@dotcms.com</a></div>\\n </div>\\n </li>\\n </ul>\\n\\n <div>\\n <!-- CART -->\\n <a class=\\\"basket\\\" href=\\\"/store/cart\\\"><span class=\\\"icon mdi mdi-cart-outline\\\"></span><span class=\\\"basket-count\\\">3</span></a>\\n\\n <!-- My Account -->\\n <a href=\\\"/login/profile\\\" class=\\\"ml-3\\\">\\n <img src=\\\"/dA/792c7c9f-6b6f-427b-80ff-1643376c9999/photo/42w/50q/mountain-persona.jpg\\\" alt=\\\"Winter Enthusiast Persona\\\" class=\\\"img-circles border\\\">\\n </a>\\n </div>\\n </div>\\n </div>\\n\\n <div class=\\\"rd-navbar-main-outer\\\">\\n <div class=\\\"rd-navbar-main\\\">\\n\\n <div class=\\\"rd-navbar-panel\\\">\\n <button class=\\\"rd-navbar-toggle\\\" data-rd-navbar-toggle=\\\".rd-navbar-nav-wrap\\\" aria-label=\\\"Toggle Menu\\\"><span></span></button>\\n <div class=\\\"rd-navbar-brand\\\"><a class=\\\"brand\\\" href=\\\"/\\\" aria-label=\\\"TravelLux Home\\\"><img class=\\\"brand-logo-dark\\\" src=\\\"/application/themes/travel/images/logo.png\\\" width=\\\"195\\\" height=\\\"40\\\" alt=\\\"TravelLux Home\\\"></a></div>\\n </div>\\n\\n <!-- NAV BAR -->\\n\\n <div class=\\\"rd-navbar-main-element\\\">\\n\\n <div class=\\\"rd-navbar-nav-wrap\\\">\\n \\n \\n <ul class=\\\"rd-navbar-nav\\\">\\n <li class=\\\"rd-nav-item \\\"><a class=\\\"rd-nav-link\\\" href='/'>Home</a></li>\\n <li class=\\\"rd-nav-item \\\"><a class=\\\"rd-nav-link\\\" href='/blog' target='_self'>Travel Blog</a></li>\\n <li class=\\\"rd-nav-item \\\"><a class=\\\"rd-nav-link\\\" href='/destinations' target='_self'>Destinations</a></li>\\n </ul>\\n </div>\\n\\n <!-- RD Navbar Search-->\\n <div class=\\\"rd-navbar-search\\\">\\n <button class=\\\"rd-navbar-search-toggle rd-navbar-fixed-element-2\\\" data-rd-navbar-toggle=\\\".rd-navbar-search\\\" aria-label=\\\"Search Box Toggle\\\"><span></span></button>\\n <form class=\\\"rd-search\\\" action=\\\"/search/\\\" method=\\\"GET\\\">\\n <div class=\\\"form-wrap\\\">\\n <label class=\\\"form-label search-label\\\" for=\\\"site-search\\\">Search...</label>\\n <input class=\\\"rd-navbar-search-form-input form-input site-search\\\" type=\\\"text\\\" name=\\\"q\\\" autocomplete=\\\"off\\\">\\n </div>\\n <button class=\\\"rd-search-form-submit fa-search\\\" type=\\\"submit\\\" aria-label=\\\"Submit\\\"></button>\\n </form>\\n </div>\\n \\n </div>\\n </div>\\n </div>\\n </nav>\\n </div>\\n\\n</header>\\n \\n <div class=\\\"body-wrapper\\\">\\n \\n \\n \\n \\n \\n \\n \\n \\n \\n <section id=\\\"section-1\\\" class=\\\"section \\\"><!-- section -->\\n <div class=\\\"container\\\"><!-- container -->\\n <div class=\\\"row\\\"><!-- row -->\\n \\n \\n \\n <div class=\\\" col-lg-12 \\\"><!-- Column -->\\n \\n </div><!-- Column -->\\n\\n </div><!-- /row -->\\n </div><!-- /container -->\\n </section><!-- /section -->\\n \\n <section id=\\\"section-2\\\" class=\\\"section \\\"><!-- section -->\\n <div class=\\\"container\\\"><!-- container -->\\n <div class=\\\"row\\\"><!-- row -->\\n \\n \\n \\n <div class=\\\" col-lg-6 \\\"><!-- Column -->\\n \\n<div></div>\\n </div><!-- Column -->\\n\\n \\n \\n \\n <div class=\\\" col-lg-6 \\\"><!-- Column -->\\n \\n<div></div>\\n </div><!-- Column -->\\n\\n </div><!-- /row -->\\n </div><!-- /container -->\\n </section><!-- /section -->\\n \\n <section id=\\\"section-3\\\" class=\\\"section \\\"><!-- section -->\\n <div class=\\\"container\\\"><!-- container -->\\n <div class=\\\"row\\\"><!-- row -->\\n \\n \\n \\n <div class=\\\" col-lg-12 \\\"><!-- Column -->\\n \\n<div></div>\\n </div><!-- Column -->\\n\\n </div><!-- /row -->\\n </div><!-- /container -->\\n </section><!-- /section -->\\n \\n <section id=\\\"section-4\\\" class=\\\"section \\\"><!-- section -->\\n <div class=\\\"container\\\"><!-- container -->\\n <div class=\\\"row\\\"><!-- row -->\\n \\n \\n \\n <div class=\\\" col-lg-10 offset-lg-1 \\\"><!-- Column -->\\n \\n<div></div>\\n </div><!-- Column -->\\n\\n </div><!-- /row -->\\n </div><!-- /container -->\\n </section><!-- /section -->\\n \\n <section id=\\\"section-5\\\" class=\\\"section \\\"><!-- section -->\\n <div class=\\\"container\\\"><!-- container -->\\n <div class=\\\"row\\\"><!-- row -->\\n \\n \\n \\n <div class=\\\" col-lg-6 \\\"><!-- Column -->\\n \\n<div></div>\\n </div><!-- Column -->\\n\\n \\n \\n \\n <div class=\\\" col-lg-6 \\\"><!-- Column -->\\n \\n<div></div>\\n </div><!-- Column -->\\n\\n </div><!-- /row -->\\n </div><!-- /container -->\\n </section><!-- /section -->\\n \\n <section id=\\\"section-6\\\" class=\\\"section \\\"><!-- section -->\\n <div class=\\\"container\\\"><!-- container -->\\n <div class=\\\"row\\\"><!-- row -->\\n \\n \\n \\n <div class=\\\" col-lg-5 \\\"><!-- Column -->\\n \\n<div></div>\\n </div><!-- Column -->\\n\\n \\n \\n \\n <div class=\\\" col-lg-6 offset-lg-1 \\\"><!-- Column -->\\n \\n<div></div>\\n </div><!-- Column -->\\n\\n </div><!-- /row -->\\n </div><!-- /container -->\\n </section><!-- /section -->\\n \\n </div><!-- /body-wrapper -->\\n\\n <!-- Page Footer-->\\n\\n\\n\\n\\n<footer class=\\\"footer-classic bg-footer bg-overlay-80 context-dark\\\" style=\\\"background-image: url('/dA/bec7b960-a8bf-4f14-a22b-0d94caf217f0/image/WebP/1200w/footer-image.jpg')\\\" >\\n <div class=\\\"section-xxl\\\">\\n <div class=\\\"container\\\">\\n <div class=\\\"row row-50 justify-content-center\\\">\\n <div class=\\\"col-lg-4\\\">\\n <h2>About us</h2>\\n <p class=\\\"max-width-xl-90 text-gray-400\\\">We are TravelLux, a community of dedicated travel experts, journalists, and bloggers. Our aim is to offer you the best insight on where to go for your travel as well as to give you amazing opportunities with free benefits and bonuses for registered clients.</p>\\n <a class=\\\"footer-brand\\\" href=\\\"./\\\"><img src=\\\"/application/themes/travel/images/logo-inverse.png\\\" alt=\\\"TravelLux Home\\\" height=\\\"40\\\"/></a>\\n </div>\\n\\n\\n <div class=\\\"col-sm-8 col-md-6 col-lg-4\\\">\\n <h2>Latest blog posts</h2>\\n \\n <!-- Latest Blogs -->\\n \\n </div>\\n <div class=\\\"col-sm-8 col-md-6 col-lg-4\\\">\\n <h2>Get in touch</h2>\\n <form class=\\\"rd-form form-sm rd-mailform\\\" data-form-output=\\\"form-output-global\\\" data-form-type=\\\"contact\\\" method=\\\"post\\\" action=\\\"\\\">\\n <div class=\\\"form-wrap\\\">\\n <input class=\\\"form-input\\\" id=\\\"footer-form-name\\\" type=\\\"text\\\" name=\\\"name\\\" data-constraints=\\\"@Required\\\">\\n <label class=\\\"form-label\\\" for=\\\"footer-form-name\\\">Name</label>\\n </div>\\n <div class=\\\"form-wrap\\\">\\n <input class=\\\"form-input\\\" id=\\\"footer-form-email\\\" type=\\\"email\\\" name=\\\"email\\\" data-constraints=\\\"@Email @Required\\\">\\n <label class=\\\"form-label\\\" for=\\\"footer-form-email\\\">E-mail</label>\\n </div>\\n <div class=\\\"form-wrap\\\">\\n <label class=\\\"form-label\\\" for=\\\"footer-form-message\\\">Message</label>\\n <textarea class=\\\"form-input\\\" id=\\\"footer-form-message\\\" name=\\\"message\\\" data-constraints=\\\"@Required\\\"></textarea>\\n </div>\\n <div class=\\\"form-wrap\\\">\\n <button class=\\\"button button-md button-primary\\\" type=\\\"submit\\\">Send</button>\\n </div>\\n </form>\\n </div>\\n </div>\\n </div>\\n </div>\\n <div class=\\\"container\\\">\\n <!-- Rights-->\\n <p class=\\\"rights mb-0\\\"><span>&copy;2012 - 2020 All Rights Reserved</span> <a href=\\\"https://dotcms.com/company/policies/privacy-policy\\\">Terms of Use</a> <span>and</span> <a href=\\\"https://dotcms.com/company/policies/privacy-policy\\\">Privacy Policy</a></p>\\n </div>\\n</footer>\\n\\n<!-- Javascript-->\\n<script src=\\\"/application/themes/travel/js/core.min.js\\\"></script>\\n<script src=\\\"/application/themes/travel/js/script.js\\\"></script>\\n\\n \\n</body>\\n</html>\");",
" pm.expect(render, 'FAILED:[rendered]').equal(\"<!doctype html>\\n<html lang=\\\"en\\\">\\n\\n <head>\\n\\n \\n<meta charset=\\\"utf-8\\\">\\n<meta name=\\\"viewport\\\" content=\\\"width=device-width, height=device-height, initial-scale=1.0, maximum-scale=5.0, user-scalable=1\\\">\\n<meta http-equiv=\\\"X-UA-Compatible\\\" content=\\\"IE=edge\\\">\\n<meta charset=\\\"utf-8\\\">\\n<link rel=\\\"icon\\\" href=\\\"/application/themes/travel/images/favicon.ico\\\" type=\\\"image/x-icon\\\">\\n<title>Costa Rica Rain Forest</title>\\n<meta name=\\\"description\\\" content=\\\"Visit Costa Rica a rugged, rainforested Central American country with coastlines on the Caribbean and Pacific.\\\">\\n<meta name=\\\"language\\\" content=\\\"english\\\">\\n<meta name=\\\"author\\\" content=\\\"dotCMS\\\">\\n<meta name=\\\"copyright\\\" content=\\\"dotCMS LLC, Miami Florida, US\\\">\\n<meta property=\\\"og:title\\\" content=\\\"Costa Rica Rain Forest\\\">\\n<meta property=\\\"og:url\\\" content=\\\"https://demo.dotcms.com$!{request.getAttribute(\\\"javax.servlet.forward.request_uri\\\")}\\\">\\n<meta property=\\\"og:image\\\" content=\\\"/images/default.png\\\">\\n<link rel=\\\"canonical\\\" href=\\\"https://demo.dotcms.com$!{request.getAttribute(\\\"javax.servlet.forward.request_uri\\\")}\\\">\\n\\n<!-- CSS -->\\n<link rel=\\\"stylesheet\\\" type=\\\"text/css\\\" href=\\\"//fonts.googleapis.com/css?family=Oswald:500,600,700%7CRoboto:300,300i,700%7CCondiment%7CDella+Respira\\\">\\n<link rel=\\\"stylesheet\\\" href=\\\"/application/themes/travel/css/styles.dotsass\\\">\\n\\n</head>\\n\\n<body id=\\\"costa-rica-rain-forest\\\" >\\n\\n <header>\\n\\n <!-- RD Navbar-->\\n <div class=\\\"rd-navbar-wrap\\\">\\n <nav class=\\\"rd-navbar rd-navbar-static rd-navbar-classic\\\" data-layout=\\\"rd-navbar-fixed\\\" data-sm-layout=\\\"rd-navbar-fixed\\\" data-md-layout=\\\"rd-navbar-fixed\\\" data-md-device-layout=\\\"rd-navbar-fixed\\\" data-lg-layout=\\\"rd-navbar-static\\\" data-lg-device-layout=\\\"rd-navbar-static\\\" data-xl-layout=\\\"rd-navbar-static\\\" data-xl-device-layout=\\\"rd-navbar-static\\\" data-lg-stick-up-offset=\\\"78px\\\" data-xl-stick-up-offset=\\\"98px\\\" data-xxl-stick-up-offset=\\\"118px\\\" data-lg-stick-up=\\\"true\\\" data-xl-stick-up=\\\"true\\\" data-xxl-stick-up=\\\"true\\\">\\n <div class=\\\"rd-navbar-collapse-toggle rd-navbar-fixed-element-1\\\" data-rd-navbar-toggle=\\\".rd-navbar-collapse\\\"><span></span></div>\\n \\n <div class=\\\"rd-navbar-aside-outer rd-navbar-collapse bg-default\\\">\\n <div class=\\\"rd-navbar-aside\\\">\\n\\n <!-- CONTACT INFO -->\\n <ul class=\\\"list-inline list-inline-xxl\\\">\\n <li>\\n <div class=\\\"unit unit-spacing-xs\\\">\\n <div class=\\\"unit-left\\\"><span class=\\\"icon mdi mdi-map-marker\\\"></span></div>\\n <div class=\\\"unit-body\\\"><a href=\\\"#\\\">3059 Grand Avenue, Suite 300 Miami, FL, 33133 USA</a></div>\\n </div>\\n </li>\\n <li>\\n <div class=\\\"unit unit-spacing-xs\\\">\\n <div class=\\\"unit-left\\\"><span class=\\\"icon mdi mdi-cellphone-android\\\"></span></div>\\n <div class=\\\"unit-body\\\"><a href=\\\"tel:+1-888-404-6185\\\">+1-888-404-6185</a></div>\\n </div>\\n </li>\\n <li>\\n <div class=\\\"unit unit-spacing-xs\\\">\\n <div class=\\\"unit-left\\\"><span class=\\\"icon mdi mdi-email-outline\\\"></span></div>\\n <div class=\\\"unit-body\\\"><a href=\\\"mailto: info@dotcms.com\\\">info@dotcms.com</a></div>\\n </div>\\n </li>\\n </ul>\\n\\n <div>\\n <!-- CART -->\\n <a class=\\\"basket\\\" href=\\\"/store/cart\\\"><span class=\\\"icon mdi mdi-cart-outline\\\"></span><span class=\\\"basket-count\\\">3</span></a>\\n\\n <!-- My Account -->\\n <a href=\\\"/login/profile\\\" class=\\\"ml-3\\\">\\n <img src=\\\"/dA/0ed8e71a-47c7-4b30-a6f2-3796aa71ba49/photo/42w/50q/hiking-profile.jpg\\\" alt=\\\"Eco Enthusiast Persona\\\" class=\\\"img-circles border\\\">\\n </a>\\n </div>\\n </div>\\n </div>\\n\\n <div class=\\\"rd-navbar-main-outer\\\">\\n <div class=\\\"rd-navbar-main\\\">\\n\\n <div class=\\\"rd-navbar-panel\\\">\\n <button class=\\\"rd-navbar-toggle\\\" data-rd-navbar-toggle=\\\".rd-navbar-nav-wrap\\\" aria-label=\\\"Toggle Menu\\\"><span></span></button>\\n <div class=\\\"rd-navbar-brand\\\"><a class=\\\"brand\\\" href=\\\"/\\\" aria-label=\\\"TravelLux Home\\\"><img class=\\\"brand-logo-dark\\\" src=\\\"/application/themes/travel/images/logo.png\\\" width=\\\"195\\\" height=\\\"40\\\" alt=\\\"TravelLux Home\\\"></a></div>\\n </div>\\n\\n <!-- NAV BAR -->\\n\\n <div class=\\\"rd-navbar-main-element\\\">\\n\\n <div class=\\\"rd-navbar-nav-wrap\\\">\\n \\n \\n <ul class=\\\"rd-navbar-nav\\\">\\n <li class=\\\"rd-nav-item \\\"><a class=\\\"rd-nav-link\\\" href='/'>Home</a></li>\\n <li class=\\\"rd-nav-item \\\"><a class=\\\"rd-nav-link\\\" href='/blog' target='_self'>Travel Blog</a></li>\\n <li class=\\\"rd-nav-item \\\"><a class=\\\"rd-nav-link\\\" href='/destinations' target='_self'>Destinations</a></li>\\n </ul>\\n </div>\\n\\n <!-- RD Navbar Search-->\\n <div class=\\\"rd-navbar-search\\\">\\n <button class=\\\"rd-navbar-search-toggle rd-navbar-fixed-element-2\\\" data-rd-navbar-toggle=\\\".rd-navbar-search\\\" aria-label=\\\"Search Box Toggle\\\"><span></span></button>\\n <form class=\\\"rd-search\\\" action=\\\"/search/\\\" method=\\\"GET\\\">\\n <div class=\\\"form-wrap\\\">\\n <label class=\\\"form-label search-label\\\" for=\\\"site-search\\\">Search...</label>\\n <input class=\\\"rd-navbar-search-form-input form-input site-search\\\" type=\\\"text\\\" name=\\\"q\\\" autocomplete=\\\"off\\\">\\n </div>\\n <button class=\\\"rd-search-form-submit fa-search\\\" type=\\\"submit\\\" aria-label=\\\"Submit\\\"></button>\\n </form>\\n </div>\\n \\n </div>\\n </div>\\n </div>\\n </nav>\\n </div>\\n\\n</header>\\n \\n <div class=\\\"body-wrapper\\\">\\n \\n \\n \\n \\n \\n \\n \\n \\n \\n <section id=\\\"section-1\\\" class=\\\"section \\\"><!-- section -->\\n <div class=\\\"container\\\"><!-- container -->\\n <div class=\\\"row\\\"><!-- row -->\\n \\n \\n \\n <div class=\\\" col-lg-12 \\\"><!-- Column -->\\n \\n<!-- Container Code: /application/containers/defualt/banner.vtl -->\\n\\n <div class=\\\"banner bg-image-full bg-overlay-30 context-dark\\\" style=\\\"background-image: url('/dA/d4ed1990-b151-42ca-9bca-0725b07260de/image/1200w/50q/scenic-view-of-the-mountains-733092.jpg');\\\">\\n <div class=\\\"container\\\">\\n <div class=\\\"row justify-content-lg-center\\\">\\n <div class=\\\"col-lg-9 text-center\\\">\\n <p class=\\\"banner-title\\\">Costa Rica</p>\\n <h2 class=\\\"text-decoration-lines-2\\\">\\n <span>\\n Sanctuary For The Senses\\n <span class=\\\"text-decoration-line text-decoration-line-left\\\"></span>\\n <span class=\\\"text-decoration-line text-decoration-line-right\\\"></span>\\n </span>\\n </h2>\\n \\n </div>\\n </div>\\n </div>\\n </div>\\n\\n </div><!-- Column -->\\n\\n </div><!-- /row -->\\n </div><!-- /container -->\\n </section><!-- /section -->\\n \\n <section id=\\\"section-2\\\" class=\\\"section \\\"><!-- section -->\\n <div class=\\\"container\\\"><!-- container -->\\n <div class=\\\"row\\\"><!-- row -->\\n \\n \\n \\n <div class=\\\" col-lg-6 \\\"><!-- Column -->\\n \\n<h2>Custom Packages</h2>\\n<p>Are you looking for the ultimate in Costa Rica vacations? Our team oat TravelLux will design your custom vacation package. We do not sell cookie-cutter trips. Instead, we take the time to get to know your unique &ldquo;travel personality&rdquo;. Then we match you with a hand-picked selection of housing, tours &amp; transportation options. The result is a tailor-made Costa Rica journey that will fit you like a glove.</p>\\n<p>We email you a detailed travel itinerary filled with info, pictures and videos. Your TravelLux agent will work with you to adjust your vacation package until you are 100% satisfied. This makes for a seamless, worry-free experience that allows you to relax &amp; enjoy the voyage.</p>\\n<p><a href=\\\"/contact-us/\\\" class=\\\"btn btn-primary\\\">Book Now</a></p>\\n<article class=\\\"thumbnail-classic mb-5\\\">\\n <a class=\\\"thumbnail-classic-figure\\\" href=\\\"/dA/7b4ba67a-1793-4daf-bed9-f8ce054f1433/ski_holidays.png/1200w\\\" data-lightgallery=\\\"item\\\">\\n <picture>\\n <source media=\\\"(min-width: 800px)\\\" srcset=\\\"/dA/7b4ba67a-1793-4daf-bed9-f8ce054f1433/ski_holidays.png/1200w/50q/ski_holidays.png, /dA/7b4ba67a-1793-4daf-bed9-f8ce054f1433/ski_holidays.png/2400w/50q/ski_holidays.png 2x\\\">\\n <source media=\\\"(min-width: 480px)\\\" srcset=\\\"/dA/7b4ba67a-1793-4daf-bed9-f8ce054f1433/ski_holidays.png/600w/50q/ski_holidays.png, /dA/7b4ba67a-1793-4daf-bed9-f8ce054f1433//1200w/50q/ski_holidays.png 2x\\\">\\n <img src=\\\"/dA/7b4ba67a-1793-4daf-bed9-f8ce054f1433/ski_holidays.png/480w/50q/ski_holidays.png\\\" alt=\\\"With over 400\\\" of snow fall a year we have the longest season in the Continental US\\\" class=\\\"img-fluid\\\">\\n </picture>\\n </a>\\n <div class=\\\"thumbnail-classic-caption\\\">\\n <h4 class=\\\"thumbnail-classic-title\\\">Over 400\\\" of snow a year</h4>\\n <p class=\\\"thumbnail-classic-text\\\">With over 400\\\" of snow fall a year we have the longest season in the Continental US</p>\\n <a class=\\\"thumbnail-classic-link mdi mdi-plus-circle-outline\\\" href=\\\"/dA/7b4ba67a-1793-4daf-bed9-f8ce054f1433/1200w/50q/ski_holidays.png\\\" data-lightgallery=\\\"item\\\"></a>\\n </div> \\n</article>\\n<div></div>\\n </div><!-- Column -->\\n\\n \\n \\n \\n <div class=\\\" col-lg-6 \\\"><!-- Column -->\\n \\n<!-- Container Code: /application/containers/video.vtl -->\\n\\n<a class=\\\"video-cover bg-overlay-30 mb-5\\\" data-lightbox=\\\"iframe\\\" href=\\\"https://www.youtube.com/watch?v=1qw4ayRc1t8\\\">\\n <div class=\\\"video-img\\\" style=\\\"background-image: url(https://i.ytimg.com/vi/1qw4ayRc1t8/hqdefault.jpg);\\\"></div>\\n <span class=\\\"icon mdi mdi-play video-cover-icon\\\"></span>\\n</a><div></div>\\n </div><!-- Column -->\\n\\n </div><!-- /row -->\\n </div><!-- /container -->\\n </section><!-- /section -->\\n \\n <section id=\\\"section-3\\\" class=\\\"section \\\"><!-- section -->\\n <div class=\\\"container\\\"><!-- container -->\\n <div class=\\\"row\\\"><!-- row -->\\n \\n \\n \\n <div class=\\\" col-lg-12 \\\"><!-- Column -->\\n \\n\\r\\n<h1 class=\\\"text-center\\\">Featured Activities</h1>\\n<div class=\\\"row justify-content-center\\\">\\n <div class=\\\"col-lg-3 col-md-4 col-sm-6 col-xs-6 mb-3\\\">\\n <a class=\\\"box-info\\\" href=\\\"/activities/hiking\\\">\\n <img class=\\\"box-info-img\\\" src=\\\"/dA/6bbead53-3908-471c-966d-9e76c5dd9eba/image/270w/50q/hiking.jpg\\\" alt=\\\"Hiking\\\" width=\\\"270\\\" height=\\\"270\\\">\\n <div class=\\\"box-info-body\\\">\\n <h4 class=\\\"box-info-title\\\">\\n Hiking\\n </h4>\\n <p class=\\\"box-info-text\\\">Guided hiking tours where our guides use their training as naturalists to point out animals, unique plants and nature ...</p>\\n </div>\\n </a>\\n </div>\\n <div class=\\\"col-lg-3 col-md-4 col-sm-6 col-xs-6 mb-3\\\">\\n <a class=\\\"box-info\\\" href=\\\"/activities/zip-lining\\\">\\n <img class=\\\"box-info-img\\\" src=\\\"/dA/50757fb4-75df-4e2c-8335-35d36bdb944b/image/270w/50q/zip-line.jpg\\\" alt=\\\"Zip-Lining\\\" width=\\\"270\\\" height=\\\"270\\\">\\n <div class=\\\"box-info-body\\\">\\n <h4 class=\\\"box-info-title\\\">\\n Zip-Lining\\n </h4>\\n <p class=\\\"box-info-text\\\">Ever wondered what it is like to fly through the forest canopy? Zip-lining ais the best way to explore the forest canopy...</p>\\n </div>\\n </a>\\n </div>\\n <div class=\\\"col-lg-3 col-md-4 col-sm-6 col-xs-6 mb-3\\\">\\n <a class=\\\"box-info\\\" href=\\\"/activities/white-water-rafting\\\">\\n <img class=\\\"box-info-img\\\" src=\\\"/dA/0e9340f8-08d2-46e3-ae25-be0137c575d0/image/270w/50q/white-water-raft-2.jpg\\\" alt=\\\"White Water Rafting\\\" width=\\\"270\\\" height=\\\"270\\\">\\n <div class=\\\"box-info-body\\\">\\n <h4 class=\\\"box-info-title\\\">\\n White Water Rafting\\n </h4>\\n <p class=\\\"box-info-text\\\">For those looking for a thrill, we offer some of the worlds bets white water rafting. Class II - IV rivers.</p>\\n </div>\\n </a>\\n </div>\\n </div>\\n<div></div>\\n </div><!-- Column -->\\n\\n </div><!-- /row -->\\n </div><!-- /container -->\\n </section><!-- /section -->\\n \\n <section id=\\\"section-4\\\" class=\\\"section \\\"><!-- section -->\\n <div class=\\\"container\\\"><!-- container -->\\n <div class=\\\"row\\\"><!-- row -->\\n \\n \\n \\n <div class=\\\" col-lg-10 offset-lg-1 \\\"><!-- Column -->\\n \\n<h2>Costa Rica Family Adventure</h2>\\n<p>Tropical rainforests, zip lines, monkey sightings and waterfall swims make for the most unforgettable vacation&mdash;and photos.</p>\\n<p>Abundance abounds in Costa Rica: beautiful beaches, friendly people and endless adventures to embark upon. We dabble in it all with a family-friendly itinerary that will get everyone&rsquo;s heart pumping. Jump right in to adventure life in Costa Rica at Arenal Volcano where we zip along the treetops, get a glimpse of rural Tico life on a local farm, rappel down a canyon waterfall that lies deep in the tropical rainforest, and soak in thermal hot springs.</p>\\n<p>Journey south, crossing Lake Arenal by boat, and dive deep into the Monteverde cloud forest. Your hotel is located in the reserve, home to eight different biological zones and thousands of plants and animals. Keep your eyes peeled for monkeys and quetzals during the day and embark on night walks with your guide to experience a whole different world after dark. Spend a unique day exploring the forest giants from the ground up&mdash;climbing up specially outfitted trees and then exploring the forest on foot with your naturalist guide.</p><div></div>\\n </div><!-- Column -->\\n\\n </div><!-- /row -->\\n </div><!-- /container -->\\n </section><!-- /section -->\\n \\n <section id=\\\"section-5\\\" class=\\\"section \\\"><!-- section -->\\n <div class=\\\"container\\\"><!-- container -->\\n <div class=\\\"row\\\"><!-- row -->\\n \\n \\n \\n <div class=\\\" col-lg-6 \\\"><!-- Column -->\\n \\n<h3>Adventure travel done right</h3>\\n<p>Wherever you want to go, whatever you want to get into, we&rsquo;ve got a trip that&rsquo;ll make your dream vacation come true. Visit like a local, explore at your own pace, and eat like a king (or a vegan king, if that&rsquo;s more your thing).<strong><br /></strong></p><div></div>\\n </div><!-- Column -->\\n\\n \\n \\n \\n <div class=\\\" col-lg-6 \\\"><!-- Column -->\\n \\n<h3>Experience the world with us</h3>\\n<p>We practice Leave No Trace principles on all our trips and work with local people and businesses as often as possible.Our expert guides don&rsquo;t just know these places&mdash;they call them home. Their local knowledge unveils worlds few get to see. Hike, bike, swim and paddle with like-minded travelers who share your passion for adventure and the outdoors.</p><div></div>\\n </div><!-- Column -->\\n\\n </div><!-- /row -->\\n </div><!-- /container -->\\n </section><!-- /section -->\\n \\n <section id=\\\"section-6\\\" class=\\\"section \\\"><!-- section -->\\n <div class=\\\"container\\\"><!-- container -->\\n <div class=\\\"row\\\"><!-- row -->\\n \\n \\n \\n <div class=\\\" col-lg-5 \\\"><!-- Column -->\\n \\n\\n<article class=\\\"thumbnail-classic mb-5\\\">\\n <a class=\\\"thumbnail-classic-figure\\\" href=\\\"/dA/bfee8f08-a6d1-4fd7-8f70-476e62663eca/waterfall-costa-rica.jpg/1200w\\\" data-lightgallery=\\\"item\\\">\\n <picture>\\n <source media=\\\"(min-width: 800px)\\\" srcset=\\\"/dA/bfee8f08-a6d1-4fd7-8f70-476e62663eca/waterfall-costa-rica.jpg/1200w/50q/waterfall-costa-rica.jpg, /dA/bfee8f08-a6d1-4fd7-8f70-476e62663eca/waterfall-costa-rica.jpg/2400w/50q/waterfall-costa-rica.jpg 2x\\\">\\n <source media=\\\"(min-width: 480px)\\\" srcset=\\\"/dA/bfee8f08-a6d1-4fd7-8f70-476e62663eca/waterfall-costa-rica.jpg/600w/50q/waterfall-costa-rica.jpg, /dA/bfee8f08-a6d1-4fd7-8f70-476e62663eca//1200w/50q/waterfall-costa-rica.jpg 2x\\\">\\n <img src=\\\"/dA/bfee8f08-a6d1-4fd7-8f70-476e62663eca/waterfall-costa-rica.jpg/480w/50q/waterfall-costa-rica.jpg\\\" alt=\\\"From breathtaking waterfalls to amazing rain forest, Costa Rica has something for every Eco adventurer.\\\" class=\\\"img-fluid\\\">\\n </picture>\\n </a>\\n <div class=\\\"thumbnail-classic-caption\\\">\\n <h4 class=\\\"thumbnail-classic-title\\\">Fall into Costa Rica</h4>\\n <p class=\\\"thumbnail-classic-text\\\">From breathtaking waterfalls to amazing rain forest, Costa Rica has something for every Eco adventurer.</p>\\n <a class=\\\"thumbnail-classic-link mdi mdi-plus-circle-outline\\\" href=\\\"/dA/bfee8f08-a6d1-4fd7-8f70-476e62663eca/1200w/50q/waterfall-costa-rica.jpg\\\" data-lightgallery=\\\"item\\\"></a>\\n </div> \\n</article>\\n<div></div>\\n </div><!-- Column -->\\n\\n \\n \\n \\n <div class=\\\" col-lg-6 offset-lg-1 \\\"><!-- Column -->\\n \\n<div></div>\\n </div><!-- Column -->\\n\\n </div><!-- /row -->\\n </div><!-- /container -->\\n </section><!-- /section -->\\n \\n </div><!-- /body-wrapper -->\\n\\n <!-- Page Footer-->\\n\\n\\n\\n\\n<footer class=\\\"footer-classic bg-footer bg-overlay-80 context-dark\\\" style=\\\"background-image: url('/dA/bec7b960-a8bf-4f14-a22b-0d94caf217f0/image/WebP/1200w/footer-image.jpg')\\\" >\\n <div class=\\\"section-xxl\\\">\\n <div class=\\\"container\\\">\\n <div class=\\\"row row-50 justify-content-center\\\">\\n <div class=\\\"col-lg-4\\\">\\n <h2>About us</h2>\\n <p class=\\\"max-width-xl-90 text-gray-400\\\">We are TravelLux, a community of dedicated travel experts, journalists, and bloggers. Our aim is to offer you the best insight on where to go for your travel as well as to give you amazing opportunities with free benefits and bonuses for registered clients.</p>\\n <a class=\\\"footer-brand\\\" href=\\\"./\\\"><img src=\\\"/application/themes/travel/images/logo-inverse.png\\\" alt=\\\"TravelLux Home\\\" height=\\\"40\\\"/></a>\\n </div>\\n\\n\\n <div class=\\\"col-sm-8 col-md-6 col-lg-4\\\">\\n <h2>Latest blog posts</h2>\\n \\n <!-- Latest Blogs -->\\n \\n </div>\\n <div class=\\\"col-sm-8 col-md-6 col-lg-4\\\">\\n <h2>Get in touch</h2>\\n <form class=\\\"rd-form form-sm rd-mailform\\\" data-form-output=\\\"form-output-global\\\" data-form-type=\\\"contact\\\" method=\\\"post\\\" action=\\\"\\\">\\n <div class=\\\"form-wrap\\\">\\n <input class=\\\"form-input\\\" id=\\\"footer-form-name\\\" type=\\\"text\\\" name=\\\"name\\\" data-constraints=\\\"@Required\\\">\\n <label class=\\\"form-label\\\" for=\\\"footer-form-name\\\">Name</label>\\n </div>\\n <div class=\\\"form-wrap\\\">\\n <input class=\\\"form-input\\\" id=\\\"footer-form-email\\\" type=\\\"email\\\" name=\\\"email\\\" data-constraints=\\\"@Email @Required\\\">\\n <label class=\\\"form-label\\\" for=\\\"footer-form-email\\\">E-mail</label>\\n </div>\\n <div class=\\\"form-wrap\\\">\\n <label class=\\\"form-label\\\" for=\\\"footer-form-message\\\">Message</label>\\n <textarea class=\\\"form-input\\\" id=\\\"footer-form-message\\\" name=\\\"message\\\" data-constraints=\\\"@Required\\\"></textarea>\\n </div>\\n <div class=\\\"form-wrap\\\">\\n <button class=\\\"button button-md button-primary\\\" type=\\\"submit\\\">Send</button>\\n </div>\\n </form>\\n </div>\\n </div>\\n </div>\\n </div>\\n <div class=\\\"container\\\">\\n <!-- Rights-->\\n <p class=\\\"rights mb-0\\\"><span>&copy;2012 - 2020 All Rights Reserved</span> <a href=\\\"https://dotcms.com/company/policies/privacy-policy\\\">Terms of Use</a> <span>and</span> <a href=\\\"https://dotcms.com/company/policies/privacy-policy\\\">Privacy Policy</a></p>\\n </div>\\n</footer>\\n\\n<!-- Javascript-->\\n<script src=\\\"/application/themes/travel/js/core.min.js\\\"></script>\\n<script src=\\\"/application/themes/travel/js/script.js\\\"></script>\\n\\n \\n</body>\\n</html>\");",
"",
"});",
""
@@ -1302,6 +1302,827 @@
}
},
"response": []
},
{
"name": "GivenRequestByURI_ContainersFieldContainsFirstLevelFields",
"event": [
{
"listen": "test",
"script": {
"id": "6a1e31db-9d14-4334-b68d-ed3fc9c921a0",
"exec": [
"",
"let checker = (arr, target) => target.every(v => arr.includes(v));",
"",
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
"});",
"",
"pm.test(\"'Page' includes all properties\", function () {",
" var jsonData = pm.response.json();",
" var containers = jsonData.data.page.containers;",
"",
" var container1 = containers[0];",
"",
" // containers fields",
" pm.expect(container1.archived, 'FAILED:[container1.archived]').equal(false);",
" pm.expect(container1.categoryId, 'FAILED:[container1.categoryId]').equal(\"ad50ff07-6f7e-4b3c-a1ca-08667fecb28d\");",
" pm.expect(container1.deleted, 'FAILED:[container1.deleted]').equal(false);",
" pm.expect(container1.friendlyName, 'FAILED:[container1.friendlyName]').equal(\"container\");",
" pm.expect(container1.host.hostName, 'FAILED:[container1.host.hostName]').equal(\"demo.dotcms.com\");",
" pm.expect(container1.iDate, 'FAILED:[container1.iDate]').not.null;",
" pm.expect(container1.idate, 'FAILED:[container1.idate]').not.null;",
" pm.expect(container1.identifier, 'FAILED:[container1.identifier]').equal(\"69b3d24d-7e80-4be6-b04a-d352d16493ee\");",
" pm.expect(container1.inode, 'FAILED:[container1.inode]').equal(\"ad50ff07-6f7e-4b3c-a1ca-08667fecb28d\");",
" pm.expect(container1.languageId, 'FAILED:[container1.languageId]').equal(1);",
" pm.expect(container1.live, 'FAILED:[container1.live]').equal(true);",
" pm.expect(container1.locked, 'FAILED:[container1.locked]').equal(false);",
" pm.expect(container1.maxContentlets, 'FAILED:[container1.maxContentlets]').equal(25);",
" pm.expect(container1.modDate, 'FAILED:[container1.modDate]').not.null;",
" pm.expect(container1.modUser.userId, 'FAILED:[container1.modUser.userId]').equal(\"system\");",
" pm.expect(container1.name, 'FAILED:[container1.name]').equal(\"Default\");",
" pm.expect(container1.new, 'FAILED:[container1.new]').equal(false);",
" pm.expect(container1.notes, 'FAILED:[container1.notes]').equal(\"New File Container\");",
" pm.expect(container1.owner.userId, 'FAILED:[container1.owner.userId]').equal(\"dotcms.org.1\");",
" pm.expect(container1.parentPermissionable.hostName, 'FAILED:[container1.parentPermissionable.hostName]').equal(\"demo.dotcms.com\");",
" pm.expect(container1.path, 'FAILED:[container1.path]').equal(\"//demo.dotcms.com/application/containers/default/\");",
" pm.expect(container1.permissionId, 'FAILED:[container1.permissionId]').equal(\"69b3d24d-7e80-4be6-b04a-d352d16493ee\");",
" pm.expect(container1.permissionType, 'FAILED:[container1.permissionType]').equal(\"com.dotmarketing.portlets.containers.model.FileAssetContainer\");",
" pm.expect(container1.postLoop, 'FAILED:[container1.postLoop]').equal(\"#dotParse(\\\"//demo.dotcms.com/application/containers/default/postloop.vtl\\\")\");",
" pm.expect(container1.preLoop, 'FAILED:[container1.preLoop]').equal(\"#dotParse(\\\"//demo.dotcms.com/application/containers/default/preloop.vtl\\\")\");",
" pm.expect(container1.showOnMenu, 'FAILED:[container1.showOnMenu]').equal(false);",
" pm.expect(container1.sortOrder, 'FAILED:[container1.sortOrder]').equal(0);",
" pm.expect(container1.source, 'FAILED:[container1.source]').equal(\"FILE\");",
" pm.expect(container1.staticify, 'FAILED:[container1.staticify]').equal(false);",
" pm.expect(container1.title, 'FAILED:[container1.source]').equal(\"Default\");",
" pm.expect(container1.type, 'FAILED:[container1.type]').equal(\"containers\");",
" pm.expect(container1.useDiv, 'FAILED:[container1.useDiv]').equal(false);",
" pm.expect(container1.versionId, 'FAILED:[container1.versionId]').equal(\"69b3d24d-7e80-4be6-b04a-d352d16493ee\");",
" pm.expect(container1.versionType, 'FAILED:[container1.versionType]').equal(\"containers\");",
" pm.expect(container1.working, 'FAILED:[container1.working]').equal(true);",
" ",
" ",
"});",
""
],
"type": "text/javascript"
}
}
],
"protocolProfileBehavior": {
"disabledSystemHeaders": {
"user-agent": true
}
},
"request": {
"auth": {
"type": "basic",
"basic": [
{
"key": "username",
"value": "admin@dotcms.com",
"type": "string"
},
{
"key": "password",
"value": "admin",
"type": "string"
}
]
},
"method": "POST",
"header": [
{
"key": "Cookie",
"type": "text",
"value": "JSESSIONID=1DF38761AD8B360EF1AD42C4AF07EC35",
"disabled": true
},
{
"key": "User-Agent",
"type": "text",
"value": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36"
}
],
"body": {
"mode": "graphql",
"graphql": {
"query": "{\n page(url: \"/destinations/costa-rica\", pageMode: \"live\", languageId: \"1\") {\n containers {\n archived\n categoryId\n deleted\n friendlyName\n host {\n hostName\n }\n iDate\n idate\n identifier\n inode\n languageId\n live\n locked\n maxContentlets\n modDate\n modUser {\n userId\n }\n name\n new\n notes\n owner {\n userId\n }\n parentPermissionable {\n hostName\n }\n path\n permissionId\n permissionType\n postLoop\n preLoop\n showOnMenu\n sortOrder\n source\n staticify\n title\n type\n useDiv\n versionId\n versionType\n working\n }\n }\n}\n",
"variables": ""
}
},
"url": {
"raw": "{{serverURL}}/api/v1/graphql",
"host": [
"{{serverURL}}"
],
"path": [
"api",
"v1",
"graphql"
]
}
},
"response": []
},
{
"name": "GivenRequestByURI_ContainersFieldContainsRendered",
"event": [
{
"listen": "test",
"script": {
"id": "4bc167b7-fc5f-4022-b3d4-4753fc350d7c",
"exec": [
"",
"let checker = (arr, target) => target.every(v => arr.includes(v));",
"",
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
"});",
"",
"pm.test(\"'Page' includes all properties\", function () {",
" var jsonData = pm.response.json();",
" var containers = jsonData.data.page.containers;",
"",
" pm.expect(containers.length, 'FAILED:[containers.length]').equal(2);",
"",
" var container1rendered = containers[0].rendered;",
"",
" pm.expect(container1rendered.length, 'FAILED:[container1rendered.length]').equal(9);",
" // rendered fields",
" pm.expect(container1rendered[0].uuid, 'FAILED:[container1rendered[0].uuid]').equal(\"uuid-1\");",
" pm.expect(container1rendered[0].render, 'FAILED:[container1rendered[0].render]').equal(\"\\n <!-- Container Code: /application/containers/activity.vtl -->\\n\\n<a class=\\\"box-info\\\" href=\\\"/activities/hiking\\\">\\n\\t<img class=\\\"box-info-img\\\" src=\\\"/dA/6bbead53-3908-471c-966d-9e76c5dd9eba/image/270w/50q/hiking.jpg\\\" alt=\\\"Hiking\\\" width=\\\"270\\\" height=\\\"270\\\">\\n\\t<div class=\\\"box-info-body\\\">\\n\\t\\t<h4 class=\\\"box-info-title\\\">Hiking</h4>\\n\\t\\t<p class=\\\"box-info-text\\\">Guided hiking tours where our guides use their training as naturalists to point out animals, unique plants and nature ...</p>\\n\\t</div>\\n</a><div></div>\");",
"",
" pm.expect(container1rendered[1].uuid, 'FAILED:[container1rendered[1].uuid]').equal(\"uuid-2\");",
" pm.expect(container1rendered[1].render, 'FAILED:[container1rendered[1].render]').equal(\"\\n <h2>Custom Packages</h2>\\n<p>Are you looking for the ultimate in Costa Rica vacations? Our team oat TravelLux will design your custom vacation package. We do not sell cookie-cutter trips. Instead, we take the time to get to know your unique &ldquo;travel personality&rdquo;. Then we match you with a hand-picked selection of housing, tours &amp; transportation options. The result is a tailor-made Costa Rica journey that will fit you like a glove.</p>\\n<p>We email you a detailed travel itinerary filled with info, pictures and videos. Your TravelLux agent will work with you to adjust your vacation package until you are 100% satisfied. This makes for a seamless, worry-free experience that allows you to relax &amp; enjoy the voyage.</p>\\n<p><a href=\\\"/contact-us/\\\" class=\\\"btn btn-primary\\\">Book Now</a></p>\\n<article class=\\\"thumbnail-classic mb-5\\\">\\n <a class=\\\"thumbnail-classic-figure\\\" href=\\\"/dA/7b4ba67a-1793-4daf-bed9-f8ce054f1433/ski_holidays.png/1200w\\\" data-lightgallery=\\\"item\\\">\\n <picture>\\n <source media=\\\"(min-width: 800px)\\\" srcset=\\\"/dA/7b4ba67a-1793-4daf-bed9-f8ce054f1433/ski_holidays.png/1200w/50q/ski_holidays.png, /dA/7b4ba67a-1793-4daf-bed9-f8ce054f1433/ski_holidays.png/2400w/50q/ski_holidays.png 2x\\\">\\n <source media=\\\"(min-width: 480px)\\\" srcset=\\\"/dA/7b4ba67a-1793-4daf-bed9-f8ce054f1433/ski_holidays.png/600w/50q/ski_holidays.png, /dA/7b4ba67a-1793-4daf-bed9-f8ce054f1433//1200w/50q/ski_holidays.png 2x\\\">\\n <img src=\\\"/dA/7b4ba67a-1793-4daf-bed9-f8ce054f1433/ski_holidays.png/480w/50q/ski_holidays.png\\\" alt=\\\"With over 400\\\" of snow fall a year we have the longest season in the Continental US\\\" class=\\\"img-fluid\\\">\\n </picture>\\n </a>\\n <div class=\\\"thumbnail-classic-caption\\\">\\n <h4 class=\\\"thumbnail-classic-title\\\">Over 400\\\" of snow a year</h4>\\n <p class=\\\"thumbnail-classic-text\\\">With over 400\\\" of snow fall a year we have the longest season in the Continental US</p>\\n <a class=\\\"thumbnail-classic-link mdi mdi-plus-circle-outline\\\" href=\\\"/dA/7b4ba67a-1793-4daf-bed9-f8ce054f1433/1200w/50q/ski_holidays.png\\\" data-lightgallery=\\\"item\\\"></a>\\n </div> \\n</article>\\n<div></div>\");",
"",
" pm.expect(container1rendered[2].uuid, 'FAILED:[container1rendered[2].uuid]').equal(\"uuid-3\");",
" pm.expect(container1rendered[2].render, 'FAILED:[container1rendered[2].render]').equal(\"\\n <!-- Container Code: /application/containers/video.vtl -->\\n\\n<a class=\\\"video-cover bg-overlay-30 mb-5\\\" data-lightbox=\\\"iframe\\\" href=\\\"https://www.youtube.com/watch?v=1qw4ayRc1t8\\\">\\n <div class=\\\"video-img\\\" style=\\\"background-image: url(https://i.ytimg.com/vi/1qw4ayRc1t8/hqdefault.jpg);\\\"></div>\\n <span class=\\\"icon mdi mdi-play video-cover-icon\\\"></span>\\n</a><div></div>\");",
" ",
" pm.expect(container1rendered[3].uuid, 'FAILED:[container1rendered[3].uuid]').equal(\"uuid-4\");",
" pm.expect(container1rendered[3].render, 'FAILED:[container1rendered[3].render]').equal(\"\\n \\r\\n<h1 class=\\\"text-center\\\">Featured Activities</h1>\\n<div class=\\\"row justify-content-center\\\">\\n </div>\\n<div></div>\");",
"",
" pm.expect(container1rendered[4].uuid, 'FAILED:[container1rendered[4].uuid]').equal(\"uuid-5\");",
" pm.expect(container1rendered[4].render, 'FAILED:[container1rendered[4].render]').equal(\"\\n <h2>Costa Rica Family Adventure</h2>\\n<p>Tropical rainforests, zip lines, monkey sightings and waterfall swims make for the most unforgettable vacation&mdash;and photos.</p>\\n<p>Abundance abounds in Costa Rica: beautiful beaches, friendly people and endless adventures to embark upon. We dabble in it all with a family-friendly itinerary that will get everyone&rsquo;s heart pumping. Jump right in to adventure life in Costa Rica at Arenal Volcano where we zip along the treetops, get a glimpse of rural Tico life on a local farm, rappel down a canyon waterfall that lies deep in the tropical rainforest, and soak in thermal hot springs.</p>\\n<p>Journey south, crossing Lake Arenal by boat, and dive deep into the Monteverde cloud forest. Your hotel is located in the reserve, home to eight different biological zones and thousands of plants and animals. Keep your eyes peeled for monkeys and quetzals during the day and embark on night walks with your guide to experience a whole different world after dark. Spend a unique day exploring the forest giants from the ground up&mdash;climbing up specially outfitted trees and then exploring the forest on foot with your naturalist guide.</p><div></div>\");",
"",
" pm.expect(container1rendered[5].uuid, 'FAILED:[container1rendered[5].uuid]').equal(\"uuid-6\");",
" pm.expect(container1rendered[5].render, 'FAILED:[container1rendered[5].render]').equal(\"\\n <h3>Adventure travel done right</h3>\\n<p>Wherever you want to go, whatever you want to get into, we&rsquo;ve got a trip that&rsquo;ll make your dream vacation come true. Visit like a local, explore at your own pace, and eat like a king (or a vegan king, if that&rsquo;s more your thing).<strong><br /></strong></p><div></div>\");",
"",
" pm.expect(container1rendered[6].uuid, 'FAILED:[container1rendered[6].uuid]').equal(\"uuid-7\");",
" pm.expect(container1rendered[6].render, 'FAILED:[container1rendered[6].render]').equal(\"\\n <h3>Experience the world with us</h3>\\n<p>We practice Leave No Trace principles on all our trips and work with local people and businesses as often as possible.Our expert guides don&rsquo;t just know these places&mdash;they call them home. Their local knowledge unveils worlds few get to see. Hike, bike, swim and paddle with like-minded travelers who share your passion for adventure and the outdoors.</p><div></div>\");",
"",
" pm.expect(container1rendered[7].uuid, 'FAILED:[container1rendered[7].uuid]').equal(\"uuid-8\");",
" pm.expect(container1rendered[7].render, 'FAILED:[container1rendered[7].render]').equal(\"\\n \\n<article class=\\\"thumbnail-classic mb-5\\\">\\n <a class=\\\"thumbnail-classic-figure\\\" href=\\\"/dA/bfee8f08-a6d1-4fd7-8f70-476e62663eca/waterfall-costa-rica.jpg/1200w\\\" data-lightgallery=\\\"item\\\">\\n <picture>\\n <source media=\\\"(min-width: 800px)\\\" srcset=\\\"/dA/bfee8f08-a6d1-4fd7-8f70-476e62663eca/waterfall-costa-rica.jpg/1200w/50q/waterfall-costa-rica.jpg, /dA/bfee8f08-a6d1-4fd7-8f70-476e62663eca/waterfall-costa-rica.jpg/2400w/50q/waterfall-costa-rica.jpg 2x\\\">\\n <source media=\\\"(min-width: 480px)\\\" srcset=\\\"/dA/bfee8f08-a6d1-4fd7-8f70-476e62663eca/waterfall-costa-rica.jpg/600w/50q/waterfall-costa-rica.jpg, /dA/bfee8f08-a6d1-4fd7-8f70-476e62663eca//1200w/50q/waterfall-costa-rica.jpg 2x\\\">\\n <img src=\\\"/dA/bfee8f08-a6d1-4fd7-8f70-476e62663eca/waterfall-costa-rica.jpg/480w/50q/waterfall-costa-rica.jpg\\\" alt=\\\"From breathtaking waterfalls to amazing rain forest, Costa Rica has something for every Eco adventurer.\\\" class=\\\"img-fluid\\\">\\n </picture>\\n </a>\\n <div class=\\\"thumbnail-classic-caption\\\">\\n <h4 class=\\\"thumbnail-classic-title\\\">Fall into Costa Rica</h4>\\n <p class=\\\"thumbnail-classic-text\\\">From breathtaking waterfalls to amazing rain forest, Costa Rica has something for every Eco adventurer.</p>\\n <a class=\\\"thumbnail-classic-link mdi mdi-plus-circle-outline\\\" href=\\\"/dA/bfee8f08-a6d1-4fd7-8f70-476e62663eca/1200w/50q/waterfall-costa-rica.jpg\\\" data-lightgallery=\\\"item\\\"></a>\\n </div> \\n</article>\\n<div></div>\");",
"",
" pm.expect(container1rendered[8].uuid, 'FAILED:[container1rendered[8].uuid]').equal(\"uuid-9\");",
" pm.expect(container1rendered[8].render, 'FAILED:[container1rendered[8].render]').equal(\"\\n <div></div>\");",
" ",
"});",
""
],
"type": "text/javascript"
}
}
],
"protocolProfileBehavior": {
"disabledSystemHeaders": {
"user-agent": true
}
},
"request": {
"auth": {
"type": "basic",
"basic": [
{
"key": "username",
"value": "admin@dotcms.com",
"type": "string"
},
{
"key": "password",
"value": "admin",
"type": "string"
}
]
},
"method": "POST",
"header": [
{
"key": "Cookie",
"type": "text",
"value": "JSESSIONID=1DF38761AD8B360EF1AD42C4AF07EC35",
"disabled": true
},
{
"key": "User-Agent",
"type": "text",
"value": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36"
}
],
"body": {
"mode": "graphql",
"graphql": {
"query": "{\n page(url: \"/destinations/costa-rica\", pageMode: \"live\", languageId: \"1\") {\n containers {\n rendered {\n uuid\n render\n }\n }\n \n }\n}\n",
"variables": ""
}
},
"url": {
"raw": "{{serverURL}}/api/v1/graphql",
"host": [
"{{serverURL}}"
],
"path": [
"api",
"v1",
"graphql"
]
}
},
"response": []
},
{
"name": "GivenRequestByURI_ContainersFieldContainsContainerStructure",
"event": [
{
"listen": "test",
"script": {
"id": "81e885fb-9c24-4249-b80a-d3fab80927b7",
"exec": [
"",
"let checker = (arr, target) => target.every(v => arr.includes(v));",
"",
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
"});",
"",
"pm.test(\"'Page' includes all properties\", function () {",
" var jsonData = pm.response.json();",
"",
" pm.expect(jsonData.data.page.containers.length, 'FAILED:[jsonData.data.page.containers.length]').equal(2);",
"",
" var containerStructuresFirstContainer = jsonData.data.page.containers[0].containerStructures;",
"",
" pm.expect(containerStructuresFirstContainer.length, 'FAILED:[containerStructuresFirstContainer.length]').equal(9);",
"",
" var containerStructure = containerStructuresFirstContainer[0];",
"",
" // containerStructure fields",
" pm.expect(containerStructure.id, 'FAILED:[containerStructure.id]').equal(\"86a06a2e-ba40-43d4-a3ad-3a870d43bfc1\");",
" pm.expect(containerStructure.structureId, 'FAILED:[containerStructure.structureId]').equal(\"799f176a-d32e-4844-a07c-1b5fcd107578\");",
" pm.expect(containerStructure.containerInode, 'FAILED:[containerStructure.containerInode]').equal(\"61f070f0-8def-4858-ab76-f22bcb6feaaa\");",
" pm.expect(containerStructure.containerId, 'FAILED:[containerStructure.containerId]').equal(\"69b3d24d-7e80-4be6-b04a-d352d16493ee\");",
" pm.expect(containerStructure.code, 'FAILED:[containerStructure.code]').equal(\"#dotParse(\\\"//demo.dotcms.com/application/containers/default/blog.vtl\\\")\");",
"",
" ",
" ",
"});",
""
],
"type": "text/javascript"
}
}
],
"protocolProfileBehavior": {
"disabledSystemHeaders": {
"user-agent": true
}
},
"request": {
"auth": {
"type": "basic",
"basic": [
{
"key": "username",
"value": "admin@dotcms.com",
"type": "string"
},
{
"key": "password",
"value": "admin",
"type": "string"
}
]
},
"method": "POST",
"header": [
{
"key": "Cookie",
"type": "text",
"value": "JSESSIONID=1DF38761AD8B360EF1AD42C4AF07EC35",
"disabled": true
},
{
"key": "User-Agent",
"type": "text",
"value": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36"
}
],
"body": {
"mode": "graphql",
"graphql": {
"query": "{\n page(url: \"/destinations/costa-rica\", pageMode: \"live\", languageId: \"1\") {\n containers {\n containerStructures {\n id\n structureId\n containerInode\n containerId\n code\n }\n }\n \n }\n}\n",
"variables": ""
}
},
"url": {
"raw": "{{serverURL}}/api/v1/graphql",
"host": [
"{{serverURL}}"
],
"path": [
"api",
"v1",
"graphql"
]
}
},
"response": []
},
{
"name": "pre_ImportBundleWithContentWithTagsAndCats",
"event": [
{
"listen": "test",
"script": {
"id": "d2b9fac1-95f0-48cf-a257-1a20155a1fb6",
"exec": [
"pm.test(\"Bundle uploaded sucessfully\", function () {",
" pm.response.to.have.status(200);",
"",
" var jsonData = pm.response.json();",
" console.log(jsonData);",
"",
" pm.expect(jsonData[\"bundleName\"]).to.eql(\"costa-rica-family-cats-tags.tar.gz\");",
" pm.expect(jsonData[\"status\"]).to.eql(\"SUCCESS\");",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"auth": {
"type": "basic",
"basic": [
{
"key": "username",
"value": "admin@dotcms.com",
"type": "string"
},
{
"key": "password",
"value": "admin",
"type": "string"
}
]
},
"method": "POST",
"header": [
{
"key": "Content-Type",
"type": "text",
"value": "application/octet-stream"
},
{
"key": "Content-Disposition",
"type": "text",
"value": "attachment"
}
],
"body": {
"mode": "formdata",
"formdata": [
{
"key": "file",
"type": "file",
"src": "resources/GraphQL/costa-rica-family-cats-tags.tar.gz"
}
],
"options": {
"formdata": {}
}
},
"url": {
"raw": "{{serverURL}}/api/bundle?sync=true",
"host": [
"{{serverURL}}"
],
"path": [
"api",
"bundle"
],
"query": [
{
"key": "sync",
"value": "true"
},
{
"key": "AUTH_TOKEN",
"value": "",
"disabled": true
}
]
},
"description": "Imports a Bundle that includes:\n* Content `Costa Rica Family Adventure` with \n`taggie` field with `costa rica` and `winterenthusiast` tags and `cat` field with `All Incluse` and `Tours` categories"
},
"response": []
},
{
"name": "GivenRequestByURI_ContainersFieldContainsContainerContentlets",
"event": [
{
"listen": "test",
"script": {
"id": "d38eb470-9084-4546-9048-740c9c9fac7c",
"exec": [
"",
"let checker = (arr, target) => target.every(v => arr.includes(v));",
"",
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
"});",
"",
"pm.test(\"'Page' includes all properties\", function () {",
" var jsonData = pm.response.json();",
" var containerContentlets = jsonData.data.page.containers[0].containerContentlets;",
"",
" var containerContentlets1 = containerContentlets[0];",
" // containerContentlets fields",
" pm.expect(containerContentlets1.uuid, 'FAILED:[containerContentlets1.uuid]').equal(\"uuid-9\");",
" pm.expect(containerContentlets1.contentlets[0].identifier, 'FAILED:[containerContentlets1.contentlets[0]].identifier').equal(\"7c9cb3a7-bb68-4fd0-b21d-03ec4be491a7\");",
" pm.expect(containerContentlets1.contentlets[0].title, 'FAILED:[containerContentlets1.contentlets[0]].title').equal(\"Costa Rica FAQ\");",
"",
" var containerContentlets2 = containerContentlets[1];",
" // containerContentlets fields",
" pm.expect(containerContentlets2.uuid, 'FAILED:[containerContentlets2.uuid]').equal(\"uuid-1\");",
" pm.expect(containerContentlets2.contentlets[0].identifier, 'FAILED:[containerContentlets2.contentlets[0]].identifier').equal(\"6bbead53-3908-471c-966d-9e76c5dd9eba\");",
" pm.expect(containerContentlets2.contentlets[0].title, 'FAILED:[containerContentlets2.contentlets[0]].title').equal(\"Hiking\");",
"",
" var containerContentlets3 = containerContentlets[2];",
" // containerContentlets fields",
" pm.expect(containerContentlets3.uuid, 'FAILED:[containerContentlets3.uuid]').equal(\"uuid-3\");",
" pm.expect(containerContentlets3.contentlets[0].identifier, 'FAILED:[containerContentlets3.contentlets[0]].identifier').equal(\"f40c6030-3532-4e75-9ca8-0d92261264e3\");",
" pm.expect(containerContentlets3.contentlets[0].title, 'FAILED:[containerContentlets3.contentlets[0]].title').equal(\"Nat Geo Top Rated Eco Lodge, Episode 2 of 4\");",
"",
" var containerContentlets4 = containerContentlets[3];",
" // containerContentlets fields",
" pm.expect(containerContentlets4.uuid, 'FAILED:[containerContentlets4.uuid]').equal(\"uuid-4\");",
" pm.expect(containerContentlets4.contentlets[0].identifier, 'FAILED:[containerContentlets4.contentlets[0]].identifier').equal(\"fb6a06da-8c0f-4828-99c5-91b03b17eaf7\");",
" pm.expect(containerContentlets4.contentlets[0].title, 'FAILED:[containerContentlets4.contentlets[0]].title').equal(\"Destination Related Activities\");",
"",
" var containerContentlets5 = containerContentlets[4];",
" // containerContentlets fields",
" pm.expect(containerContentlets5.uuid, 'FAILED:[containerContentlets5.uuid]').equal(\"uuid-5\");",
" pm.expect(containerContentlets5.contentlets[0].identifier, 'FAILED:[containerContentlets5.contentlets[0]].identifier').equal(\"3e6533b7-c3ba-4ec4-8bca-6a88d54cd037\");",
" pm.expect(containerContentlets5.contentlets[0].title, 'FAILED:[containerContentlets5.contentlets[0]].title').equal(\"Costa Rica Family Adventure\");",
" // tags ",
" pm.expect(containerContentlets5.contentlets[0].taggies, 'FAILED:[containerContentlets5.contentlets[0].taggies]').equal(\"costa rica,winterenthusiast:persona\");",
" // cats ",
" pm.expect(containerContentlets5.contentlets[0].cats, 'FAILED:[containerContentlets5.contentlets[0].cats]').equal(\"[{allInclusive=All Inclusive}, {tours=Tours}]\");",
"",
"",
" var containerContentlets6 = containerContentlets[5];",
" // containerContentlets fields",
" pm.expect(containerContentlets6.uuid, 'FAILED:[containerContentlets6.uuid]').equal(\"uuid-6\");",
" pm.expect(containerContentlets6.contentlets[0].identifier, 'FAILED:[containerContentlets6.contentlets[0]].identifier').equal(\"57abf7cc-8b91-4cb9-8e5d-bc7f0ff54665\");",
" pm.expect(containerContentlets6.contentlets[0].title, 'FAILED:[containerContentlets6.contentlets[0]].title').equal(\"Adventure travel done right\");",
"",
" var containerContentlets7 = containerContentlets[6];",
" // containerContentlets fields",
" pm.expect(containerContentlets7.uuid, 'FAILED:[containerContentlets7.uuid]').equal(\"uuid-7\");",
" pm.expect(containerContentlets7.contentlets[0].identifier, 'FAILED:[containerContentlets7.contentlets[0]].identifier').equal(\"5a4e4ef3-884f-4258-9009-f8fa83236242\");",
" pm.expect(containerContentlets7.contentlets[0].title, 'FAILED:[containerContentlets7.contentlets[0]].title').equal(\"Experience the world with us\");",
"",
" var containerContentlets8 = containerContentlets[7];",
" // containerContentlets fields",
" pm.expect(containerContentlets8.uuid, 'FAILED:[containerContentlets8.uuid]').equal(\"uuid-8\");",
" pm.expect(containerContentlets8.contentlets[0].identifier, 'FAILED:[containerContentlets8.contentlets[0]].identifier').equal(\"bfee8f08-a6d1-4fd7-8f70-476e62663eca\");",
" pm.expect(containerContentlets8.contentlets[0].title, 'FAILED:[containerContentlets8.contentlets[0]].title').equal(\"Fall into Costa Rica\");",
"",
" var containerContentlets9 = containerContentlets[8];",
" // containerContentlets fields",
" pm.expect(containerContentlets9.uuid, 'FAILED:[containerContentlets9.uuid]').equal(\"uuid-2\");",
" pm.expect(containerContentlets9.contentlets[0].identifier, 'FAILED:[containerContentlets9.contentlets[0]].identifier').equal(\"f827c41a-5689-4733-b4d7-916d3a31c9c6\");",
" pm.expect(containerContentlets9.contentlets[0].title, 'FAILED:[containerContentlets9.contentlets[0]].title').equal(\"Costa Rica Intro\");",
"",
" pm.expect(containerContentlets9.contentlets[1].identifier, 'FAILED:[containerContentlets9.contentlets[0]].identifier').equal(\"7b4ba67a-1793-4daf-bed9-f8ce054f1433\");",
" pm.expect(containerContentlets9.contentlets[1].title, 'FAILED:[containerContentlets9.contentlets[0]].title').equal(\"Over 400\\\" of snow a year\");",
" ",
" ",
"});",
""
],
"type": "text/javascript"
}
}
],
"protocolProfileBehavior": {
"disabledSystemHeaders": {
"user-agent": true
}
},
"request": {
"auth": {
"type": "basic",
"basic": [
{
"key": "username",
"value": "admin@dotcms.com",
"type": "string"
},
{
"key": "password",
"value": "admin",
"type": "string"
}
]
},
"method": "POST",
"header": [
{
"key": "Cookie",
"type": "text",
"value": "JSESSIONID=1DF38761AD8B360EF1AD42C4AF07EC35",
"disabled": true
},
{
"key": "User-Agent",
"type": "text",
"value": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36"
}
],
"body": {
"mode": "graphql",
"graphql": {
"query": "{\n page(url: \"/destinations/costa-rica\", pageMode: \"live\", languageId: \"1\") {\n containers {\n containerContentlets {\n uuid\n contentlets {\n identifier\n title\n taggies: map(key:\"taggies\") \n cats: map(key:\"cats\")\n }\n }\n }\n \n }\n}\n",
"variables": ""
}
},
"url": {
"raw": "{{serverURL}}/api/v1/graphql",
"host": [
"{{serverURL}}"
],
"path": [
"api",
"v1",
"graphql"
]
}
},
"response": []
},
{
"name": "pre_ImportBundleWithMexicoPageAndRule",
"event": [
{
"listen": "test",
"script": {
"id": "63944a3e-4ff3-472b-b58b-c7ad0008b659",
"exec": [
"pm.test(\"Bundle uploaded sucessfully\", function () {",
" pm.response.to.have.status(200);",
"",
" var jsonData = pm.response.json();",
" console.log(jsonData);",
"",
" pm.expect(jsonData[\"bundleName\"]).to.eql(\"mexico-pageasset.tar.gz\");",
" pm.expect(jsonData[\"status\"]).to.eql(\"SUCCESS\");",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"auth": {
"type": "basic",
"basic": [
{
"key": "username",
"value": "admin@dotcms.com",
"type": "string"
},
{
"key": "password",
"value": "admin",
"type": "string"
}
]
},
"method": "POST",
"header": [
{
"key": "Content-Type",
"type": "text",
"value": "application/octet-stream"
},
{
"key": "Content-Disposition",
"type": "text",
"value": "attachment"
}
],
"body": {
"mode": "formdata",
"formdata": [
{
"key": "file",
"type": "file",
"src": "resources/GraphQL/mexico-pageasset.tar.gz"
}
],
"options": {
"formdata": {}
}
},
"url": {
"raw": "{{serverURL}}/api/bundle?sync=true",
"host": [
"{{serverURL}}"
],
"path": [
"api",
"bundle"
],
"query": [
{
"key": "sync",
"value": "true"
},
{
"key": "AUTH_TOKEN",
"value": "",
"disabled": true
}
]
}
},
"response": []
},
{
"name": "GivenRequestByURI_FireRulesTrue_PREVIEW_MODE_pageRuleShouldFire",
"event": [
{
"listen": "test",
"script": {
"id": "f8e9a9c0-b8f4-407a-aa02-5cd2f280e6fc",
"exec": [
"",
"let checker = (arr, target) => target.every(v => arr.includes(v));",
"",
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
"});",
"",
"pm.test(\"Rule fired\", function () {",
" var rulesFired = pm.response.headers.get(\"rulesFired\");",
" pm.expect(rulesFired, 'FAILED:[rulesFired').equal(\"true\");",
" ",
"});",
""
],
"type": "text/javascript"
}
}
],
"protocolProfileBehavior": {
"disabledSystemHeaders": {
"user-agent": true
}
},
"request": {
"auth": {
"type": "basic",
"basic": [
{
"key": "username",
"value": "admin@dotcms.com",
"type": "string"
},
{
"key": "password",
"value": "admin",
"type": "string"
}
]
},
"method": "POST",
"header": [
{
"key": "Cookie",
"type": "text",
"value": "JSESSIONID=1DF38761AD8B360EF1AD42C4AF07EC35",
"disabled": true
},
{
"key": "User-Agent",
"type": "text",
"value": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36"
}
],
"body": {
"mode": "graphql",
"graphql": {
"query": "{\n page(url: \"/destinations/mexico\", pageMode: \"preview_mode\", languageId: \"1\", fireRules: true) {\n title\n }\n}\n",
"variables": ""
}
},
"url": {
"raw": "{{serverURL}}/api/v1/graphql",
"host": [
"{{serverURL}}"
],
"path": [
"api",
"v1",
"graphql"
]
}
},
"response": []
},
{
"name": "GivenRequestByURI_FireRulesFALSE_PREVIEW_MODE_pageRuleShouldNOTFire",
"event": [
{
"listen": "test",
"script": {
"id": "6ec045de-e014-4084-a5b3-78cd87b3f513",
"exec": [
"",
"let checker = (arr, target) => target.every(v => arr.includes(v));",
"",
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
"});",
"",
"pm.test(\"Rule NOT fired\", function () {",
" var rulesFired = pm.response.headers.get(\"rulesFired\");",
" pm.expect(rulesFired, 'FAILED:[rulesFired').equal(undefined);",
" ",
"});",
""
],
"type": "text/javascript"
}
}
],
"protocolProfileBehavior": {
"disabledSystemHeaders": {
"user-agent": true
}
},
"request": {
"auth": {
"type": "basic",
"basic": [
{
"key": "username",
"value": "admin@dotcms.com",
"type": "string"
},
{
"key": "password",
"value": "admin",
"type": "string"
}
]
},
"method": "POST",
"header": [
{
"key": "Cookie",
"type": "text",
"value": "JSESSIONID=1DF38761AD8B360EF1AD42C4AF07EC35",
"disabled": true
},
{
"key": "User-Agent",
"type": "text",
"value": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36"
}
],
"body": {
"mode": "graphql",
"graphql": {
"query": "{\n page(url: \"/destinations/costa-rica\", pageMode: \"preview_mode\", languageId: \"1\", fireRules: false) {\n title\n viewAs {\n persona {\n name\n }\n }\n }\n}\n",
"variables": ""
}
},
"url": {
"raw": "{{serverURL}}/api/v1/graphql",
"host": [
"{{serverURL}}"
],
"path": [
"api",
"v1",
"graphql"
]
}
},
"response": []
}
],
"protocolProfileBehavior": {}
@@ -3683,87 +4504,87 @@
],
"variable": [
{
"id": "16acbba9-2597-4be2-9efb-a49ef05e684f",
"id": "dd31fbc5-8c36-4c8a-b13d-906a124230ca",
"key": "languageId",
"value": 1592433023079
},
{
"id": "de1be12b-269e-44b7-bb23-ac4bca9ea7db",
"id": "1e41caa2-ed88-4fc2-833e-b916fee9488e",
"key": "contentId",
"value": null
},
{
"id": "abe08a03-a2a2-4058-baca-4543dbefb483",
"id": "f5d52420-cf17-4f6e-922b-16ba0371efe0",
"key": "contentTypeVariable",
"value": "MyCustomType53380831"
},
{
"id": "c402b9c9-9f3b-40a6-9af7-8f1e7f5d2d9f",
"id": "f957965d-2016-4f2d-a32f-446be57840f0",
"key": "contentIdentifier",
"value": "78a7fd09-e804-4d6d-96e9-ca4165ab29ec"
},
{
"id": "d9ca3e29-84c9-4501-8103-816a79960c6d",
"id": "c1dd6d87-be5e-4639-b32d-546a1bd0b7b3",
"key": "structureVariable",
"value": "myStructure94983807"
},
{
"id": "0f938161-d161-45f2-b9a7-85e6944eec44",
"id": "0d164387-1836-4fa1-8759-416cef14f497",
"key": "imageContentTypeVariable",
"value": "MyImageType22920449"
},
{
"id": "a21e0513-bd8f-4020-bb18-8243717aab3e",
"id": "0fbbe0c1-8607-45e5-b5c4-768bfd120d7d",
"key": "imageContentIdentifier",
"value": "f1bde8d3-0502-482e-932b-88f7ef470a31"
},
{
"id": "d7e52160-e900-495c-ac20-5034dd3c7cdd",
"id": "8dec6289-2ce9-40d9-837e-8ef42a9d53ad",
"key": "imageFieldVariable",
"value": "myImage"
},
{
"id": "7b22de2c-86d8-4c12-8294-0c96fd3042df",
"id": "1de3de59-2edd-4ce8-b615-321544b2720b",
"key": "showOnMenu",
"value": true
},
{
"id": "ea2d16b6-194e-438e-b950-38ecfe6215b6",
"id": "70f058da-1ccd-4ad2-a6f0-325a3859ae68",
"key": "sortOrder",
"value": 2
},
{
"id": "5e930148-2643-4514-98de-bf8af2fa4e34",
"id": "be967554-b004-47d6-b013-42c7ccd54bf6",
"key": "fileFieldVariable",
"value": "myFile"
},
{
"id": "d22b5bbd-c0a8-4096-8fc2-9a838976a333",
"id": "3518c31f-7c8e-4cd7-b901-c75b9a562649",
"key": "fileContentIdentifier",
"value": "b3e1f5ca-b6ac-4138-990d-9a96fff815cb"
},
{
"id": "3939ec8e-09ce-4eb8-8b4d-0bb7f8749a5b",
"id": "9f7f05a2-16c8-4e81-a2a0-fc3c6cc80d98",
"key": "childContentTypeVariable",
"value": "childType23553509"
},
{
"id": "9beb1284-309b-4056-b9af-ea9be8169335",
"id": "9c16ca70-6f94-4894-b247-b415ecf6fc6e",
"key": "parentContentTypeVariable",
"value": "parentType65070125"
},
{
"id": "6c71d02c-7323-40f6-b1c0-c4ce25c556f1",
"id": "8bc8816e-583b-46a1-ac63-cf5f5c113591",
"key": "relFieldVariable",
"value": "rel"
},
{
"id": "cc50683e-f7d7-448f-ad7f-91ae133f3242",
"id": "fbdd03fa-bc82-4fd8-8b3a-2059cf105119",
"key": "childContentIdentifier",
"value": "6a0d1ea2-a282-45d1-a838-88816ad62ac6"
},
{
"id": "b6c592b4-b3e8-42b0-afd1-648fc8ad7b2a",
"id": "a90210cb-0915-4165-ac0e-cadc549d551d",
"key": "parentContentIdentifier",
"value": "2921b23c-2a96-4de9-afee-832aa01500db"
}
@@ -0,0 +1,2052 @@
{
"info": {
"_postman_id": "f01856b1-ba87-4b3a-baec-db23aa455f21",
"name": "Integrity Checker From Sender",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"item": [
{
"name": "Generate ",
"item": [
{
"name": "old Way",
"item": [
{
"name": "Happy path",
"item": [
{
"name": "set USE_JWT_TOKEN_IN_PUSH_PUBLISH to false",
"request": {
"auth": {
"type": "basic",
"basic": [
{
"key": "password",
"value": "admin",
"type": "string"
},
{
"key": "username",
"value": "admin@dotcms.com",
"type": "string"
}
]
},
"method": "PUT",
"header": [],
"body": {
"mode": "raw",
"raw": "{\n \"USE_JWT_TOKEN_IN_PUSH_PUBLISH\": false\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{serverURL}}/api//v1/configuration",
"host": [
"{{serverURL}}"
],
"path": [
"api",
"",
"v1",
"configuration"
]
}
},
"response": []
},
{
"name": "login",
"event": [
{
"listen": "test",
"script": {
"id": "d1325914-a9be-42cc-8c1f-63c2192d2fba",
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"auth": {
"type": "basic",
"basic": [
{
"key": "password",
"value": "admin",
"type": "string"
},
{
"key": "username",
"value": "admin@dotcms.com",
"type": "string"
}
]
},
"method": "POST",
"header": [],
"body": {
"mode": "raw",
"raw": "{\n \"backEndLogin\": true,\n \"country\": \"US\",\n \"language\": \"en\",\n \"password\": \"admin\",\n \"rememberMe\": false,\n \"userId\": \"admin@dotcms.com\"\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{serverURL}}/api/v1/authentication",
"host": [
"{{serverURL}}"
],
"path": [
"api",
"v1",
"authentication"
]
}
},
"response": []
},
{
"name": "create environment",
"event": [
{
"listen": "prerequest",
"script": {
"id": "fb45d570-c6c5-46fd-b3b2-1c8a1ef74c03",
"exec": [
"",
"environmentName = 'environment-' + new Date().getMilliseconds();",
"pm.collectionVariables.set(\"environmentName\", environmentName);",
""
],
"type": "text/javascript"
}
},
{
"listen": "test",
"script": {
"id": "7ef880d0-9d8b-4e0b-9265-3d9abdb71b7f",
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
"",
" var environmentId = pm.response.text();",
" pm.collectionVariables.set(\"environmentId\", environmentId);",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"auth": {
"type": "basic",
"basic": [
{
"key": "password",
"value": "admin",
"type": "string"
},
{
"key": "username",
"value": "admin@dotcms.com",
"type": "string"
}
]
},
"method": "POST",
"header": [
{
"key": "Origin",
"value": "{{origin}}",
"type": "text"
}
],
"body": {
"mode": "formdata",
"formdata": []
},
"url": {
"raw": "{{serverURL}}/DotAjaxDirector/com.dotcms.publisher.environment.ajax.EnvironmentAjaxAction/cmd/addEnvironment?environmentName={{environmentName}}&pushType=pushToOne&whoCanUseSelect=e7d4e34e-5127-45fc-8123-d48b62d510e3&whoCanUse=e7d4e34e-5127-45fc-8123-d48b62d510e3",
"host": [
"{{serverURL}}"
],
"path": [
"DotAjaxDirector",
"com.dotcms.publisher.environment.ajax.EnvironmentAjaxAction",
"cmd",
"addEnvironment"
],
"query": [
{
"key": "environmentName",
"value": "{{environmentName}}"
},
{
"key": "pushType",
"value": "pushToOne"
},
{
"key": "whoCanUseSelect",
"value": "e7d4e34e-5127-45fc-8123-d48b62d510e3"
},
{
"key": "whoCanUse",
"value": "e7d4e34e-5127-45fc-8123-d48b62d510e3"
}
]
}
},
"response": []
},
{
"name": "create end point",
"event": [
{
"listen": "prerequest",
"script": {
"id": "d62ce929-8806-4d31-92bc-cf8cc1814f69",
"exec": [
"endpointName = 'endpoint-' + new Date().getMilliseconds();",
"pm.collectionVariables.set(\"endpointName\", endpointName);",
""
],
"type": "text/javascript"
}
},
{
"listen": "test",
"script": {
"id": "ab0964bd-799b-47c5-9271-5042516bded9",
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
"",
" var environmentId = pm.response.text();",
" pm.collectionVariables.set(\"endpintId\", environmentId);",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"auth": {
"type": "basic",
"basic": [
{
"key": "password",
"value": "admin",
"type": "string"
},
{
"key": "username",
"value": "admin@dotcms.com",
"type": "string"
}
]
},
"method": "POST",
"header": [
{
"key": "Origin",
"type": "text",
"value": "{{origin}}"
}
],
"body": {
"mode": "formdata",
"formdata": []
},
"url": {
"raw": "{{serverURL}}/DotAjaxDirector/com.dotcms.publisher.endpoint.ajax.PublishingEndpointAjaxAction/cmd/addEndpoint?sending=false&serverName={{endpointName}}&environmentId={{environmentId}}&protocol=http&address=localhost&port=8080&authKey=123&enabled=on",
"host": [
"{{serverURL}}"
],
"path": [
"DotAjaxDirector",
"com.dotcms.publisher.endpoint.ajax.PublishingEndpointAjaxAction",
"cmd",
"addEndpoint"
],
"query": [
{
"key": "sending",
"value": "false"
},
{
"key": "serverName",
"value": "{{endpointName}}"
},
{
"key": "environmentId",
"value": "{{environmentId}}"
},
{
"key": "protocol",
"value": "http"
},
{
"key": "address",
"value": "localhost"
},
{
"key": "port",
"value": "8080"
},
{
"key": "authKey",
"value": "123"
},
{
"key": "enabled",
"value": "on"
}
]
}
},
"response": []
},
{
"name": "create receiver",
"event": [
{
"listen": "prerequest",
"script": {
"id": "4e2de1ab-af19-4ebe-9743-3f9655047bfa",
"exec": [
"receiverName = 'receiverName-' + new Date().getMilliseconds();",
"pm.collectionVariables.set(\"receiverName\", receiverName);",
""
],
"type": "text/javascript"
}
},
{
"listen": "test",
"script": {
"id": "f367f14e-7818-4aaf-bcc4-5cbecd849609",
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"auth": {
"type": "basic",
"basic": [
{
"key": "password",
"value": "admin",
"type": "string"
},
{
"key": "username",
"value": "admin@dotcms.com",
"type": "string"
}
]
},
"method": "POST",
"header": [
{
"key": "Origin",
"type": "text",
"value": "{{origin}}"
}
],
"body": {
"mode": "formdata",
"formdata": []
},
"url": {
"raw": "{{serverURL}}/DotAjaxDirector/com.dotcms.publisher.endpoint.ajax.PublishingEndpointAjaxAction/cmd/addEndpoint?sending=true&serverName={{receiverName}}&environmentId={{environmentId}}&address=localhost&authKey=123&enabled=on",
"host": [
"{{serverURL}}"
],
"path": [
"DotAjaxDirector",
"com.dotcms.publisher.endpoint.ajax.PublishingEndpointAjaxAction",
"cmd",
"addEndpoint"
],
"query": [
{
"key": "sending",
"value": "true"
},
{
"key": "serverName",
"value": "{{receiverName}}"
},
{
"key": "environmentId",
"value": "{{environmentId}}"
},
{
"key": "address",
"value": "localhost"
},
{
"key": "authKey",
"value": "123"
},
{
"key": "enabled",
"value": "on"
}
]
}
},
"response": []
},
{
"name": "generate Integrity Checker",
"event": [
{
"listen": "test",
"script": {
"id": "cdfbfaa0-b329-41a7-b7d2-84a870997efb",
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
"",
" response = pm.response.json();",
"",
" pm.expect(response['success']).to.equals(true, );",
" pm.expect(response['message']).to.equals(\"Integrity Checking Initialized...\");",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{serverURL}}/api/integrity/checkintegrity/endpoint/{{endpintId}}",
"host": [
"{{serverURL}}"
],
"path": [
"api",
"integrity",
"checkintegrity",
"endpoint",
"{{endpintId}}"
]
}
},
"response": []
},
{
"name": "set USE_JWT_TOKEN_IN_PUSH_PUBLISH to true",
"request": {
"method": "PUT",
"header": [],
"body": {
"mode": "raw",
"raw": "{\n \"USE_JWT_TOKEN_IN_PUSH_PUBLISH\": true\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{serverURL}}/api//v1/configuration",
"host": [
"{{serverURL}}"
],
"path": [
"api",
"",
"v1",
"configuration"
]
}
},
"response": []
},
{
"name": "{{serverURL}}/api/v1/apitoken",
"event": [
{
"listen": "test",
"script": {
"id": "e0cfe90b-1514-4feb-a1b9-d1f16e6cc6b9",
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
"",
" var jsonData = pm.response.json();",
" pm.expect(jsonData.errors.length).to.eql(0);",
" pm.expect(jsonData.entity).to.not.eq(undefined);",
" pm.expect(jsonData.entity.jwt).to.not.eq(undefined);",
" pm.collectionVariables.set(\"token\", jsonData.entity.jwt);",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"auth": {
"type": "basic",
"basic": [
{
"key": "password",
"value": "admin",
"type": "string"
},
{
"key": "username",
"value": "admin@dotcms.com",
"type": "string"
}
]
},
"method": "POST",
"header": [],
"body": {
"mode": "raw",
"raw": "{\n \"claims\": {\n \"label\": \"testing\"\n },\n \"expirationSeconds\": 1,\n \"network\": \"0.0.0.0/0\",\n \"userId\": \"dotcms.org.1\"\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{serverURL}}/api/v1/apitoken",
"host": [
"{{serverURL}}"
],
"path": [
"api",
"v1",
"apitoken"
]
}
},
"response": []
}
],
"protocolProfileBehavior": {},
"_postman_isSubFolder": true
},
{
"name": "invalid token",
"item": [
{
"name": "set USE_JWT_TOKEN_IN_PUSH_PUBLISH to false",
"request": {
"auth": {
"type": "basic",
"basic": [
{
"key": "password",
"value": "admin",
"type": "string"
},
{
"key": "username",
"value": "admin@dotcms.com",
"type": "string"
}
]
},
"method": "PUT",
"header": [],
"body": {
"mode": "raw",
"raw": "{\n \"USE_JWT_TOKEN_IN_PUSH_PUBLISH\": false\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{serverURL}}/api//v1/configuration",
"host": [
"{{serverURL}}"
],
"path": [
"api",
"",
"v1",
"configuration"
]
}
},
"response": []
},
{
"name": "login",
"event": [
{
"listen": "test",
"script": {
"id": "c27614e2-dd39-44d6-9103-33a4771661d0",
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"auth": {
"type": "basic",
"basic": [
{
"key": "password",
"value": "admin",
"type": "string"
},
{
"key": "username",
"value": "admin@dotcms.com",
"type": "string"
}
]
},
"method": "POST",
"header": [],
"body": {
"mode": "raw",
"raw": "{\n \"backEndLogin\": true,\n \"country\": \"US\",\n \"language\": \"en\",\n \"password\": \"admin\",\n \"rememberMe\": false,\n \"userId\": \"admin@dotcms.com\"\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{serverURL}}/api/v1/authentication",
"host": [
"{{serverURL}}"
],
"path": [
"api",
"v1",
"authentication"
]
}
},
"response": []
},
{
"name": "create environment",
"event": [
{
"listen": "prerequest",
"script": {
"id": "7452a212-04c5-4e98-b438-b7e977342888",
"exec": [
"",
"environmentName = 'environment-' + new Date().getMilliseconds();",
"pm.collectionVariables.set(\"environmentName\", environmentName);",
""
],
"type": "text/javascript"
}
},
{
"listen": "test",
"script": {
"id": "7b7fbca7-4558-4d0b-b42e-acf545fb9924",
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
"",
" var environmentId = pm.response.text();",
" pm.collectionVariables.set(\"environmentId\", environmentId);",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"auth": {
"type": "basic",
"basic": [
{
"key": "password",
"value": "admin",
"type": "string"
},
{
"key": "username",
"value": "admin@dotcms.com",
"type": "string"
}
]
},
"method": "POST",
"header": [
{
"key": "Origin",
"value": "{{origin}}",
"type": "text"
}
],
"body": {
"mode": "formdata",
"formdata": []
},
"url": {
"raw": "{{serverURL}}/DotAjaxDirector/com.dotcms.publisher.environment.ajax.EnvironmentAjaxAction/cmd/addEnvironment?environmentName={{environmentName}}&pushType=pushToOne&whoCanUseSelect=e7d4e34e-5127-45fc-8123-d48b62d510e3&whoCanUse=e7d4e34e-5127-45fc-8123-d48b62d510e3",
"host": [
"{{serverURL}}"
],
"path": [
"DotAjaxDirector",
"com.dotcms.publisher.environment.ajax.EnvironmentAjaxAction",
"cmd",
"addEnvironment"
],
"query": [
{
"key": "environmentName",
"value": "{{environmentName}}"
},
{
"key": "pushType",
"value": "pushToOne"
},
{
"key": "whoCanUseSelect",
"value": "e7d4e34e-5127-45fc-8123-d48b62d510e3"
},
{
"key": "whoCanUse",
"value": "e7d4e34e-5127-45fc-8123-d48b62d510e3"
}
]
}
},
"response": []
},
{
"name": "create end point",
"event": [
{
"listen": "prerequest",
"script": {
"id": "525f2862-6e5e-4ef7-8485-4bf6242f1ae9",
"exec": [
"endpointName = 'endpoint-' + new Date().getMilliseconds();",
"pm.collectionVariables.set(\"endpointName\", endpointName);",
""
],
"type": "text/javascript"
}
},
{
"listen": "test",
"script": {
"id": "79c8c9ff-7d29-4e5c-951d-8936cbf41123",
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
"",
" var environmentId = pm.response.text();",
" pm.collectionVariables.set(\"endpintId\", environmentId);",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"auth": {
"type": "basic",
"basic": [
{
"key": "password",
"value": "admin",
"type": "string"
},
{
"key": "username",
"value": "admin@dotcms.com",
"type": "string"
}
]
},
"method": "POST",
"header": [
{
"key": "Origin",
"type": "text",
"value": "{{origin}}"
}
],
"body": {
"mode": "formdata",
"formdata": []
},
"url": {
"raw": "{{serverURL}}/DotAjaxDirector/com.dotcms.publisher.endpoint.ajax.PublishingEndpointAjaxAction/cmd/addEndpoint?sending=false&serverName={{endpointName}}&environmentId={{environmentId}}&protocol=http&address=localhost&port=8080&authKey=123&enabled=on",
"host": [
"{{serverURL}}"
],
"path": [
"DotAjaxDirector",
"com.dotcms.publisher.endpoint.ajax.PublishingEndpointAjaxAction",
"cmd",
"addEndpoint"
],
"query": [
{
"key": "sending",
"value": "false"
},
{
"key": "serverName",
"value": "{{endpointName}}"
},
{
"key": "environmentId",
"value": "{{environmentId}}"
},
{
"key": "protocol",
"value": "http"
},
{
"key": "address",
"value": "localhost"
},
{
"key": "port",
"value": "8080"
},
{
"key": "authKey",
"value": "123"
},
{
"key": "enabled",
"value": "on"
}
]
}
},
"response": []
},
{
"name": "create receiver",
"event": [
{
"listen": "prerequest",
"script": {
"id": "f18da640-cdcc-4b0b-a03d-ac9faac33d9e",
"exec": [
"receiverName = 'receiverName-' + new Date().getMilliseconds();",
"pm.collectionVariables.set(\"receiverName\", receiverName);",
""
],
"type": "text/javascript"
}
},
{
"listen": "test",
"script": {
"id": "5f3158f3-5f86-43c1-865e-9fc66fa21422",
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"auth": {
"type": "basic",
"basic": [
{
"key": "password",
"value": "admin",
"type": "string"
},
{
"key": "username",
"value": "admin@dotcms.com",
"type": "string"
}
]
},
"method": "POST",
"header": [
{
"key": "Origin",
"type": "text",
"value": "{{origin}}"
}
],
"body": {
"mode": "formdata",
"formdata": []
},
"url": {
"raw": "{{serverURL}}/DotAjaxDirector/com.dotcms.publisher.endpoint.ajax.PublishingEndpointAjaxAction/cmd/addEndpoint?sending=true&serverName={{receiverName}}&environmentId={{environmentId}}&address=localhost&authKey=invalid&enabled=on",
"host": [
"{{serverURL}}"
],
"path": [
"DotAjaxDirector",
"com.dotcms.publisher.endpoint.ajax.PublishingEndpointAjaxAction",
"cmd",
"addEndpoint"
],
"query": [
{
"key": "sending",
"value": "true"
},
{
"key": "serverName",
"value": "{{receiverName}}"
},
{
"key": "environmentId",
"value": "{{environmentId}}"
},
{
"key": "address",
"value": "localhost"
},
{
"key": "authKey",
"value": "invalid"
},
{
"key": "enabled",
"value": "on"
}
]
}
},
"response": []
},
{
"name": "generate Integrity Checker",
"event": [
{
"listen": "test",
"script": {
"id": "44ce47f8-d6e5-45d0-bbf4-cfd4ff7afa25",
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(401);",
" endPoint = pm.collectionVariables.get(\"endpintId\");",
" pm.expect(pm.response.text()).to.equals(\"Invalid token Response indicating Not Authorized received from Endpoint. Please check Auth Token. Endpoint Id: \" + endPoint);",
"",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"auth": {
"type": "noauth"
},
"method": "GET",
"header": [],
"url": {
"raw": "{{serverURL}}/api/integrity/checkintegrity/endpoint/{{endpintId}}",
"host": [
"{{serverURL}}"
],
"path": [
"api",
"integrity",
"checkintegrity",
"endpoint",
"{{endpintId}}"
]
}
},
"response": []
},
{
"name": "set USE_JWT_TOKEN_IN_PUSH_PUBLISH to true",
"request": {
"method": "PUT",
"header": [],
"body": {
"mode": "raw",
"raw": "{\n \"USE_JWT_TOKEN_IN_PUSH_PUBLISH\": true\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{serverURL}}/api//v1/configuration",
"host": [
"{{serverURL}}"
],
"path": [
"api",
"",
"v1",
"configuration"
]
}
},
"response": []
}
],
"protocolProfileBehavior": {},
"_postman_isSubFolder": true
}
],
"protocolProfileBehavior": {},
"_postman_isSubFolder": true
},
{
"name": "using JWT",
"item": [
{
"name": "Happy path",
"item": [
{
"name": "request_token",
"event": [
{
"listen": "test",
"script": {
"id": "37a7ff6b-f988-4f89-b54b-a3aa04d70efd",
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
"",
" var jsonData = pm.response.json();",
" pm.expect(jsonData.errors.length).to.eql(0);",
" pm.expect(jsonData.entity).to.not.eq(undefined);",
" pm.expect(jsonData.entity.jwt).to.not.eq(undefined);",
" pm.collectionVariables.set(\"token\", jsonData.entity.jwt);",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"auth": {
"type": "basic",
"basic": [
{
"key": "password",
"value": "admin",
"type": "string"
},
{
"key": "username",
"value": "admin@dotcms.com",
"type": "string"
}
]
},
"method": "POST",
"header": [],
"body": {
"mode": "raw",
"raw": "{\n \"claims\": {\n \"label\": \"testing\"\n },\n \"expirationSeconds\": 94571365,\n \"network\": \"0.0.0.0/0\",\n \"userId\": \"dotcms.org.1\"\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{serverURL}}/api/v1/apitoken",
"host": [
"{{serverURL}}"
],
"path": [
"api",
"v1",
"apitoken"
]
}
},
"response": []
},
{
"name": "create environment",
"event": [
{
"listen": "prerequest",
"script": {
"id": "79b5fbd1-dc2e-4faa-987c-1a953cdc849b",
"exec": [
"",
"environmentName = 'environment-' + new Date().getMilliseconds();",
"pm.collectionVariables.set(\"environmentName\", environmentName);",
""
],
"type": "text/javascript"
}
},
{
"listen": "test",
"script": {
"id": "95b178ba-7cfb-44f7-9b16-518f6e98f002",
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
"",
" var environmentId = pm.response.text();",
" pm.collectionVariables.set(\"environmentId\", environmentId);",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"auth": {
"type": "basic",
"basic": [
{
"key": "password",
"value": "admin",
"type": "string"
},
{
"key": "username",
"value": "admin@dotcms.com",
"type": "string"
}
]
},
"method": "POST",
"header": [
{
"key": "Origin",
"value": "{{origin}}",
"type": "text"
}
],
"body": {
"mode": "formdata",
"formdata": []
},
"url": {
"raw": "{{serverURL}}/DotAjaxDirector/com.dotcms.publisher.environment.ajax.EnvironmentAjaxAction/cmd/addEnvironment?environmentName={{environmentName}}&pushType=pushToOne&whoCanUseSelect=e7d4e34e-5127-45fc-8123-d48b62d510e3&whoCanUse=e7d4e34e-5127-45fc-8123-d48b62d510e3",
"host": [
"{{serverURL}}"
],
"path": [
"DotAjaxDirector",
"com.dotcms.publisher.environment.ajax.EnvironmentAjaxAction",
"cmd",
"addEnvironment"
],
"query": [
{
"key": "environmentName",
"value": "{{environmentName}}"
},
{
"key": "pushType",
"value": "pushToOne"
},
{
"key": "whoCanUseSelect",
"value": "e7d4e34e-5127-45fc-8123-d48b62d510e3"
},
{
"key": "whoCanUse",
"value": "e7d4e34e-5127-45fc-8123-d48b62d510e3"
}
]
}
},
"response": []
},
{
"name": "create end point",
"event": [
{
"listen": "prerequest",
"script": {
"id": "19df49ca-b1a2-4ee2-8bdd-d33bfb9146a2",
"exec": [
"endpointName = 'endpoint-' + new Date().getMilliseconds();",
"pm.collectionVariables.set(\"endpointName\", endpointName);",
""
],
"type": "text/javascript"
}
},
{
"listen": "test",
"script": {
"id": "202775bf-90a4-4e4b-89c5-c786a9029ab8",
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
"",
" var environmentId = pm.response.text();",
" pm.collectionVariables.set(\"endpintId\", environmentId);",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"auth": {
"type": "basic",
"basic": [
{
"key": "password",
"value": "admin",
"type": "string"
},
{
"key": "username",
"value": "admin@dotcms.com",
"type": "string"
}
]
},
"method": "POST",
"header": [
{
"key": "Origin",
"type": "text",
"value": "{{origin}}"
}
],
"body": {
"mode": "formdata",
"formdata": []
},
"url": {
"raw": "{{serverURL}}/DotAjaxDirector/com.dotcms.publisher.endpoint.ajax.PublishingEndpointAjaxAction/cmd/addEndpoint?sending=false&serverName={{endpointName}}&environmentId={{environmentId}}&protocol=http&address=localhost&port=8080&authKey={{token}}&enabled=on",
"host": [
"{{serverURL}}"
],
"path": [
"DotAjaxDirector",
"com.dotcms.publisher.endpoint.ajax.PublishingEndpointAjaxAction",
"cmd",
"addEndpoint"
],
"query": [
{
"key": "sending",
"value": "false"
},
{
"key": "serverName",
"value": "{{endpointName}}"
},
{
"key": "environmentId",
"value": "{{environmentId}}"
},
{
"key": "protocol",
"value": "http"
},
{
"key": "address",
"value": "localhost"
},
{
"key": "port",
"value": "8080"
},
{
"key": "authKey",
"value": "{{token}}"
},
{
"key": "enabled",
"value": "on"
}
]
}
},
"response": []
},
{
"name": "generate Integrity Checker",
"event": [
{
"listen": "test",
"script": {
"id": "ddd02cc3-77be-4591-a237-2549fe073525",
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
"",
" response = pm.response.json();",
"",
" pm.expect(response['success']).to.equals(true, );",
" pm.expect(response['message']).to.equals(\"Integrity Checking Initialized...\");",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{serverURL}}/api/integrity/checkintegrity/endpoint/{{endpintId}}",
"host": [
"{{serverURL}}"
],
"path": [
"api",
"integrity",
"checkintegrity",
"endpoint",
"{{endpintId}}"
]
}
},
"response": []
}
],
"protocolProfileBehavior": {},
"_postman_isSubFolder": true
},
{
"name": "Invalid Token",
"item": [
{
"name": "login",
"event": [
{
"listen": "test",
"script": {
"id": "0a008749-4206-443e-aa62-cf4ae75c5596",
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"auth": {
"type": "basic",
"basic": [
{
"key": "password",
"value": "admin",
"type": "string"
},
{
"key": "username",
"value": "admin@dotcms.com",
"type": "string"
}
]
},
"method": "POST",
"header": [],
"body": {
"mode": "raw",
"raw": "{\n \"backEndLogin\": true,\n \"country\": \"US\",\n \"language\": \"en\",\n \"password\": \"admin\",\n \"rememberMe\": false,\n \"userId\": \"admin@dotcms.com\"\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{serverURL}}/api/v1/authentication",
"host": [
"{{serverURL}}"
],
"path": [
"api",
"v1",
"authentication"
]
}
},
"response": []
},
{
"name": "create environment",
"event": [
{
"listen": "prerequest",
"script": {
"id": "79ca1b4c-7a87-4b1b-8c6a-1c88c8c451d1",
"exec": [
"",
"environmentName = 'environment-' + new Date().getMilliseconds();",
"pm.collectionVariables.set(\"environmentName\", environmentName);",
""
],
"type": "text/javascript"
}
},
{
"listen": "test",
"script": {
"id": "7ec74343-5968-418d-9d02-79df35354cf7",
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
"",
" var environmentId = pm.response.text();",
" pm.collectionVariables.set(\"environmentId\", environmentId);",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"auth": {
"type": "basic",
"basic": [
{
"key": "password",
"value": "admin",
"type": "string"
},
{
"key": "username",
"value": "admin@dotcms.com",
"type": "string"
}
]
},
"method": "POST",
"header": [
{
"key": "Origin",
"value": "{{origin}}",
"type": "text"
}
],
"body": {
"mode": "formdata",
"formdata": []
},
"url": {
"raw": "{{serverURL}}/DotAjaxDirector/com.dotcms.publisher.environment.ajax.EnvironmentAjaxAction/cmd/addEnvironment?environmentName={{environmentName}}&pushType=pushToOne&whoCanUseSelect=e7d4e34e-5127-45fc-8123-d48b62d510e3&whoCanUse=e7d4e34e-5127-45fc-8123-d48b62d510e3",
"host": [
"{{serverURL}}"
],
"path": [
"DotAjaxDirector",
"com.dotcms.publisher.environment.ajax.EnvironmentAjaxAction",
"cmd",
"addEnvironment"
],
"query": [
{
"key": "environmentName",
"value": "{{environmentName}}"
},
{
"key": "pushType",
"value": "pushToOne"
},
{
"key": "whoCanUseSelect",
"value": "e7d4e34e-5127-45fc-8123-d48b62d510e3"
},
{
"key": "whoCanUse",
"value": "e7d4e34e-5127-45fc-8123-d48b62d510e3"
}
]
}
},
"response": []
},
{
"name": "create end point",
"event": [
{
"listen": "prerequest",
"script": {
"id": "cf59260c-bdd8-466f-959c-b1e4b7e05fc5",
"exec": [
"endpointName = 'endpoint-' + new Date().getMilliseconds();",
"pm.collectionVariables.set(\"endpointName\", endpointName);",
""
],
"type": "text/javascript"
}
},
{
"listen": "test",
"script": {
"id": "369ce43a-c4d6-4cd9-831d-55475aa3d3e3",
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
"",
" var environmentId = pm.response.text();",
" pm.collectionVariables.set(\"endpintId\", environmentId);",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"auth": {
"type": "basic",
"basic": [
{
"key": "password",
"value": "admin",
"type": "string"
},
{
"key": "username",
"value": "admin@dotcms.com",
"type": "string"
}
]
},
"method": "POST",
"header": [
{
"key": "Origin",
"type": "text",
"value": "{{origin}}"
}
],
"body": {
"mode": "formdata",
"formdata": []
},
"url": {
"raw": "{{serverURL}}/DotAjaxDirector/com.dotcms.publisher.endpoint.ajax.PublishingEndpointAjaxAction/cmd/addEndpoint?sending=false&serverName={{endpointName}}&environmentId={{environmentId}}&protocol=http&address=localhost&port=8080&authKey=123&enabled=on",
"host": [
"{{serverURL}}"
],
"path": [
"DotAjaxDirector",
"com.dotcms.publisher.endpoint.ajax.PublishingEndpointAjaxAction",
"cmd",
"addEndpoint"
],
"query": [
{
"key": "sending",
"value": "false"
},
{
"key": "serverName",
"value": "{{endpointName}}"
},
{
"key": "environmentId",
"value": "{{environmentId}}"
},
{
"key": "protocol",
"value": "http"
},
{
"key": "address",
"value": "localhost"
},
{
"key": "port",
"value": "8080"
},
{
"key": "authKey",
"value": "123"
},
{
"key": "enabled",
"value": "on"
}
]
}
},
"response": []
},
{
"name": "Shoyld response with 401",
"event": [
{
"listen": "test",
"script": {
"id": "2cf7bcef-dea9-413f-812a-27e25060cbe4",
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(401);",
" endPoint = pm.collectionVariables.get(\"endpintId\");",
" pm.expect(pm.response.text()).to.equals(\"Invalid token Response indicating Not Authorized received from Endpoint. Please check Auth Token. Endpoint Id: \" + endPoint);",
"",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"auth": {
"type": "noauth"
},
"method": "GET",
"header": [],
"url": {
"raw": "{{serverURL}}/api/integrity/checkintegrity/endpoint/{{endpintId}}",
"host": [
"{{serverURL}}"
],
"path": [
"api",
"integrity",
"checkintegrity",
"endpoint",
"{{endpintId}}"
]
}
},
"response": []
}
],
"protocolProfileBehavior": {},
"_postman_isSubFolder": true
},
{
"name": "experied Token",
"item": [
{
"name": "request_token",
"event": [
{
"listen": "test",
"script": {
"id": "d7e7204e-00ab-4eb0-ae3b-458766971db7",
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
"",
" var jsonData = pm.response.json();",
" pm.expect(jsonData.errors.length).to.eql(0);",
" pm.expect(jsonData.entity).to.not.eq(undefined);",
" pm.expect(jsonData.entity.jwt).to.not.eq(undefined);",
" pm.collectionVariables.set(\"token\", jsonData.entity.jwt);",
"",
" eval(pm.collectionVariables.get(\"wait\"))(2);",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"auth": {
"type": "basic",
"basic": [
{
"key": "password",
"value": "admin",
"type": "string"
},
{
"key": "username",
"value": "admin@dotcms.com",
"type": "string"
}
]
},
"method": "POST",
"header": [],
"body": {
"mode": "raw",
"raw": "{\n \"claims\": {\n \"label\": \"testing\"\n },\n \"expirationSeconds\": 94571365,\n \"network\": \"0.0.0.0/0\",\n \"userId\": \"dotcms.org.1\"\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{serverURL}}/api/v1/apitoken",
"host": [
"{{serverURL}}"
],
"path": [
"api",
"v1",
"apitoken"
]
}
},
"response": []
},
{
"name": "create environment",
"event": [
{
"listen": "prerequest",
"script": {
"id": "c1b527d1-78c4-40f2-a49e-c321e1810908",
"exec": [
"",
"environmentName = 'environment-' + new Date().getMilliseconds();",
"pm.collectionVariables.set(\"environmentName\", environmentName);",
""
],
"type": "text/javascript"
}
},
{
"listen": "test",
"script": {
"id": "67a7ca8c-605a-461b-893b-162a04a00ebf",
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
"",
" var environmentId = pm.response.text();",
" pm.collectionVariables.set(\"environmentId\", environmentId);",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"auth": {
"type": "basic",
"basic": [
{
"key": "password",
"value": "admin",
"type": "string"
},
{
"key": "username",
"value": "admin@dotcms.com",
"type": "string"
}
]
},
"method": "POST",
"header": [
{
"key": "Origin",
"value": "{{origin}}",
"type": "text"
}
],
"body": {
"mode": "formdata",
"formdata": []
},
"url": {
"raw": "{{serverURL}}/DotAjaxDirector/com.dotcms.publisher.environment.ajax.EnvironmentAjaxAction/cmd/addEnvironment?environmentName={{environmentName}}&pushType=pushToOne&whoCanUseSelect=e7d4e34e-5127-45fc-8123-d48b62d510e3&whoCanUse=e7d4e34e-5127-45fc-8123-d48b62d510e3",
"host": [
"{{serverURL}}"
],
"path": [
"DotAjaxDirector",
"com.dotcms.publisher.environment.ajax.EnvironmentAjaxAction",
"cmd",
"addEnvironment"
],
"query": [
{
"key": "environmentName",
"value": "{{environmentName}}"
},
{
"key": "pushType",
"value": "pushToOne"
},
{
"key": "whoCanUseSelect",
"value": "e7d4e34e-5127-45fc-8123-d48b62d510e3"
},
{
"key": "whoCanUse",
"value": "e7d4e34e-5127-45fc-8123-d48b62d510e3"
}
]
}
},
"response": []
},
{
"name": "create end point",
"event": [
{
"listen": "prerequest",
"script": {
"id": "9bb94357-12a4-4808-ada7-32415dc9146e",
"exec": [
"endpointName = 'endpoint-' + new Date().getMilliseconds();",
"pm.collectionVariables.set(\"endpointName\", endpointName);",
""
],
"type": "text/javascript"
}
},
{
"listen": "test",
"script": {
"id": "9f245535-9fd6-49b6-87bd-1363ff614bb3",
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
"",
" var environmentId = pm.response.text();",
" pm.collectionVariables.set(\"endpintId\", environmentId);",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"auth": {
"type": "basic",
"basic": [
{
"key": "password",
"value": "admin",
"type": "string"
},
{
"key": "username",
"value": "admin@dotcms.com",
"type": "string"
}
]
},
"method": "POST",
"header": [
{
"key": "Origin",
"type": "text",
"value": "{{origin}}"
}
],
"body": {
"mode": "formdata",
"formdata": []
},
"url": {
"raw": "{{serverURL}}/DotAjaxDirector/com.dotcms.publisher.endpoint.ajax.PublishingEndpointAjaxAction/cmd/addEndpoint?sending=false&serverName={{endpointName}}&environmentId={{environmentId}}&protocol=http&address=localhost&port=8080&authKey=123&enabled=on",
"host": [
"{{serverURL}}"
],
"path": [
"DotAjaxDirector",
"com.dotcms.publisher.endpoint.ajax.PublishingEndpointAjaxAction",
"cmd",
"addEndpoint"
],
"query": [
{
"key": "sending",
"value": "false"
},
{
"key": "serverName",
"value": "{{endpointName}}"
},
{
"key": "environmentId",
"value": "{{environmentId}}"
},
{
"key": "protocol",
"value": "http"
},
{
"key": "address",
"value": "localhost"
},
{
"key": "port",
"value": "8080"
},
{
"key": "authKey",
"value": "123"
},
{
"key": "enabled",
"value": "on"
}
]
}
},
"response": []
},
{
"name": "Shoyld response with 401",
"event": [
{
"listen": "test",
"script": {
"id": "8f014fe7-aa64-4c9d-9818-ce95613e44e5",
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(401);",
" endPoint = pm.collectionVariables.get(\"endpintId\");",
" pm.expect(pm.response.text()).to.equals(\"Invalid token Response indicating Not Authorized received from Endpoint. Please check Auth Token. Endpoint Id: \" + endPoint);",
"",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"auth": {
"type": "noauth"
},
"method": "GET",
"header": [],
"url": {
"raw": "{{serverURL}}/api/integrity/checkintegrity/endpoint/{{endpintId}}",
"host": [
"{{serverURL}}"
],
"path": [
"api",
"integrity",
"checkintegrity",
"endpoint",
"{{endpintId}}"
]
}
},
"response": []
}
],
"protocolProfileBehavior": {},
"_postman_isSubFolder": true
}
],
"protocolProfileBehavior": {},
"_postman_isSubFolder": true
}
],
"protocolProfileBehavior": {}
}
],
"event": [
{
"listen": "prerequest",
"script": {
"id": "78ded004-2f45-41e0-9140-aef5b8a20e8a",
"type": "text/javascript",
"exec": [
"wait = (ms) => {",
" var start = new Date().getTime();",
" var end = start;",
" while(end < start + ms) {",
" end = new Date().getTime();",
" }",
"};",
"",
"pm.collectionVariables.set(\"wait\", wait.toString());",
"",
"serverURL = pm.environment.get(\"serverURL\");",
"",
"console.log('hostName', serverURL);",
"pm.collectionVariables.set(\"origin\", serverURL);"
]
}
},
{
"listen": "test",
"script": {
"id": "0c989cc6-7c8d-4703-90b6-6db31ccd17cc",
"type": "text/javascript",
"exec": [
""
]
}
}
],
"variable": [
{
"id": "bc22274e-f650-481b-8a31-4a88756e02b6",
"key": "endpointName",
"value": "endpoint-602"
},
{
"id": "6dc3e100-63fb-4e0a-bcfa-27c0310501d9",
"key": "environmentName",
"value": "environment-518"
},
{
"id": "e6d5de63-d39b-4d2c-bf71-7aaa38528521",
"key": "origin",
"value": "http://localhost:8080"
},
{
"id": "1881fdff-7ea3-402d-9f21-2e293228b5d8",
"key": "environmentId",
"value": "45bfcadd-77fb-4029-ac05-62cb369ace8f"
},
{
"id": "13065228-c0a5-4017-b01a-c9206d3f0a61",
"key": "endpintId",
"value": "fed35809-457d-40b0-89ed-356314d35b57"
},
{
"id": "0619abae-7a60-46b4-953e-ff96324d0c9c",
"key": "receiverName",
"value": "receiverName-221"
},
{
"id": "1315fdb5-87d7-4cb8-9269-caf32e15a0ff",
"key": "token",
"value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhcGljZjhiZmQyMC0zNTE1LTQxNzgtOWQwZi1kYWUxNTY1YmViZmYiLCJ4bW9kIjoxNjAxMDU0MzQyMDAwLCJuYmYiOjE2MDEwNTQzNDIsImlzcyI6ImFjNWMwMDc2N2YiLCJsYWJlbCI6InRlc3RpbmciLCJleHAiOjE2OTU2MjU3MDcsImlhdCI6MTYwMTA1NDM0MiwianRpIjoiZWIxYmFkMTQtZmFjZi00MWNiLTkzNTctMWEzODdiODJjY2JjIn0.ezWYPQ77-Gzr7MN0V7ne_3MedeJ9IgmU3By8RE-flvk"
},
{
"id": "b323d4b0-900a-4966-924f-839cb70f13d3",
"key": "wait",
"value": "(ms) => {\n var start = new Date().getTime();\n var end = start;\n while(end < start + ms) {\n end = new Date().getTime();\n }\n}"
}
],
"protocolProfileBehavior": {}
}
@@ -0,0 +1,837 @@
{
"info": {
"_postman_id": "bf248c74-2817-4a70-9d13-d4569d5cfbc7",
"name": "Integrity Checker JWT Token Test",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"item": [
{
"name": "generate",
"item": [
{
"name": "with_admin_user",
"item": [
{
"name": "request_token",
"event": [
{
"listen": "test",
"script": {
"id": "8528c13c-c1b3-4a40-96b8-544d7f693cad",
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
"",
" var jsonData = pm.response.json();",
" pm.expect(jsonData.errors.length).to.eql(0);",
" pm.expect(jsonData.entity).to.not.eq(undefined);",
" pm.expect(jsonData.entity.jwt).to.not.eq(undefined);",
" pm.collectionVariables.set(\"token\", jsonData.entity.jwt);",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"auth": {
"type": "basic",
"basic": [
{
"key": "password",
"value": "admin",
"type": "string"
},
{
"key": "username",
"value": "admin@dotcms.com",
"type": "string"
}
]
},
"method": "POST",
"header": [],
"body": {
"mode": "raw",
"raw": "{\n \"claims\": {\n \"label\": \"testing\"\n },\n \"expirationSeconds\": 94571365,\n \"network\": \"0.0.0.0/0\",\n \"userId\": \"dotcms.org.1\"\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{serverURL}}/api/v1/apitoken",
"host": [
"{{serverURL}}"
],
"path": [
"api",
"v1",
"apitoken"
]
}
},
"response": []
},
{
"name": "logout",
"event": [
{
"listen": "test",
"script": {
"id": "483d1c70-0e40-4541-a32f-879e0e15defc",
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{serverURL}}/api/v1/logout",
"host": [
"{{serverURL}}"
],
"path": [
"api",
"v1",
"logout"
]
}
},
"response": []
},
{
"name": "request_integrity_checker_generate",
"event": [
{
"listen": "test",
"script": {
"id": "3d922fc4-9843-49c5-b8fb-57a301bd2b37",
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
"",
" var transactionId = pm.response.text();",
" pm.collectionVariables.set(\"transactionId\", transactionId);",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"auth": {
"type": "bearer",
"bearer": [
{
"key": "token",
"value": "{{token}}",
"type": "string"
}
]
},
"method": "POST",
"header": [],
"body": {
"mode": "formdata",
"formdata": []
},
"url": {
"raw": "{{serverURL}}/api/integrity/_generateintegritydata",
"host": [
"{{serverURL}}"
],
"path": [
"api",
"integrity",
"_generateintegritydata"
]
}
},
"response": []
},
{
"name": "check_transaction_id",
"event": [
{
"listen": "test",
"script": {
"id": "324b2288-3fe9-4293-8ad5-78a787e093de",
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"auth": {
"type": "bearer",
"bearer": [
{
"key": "token",
"value": "{{token}}",
"type": "string"
}
]
},
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "multipart/form-data",
"type": "text"
}
],
"body": {
"mode": "file",
"file": {
"src": "/build/src/core/dotCMS/src/curl-test/resources/Push_publish/bundle_test-01EAJHA26FZGPAEZBSGJGNG82A.tar.gz"
}
},
"url": {
"raw": "{{serverURL}}/api/integrity/{{transactionId}}/status",
"host": [
"{{serverURL}}"
],
"path": [
"api",
"integrity",
"{{transactionId}}",
"status"
]
}
},
"response": []
}
],
"protocolProfileBehavior": {},
"_postman_isSubFolder": true
},
{
"name": "using_basic_authentication_instead_JWT_token",
"item": [
{
"name": "request_pp",
"event": [
{
"listen": "test",
"script": {
"id": "9f2fc11c-3c1b-4922-8308-854f1ddfcc8d",
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(401);",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"auth": {
"type": "basic",
"basic": [
{
"key": "password",
"value": "admin",
"type": "string"
},
{
"key": "username",
"value": "admin@dotcms.com",
"type": "string"
}
]
},
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/octet-stream",
"type": "text"
},
{
"key": "Content-Disposition",
"value": "attachment",
"type": "text"
}
],
"body": {
"mode": "file",
"file": {
"src": "/build/src/core/dotCMS/src/curl-test/resources/Push_publish/bundle_test-01EAJHA26FZGPAEZBSGJGNG82A.tar.gz"
},
"options": {
"file": {}
}
},
"url": {
"raw": "{{serverURL}}/api/bundlePublisher/publish",
"host": [
"{{serverURL}}"
],
"path": [
"api",
"bundlePublisher",
"publish"
]
}
},
"response": []
}
],
"protocolProfileBehavior": {},
"_postman_isSubFolder": true
},
{
"name": "fail_not_token_sent",
"item": [
{
"name": "request_integrity_generate",
"event": [
{
"listen": "test",
"script": {
"id": "744a21f4-a854-4557-9f12-0dc055d7363e",
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(401);",
"});",
"",
"pm.test(\"Check WWW-Authenticate header\", function () {",
" expected = 'Bearer realm=\"example\",error=\"invalid_token\",error_key=\"__invalid_token__\",error_description=\"\"';",
"",
" pm.response.to.be.header('WWW-Authenticate', expected);",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"auth": {
"type": "noauth"
},
"method": "POST",
"header": [
{
"key": "Content-Type",
"type": "text",
"value": "application/octet-stream"
},
{
"key": "Content-Disposition",
"type": "text",
"value": "attachment"
}
],
"body": {
"mode": "file",
"file": {
"src": "/build/src/core/dotCMS/src/curl-test/resources/Push_publish/bundle_test-01EAJHA26FZGPAEZBSGJGNG82A.tar.gz"
}
},
"url": {
"raw": "{{serverURL}}/api/integrity/_generateintegritydata",
"host": [
"{{serverURL}}"
],
"path": [
"api",
"integrity",
"_generateintegritydata"
]
}
},
"response": []
}
],
"protocolProfileBehavior": {},
"_postman_isSubFolder": true
},
{
"name": "fail_not_valid_token",
"item": [
{
"name": "request_integrity_generate",
"event": [
{
"listen": "test",
"script": {
"id": "29df161e-45c2-41ba-a911-79d215d8f68b",
"exec": [
"pm.test(\"Status code is 401\", function () {",
" pm.response.to.have.status(401);",
"});",
"",
"pm.test(\"Check WWW-Authenticate header\", function () {",
" expected = 'Bearer realm=\"example\",error=\"invalid_token\",error_key=\"__invalid_token__\",error_description=\"\"';",
"",
" pm.response.to.be.header('WWW-Authenticate', expected);",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"auth": {
"type": "bearer",
"bearer": [
{
"key": "token",
"value": "not_valid_token",
"type": "string"
}
]
},
"method": "POST",
"header": [
{
"key": "Content-Type",
"type": "text",
"value": "application/octet-stream"
},
{
"key": "Content-Disposition",
"type": "text",
"value": "attachment"
}
],
"body": {
"mode": "file",
"file": {
"src": ""
}
},
"url": {
"raw": "{{serverURL}}/api/integrity/_generateintegritydata",
"host": [
"{{serverURL}}"
],
"path": [
"api",
"integrity",
"_generateintegritydata"
]
}
},
"response": []
}
],
"protocolProfileBehavior": {},
"_postman_isSubFolder": true
}
],
"protocolProfileBehavior": {}
},
{
"name": "Fix conflicts",
"item": [
{
"name": "with_admin_user",
"item": [
{
"name": "create folder",
"event": [
{
"listen": "test",
"script": {
"id": "53422518-17a5-4800-acc0-c0ff2c9a0ac3",
"exec": [
"pm.test(\"Bundle uploaded sucessfully\", function () {",
" pm.response.to.have.status(200);",
"",
" var jsonData = pm.response.json();",
" console.log(jsonData);",
"",
" pm.expect(jsonData[\"bundleName\"]).to.eql(\"folder_integrity_test.tar.gz\");",
" pm.expect(jsonData[\"status\"]).to.eql(\"SUCCESS\");",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"auth": {
"type": "basic",
"basic": [
{
"key": "username",
"value": "admin@dotcms.com",
"type": "string"
},
{
"key": "password",
"value": "admin",
"type": "string"
}
]
},
"method": "POST",
"header": [
{
"key": "Content-Type",
"type": "text",
"value": "application/octet-stream"
},
{
"key": "Content-Disposition",
"type": "text",
"value": "attachment"
}
],
"body": {
"mode": "formdata",
"formdata": [
{
"key": "file",
"contentType": "application/tar+gzip ",
"type": "file",
"src": "/build/src/core/dotCMS/src/curl-test/resources/Push_publish/folder_integrity_test.tar.gz"
}
],
"options": {
"formdata": {}
}
},
"url": {
"raw": "{{serverURL}}/api/bundle/sync",
"host": [
"{{serverURL}}"
],
"path": [
"api",
"bundle",
"sync"
]
},
"description": "Imports a Bundle that includes:\n* A piece of content of type \"Rich text\" with \"test Content\" as title and body"
},
"response": []
},
{
"name": "request_token",
"event": [
{
"listen": "test",
"script": {
"id": "5ced69a2-1210-4397-8720-0a75f3212ad5",
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
"",
" var jsonData = pm.response.json();",
" pm.expect(jsonData.errors.length).to.eql(0);",
" pm.expect(jsonData.entity).to.not.eq(undefined);",
" pm.expect(jsonData.entity.jwt).to.not.eq(undefined);",
" pm.collectionVariables.set(\"token\", jsonData.entity.jwt);",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"auth": {
"type": "basic",
"basic": [
{
"key": "password",
"value": "admin",
"type": "string"
},
{
"key": "username",
"value": "admin@dotcms.com",
"type": "string"
}
]
},
"method": "POST",
"header": [],
"body": {
"mode": "raw",
"raw": "{\n \"claims\": {\n \"label\": \"testing\"\n },\n \"expirationSeconds\": 94571365,\n \"network\": \"0.0.0.0/0\",\n \"userId\": \"dotcms.org.1\"\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{serverURL}}/api/v1/apitoken",
"host": [
"{{serverURL}}"
],
"path": [
"api",
"v1",
"apitoken"
]
}
},
"response": []
},
{
"name": "logout",
"event": [
{
"listen": "test",
"script": {
"id": "0c961544-d102-4b53-b07d-156e7c2b3966",
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{serverURL}}/api/v1/logout",
"host": [
"{{serverURL}}"
],
"path": [
"api",
"v1",
"logout"
]
}
},
"response": []
},
{
"name": "Execute Task Upgrade",
"request": {
"auth": {
"type": "basic",
"basic": [
{
"key": "password",
"value": "admin",
"type": "string"
},
{
"key": "username",
"value": "admin@dotcms.com",
"type": "string"
}
]
},
"method": "POST",
"header": [],
"body": {
"mode": "raw",
"raw": "{\n \"upgradeTaskClass\":\"com.dotmarketing.startup.runonce.Task05390RemoveEndpointIdForeignKeyInIntegrityResolverTables\"\n}\n",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "http://localhost:8080/api/v1/upgradetask",
"protocol": "http",
"host": [
"localhost"
],
"port": "8080",
"path": [
"api",
"v1",
"upgradetask"
]
}
},
"response": []
},
{
"name": "request_fix_conflicts",
"event": [
{
"listen": "test",
"script": {
"id": "19f3c1ff-6198-4fb0-9a62-af68ae085972",
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"auth": {
"type": "bearer",
"bearer": [
{
"key": "token",
"value": "{{token}}",
"type": "string"
}
]
},
"method": "POST",
"header": [
{
"warning": "This is a duplicate header and will be overridden by the Content-Type header generated by Postman.",
"key": "Content-Type",
"value": "multipart/form-data",
"type": "text"
}
],
"body": {
"mode": "formdata",
"formdata": [
{
"key": "DATA_TO_FIX",
"type": "file",
"src": "/build/src/core/dotCMS/src/curl-test/resources/Push_publish/DataToFix.zip"
},
{
"key": "TYPE",
"value": "FOLDERS",
"type": "text"
}
]
},
"url": {
"raw": "{{serverURL}}/api/integrity/_fixconflictsfromremote",
"host": [
"{{serverURL}}"
],
"path": [
"api",
"integrity",
"_fixconflictsfromremote"
]
}
},
"response": []
},
{
"name": "check folder id",
"event": [
{
"listen": "test",
"script": {
"id": "fe24a02b-8770-481f-808f-88193f19af81",
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
"",
" var jsonData = pm.response.json();",
" pm.expect(jsonData[\"entity\"][\"identifier\"]).to.eql(\"ecf8d1d2-d150-4af8-9a0b-93bd53143c11\");",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"auth": {
"type": "basic",
"basic": [
{
"key": "password",
"value": "admin",
"type": "string"
},
{
"key": "username",
"value": "admin@dotcms.com",
"type": "string"
}
]
},
"method": "GET",
"header": [],
"url": {
"raw": "{{serverURL}}/api/v1/folder/sitename/default/uri/folder_integrity_test",
"host": [
"{{serverURL}}"
],
"path": [
"api",
"v1",
"folder",
"sitename",
"default",
"uri",
"folder_integrity_test"
]
}
},
"response": []
}
],
"protocolProfileBehavior": {},
"_postman_isSubFolder": true
}
],
"protocolProfileBehavior": {}
}
],
"event": [
{
"listen": "prerequest",
"script": {
"id": "d94022a9-d19d-40ab-a076-be58a63cf3b3",
"type": "text/javascript",
"exec": [
""
]
}
},
{
"listen": "test",
"script": {
"id": "f64070e5-f3ce-4a59-bc69-b4478ae941eb",
"type": "text/javascript",
"exec": [
""
]
}
}
],
"variable": [
{
"id": "81a21cd1-affe-4e42-914a-894e7ffde1e9",
"key": "serverURL",
"value": "http://localhost:8080"
},
{
"id": "547ffe1c-a555-4e44-903f-c80060dccb1c",
"key": "token",
"value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhcGk1MGY1N2RlNi02MjJhLTQyMWYtYThjYy0wZTY0ZDIwOGY3NTgiLCJ4bW9kIjoxNjAwOTgwMTYyMDAwLCJuYmYiOjE2MDA5ODAxNjIsImlzcyI6IjZmZWRmNjBhMTYiLCJsYWJlbCI6InRlc3RpbmciLCJleHAiOjE2OTU1NTE1MjcsImlhdCI6MTYwMDk4MDE2MiwianRpIjoiZjAwYjcyZmItNDQ0YS00MGUzLWI1MzItMDBiZDk4NzFmMTk3In0.S3W8wZV8SAtTxPwlxoB1nzd301wvNDZDEFzVis-UdfA"
},
{
"id": "1f57fb71-9bc1-4a31-b3b4-4ab44647f181",
"key": "bundle_id",
"value": "bundle_1598948729120"
},
{
"id": "b4166b36-71b9-4cc9-ab25-4514b88e07c7",
"key": "transactionId",
"value": "48db03da-d303-47cc-af29-9be490e99648"
}
],
"protocolProfileBehavior": {}
}
@@ -0,0 +1,811 @@
{
"info": {
"_postman_id": "22bb06cd-de6e-4a5c-94f4-d37f96ff55fc",
"name": "Push Publish JWT Token Test",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"item": [
{
"name": "success_PP_with_admin_user",
"item": [
{
"name": "request_token",
"event": [
{
"listen": "test",
"script": {
"id": "0a8e3131-f2b8-44f1-bad5-00e235d48808",
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
"",
" var jsonData = pm.response.json();",
" pm.expect(jsonData.errors.length).to.eql(0);",
" pm.expect(jsonData.entity).to.not.eq(undefined);",
" pm.expect(jsonData.entity.jwt).to.not.eq(undefined);",
" pm.collectionVariables.set(\"token\", jsonData.entity.jwt);",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"auth": {
"type": "basic",
"basic": [
{
"key": "password",
"value": "admin",
"type": "string"
},
{
"key": "username",
"value": "admin@dotcms.com",
"type": "string"
}
]
},
"method": "POST",
"header": [],
"body": {
"mode": "raw",
"raw": "{\n \"claims\": {\n \"label\": \"testing\"\n },\n \"expirationSeconds\": 94571365,\n \"network\": \"0.0.0.0/0\",\n \"userId\": \"dotcms.org.1\"\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{serverURL}}/api/v1/apitoken",
"host": [
"{{serverURL}}"
],
"path": [
"api",
"v1",
"apitoken"
]
}
},
"response": []
},
{
"name": "logout",
"event": [
{
"listen": "test",
"script": {
"id": "42ac05a3-4362-4566-a7d9-44d30d9b455d",
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{serverURL}}/api/v1/logout",
"host": [
"{{serverURL}}"
],
"path": [
"api",
"v1",
"logout"
]
}
},
"response": []
},
{
"name": "request_pp",
"event": [
{
"listen": "test",
"script": {
"id": "e945d191-0719-4c12-9b63-27a6e1042924",
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
"",
" var jsonData = pm.response.json();",
" pm.expect(jsonData.id).to.not.eq(undefined);",
" pm.collectionVariables.set(\"bundle_id\", jsonData.id);",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"auth": {
"type": "bearer",
"bearer": [
{
"key": "token",
"value": "{{token}}",
"type": "string"
}
]
},
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/octet-stream",
"type": "text"
},
{
"key": "Content-Disposition",
"value": "attachment",
"type": "text"
}
],
"body": {
"mode": "file",
"file": {
"src": "/build/src/core/dotCMS/src/curl-test/resources/Push_publish/bundle_test-01EAJHA26FZGPAEZBSGJGNG82A.tar.gz"
}
},
"url": {
"raw": "{{serverURL}}/api/bundlePublisher/publish",
"host": [
"{{serverURL}}"
],
"path": [
"api",
"bundlePublisher",
"publish"
]
}
},
"response": []
},
{
"name": "checking_content_types_after_pp",
"event": [
{
"listen": "test",
"script": {
"id": "751167f1-3418-4e0a-92e8-49f7caca4d4f",
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{serverURL}}/api/auditPublishing/get/{{bundle_id}}",
"host": [
"{{serverURL}}"
],
"path": [
"api",
"auditPublishing",
"get",
"{{bundle_id}}"
]
}
},
"response": []
}
],
"protocolProfileBehavior": {}
},
{
"name": "expired_token",
"item": [
{
"name": "request_token",
"event": [
{
"listen": "test",
"script": {
"id": "5e50fabd-4ee6-4432-a503-8cce3612c5ce",
"exec": [
"wait = (ms) => {",
" var start = new Date().getTime();",
" var end = start;",
" while(end < start + ms) {",
" end = new Date().getTime();",
" }",
"};",
"",
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
"",
" var jsonData = pm.response.json();",
" pm.expect(jsonData.errors.length).to.eql(0);",
" pm.expect(jsonData.entity).to.not.eq(undefined);",
" pm.expect(jsonData.entity.jwt).to.not.eq(undefined);",
" pm.collectionVariables.set(\"token\", jsonData.entity.jwt);",
"",
" wait(2000);",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"auth": {
"type": "basic",
"basic": [
{
"key": "password",
"value": "admin",
"type": "string"
},
{
"key": "username",
"value": "admin@dotcms.com",
"type": "string"
}
]
},
"method": "POST",
"header": [],
"body": {
"mode": "raw",
"raw": "{\n \"claims\": {\n \"label\": \"testing\"\n },\n \"expirationSeconds\": 1,\n \"network\": \"0.0.0.0/0\",\n \"userId\": \"dotcms.org.1\"\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{serverURL}}/api/v1/apitoken",
"host": [
"{{serverURL}}"
],
"path": [
"api",
"v1",
"apitoken"
]
}
},
"response": []
},
{
"name": "logout",
"event": [
{
"listen": "test",
"script": {
"id": "74b070c5-3173-4d5e-be9a-f47a6bbea2f6",
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{serverURL}}/api/v1/logout",
"host": [
"{{serverURL}}"
],
"path": [
"api",
"v1",
"logout"
]
}
},
"response": []
},
{
"name": "request_pp",
"event": [
{
"listen": "test",
"script": {
"id": "1d620684-1f81-4b98-96b4-6fba9efc5646",
"exec": [
"pm.test(\"Status code is 401\", function () {",
" pm.response.to.have.status(401);",
"});",
"",
"pm.test(\"Check WWW-Authenticate header\", function () {",
" expected = 'Bearer realm=\"example\",error=\"invalid_token\",error_key=\"__invalid_token__\",error_description=\"\"';",
"",
" pm.response.to.be.header('WWW-Authenticate', expected);",
"});",
"",
""
],
"type": "text/javascript"
}
}
],
"request": {
"auth": {
"type": "bearer",
"bearer": [
{
"key": "token",
"value": "{{token}}",
"type": "string"
}
]
},
"method": "POST",
"header": [
{
"key": "Content-Type",
"type": "text",
"value": "application/octet-stream"
},
{
"key": "Content-Disposition",
"type": "text",
"value": "attachment"
}
],
"body": {
"mode": "file",
"file": {
"src": "/build/src/core/dotCMS/src/curl-test/resources/Push_publish/bundle_test-01EAJHA26FZGPAEZBSGJGNG82A.tar.gz"
}
},
"url": {
"raw": "{{serverURL}}/api/bundlePublisher/publish",
"host": [
"{{serverURL}}"
],
"path": [
"api",
"bundlePublisher",
"publish"
]
}
},
"response": []
}
],
"protocolProfileBehavior": {}
},
{
"name": "fail_not_valid_token",
"item": [
{
"name": "request_pp",
"event": [
{
"listen": "test",
"script": {
"id": "39a14780-626f-48e2-b9ca-031e07ba8dd3",
"exec": [
"pm.test(\"Status code is 401\", function () {",
" pm.response.to.have.status(401);",
"});",
"",
"pm.test(\"Check WWW-Authenticate header\", function () {",
" expected = 'Bearer realm=\"example\",error=\"invalid_token\",error_key=\"__invalid_token__\",error_description=\"\"';",
"",
" pm.response.to.be.header('WWW-Authenticate', expected);",
"});",
"",
""
],
"type": "text/javascript"
}
}
],
"request": {
"auth": {
"type": "bearer",
"bearer": [
{
"key": "token",
"value": "not_valid_token",
"type": "string"
}
]
},
"method": "POST",
"header": [
{
"key": "Content-Type",
"type": "text",
"value": "application/octet-stream"
},
{
"key": "Content-Disposition",
"type": "text",
"value": "attachment"
}
],
"body": {
"mode": "file",
"file": {
"src": "/build/src/core/dotCMS/src/curl-test/resources/Push_publish/bundle_test-01EAJHA26FZGPAEZBSGJGNG82A.tar.gz"
}
},
"url": {
"raw": "{{serverURL}}/api/bundlePublisher/publish",
"host": [
"{{serverURL}}"
],
"path": [
"api",
"bundlePublisher",
"publish"
]
}
},
"response": []
}
],
"protocolProfileBehavior": {}
},
{
"name": "fail_not_token_sent",
"item": [
{
"name": "request_pp",
"event": [
{
"listen": "test",
"script": {
"id": "7d99f2ae-4cc0-4e88-8fff-eccee8c59016",
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(401);",
"});",
"",
"pm.test(\"Check WWW-Authenticate header\", function () {",
" expected = 'Bearer realm=\"example\",error=\"invalid_token\",error_key=\"__invalid_token__\",error_description=\"\"';",
"",
" pm.response.to.be.header('WWW-Authenticate', expected);",
"});",
""
],
"type": "text/javascript"
}
}
],
"request": {
"auth": {
"type": "noauth"
},
"method": "POST",
"header": [
{
"key": "Content-Type",
"type": "text",
"value": "application/octet-stream"
},
{
"key": "Content-Disposition",
"type": "text",
"value": "attachment"
}
],
"body": {
"mode": "file",
"file": {
"src": "/build/src/core/dotCMS/src/curl-test/resources/Push_publish/bundle_test-01EAJHA26FZGPAEZBSGJGNG82A.tar.gz"
}
},
"url": {
"raw": "{{serverURL}}/api/bundlePublisher/publish",
"host": [
"{{serverURL}}"
],
"path": [
"api",
"bundlePublisher",
"publish"
]
}
},
"response": []
}
],
"protocolProfileBehavior": {}
},
{
"name": "fail_empty_body",
"item": [
{
"name": "request_token",
"event": [
{
"listen": "test",
"script": {
"id": "e8b2bb8a-adbc-44ee-9c99-ebcb47486d65",
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
"",
" var jsonData = pm.response.json();",
" pm.expect(jsonData.errors.length).to.eql(0);",
" pm.expect(jsonData.entity).to.not.eq(undefined);",
" pm.expect(jsonData.entity.jwt).to.not.eq(undefined);",
" pm.collectionVariables.set(\"token\", jsonData.entity.jwt);",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"auth": {
"type": "basic",
"basic": [
{
"key": "password",
"value": "admin",
"type": "string"
},
{
"key": "username",
"value": "admin@dotcms.com",
"type": "string"
}
]
},
"method": "POST",
"header": [],
"body": {
"mode": "raw",
"raw": "{\n \"claims\": {\n \"label\": \"testing\"\n },\n \"expirationSeconds\": 94571365,\n \"network\": \"0.0.0.0/0\",\n \"userId\": \"dotcms.org.1\"\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{serverURL}}/api/v1/apitoken",
"host": [
"{{serverURL}}"
],
"path": [
"api",
"v1",
"apitoken"
]
}
},
"response": []
},
{
"name": "logout",
"event": [
{
"listen": "test",
"script": {
"id": "6b5a4ead-f1f9-4202-b2e6-5ef183bf584e",
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{serverURL}}/api/v1/logout",
"host": [
"{{serverURL}}"
],
"path": [
"api",
"v1",
"logout"
]
}
},
"response": []
},
{
"name": "request_pp",
"event": [
{
"listen": "test",
"script": {
"id": "f9d64d8c-114e-4e8e-a0eb-e4e0cb55fea5",
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(400);",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"auth": {
"type": "bearer",
"bearer": [
{
"key": "token",
"value": "{{token}}",
"type": "string"
}
]
},
"method": "POST",
"header": [
{
"key": "Content-Type",
"type": "text",
"value": "application/octet-stream"
},
{
"key": "Content-Disposition",
"type": "text",
"value": "attachment"
}
],
"body": {
"mode": "file",
"file": {
"src": ""
}
},
"url": {
"raw": "{{serverURL}}/api/bundlePublisher/publish",
"host": [
"{{serverURL}}"
],
"path": [
"api",
"bundlePublisher",
"publish"
]
}
},
"response": []
}
],
"protocolProfileBehavior": {}
},
{
"name": "using_basic_authentication_instead_JWT_token",
"item": [
{
"name": "request_pp",
"event": [
{
"listen": "test",
"script": {
"id": "a888be06-630c-475d-a824-553437cf848f",
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(401);",
"});",
"",
"pm.test(\"Check WWW-Authenticate header\", function () {",
" expected = 'Bearer realm=\"example\",error=\"invalid_token\",error_key=\"__invalid_token__\",error_description=\"\"';",
"",
" pm.response.to.be.header('WWW-Authenticate', expected);",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"auth": {
"type": "basic",
"basic": [
{
"key": "password",
"value": "admin",
"type": "string"
},
{
"key": "username",
"value": "admin@dotcms.com",
"type": "string"
}
]
},
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/octet-stream",
"type": "text"
},
{
"key": "Content-Disposition",
"value": "attachment",
"type": "text"
}
],
"body": {
"mode": "file",
"file": {
"src": "/build/src/core/dotCMS/src/curl-test/resources/Push_publish/bundle_test-01EAJHA26FZGPAEZBSGJGNG82A.tar.gz"
},
"options": {
"file": {}
}
},
"url": {
"raw": "{{serverURL}}/api/bundlePublisher/publish",
"host": [
"{{serverURL}}"
],
"path": [
"api",
"bundlePublisher",
"publish"
]
}
},
"response": []
}
],
"protocolProfileBehavior": {}
}
],
"event": [
{
"listen": "prerequest",
"script": {
"id": "4672b9ca-6159-4a6f-878d-c0557df8bf19",
"type": "text/javascript",
"exec": [
""
]
}
},
{
"listen": "test",
"script": {
"id": "7fc5d703-2526-4797-adc1-47f079440c37",
"type": "text/javascript",
"exec": [
""
]
}
}
],
"variable": [
{
"id": "0288f532-e3dc-4589-b095-11fe96a56e1c",
"key": "serverURL",
"value": "http://localhost:8080"
},
{
"id": "2c19ae47-80bb-4a09-b680-617b0e496062",
"key": "token",
"value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhcGk0MjI2YjE0Yy1iMGQ3LTRmNmUtYTQ1ZC0wMmNjOGVkZDBkYWEiLCJ4bW9kIjoxNjAwMjAxMjUyMDAwLCJuYmYiOjE2MDAyMDEyNTIsImlzcyI6ImMyOTJhMWYyYWQiLCJsYWJlbCI6InRlc3RpbmciLCJleHAiOjE2MDAyMDEyNTMsImlhdCI6MTYwMDIwMTI1MiwianRpIjoiNDVlYWNjNGQtNWQ4ZS00NDA0LWIzNzEtYTgwYTBhZjgzMjkzIn0._BFWPMWBhPCLlteq3rr4Bst0h6y6Mea8etnAksJnWGg"
},
{
"id": "07068927-39d7-45aa-98d8-e9bb0617dee7",
"key": "bundle_id",
"value": "bundle_1598948729120"
}
],
"protocolProfileBehavior": {}
}

Large diffs are not rendered by default.

@@ -0,0 +1,164 @@
{
"info": {
"_postman_id": "41780f23-6f2e-4fe7-851b-f9ae3ad2c121",
"name": "Save Layout With Relative Path",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"item": [
{
"name": "push contents with relationship Copy",
"event": [
{
"listen": "test",
"script": {
"id": "53158e3d-0f27-4f91-ae6d-e7f7ceb2dcef",
"exec": [
"pm.test(\"Bundle uploaded sucessfully\", function () {",
" pm.response.to.have.status(200);",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"auth": {
"type": "basic",
"basic": [
{
"key": "username",
"value": "admin@dotcms.com",
"type": "string"
},
{
"key": "password",
"value": "admin",
"type": "string"
}
]
},
"method": "POST",
"header": [
{
"key": "Content-Type",
"type": "text",
"value": "application/octet-stream"
},
{
"key": "Content-Disposition",
"type": "text",
"value": "attachment"
}
],
"body": {
"mode": "formdata",
"formdata": [
{
"key": "file",
"contentType": "application/tar+gzip ",
"type": "file",
"src": "/build/src/core/dotCMS/src/curl-test/resources/resolve_relative_path_in_layout_end_point/page_test_bundle-01EEZJ5H186WF88K28VHB3P9A6.tar.gz"
}
],
"options": {
"formdata": {}
}
},
"url": {
"raw": "{{serverURL}}/api/bundle/sync",
"host": [
"{{serverURL}}"
],
"path": [
"api",
"bundle",
"sync"
],
"query": [
{
"key": "AUTH_TOKEN",
"value": "",
"disabled": true
}
]
},
"description": "Imports a Bundle that includes:\n* A piece of content of type \"Rich text\" with \"test Content\" as title and body"
},
"response": []
},
{
"name": "Save Layout With relative Path",
"event": [
{
"listen": "test",
"script": {
"id": "c50e3863-b57f-491d-8b31-b8e790169ecf",
"exec": [
"pm.test(\"Relative path should be save as Absolute path\", function () {",
" pm.response.to.have.status(200);",
"",
" var jsonData = pm.response.json();",
" var layout = jsonData[\"entity\"][\"layout\"];",
" var rows = layout[\"body\"][\"rows\"]",
"",
" pm.expect(rows.length).to.eql(1);",
"",
" var columns = rows[0].columns;",
" pm.expect(columns.length).to.eql(1);",
"",
" var containers = columns[0].containers;",
" pm.expect(containers.length).to.eql(1);",
"",
" pm.expect(containers[0].identifier).to.eql(\"/application/containers/default/\");",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"auth": {
"type": "basic",
"basic": [
{
"key": "password",
"value": "admin",
"type": "string"
},
{
"key": "username",
"value": "admin@dotcms.com",
"type": "string"
}
]
},
"method": "POST",
"header": [],
"body": {
"mode": "raw",
"raw": "{\n \"title\":null,\n \"themeId\":\"d7b0ebc2-37ca-4a5a-b769-e8a3ff187661\",\n \"layout\":{\n \"body\":{\n \"rows\":[\n {\n \"styleClass\":null,\n \"columns\":[\n {\n \"styleClass\":null,\n \"leftOffset\":1,\n \"width\":10,\n \"containers\":[\n {\n \"identifier\":\"/application/containers/default/\",\n \"uuid\":\"1234\"\n }\n ]\n }\n ]\n }\n ]\n },\n \"header\":true,\n \"footer\":true,\n \"sidebar\":{\n \"location\":\"\",\n \"containers\":[],\n \"width\":\"small\"\n }\n }\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{serverURL}}/api/v1/page/24ca92bb-c71a-4d85-9a9c-a2719df9b05b/layout",
"host": [
"{{serverURL}}"
],
"path": [
"api",
"v1",
"page",
"24ca92bb-c71a-4d85-9a9c-a2719df9b05b",
"layout"
]
}
},
"response": []
}
],
"protocolProfileBehavior": {}
}
@@ -86,6 +86,69 @@
},
"response": []
},
{
"name": "Can not Delete",
"event": [
{
"listen": "test",
"script": {
"id": "bcd3dae3-8bc9-49ac-aa81-295c3bcb7b73",
"exec": [
"pm.test(\"Status code should be 400\", function () {",
" pm.response.to.have.status(400);",
"});",
"",
"",
"",
""
],
"type": "text/javascript"
}
}
],
"request": {
"auth": {
"type": "basic",
"basic": [
{
"key": "password",
"value": "admin",
"type": "string"
},
{
"key": "username",
"value": "admin@dotcms.com",
"type": "string"
},
{
"key": "saveHelperData",
"type": "any"
},
{
"key": "showPassword",
"value": false,
"type": "boolean"
}
]
},
"method": "DELETE",
"header": [],
"url": {
"raw": "{{serverURL}}/api/v1/templates/{{temporaryInode}}",
"host": [
"{{serverURL}}"
],
"path": [
"api",
"v1",
"templates",
"{{temporaryInode}}"
]
},
"description": "Can not delete, non-archive template"
},
"response": []
},
{
"name": "Edit Template",
"event": [
@@ -447,6 +510,14 @@
" pm.response.to.have.status(200);",
"});",
"",
"pm.test(\"publishedInodes\", function () {",
" pm.expect(jsonData.entity.failedInodes.length).to.eql(0);",
"});",
"",
"",
"pm.test(\"publishedInodes\", function () {",
" pm.expect(jsonData.entity.publishedInodes.length).to.eql(1);",
"});",
""
],
"type": "text/javascript"
@@ -581,9 +652,20 @@
"script": {
"id": "4f94abfe-0ea3-4e88-9539-820f23940338",
"exec": [
"var jsonData = pm.response.json();",
"",
"pm.test(\"Status code should be ok 200\", function () {",
" pm.response.to.have.status(200);",
"});",
"",
"pm.test(\"publishedInodes\", function () {",
" pm.expect(jsonData.entity.failedInodes.length).to.eql(0);",
"});",
"",
"",
"pm.test(\"publishedInodes\", function () {",
" pm.expect(jsonData.entity.unpublishedInodes.length).to.eql(1);",
"});",
""
],
"type": "text/javascript"
@@ -1192,22 +1274,22 @@
],
"variable": [
{
"id": "0bec4b5f-2e72-49bd-b2e3-588ce89c48c5",
"id": "ae13f2a4-314e-4cdf-b0bc-870f6091f8d3",
"key": "temporalInode",
"value": "27928cad-5c13-41a9-bcf1-ce23a6cb55c8"
},
{
"id": "5c09bfc2-b34d-4a6c-bf62-79c17c7bb83f",
"id": "509c1ecc-6038-4308-b46e-b69b76a7a266",
"key": "temporalIdentifier",
"value": "037debee-59cb-42b7-b0d1-1cab580c2b76"
},
{
"id": "d4edf47f-6119-4de9-8580-a6f4deac5957",
"id": "81b381d5-f887-4741-9114-647bda4b9c83",
"key": "temporaryInode",
"value": "91e12d76-17f0-4c3e-b956-e5f7c9d7db37"
"value": "bc59f5c6-9188-4fc0-ba62-54a4d801eeee"
},
{
"id": "00eeb466-3181-4d4b-9e73-dfce92fff944",
"id": "890af0c0-6a46-47c7-91c7-c188cae05f19",
"key": "temporaryIdentifier",
"value": "909162a8-1776-40e9-ac4c-6d927b718fe3"
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Empty file.
@@ -15,41 +15,46 @@
import com.dotcms.graphql.DotGraphQLHttpServletTest;
import com.dotcms.junit.MainBaseSuite;
import com.dotcms.mock.request.CachedParameterDecoratorTest;
import com.dotcms.publisher.bundle.business.BundleAPITest;
import com.dotcms.publisher.bundle.business.BundleFactoryTest;
import com.dotcms.publisher.business.PublishAuditAPITest;
import com.dotcms.publishing.PublisherFilterImplTest;
import com.dotcms.publishing.PushPublishFiltersInitializerTest;
import com.dotcms.publishing.job.SiteSearchJobImplTest;
import com.dotcms.rendering.velocity.directive.DotParseTest;
import com.dotcms.rendering.velocity.servlet.VelocityServletIntegrationTest;
import com.dotcms.rendering.velocity.viewtools.DotTemplateToolTest;
import com.dotcms.rendering.velocity.viewtools.JSONToolTest;
import com.dotcms.rest.BundlePublisherResourceIntegrationTest;
import com.dotcms.rest.BundleResourceTest;
import com.dotcms.rest.IntegrityResourceIntegrationTest;
import com.dotcms.rest.api.v1.apps.AppsResourceTest;
import com.dotcms.rest.api.v1.folder.FolderResourceTest;
import com.dotcms.rest.api.v1.pushpublish.PushPublishFilterResourceTest;
import com.dotcms.rest.api.v1.user.UserResourceIntegrationTest;
import com.dotcms.saml.IdentityProviderConfigurationFactoryTest;
import com.dotcms.saml.SamlConfigurationServiceTest;
import com.dotcms.security.apps.AppsAPIImplTest;
import com.dotcms.security.apps.AppsCacheImplTest;
import com.dotcms.translate.GoogleTranslationServiceIntegrationTest;
import com.dotmarketing.image.focalpoint.FocalPointAPITest;
import com.dotmarketing.portlets.cmsmaintenance.factories.CMSMaintenanceFactoryTest;
import com.dotmarketing.portlets.containers.business.ContainerFactoryImplTest;
import com.dotmarketing.portlets.containers.business.ContainerStructureFinderStrategyResolverTest;
import com.dotmarketing.portlets.contentlet.business.HostAPITest;
import com.dotmarketing.portlets.contentlet.business.web.ContentletWebAPIImplIntegrationTest;
import com.dotmarketing.portlets.contentlet.model.IntegrationResourceLinkTest;
import com.dotmarketing.portlets.fileassets.business.FileAssetAPIImplIntegrationTest;
import com.dotmarketing.portlets.fileassets.business.FileAssetAPIImplTest;
import com.dotmarketing.portlets.fileassets.business.FileAssetFactoryIntegrationTest;
import com.dotmarketing.portlets.folders.business.FolderFactoryImplTest;
import com.dotmarketing.portlets.templates.business.TemplateFactoryImplTest;
import com.dotmarketing.portlets.workflows.actionlet.PushNowActionletTest;
import com.dotmarketing.portlets.workflows.model.TestWorkflowAction;
import com.dotmarketing.quartz.DotStatefulJobTest;
import com.dotmarketing.quartz.job.CleanUpFieldReferencesJobTest;
import com.dotmarketing.startup.runonce.Task05195CreatesDestroyActionAndAssignDestroyDefaultActionsToTheSystemWorkflowTest;
import com.dotmarketing.startup.runonce.Task05210CreateDefaultDotAssetTest;
import com.dotmarketing.startup.runonce.Task05225RemoveLoadRecordsToIndexTest;
import com.dotmarketing.startup.runonce.Task05305AddPushPublishFilterColumnTest;
import com.dotmarketing.startup.runonce.Task05350AddDotSaltClusterColumnTest;
import com.dotmarketing.startup.runonce.Task05370AddAppsPortletToLayoutTest;
import com.dotmarketing.quartz.job.IntegrityDataGenerationJobTest;
import com.dotmarketing.startup.runonce.*;
import com.dotmarketing.util.ConfigTest;
import com.dotmarketing.util.TestConfig;
import com.liferay.portal.language.LanguageUtilTest;
@@ -273,13 +278,16 @@
CleanUpFieldReferencesJobTest.class,
ESContentletAPIImplTest.class,
CachedParameterDecoratorTest.class,
ContainerFactoryImplTest.class,
TemplateFactoryImplTest.class,
TestConfig.class,
ConfigTest.class,
PublishAuditAPITest.class,
BundleFactoryTest.class,
com.dotcms.security.apps.SecretsStoreKeyStoreImplTest.class,
AppsAPIImplTest.class,
AppsResourceTest.class,
AppsCacheImplTest.class,
VelocityServletIntegrationTest.class,
DotAssetAPITest.class,
DotAssetBaseTypeToContentTypeStrategyImplTest.class,
@@ -315,10 +323,20 @@
IdentityProviderConfigurationFactoryTest.class,
EMAWebInterceptorTest.class,
GoogleTranslationServiceIntegrationTest.class,
Task05380ChangeContainerPathToAbsoluteTest.class,
DotTemplateToolTest.class,
ContentletWebAPIImplIntegrationTest.class,
Task05370AddAppsPortletToLayoutTest.class,
FolderFactoryImplTest.class,
DotSamlResourceTest.class,
DotStatefulJobTest.class
DotStatefulJobTest.class,
BundleAPITest.class,
Task05390MakeRoomForLongerJobDetailTest.class,
IntegrityDataGenerationJobTest.class,
Task05395RemoveEndpointIdForeignKeyInIntegrityResolverTablesIntegrationTest.class,
JSONToolTest.class,
BundlePublisherResourceIntegrationTest.class,
IntegrityResourceIntegrationTest.class
})
public class MainSuite {

@@ -1,5 +1,6 @@
package com.dotcms.concurrent;

import com.dotcms.concurrent.DotConcurrentFactory.SubmitterConfigBuilder;
import com.dotmarketing.util.json.JSONException;

import org.junit.Test;
@@ -10,6 +11,160 @@

public class DotConcurrentFactoryTest {

@Test
public void testDefaultOne_Submitter_Config() throws JSONException{

final String submitterName = "testsubmitter";
final DotConcurrentFactory dotConcurrentFactory =
DotConcurrentFactory.getInstance();

final DotSubmitter submitter =
dotConcurrentFactory.getSubmitter(submitterName,
new SubmitterConfigBuilder().poolSize(2)
.maxPoolSize(4).queueCapacity(500).build()
);

System.out.println(submitter);

IntStream.range(0, 40).forEach(
n -> {

if (n % 10 == 0) {

System.out.println(submitter);
}
submitter.execute(new PrintTask("Thread" + n));
}
);

//check active thread, if zero then shut down the thread pool
for (;;) {
int count = submitter.getActiveCount();
System.out.println("Active Threads : " + count);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count == 0) {
//submitter.shutdown();
break;
}
}

System.out.print("Staring a new one submitter");

final DotSubmitter submitter2 =
dotConcurrentFactory.getSubmitter(submitterName);

System.out.println(submitter2);

assertTrue(submitter == submitter2);

IntStream.range(0, 20).forEach(
n -> {
submitter2.execute(new PrintTask("Thread" + n));
}
);

//check active thread, if zero then shut down the thread pool
for (;;) {
int count = submitter2.getActiveCount();
System.out.println("Active Threads : " + count);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count == 0) {
submitter2.shutdown();
break;
}
}

}

@Test
public void testDefaultOne_Submitter_Config_shutdown_keep_config() throws JSONException{

final String submitterName = "testsubmitter";
final DotConcurrentFactory dotConcurrentFactory =
DotConcurrentFactory.getInstance();

final DotSubmitter submitter =
dotConcurrentFactory.getSubmitter(submitterName,
new SubmitterConfigBuilder().poolSize(2)
.maxPoolSize(4).queueCapacity(500).build()
);

System.out.println(submitter);

IntStream.range(0, 40).forEach(
n -> {

if (n % 10 == 0) {

System.out.println(submitter);
}
submitter.execute(new PrintTask("Thread" + n));
}
);

assertEquals(2, submitter.getPoolSize());
assertEquals(4, submitter.getMaxPoolSize());

//check active thread, if zero then shut down the thread pool
for (;;) {
int count = submitter.getActiveCount();
System.out.println("Active Threads : " + count);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count == 0) {
//submitter.shutdown();
break;
}
}

submitter.shutdown();

System.out.print("Staring a new one submitter");

final DotSubmitter submitter2 =
dotConcurrentFactory.getSubmitter(submitterName);

System.out.println(submitter2);

assertFalse(submitter == submitter2);

IntStream.range(0, 20).forEach(
n -> {
submitter2.execute(new PrintTask("Thread" + n));
}
);

assertEquals(2, submitter2.getPoolSize());
assertEquals(4, submitter2.getMaxPoolSize());

//check active thread, if zero then shut down the thread pool
for (;;) {
int count = submitter2.getActiveCount();
System.out.println("Active Threads : " + count);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count == 0) {
submitter2.shutdown();
break;
}
}

}

@Test
public void testDefaultOne() throws JSONException{

@@ -112,7 +267,6 @@ public void run() {
*
* @throws Exception
*/

@Test
public void test_delayed_queue() throws Exception{

@@ -134,13 +288,6 @@ public void test_delayed_queue() throws Exception{

// all of the jobs have been run
assert(aInt.get()==50);





}




}
@@ -1,5 +1,6 @@
package com.dotcms.content.elasticsearch.business;

import static com.dotcms.content.elasticsearch.business.ESMappingAPIImpl.TEXT;
import static com.dotcms.datagen.TestDataUtils.getCommentsLikeContentType;
import static com.dotcms.datagen.TestDataUtils.getNewsLikeContentType;
import static com.dotcms.datagen.TestDataUtils.relateContentTypes;
@@ -34,6 +35,7 @@
import com.dotcms.datagen.FieldDataGen;
import com.dotcms.datagen.FileAssetDataGen;
import com.dotcms.datagen.SiteDataGen;
import com.dotcms.datagen.TestDataUtils;
import com.dotcms.util.CollectionsUtils;
import com.dotcms.util.IntegrationTestInitService;
import com.dotmarketing.beans.Host;
@@ -59,6 +61,7 @@
import com.liferay.portal.model.User;
import com.liferay.util.FileUtil;
import com.liferay.util.StringPool;
import com.dotmarketing.util.Config;
import java.io.File;

import java.util.ArrayList;
@@ -145,6 +148,92 @@ public void test_toMap_fileasset_txt_shouldSuccess() throws Exception {

}

/**
* Method to test: {@link ESMappingAPIImpl#toMap(Contentlet)}
* Given Scenario: When the property `CREATE_TEXT_INDEX_FIELD_FOR_NON_TEXT_FIELDS` is set to false, _text fields shouldn't be included in the map
* ExpectedResult: The result map should not contain any _text field
*/
@Test
public void test_toMap_when_textFieldShouldNotBeIncluded(){
final ESMappingAPIImpl esMappingAPI = new ESMappingAPIImpl();
Config.setProperty("CREATE_TEXT_INDEX_FIELD_FOR_NON_TEXT_FIELDS", false);

final Map<String, Object> contentletMap = esMappingAPI.toMap(TestDataUtils
.getNewsContent(true, language.getId(), getNewsLikeContentType().id()));

assertNotNull(contentletMap);

assertFalse(contentletMap.containsKey((ESMappingConstants.STRUCTURE_TYPE + TEXT).toLowerCase()));
assertFalse(contentletMap.containsKey((ESMappingConstants.BASE_TYPE + TEXT).toLowerCase()));
assertFalse(contentletMap.containsKey((ESMappingConstants.MOD_DATE + TEXT).toLowerCase()));
assertFalse(contentletMap.containsKey((ESMappingConstants.LIVE + TEXT).toLowerCase()));
assertFalse(contentletMap.containsKey((ESMappingConstants.WORKING + TEXT).toLowerCase()));
assertFalse(contentletMap.containsKey((ESMappingConstants.LOCKED + TEXT).toLowerCase()));
assertFalse(contentletMap.containsKey((ESMappingConstants.DELETED + TEXT).toLowerCase()));
assertFalse(contentletMap.containsKey((ESMappingConstants.LANGUAGE_ID + TEXT).toLowerCase()));
assertFalse(contentletMap.containsKey((ESMappingConstants.PUBLISH_DATE + TEXT).toLowerCase()));
assertFalse(contentletMap.containsKey((ESMappingConstants.EXPIRE_DATE + TEXT).toLowerCase()));
assertFalse(contentletMap.containsKey((ESMappingConstants.VERSION_TS + TEXT).toLowerCase()));

assertFalse(contentletMap.containsKey((ESMappingConstants.WORKFLOW_MOD_DATE + TEXT).toLowerCase()));

assertFalse(contentletMap.containsKey((ESMappingConstants.OWNER_CAN_READ + TEXT).toLowerCase()));
assertFalse(contentletMap.containsKey((ESMappingConstants.OWNER_CAN_WRITE + TEXT).toLowerCase()));
assertFalse(contentletMap.containsKey((ESMappingConstants.OWNER_CAN_PUBLISH + TEXT).toLowerCase()));
}

/**
* Method to test: {@link ESMappingAPIImpl#toMap(Contentlet)}
* Given Scenario: When the property `CREATE_TEXT_INDEX_FIELD_FOR_NON_TEXT_FIELDS` is set to true, _text fields should be included in the map
* ExpectedResult: The result map should contain all _text fields
*/
@Test
public void test_toMap_when_textFieldShouldBeIncluded(){
final ESMappingAPIImpl esMappingAPI = new ESMappingAPIImpl();

Config.setProperty("CREATE_TEXT_INDEX_FIELD_FOR_NON_TEXT_FIELDS", true);
try {
final Map<String, Object> contentletMap = esMappingAPI.toMap(TestDataUtils
.getNewsContent(true, language.getId(), getNewsLikeContentType().id()));

assertNotNull(contentletMap);

assertTrue(contentletMap
.containsKey((ESMappingConstants.STRUCTURE_TYPE + TEXT).toLowerCase()));
assertTrue(
contentletMap.containsKey((ESMappingConstants.BASE_TYPE + TEXT).toLowerCase()));
assertTrue(
contentletMap.containsKey((ESMappingConstants.MOD_DATE + TEXT).toLowerCase()));
assertTrue(contentletMap.containsKey((ESMappingConstants.LIVE + TEXT).toLowerCase()));
assertTrue(
contentletMap.containsKey((ESMappingConstants.WORKING + TEXT).toLowerCase()));
assertTrue(contentletMap.containsKey((ESMappingConstants.LOCKED + TEXT).toLowerCase()));
assertTrue(
contentletMap.containsKey((ESMappingConstants.DELETED + TEXT).toLowerCase()));
assertTrue(contentletMap
.containsKey((ESMappingConstants.LANGUAGE_ID + TEXT).toLowerCase()));
assertTrue(contentletMap
.containsKey((ESMappingConstants.PUBLISH_DATE + TEXT).toLowerCase()));
assertTrue(contentletMap
.containsKey((ESMappingConstants.EXPIRE_DATE + TEXT).toLowerCase()));
assertTrue(contentletMap
.containsKey((ESMappingConstants.VERSION_TS + TEXT).toLowerCase()));

assertTrue(contentletMap
.containsKey((ESMappingConstants.WORKFLOW_MOD_DATE + TEXT).toLowerCase()));

assertTrue(contentletMap
.containsKey((ESMappingConstants.OWNER_CAN_READ + TEXT).toLowerCase()));
assertTrue(contentletMap
.containsKey((ESMappingConstants.OWNER_CAN_WRITE + TEXT).toLowerCase()));
assertTrue(contentletMap
.containsKey((ESMappingConstants.OWNER_CAN_PUBLISH + TEXT).toLowerCase()));
} finally {
Config.setProperty("CREATE_TEXT_INDEX_FIELD_FOR_NON_TEXT_FIELDS", false);
}

}

/**
* Method to Test: {@link ESMappingAPIImpl#toMap(Contentlet)}
* When: The contentlet has a invalid host
@@ -23,6 +23,7 @@
import org.elasticsearch.common.settings.Settings;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;

@@ -33,6 +34,7 @@
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.*;

@Ignore
@RunWith(DataProviderRunner.class)
public class ESReadOnlyMonitorTest {

@@ -112,8 +112,7 @@ public String format(Method testMethod, int invocationIndex, List<Object> argume
{"textmapping", new String[]{"host.hostname"}, "keyword"},
{"strings_as_dates", new String[]{"calendarevent.originalstartdate",
"calendarevent.recurrencestart", "calendarevent.recurrenceend"}, "date"},
{"permissions", new String[]{"permissions"}, "text"},
{"hostname", new String[]{"host.hostname_text"}, "text"}
{"permissions", new String[]{"permissions"}, "text"}
};
}

@@ -47,6 +47,7 @@

private static final TemplateAPI templateAPI = APILocator.getTemplateAPI();
private static final String type = "template";
private boolean setBodyAsNull = false;

/**
* Sets body property to the TemplateDataGen instance. This will be used when a new {@link
@@ -83,6 +84,11 @@ public TemplateDataGen drawedBody(final TemplateLayout templateLayout) {
return this;
}

public TemplateDataGen drawedBody(final String drawedBody) {
this.drawedBody = drawedBody;
return this;
}

/**
* Sets footer property to the TemplateDataGen instance. This will be used when a new {@link
* Template} instance is created
@@ -262,7 +268,7 @@ public Template next() {
@Override
public Template persist(Template template) {

if (Strings.isNullOrEmpty(body)) {
if (Strings.isNullOrEmpty(body) && !setBodyAsNull) {

if (containers.isEmpty()) {
withContainer(new ContainerDataGen().nextPersisted().getIdentifier());
@@ -289,7 +295,7 @@ public Template persist(Template template) {
}

try {
final Template savedTemplate = templateAPI.saveTemplate(template, host, user, false);
final Template savedTemplate = save(template);
APILocator.getVersionableAPI().setLive(savedTemplate);

return savedTemplate;
@@ -298,6 +304,10 @@ public Template persist(Template template) {
}
}

public static Template save(final Template template) throws DotDataException, DotSecurityException {
return templateAPI.saveTemplate(template, host, user, false);
}

@WrapInTransaction
public static void remove(Template template) {
try {
@@ -313,4 +323,9 @@ public static void publish(final Template template)
PublishFactory.publishAsset(template, APILocator.systemUser(),
false, false);
}

public TemplateDataGen setBodyAsNull() {
this.setBodyAsNull = true;
return this;
}
}
@@ -1,32 +1,52 @@
package com.dotcms.datagen;

import com.dotmarketing.portlets.containers.business.FileAssetContainerUtil;
import com.dotmarketing.portlets.containers.model.Container;
import com.dotmarketing.portlets.containers.model.FileAssetContainer;
import com.dotmarketing.portlets.templates.design.bean.*;

import java.util.ArrayList;

import java.util.List;
import java.util.*;

public class TemplateLayoutDataGen {

final List<String> containersIds = new ArrayList();
final Map<String, List<String>> containersIds = new HashMap<>();
final Map<String, List<String>> containersIdsInSidebar = new HashMap<>();

public static TemplateLayoutDataGen get(){
return new TemplateLayoutDataGen();
}

public TemplateLayoutDataGen withContainer(final String identifier){
containersIds.add(identifier);
return withContainer(identifier, null);
}

public TemplateLayoutDataGen withContainer(final String identifier, final String UUID){
List<String> uuids = containersIds.get(identifier);

if (uuids == null) {
uuids = new ArrayList<>();
}

uuids.add(UUID == null ? ContainerUUID.UUID_START_VALUE : UUID);
containersIds.put(identifier, uuids);
return this;
}

public TemplateLayout next() {
final List<ContainerUUID> containers = new ArrayList<>();
public TemplateLayoutDataGen withContainerInSidebar(final String identifier, final String UUID){
List<String> uuids = containersIdsInSidebar.get(identifier);

for (int i = 0; i < containersIds.size(); i++) {
final String containersId = containersIds.get(i);
containers.add(new ContainerUUID(containersId, "1"));
if (uuids == null) {
uuids = new ArrayList<>();
}

uuids.add(UUID == null ? ContainerUUID.UUID_START_VALUE : UUID);
containersIdsInSidebar.put(identifier, uuids);
return this;
}

public TemplateLayout next() {
final List<ContainerUUID> containers = createContainerUUIDS(containersIds);
final List<ContainerUUID> containersInSidebar = createContainerUUIDS(containersIdsInSidebar);

final List<TemplateLayoutColumn> columns = new ArrayList<>();
columns.add(new TemplateLayoutColumn(containers, 100, 1, null));
@@ -37,6 +57,33 @@ public TemplateLayout next() {
final Body body = new Body(rows);
final TemplateLayout templateLayout = new TemplateLayout();
templateLayout.setBody(body);

final Sidebar sidebar = new Sidebar(containersInSidebar, "left", "20", 20);
templateLayout.setSidebar(sidebar);

return templateLayout;
}

private static List<ContainerUUID> createContainerUUIDS(Map<String, List<String>> containersIds) {
final List<ContainerUUID> containers = new ArrayList<>();

for (final String containersId : containersIds.keySet()) {
final List<String> uuids = containersIds.get(containersId);

for (final String uuid : uuids) {
containers.add(new ContainerUUID(containersId, uuid));
}
}
return containers;
}

public TemplateLayoutDataGen withContainer(final Container container, final String UUID) {
final FileAssetContainerUtil fileAssetContainerUtil = FileAssetContainerUtil.getInstance();
return withContainer(fileAssetContainerUtil.isFileAssetContainer(container) ?
fileAssetContainerUtil.getFullPath((FileAssetContainer) container) : container.getIdentifier(), UUID);
}

public TemplateLayoutDataGen withContainer(final Container container) {
return withContainer(container, null);
}
}
@@ -23,6 +23,8 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import com.dotcms.IntegrationTestBase;
import com.dotcms.contenttype.business.ContentTypeAPI;
@@ -802,6 +804,40 @@ public void testGetSchema_GivenFailuresInRelationshipField_SchemaShouldStillGene
}
}

/**
* Method to rest: {@link GraphqlAPI#getSchema()}
* Given scenario: empty schema cache
* Expected result: cache miss, generation of the schema takes place
*/
@Test
public void testGetSchema_GivenEmptyCache_SchemaShouldGenerate()
throws DotDataException {
GraphqlAPIImpl api =
Mockito.spy(new GraphqlAPIImpl());

api.invalidateSchema();
api.getSchema();
verify(api, times(1)).generateSchema();
}

/**
* Method to rest: {@link GraphqlAPI#getSchema()}
* Given scenario: schema in cache
* Expected result: cache hit, generation of the schema should not happen
*/
@Test
public void testGetSchema_GivenFilledCache_SchemaShouldNotGenerate()
throws DotDataException {
GraphqlAPIImpl api =
Mockito.spy(new GraphqlAPIImpl());

api.invalidateSchema();
final GraphQLSchema nonCachedSchema = api.getSchema(); // generate schema is called
final GraphQLSchema cachedSchema = api.getSchema(); // got from cache - generate schema is NOT called
verify(api, times(1)).generateSchema();
assertEquals(nonCachedSchema, cachedSchema);
}

@NotNull
private void setMockRelationshipAPI(Field relationshipField)
throws DotDataException, DotSecurityException {
@@ -13,14 +13,19 @@
import com.dotcms.publisher.business.PublishAuditHistory;
import com.dotcms.publisher.business.PublishAuditStatus;
import com.dotcms.publisher.business.PublishAuditStatus.Status;
import com.dotcms.publisher.pusher.PushPublisherConfig;
import com.dotcms.publishing.FilterDescriptor;
import com.dotcms.publishing.PublisherAPIImpl;
import com.dotcms.util.IntegrationTestInitService;
import com.dotmarketing.business.APILocator;
import com.dotmarketing.exception.DotDataException;
import com.dotmarketing.util.DateUtil;
import com.dotmarketing.util.UUIDGenerator;
import com.google.common.collect.ImmutableMap;
import com.liferay.portal.model.User;
import java.util.Calendar;
import java.util.Date;
import java.util.Map;
import java.util.Set;
import org.junit.BeforeClass;
import org.junit.Test;
@@ -222,4 +227,41 @@ public void test_deleteBundleById_uploadedBundle() throws DotDataException, DotP
bundleAPI.deleteBundleAndDependencies(bundleId,adminUser);
assertNull(publishAuditStatus.getPublishAuditStatus(bundleId));
}

/**
* Method to test: {@link Bundle#bundleTgzExists()}
* Given Scenario: This test is for checking if a bundle tar.gz file was already generated.
* ExpectedResult: false, since the bundle is created but no generated.
*/
@Test
public void test_bundleTgzExists_returnFalse() throws DotDataException {
final String bundleId = insertPublishingBundle(adminUser.getUserId(),new Date());
final Bundle bundle = bundleAPI.getBundleById(bundleId);
assertFalse(bundle.bundleTgzExists());
}

private static void createFilter(){
final Map<String,Object> filtersMap =
ImmutableMap.of("dependencies",true,"relationships",true);
final FilterDescriptor filterDescriptor =
new FilterDescriptor("filterTestAPI.yml","Filter Test Title",filtersMap,true,"Reviewer,dotcms.org.2789");

APILocator.getPublisherAPI().addFilterDescriptor(filterDescriptor);
}

/**
* Method to test: {@link Bundle#bundleTgzExists()} and {@link BundleAPI#generateTarGzipBundleFile(Bundle)}
* Given Scenario: This test is for checking if a bundle tar.gz file was already generated.
* ExpectedResult: true, since the bundle is created but no generated.
*/
@Test
public void test_bundleTgzExists_returnTrue() throws DotDataException {
final String bundleId = insertPublishingBundle(adminUser.getUserId(),new Date());
createFilter();//A Default filter is needed
final Bundle bundle = bundleAPI.getBundleById(bundleId);
bundle.setOperation(PushPublisherConfig.Operation.PUBLISH.ordinal());
bundleAPI.generateTarGzipBundleFile(bundle);
assertTrue(bundle.bundleTgzExists());
PublisherAPIImpl.class.cast(APILocator.getPublisherAPI()).getFilterDescriptorMap().clear();
}
}
@@ -1,6 +1,8 @@
package com.dotcms.rendering.velocity.services;

import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import com.dotcms.api.web.HttpServletRequestThreadLocal;
import com.dotcms.contenttype.business.ContentTypeAPI;
@@ -15,6 +17,7 @@
import com.dotcms.mock.request.MockAttributeRequest;
import com.dotcms.mock.request.MockHttpRequest;
import com.dotcms.mock.request.MockSessionRequest;
import com.dotcms.rendering.velocity.directive.ParseContainer;
import com.dotcms.util.IntegrationTestInitService;
import com.dotmarketing.beans.ContainerStructure;
import com.dotmarketing.beans.Host;
@@ -61,11 +64,9 @@
import com.tngtech.java.junit.dataprovider.DataProvider;
import com.tngtech.java.junit.dataprovider.DataProviderRunner;
import com.tngtech.java.junit.dataprovider.UseDataProvider;
import io.vavr.Function2;
import org.jetbrains.annotations.NotNull;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.*;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import com.dotcms.visitor.domain.Visitor;
@@ -194,7 +195,7 @@ public static void prepareGlobalData() throws Exception {
persona = new PersonaDataGen().keyTag("persona"+System.currentTimeMillis()).hostFolder(host.getIdentifier()).nextPersisted();

visitor = mock(Visitor.class);
Mockito.when(visitor.getPersona()).thenReturn(persona);
when(visitor.getPersona()).thenReturn(persona);
}

private static void createTestPage() throws Exception{
@@ -390,7 +391,7 @@ public void ContentFallbackFalse_PageFallbackTrue_PageEnglish_ViewEnglishContent
HttpServletRequest mockRequest = new MockSessionRequest(
new MockAttributeRequest(new MockHttpRequest("localhost", "/").request()).request())
.request();
Mockito.when(mockRequest.getParameter("host_id")).thenReturn(site.getIdentifier());
when(mockRequest.getParameter("host_id")).thenReturn(site.getIdentifier());
mockRequest.setAttribute(WebKeys.HTMLPAGE_LANGUAGE, "1");
HttpServletRequestThreadLocal.INSTANCE.setRequest(mockRequest);
final HttpServletResponse mockResponse = mock(HttpServletResponse.class);
@@ -401,7 +402,7 @@ public void ContentFallbackFalse_PageFallbackTrue_PageEnglish_ViewEnglishContent
.setPageMode(PageMode.LIVE)
.build(),
mockRequest, mockResponse);
Assert.assertTrue("ENG = "+html , html.contains("content1") && html.contains("content2"));
assertTrue("ENG = "+html , html.contains("content1") && html.contains("content2"));
}

///////
@@ -450,7 +451,7 @@ public void render_spanish_contentlets_on_english_page(
new MockAttributeRequest(new MockHttpRequest("localhost", "/").request()).request())
.request();
final HttpSession httpSession = mockRequest.getSession();
Mockito.when(mockRequest.getParameter("host_id")).thenReturn(site.getIdentifier());
when(mockRequest.getParameter("host_id")).thenReturn(site.getIdentifier());
mockRequest
.setAttribute(WebKeys.HTMLPAGE_LANGUAGE, String.valueOf(spanishLanguage.getId()));
httpSession.setAttribute(WebKeys.HTMLPAGE_LANGUAGE, String.valueOf(spanishLanguage.getId()));
@@ -468,18 +469,36 @@ public void render_spanish_contentlets_on_english_page(
.setPageMode(PageMode.LIVE)
.build(),
mockRequest, mockResponse);
Assert.assertTrue("ESP = "+html , html.contains("content2Spa") && html.contains("content3Spa"));
assertTrue("ESP = "+html , html.contains("content2Spa") && html.contains("content3Spa"));
}
///////

@NotNull
private HTMLPageAsset createHtmlPageAsset(
final Template template,
final Template template,
final String pageName,
final long languageId)

throws DotSecurityException, DotDataException {
final HTMLPageAsset pageEnglishVersion = new HTMLPageDataGen(folder,template).languageId(languageId).pageURL(pageName).title(pageName).cacheTTL(0).nextPersisted();
return createHtmlPageAsset(site, template, pageName, languageId);
}

@NotNull
private HTMLPageAsset createHtmlPageAsset(
final Host host, final Template template,
final String pageName,
final long languageId)

throws DotSecurityException, DotDataException {

final Folder folder = new FolderDataGen().site(host).nextPersisted();
final HTMLPageAsset pageEnglishVersion = new HTMLPageDataGen(folder,template)
.languageId(languageId)
.pageURL(pageName)
.title(pageName)
.cacheTTL(0)
.nextPersisted();

pageEnglishVersion.setIndexPolicy(IndexPolicy.WAIT_FOR);
pageEnglishVersion.setIndexPolicyDependencies(IndexPolicy.WAIT_FOR);
pageEnglishVersion.setBoolProperty(Contentlet.IS_TEST_MODE, true);
@@ -525,7 +544,7 @@ public void ContentFallbackFalse_PageFallbackTrue_PageEnglishAndSpanish_ViewEngl
HttpServletRequest mockRequest = new MockSessionRequest(
new MockAttributeRequest(new MockHttpRequest("localhost", "/").request()).request())
.request();
Mockito.when(mockRequest.getParameter("host_id")).thenReturn(site.getIdentifier());
when(mockRequest.getParameter("host_id")).thenReturn(site.getIdentifier());
mockRequest.setAttribute(WebKeys.HTMLPAGE_LANGUAGE, "1");
HttpServletRequestThreadLocal.INSTANCE.setRequest(mockRequest);
final HttpServletResponse mockResponse = mock(HttpServletResponse.class);
@@ -536,12 +555,12 @@ public void ContentFallbackFalse_PageFallbackTrue_PageEnglishAndSpanish_ViewEngl
.setPageMode(PageMode.LIVE)
.build(),
mockRequest, mockResponse);
Assert.assertTrue("ENG = "+html , html.contains("content1") && html.contains("content2"));
assertTrue("ENG = "+html , html.contains("content1") && html.contains("content2"));

mockRequest = new MockSessionRequest(
new MockAttributeRequest(new MockHttpRequest("localhost", "/").request()).request())
.request();
Mockito.when(mockRequest.getParameter("host_id")).thenReturn(site.getIdentifier());
when(mockRequest.getParameter("host_id")).thenReturn(site.getIdentifier());
mockRequest
.setAttribute(WebKeys.HTMLPAGE_LANGUAGE, String.valueOf(spanishLanguage.getId()));
HttpServletRequestThreadLocal.INSTANCE.setRequest(mockRequest);
@@ -552,7 +571,7 @@ public void ContentFallbackFalse_PageFallbackTrue_PageEnglishAndSpanish_ViewEngl
.setPageMode(PageMode.LIVE)
.build(),
mockRequest, mockResponse);
Assert.assertTrue("ESP = "+html , html.contains("content2Spa") && html.contains("content3Spa"));
assertTrue("ESP = "+html , html.contains("content2Spa") && html.contains("content3Spa"));

}

@@ -589,12 +608,12 @@ public void ContentFallbackFalse_PageFallbackTrue_PageSpanish_ViewEnglish404_Vie
.setPageMode(PageMode.LIVE)
.build(),
mockRequest, mockResponse);
Assert.assertTrue("ESP = "+html , html.contains("content3Spacontent2Spa"));
assertTrue("ESP = "+html , html.contains("content3Spacontent2Spa"));

mockRequest = new MockSessionRequest(
new MockAttributeRequest(new MockHttpRequest("localhost", "/").request()).request())
.request();
Mockito.when(mockRequest.getParameter("host_id")).thenReturn(site.getIdentifier());
when(mockRequest.getParameter("host_id")).thenReturn(site.getIdentifier());
mockRequest.setAttribute(WebKeys.HTMLPAGE_LANGUAGE, "1");
HttpServletRequestThreadLocal.INSTANCE.setRequest(mockRequest);

@@ -635,7 +654,7 @@ public void ContentFallbackFalse_PageFallbackFalse_PageEnglish_ViewEnglishConten
HttpServletRequest mockRequest = new MockSessionRequest(
new MockAttributeRequest(new MockHttpRequest("localhost", "/").request()).request())
.request();
Mockito.when(mockRequest.getParameter("host_id")).thenReturn(site.getIdentifier());
when(mockRequest.getParameter("host_id")).thenReturn(site.getIdentifier());
mockRequest.setAttribute(WebKeys.HTMLPAGE_LANGUAGE, "1");
HttpServletRequestThreadLocal.INSTANCE.setRequest(mockRequest);
final HttpServletResponse mockResponse = mock(HttpServletResponse.class);
@@ -646,7 +665,7 @@ public void ContentFallbackFalse_PageFallbackFalse_PageEnglish_ViewEnglishConten
.setPageMode(PageMode.LIVE)
.build(),
mockRequest, mockResponse);
Assert.assertTrue("ENG = "+html , html.contains("content2content1"));
assertTrue("ENG = "+html , html.contains("content2content1"));

mockRequest = new MockSessionRequest(
new MockAttributeRequest(new MockHttpRequest("localhost", "/").request()).request())
@@ -705,7 +724,7 @@ public void ContentFallbackFalse_PageFallbackFalse_PageEnglishAndSpanish_ViewEng
HttpServletRequest mockRequest = new MockSessionRequest(
new MockAttributeRequest(new MockHttpRequest("localhost", "/").request()).request())
.request();
Mockito.when(mockRequest.getParameter("host_id")).thenReturn(site.getIdentifier());
when(mockRequest.getParameter("host_id")).thenReturn(site.getIdentifier());
mockRequest.setAttribute(WebKeys.HTMLPAGE_LANGUAGE, "1");
HttpServletRequestThreadLocal.INSTANCE.setRequest(mockRequest);
final HttpServletResponse mockResponse = mock(HttpServletResponse.class);
@@ -716,12 +735,12 @@ public void ContentFallbackFalse_PageFallbackFalse_PageEnglishAndSpanish_ViewEng
.setPageMode(PageMode.LIVE)
.build(),
mockRequest, mockResponse);
Assert.assertTrue("ENG = "+html , html.contains("content1") && html.contains("content2"));
assertTrue("ENG = "+html , html.contains("content1") && html.contains("content2"));

mockRequest = new MockSessionRequest(
new MockAttributeRequest(new MockHttpRequest("localhost", "/").request()).request())
.request();
Mockito.when(mockRequest.getParameter("host_id")).thenReturn(site.getIdentifier());
when(mockRequest.getParameter("host_id")).thenReturn(site.getIdentifier());
mockRequest
.setAttribute(WebKeys.HTMLPAGE_LANGUAGE, String.valueOf(spanishLanguage.getId()));
HttpServletRequestThreadLocal.INSTANCE.setRequest(mockRequest);
@@ -732,7 +751,7 @@ public void ContentFallbackFalse_PageFallbackFalse_PageEnglishAndSpanish_ViewEng
.setPageMode(PageMode.LIVE)
.build(),
mockRequest, mockResponse);
Assert.assertTrue("ESP = "+html , html.contains("content2Spa") && html.contains("content3Spa"));
assertTrue("ESP = "+html , html.contains("content2Spa") && html.contains("content3Spa"));
}

/**
@@ -772,7 +791,7 @@ public void ContentFallbackTrue_PageFallbackTrue_PageEnglishAndSpanish_ViewEngli
HttpServletRequest mockRequest = new MockSessionRequest(
new MockAttributeRequest(new MockHttpRequest("localhost", "/").request()).request())
.request();
Mockito.when(mockRequest.getParameter("host_id")).thenReturn(site.getIdentifier());
when(mockRequest.getParameter("host_id")).thenReturn(site.getIdentifier());
mockRequest.setAttribute(WebKeys.HTMLPAGE_LANGUAGE, "1");
HttpServletRequestThreadLocal.INSTANCE.setRequest(mockRequest);
final HttpServletResponse mockResponse = mock(HttpServletResponse.class);
@@ -783,12 +802,12 @@ public void ContentFallbackTrue_PageFallbackTrue_PageEnglishAndSpanish_ViewEngli
.setPageMode(PageMode.LIVE)
.build(),
mockRequest, mockResponse);
Assert.assertTrue("ENG = "+html , html.contains("content1") && html.contains("content2"));
assertTrue("ENG = "+html , html.contains("content1") && html.contains("content2"));

mockRequest = new MockSessionRequest(
new MockAttributeRequest(new MockHttpRequest("localhost", "/").request()).request())
.request();
Mockito.when(mockRequest.getParameter("host_id")).thenReturn(site.getIdentifier());
when(mockRequest.getParameter("host_id")).thenReturn(site.getIdentifier());
mockRequest
.setAttribute(WebKeys.HTMLPAGE_LANGUAGE, String.valueOf(spanishLanguage.getId()));
HttpServletRequestThreadLocal.INSTANCE.setRequest(mockRequest);
@@ -799,7 +818,7 @@ public void ContentFallbackTrue_PageFallbackTrue_PageEnglishAndSpanish_ViewEngli
.setPageMode(PageMode.LIVE)
.build(),
mockRequest, mockResponse);
Assert.assertTrue("ESP = "+html , html.contains("content3Spa")
assertTrue("ESP = "+html , html.contains("content3Spa")
&& html.contains("content2Spa") && html.contains("content1"));
}

@@ -825,7 +844,7 @@ public void ContentFallbackTrue_PageFallbackFalse_PageEnglish_ViewEnglishContent
HttpServletRequest mockRequest = new MockSessionRequest(
new MockAttributeRequest(new MockHttpRequest("localhost", "/").request()).request())
.request();
Mockito.when(mockRequest.getParameter("host_id")).thenReturn(site.getIdentifier());
when(mockRequest.getParameter("host_id")).thenReturn(site.getIdentifier());
mockRequest.setAttribute(WebKeys.HTMLPAGE_LANGUAGE, "1");
HttpServletRequestThreadLocal.INSTANCE.setRequest(mockRequest);
final HttpServletResponse mockResponse = mock(HttpServletResponse.class);
@@ -836,7 +855,7 @@ public void ContentFallbackTrue_PageFallbackFalse_PageEnglish_ViewEnglishContent
.setPageMode(PageMode.LIVE)
.build(),
mockRequest, mockResponse);
Assert.assertTrue("ENG = "+html , html.contains("content2content1"));
assertTrue("ENG = "+html , html.contains("content2content1"));

mockRequest = new MockSessionRequest(
new MockAttributeRequest(new MockHttpRequest("localhost", "/").request()).request())
@@ -914,7 +933,7 @@ public void constantField_notUpdatedCache_whenChanged(final Container container,
new MockAttributeRequest(new MockHttpRequest("localhost", "/").request())
.request())
.request();
Mockito.when(mockRequest.getParameter("host_id")).thenReturn(site.getIdentifier());
when(mockRequest.getParameter("host_id")).thenReturn(site.getIdentifier());
mockRequest.setAttribute(WebKeys.HTMLPAGE_LANGUAGE, "1");
HttpServletRequestThreadLocal.INSTANCE.setRequest(mockRequest);
final HttpServletResponse mockResponse = mock(HttpServletResponse.class);
@@ -926,7 +945,7 @@ public void constantField_notUpdatedCache_whenChanged(final Container container,
.setPageMode(PageMode.LIVE)
.build(),
mockRequest, mockResponse);
Assert.assertTrue(html, html.contains("original code"));
assertTrue(html, html.contains("original code"));

fields = contentType.fields();
codeField = (ImmutableConstantField) fields.stream()
@@ -940,7 +959,7 @@ public void constantField_notUpdatedCache_whenChanged(final Container container,
new MockAttributeRequest(new MockHttpRequest("localhost", "/").request())
.request())
.request();
Mockito.when(mockRequest.getParameter("host_id")).thenReturn(site.getIdentifier());
when(mockRequest.getParameter("host_id")).thenReturn(site.getIdentifier());
mockRequest.setAttribute(WebKeys.HTMLPAGE_LANGUAGE, "1");
HttpServletRequestThreadLocal.INSTANCE.setRequest(mockRequest);
html = APILocator.getHTMLPageAssetRenderedAPI()
@@ -951,7 +970,7 @@ public void constantField_notUpdatedCache_whenChanged(final Container container,
.setPageMode(PageMode.LIVE)
.build(),
mockRequest, mockResponse);
Assert.assertTrue(html, html.contains("this has been changed"));
assertTrue(html, html.contains("this has been changed"));
}finally {
APILocator.getContentTypeAPI(systemUser).delete(contentType);
}
@@ -985,7 +1004,7 @@ public void containerArchived_PageShouldResolve() throws Exception {
new MockAttributeRequest(new MockHttpRequest("localhost", "/").request())
.request())
.request();
Mockito.when(mockRequest.getParameter("host_id")).thenReturn(site.getIdentifier());
when(mockRequest.getParameter("host_id")).thenReturn(site.getIdentifier());
mockRequest.setAttribute(WebKeys.HTMLPAGE_LANGUAGE, "1");
HttpServletRequestThreadLocal.INSTANCE.setRequest(mockRequest);
final HttpServletResponse mockResponse = mock(HttpServletResponse.class);
@@ -997,7 +1016,7 @@ public void containerArchived_PageShouldResolve() throws Exception {
.setPageMode(PageMode.LIVE)
.build(),
mockRequest, mockResponse);
Assert.assertTrue(html, html.contains("content1") && html.contains("content2"));
assertTrue(html, html.contains("content1") && html.contains("content2"));

WebAssetFactory.unLockAsset(container);
WebAssetFactory.archiveAsset(container, systemUser);
@@ -1007,7 +1026,7 @@ public void containerArchived_PageShouldResolve() throws Exception {
new MockAttributeRequest(new MockHttpRequest("localhost", "/").request())
.request())
.request();
Mockito.when(mockRequest.getParameter("host_id")).thenReturn(site.getIdentifier());
when(mockRequest.getParameter("host_id")).thenReturn(site.getIdentifier());
mockRequest.setAttribute(WebKeys.HTMLPAGE_LANGUAGE, "1");
HttpServletRequestThreadLocal.INSTANCE.setRequest(mockRequest);
html = APILocator.getHTMLPageAssetRenderedAPI()
@@ -1018,7 +1037,7 @@ public void containerArchived_PageShouldResolve() throws Exception {
.setPageMode(PageMode.LIVE)
.build(),
mockRequest, mockResponse);
Assert.assertTrue(html, html.isEmpty());
assertTrue(html, html.isEmpty());

WebAssetFactory.unArchiveAsset(container);
WebAssetFactory.publishAsset(container, systemUser);
@@ -1028,7 +1047,7 @@ public void containerArchived_PageShouldResolve() throws Exception {
new MockAttributeRequest(new MockHttpRequest("localhost", "/").request())
.request())
.request();
Mockito.when(mockRequest.getParameter("host_id")).thenReturn(site.getIdentifier());
when(mockRequest.getParameter("host_id")).thenReturn(site.getIdentifier());
mockRequest.setAttribute(WebKeys.HTMLPAGE_LANGUAGE, "1");
HttpServletRequestThreadLocal.INSTANCE.setRequest(mockRequest);
html = APILocator.getHTMLPageAssetRenderedAPI()
@@ -1039,7 +1058,7 @@ public void containerArchived_PageShouldResolve() throws Exception {
.setPageMode(PageMode.LIVE)
.build(),
mockRequest, mockResponse);
Assert.assertTrue(html, html.contains("content1") && html.contains("content2"));
assertTrue(html, html.contains("content1") && html.contains("content2"));
}finally {
if (!(container instanceof FileAssetContainer)) {
WebAssetFactory.unArchiveAsset(container);
@@ -1066,16 +1085,16 @@ public void shouldReturnPageHTMLForPersona(final Container container, final Temp
createMultiTree(pageEnglishVersion.getIdentifier(), container.getIdentifier());

final HttpServletRequest mockRequest = mock(HttpServletRequest.class);
Mockito.when(mockRequest.getParameter("host_id")).thenReturn(site.getIdentifier());
when(mockRequest.getParameter("host_id")).thenReturn(site.getIdentifier());
mockRequest.setAttribute(WebKeys.HTMLPAGE_LANGUAGE, "1");
HttpServletRequestThreadLocal.INSTANCE.setRequest(mockRequest);
Mockito.when(mockRequest.getAttribute(WebKeys.CURRENT_HOST)).thenReturn(site);
Mockito.when(mockRequest.getRequestURI()).thenReturn(pageEnglishVersion.getURI());
when(mockRequest.getAttribute(WebKeys.CURRENT_HOST)).thenReturn(site);
when(mockRequest.getRequestURI()).thenReturn(pageEnglishVersion.getURI());

final HttpServletResponse mockResponse = mock(HttpServletResponse.class);

final HttpSession session = createHttpSession(mockRequest);
Mockito.when(session.getAttribute(WebKeys.VISITOR)).thenReturn(visitor);
when(session.getAttribute(WebKeys.VISITOR)).thenReturn(visitor);

String html = APILocator.getHTMLPageAssetRenderedAPI().getPageHtml(
PageContextBuilder.builder()
@@ -1084,9 +1103,9 @@ public void shouldReturnPageHTMLForPersona(final Container container, final Temp
.setPageMode(PageMode.LIVE)
.build(),
mockRequest, mockResponse);
Assert.assertTrue(html , html.contains("content4"));
assertTrue(html , html.contains("content4"));

Mockito.when(session.getAttribute(WebKeys.VISITOR)).thenReturn(null);
when(session.getAttribute(WebKeys.VISITOR)).thenReturn(null);

html = APILocator.getHTMLPageAssetRenderedAPI().getPageHtml(
PageContextBuilder.builder()
@@ -1095,15 +1114,15 @@ public void shouldReturnPageHTMLForPersona(final Container container, final Temp
.setPageMode(PageMode.LIVE)
.build(),
mockRequest, mockResponse);
Assert.assertTrue(html , html.contains("content1") && html.contains("content2"));
assertTrue(html , html.contains("content1") && html.contains("content2"));

}

private HttpSession createHttpSession(final HttpServletRequest mockRequest) {
final HttpSession session = mock(HttpSession.class);
Mockito.when(mockRequest.getSession()).thenReturn(session);
Mockito.when(mockRequest.getSession(false)).thenReturn(session);
Mockito.when(mockRequest.getSession(true)).thenReturn(session);
when(mockRequest.getSession()).thenReturn(session);
when(mockRequest.getSession(false)).thenReturn(session);
when(mockRequest.getSession(true)).thenReturn(session);
return session;
}

@@ -1140,16 +1159,16 @@ public void shouldReturnPageHTMLForLegacyUUID(final Container container, final T
APILocator.getMultiTreeAPI().saveMultiTree(multiTree);

final HttpServletRequest mockRequest = mock(HttpServletRequest.class);
Mockito.when(mockRequest.getParameter("host_id")).thenReturn(site.getIdentifier());
when(mockRequest.getParameter("host_id")).thenReturn(site.getIdentifier());
mockRequest.setAttribute(WebKeys.HTMLPAGE_LANGUAGE, "1");
HttpServletRequestThreadLocal.INSTANCE.setRequest(mockRequest);
Mockito.when(mockRequest.getAttribute(WebKeys.CURRENT_HOST)).thenReturn(site);
Mockito.when(mockRequest.getRequestURI()).thenReturn(page.getURI());
when(mockRequest.getAttribute(WebKeys.CURRENT_HOST)).thenReturn(site);
when(mockRequest.getRequestURI()).thenReturn(page.getURI());

final HttpServletResponse mockResponse = mock(HttpServletResponse.class);

final HttpSession session = createHttpSession(mockRequest);
Mockito.when(session.getAttribute(WebKeys.VISITOR)).thenReturn(null);
when(session.getAttribute(WebKeys.VISITOR)).thenReturn(null);

String html = APILocator.getHTMLPageAssetRenderedAPI().getPageHtml(
PageContextBuilder.builder()
@@ -1193,16 +1212,16 @@ public void shouldReturnPageHTMLForLegacyUUIDAndMultiTree(final Container contai
APILocator.getMultiTreeAPI().saveMultiTree(multiTree);

final HttpServletRequest mockRequest = mock(HttpServletRequest.class);
Mockito.when(mockRequest.getParameter("host_id")).thenReturn(site.getIdentifier());
when(mockRequest.getParameter("host_id")).thenReturn(site.getIdentifier());
mockRequest.setAttribute(WebKeys.HTMLPAGE_LANGUAGE, "1");
HttpServletRequestThreadLocal.INSTANCE.setRequest(mockRequest);
Mockito.when(mockRequest.getAttribute(WebKeys.CURRENT_HOST)).thenReturn(site);
Mockito.when(mockRequest.getRequestURI()).thenReturn(page.getURI());
when(mockRequest.getAttribute(WebKeys.CURRENT_HOST)).thenReturn(site);
when(mockRequest.getRequestURI()).thenReturn(page.getURI());

final HttpServletResponse mockResponse = mock(HttpServletResponse.class);

final HttpSession session = createHttpSession(mockRequest);
Mockito.when(session.getAttribute(WebKeys.VISITOR)).thenReturn(null);
when(session.getAttribute(WebKeys.VISITOR)).thenReturn(null);

String html = APILocator.getHTMLPageAssetRenderedAPI().getPageHtml(
PageContextBuilder.builder()
@@ -1237,7 +1256,7 @@ public void shouldReturnParserContainerUUID(final Container container, final Tem

final HttpSession session = createHttpSession(mockRequest);

Mockito.when(session.getAttribute(WebKeys.VISITOR)).thenReturn(null);
when(session.getAttribute(WebKeys.VISITOR)).thenReturn(null);

systemUser.isBackendUser();

@@ -1256,79 +1275,101 @@ public void shouldReturnParserContainerUUID(final Container container, final Tem
"<div data-dot-object=\"contentlet\" .*>.*</div>" +
"</div>";

Assert.assertTrue(html.matches(regexExpected));
assertTrue(html.matches(regexExpected));
}

/**
* Method to test: {@link com.dotmarketing.portlets.htmlpageasset.business.render.HTMLPageAssetRenderedAPIImpl#getPageHtml(PageContext, HttpServletRequest, HttpServletResponse)}
* Given Scenario: Create a page with File Container linked with relative path in the template
* ExpectedResult: should work
*
* @throws Exception
*/
@Test
public void shouldRenderRelativeContainerPath() throws Exception {

final FileAssetContainer container = createFileContainer();
final Template template = new TemplateDataGen().title("PageContextBuilderTemplate"+System.currentTimeMillis())
.host(site)
.withContainer(container.getPath(), UUID)
.nextPersisted();
PublishFactory.publishAsset(template, systemUser, false, false);

final String pageName = "testPage-"+System.currentTimeMillis();
final HTMLPageAsset pageEnglishVersion = createHtmlPageAsset(template, pageName, 1);
@DataProvider(format = "%m page Host: %p[0] Template Host: %p[1] Container Host: %p[2]")
public static Object[][] fileContainerCases() throws Exception {
if (systemUser == null) {
prepareGlobalData();
}

createMultiTree(pageEnglishVersion.getIdentifier(), container.getIdentifier());
final Function2<FileAssetContainer, Host, String> relativePath =
(FileAssetContainer container, Host host) -> container.getPath();
final Function2<FileAssetContainer, Host, String> absolutePath =
(FileAssetContainer container, Host host) -> "//" + host.getName() + container.getPath();

final HttpServletRequest mockRequest = createHttpServletRequest(pageEnglishVersion);
Mockito.when(mockRequest.getParameter(WebKeys.PAGE_MODE_PARAMETER)).thenReturn(PageMode.LIVE.toString());
final Function2<Host, String, Template> advanceTemplate =
(final Host templateHost, final String containerPath) -> createAdvancedTemplate(templateHost, containerPath);
final Function2<Host, String, Template> drawedTemplate =
(final Host templateHost, final String containerPath) -> createDrawedTemplate(templateHost, containerPath);

final HttpServletResponse mockResponse = mock(HttpServletResponse.class);
final HttpSession session = createHttpSession(mockRequest);
Mockito.when(session.getAttribute(WebKeys.VISITOR)).thenReturn(null);
Mockito.when(session.getAttribute(WebKeys.CMS_USER)).thenReturn(systemUser);
final Host anotherHost = new SiteDataGen().nextPersisted();
final Host defaultHost = APILocator.getHostAPI().findDefaultHost(APILocator.systemUser(), true);
final Host currentHost = site;

final String html = APILocator.getHTMLPageAssetRenderedAPI().getPageHtml(
PageContextBuilder.builder()
.setUser(systemUser)
.setPageUri(pageEnglishVersion.getURI())
.setPageMode(PageMode.LIVE)
.build(),
mockRequest, mockResponse);
final HttpServletRequest request = mock(HttpServletRequest.class);
HttpServletRequestThreadLocal.INSTANCE.setRequest(request);
when(request.getAttribute(WebKeys.CURRENT_HOST)).thenReturn(currentHost);

Assert.assertTrue(html, html.contains("content1") && html.contains("content2"));
return new Object[][] {
{ currentHost, currentHost, currentHost, relativePath, advanceTemplate, true},
{ currentHost, currentHost, anotherHost, relativePath, advanceTemplate, false},
{ currentHost, anotherHost, anotherHost, relativePath, advanceTemplate, false},
{ currentHost, currentHost, defaultHost, relativePath, advanceTemplate, true},
{ currentHost, anotherHost, defaultHost, relativePath, advanceTemplate, true},

{ currentHost, currentHost, currentHost, absolutePath, advanceTemplate, true},
{ currentHost, currentHost, anotherHost, absolutePath, advanceTemplate, true},
{ currentHost, anotherHost, anotherHost, absolutePath, advanceTemplate, true},
{ currentHost, currentHost, defaultHost, absolutePath, advanceTemplate, true},
{ currentHost, anotherHost, defaultHost, absolutePath, advanceTemplate, true},

{ currentHost, currentHost, currentHost, relativePath, drawedTemplate, true},
{ currentHost, currentHost, anotherHost, relativePath, drawedTemplate, false},
{ currentHost, anotherHost, anotherHost, relativePath, drawedTemplate, false},
{ currentHost, currentHost, defaultHost, relativePath, drawedTemplate, true},
{ currentHost, anotherHost, defaultHost, relativePath, drawedTemplate, true},

{ currentHost, currentHost, currentHost, absolutePath, drawedTemplate, true},
{ currentHost, currentHost, anotherHost, absolutePath, drawedTemplate, true},
{ currentHost, anotherHost, anotherHost, absolutePath, drawedTemplate, true},
{ currentHost, currentHost, defaultHost, absolutePath, drawedTemplate, true},
{ currentHost, anotherHost, defaultHost, absolutePath, drawedTemplate, true}
};
}

/**
* Method to test: {@link com.dotmarketing.portlets.htmlpageasset.business.render.HTMLPageAssetRenderedAPIImpl#getPageHtml(PageContext, HttpServletRequest, HttpServletResponse)}
* Given Scenario: Create a page and a advance template using a File Container in another site
* Given Scenario:
* - Template, FileContainer and Page in the same site or different site
* - Using Advance Template or not Advance Template
* - Using relative or absolute path
* ExpectedResult: should work
*
* @throws Exception
*/
@Test
public void shouldRenderUsingOtherSiteContainer() throws Exception {
final Host defaultHost = APILocator.getHostAPI().findDefaultHost(APILocator.systemUser(), true);
final FileAssetContainer container = createFileContainer(defaultHost);
final Template template = new TemplateDataGen().title("PageContextBuilderTemplate"+System.currentTimeMillis())
.host(site)
.withContainer("//" + defaultHost.getName() + container.getPath(), UUID)
.nextPersisted();
@UseDataProvider("fileContainerCases")
public void shouldRenderTemplateAndContainers(
final Host pageHost,
final Host templateHost,
final Host containerHost,
final Function2<FileAssetContainer, Host, String> pathConverter,
final Function2<Host, String, Template> templateCreator,
final boolean shouldWork)
throws Exception {

final FileAssetContainer container = createFileContainer(containerHost);
final String path = pathConverter.apply(container, containerHost);
final Template template = templateCreator.apply(templateHost, path);
PublishFactory.publishAsset(template, systemUser, false, false);

final String pageName = "testPage-"+System.currentTimeMillis();
final HTMLPageAsset pageEnglishVersion = createHtmlPageAsset(template, pageName, 1);
final HTMLPageAsset pageEnglishVersion = createHtmlPageAsset(pageHost, template, pageName, 1);

createMultiTree(pageEnglishVersion.getIdentifier(), container.getIdentifier());
createMultiTree(pageEnglishVersion.getIdentifier(), container.getIdentifier(),
template.isDrawed() ? ContainerUUID.UUID_START_VALUE :
ParseContainer.getDotParserContainerUUID(ContainerUUID.UUID_START_VALUE));

final HttpServletRequest mockRequest = createHttpServletRequest(pageEnglishVersion);
Mockito.when(mockRequest.getParameter(WebKeys.PAGE_MODE_PARAMETER)).thenReturn(PageMode.LIVE.toString());
when(mockRequest.getParameter(WebKeys.PAGE_MODE_PARAMETER)).thenReturn(PageMode.LIVE.toString());

final HttpServletResponse mockResponse = mock(HttpServletResponse.class);
final HttpSession session = createHttpSession(mockRequest);
Mockito.when(session.getAttribute(WebKeys.VISITOR)).thenReturn(null);
Mockito.when(session.getAttribute(WebKeys.CMS_USER)).thenReturn(systemUser);
when(session.getAttribute(WebKeys.VISITOR)).thenReturn(null);
when(session.getAttribute(WebKeys.CMS_USER)).thenReturn(systemUser);

final String html = APILocator.getHTMLPageAssetRenderedAPI().getPageHtml(
PageContextBuilder.builder()
@@ -1338,57 +1379,41 @@ public void shouldRenderUsingOtherSiteContainer() throws Exception {
.build(),
mockRequest, mockResponse);

Assert.assertTrue(html, html.contains("content1") && html.contains("content2"));
if (shouldWork) {
assertTrue(
String.format("Should has content: using %s path and %s Template", path, template.isDrawed() ? "Drawed" : "Advanced"),
html.contains("content1") && html.contains("content2")
);
} else {
Assert.assertFalse(
String.format("Should has content: using %s path and %s Template", path, template.isDrawed() ? "Drawed" : "Advanced"),
html.contains("content1") && html.contains("content2")
);
}
}

/**
* Method to test: {@link com.dotmarketing.portlets.htmlpageasset.business.render.HTMLPageAssetRenderedAPIImpl#getPageHtml(PageContext, HttpServletRequest, HttpServletResponse)}
* Given Scenario: Create a page and a not advance template using a File Container in another site
* ExpectedResult: should work
*
* @throws Exception
*/
@Test
public void shouldRenderUsingOtherSiteContainerAndNotAdvanceTemplate() throws Exception {
final Host defaultHost = APILocator.getHostAPI().findDefaultHost(APILocator.systemUser(), true);
final FileAssetContainer container = createFileContainer(defaultHost);
private static Template createAdvancedTemplate(
final Host templateHost,
final String containerPath) {

return new TemplateDataGen().title("PageContextBuilderTemplate"+System.currentTimeMillis())
.host(templateHost)
.withContainer(containerPath, ContainerUUID.UUID_START_VALUE)
.nextPersisted();
}

private static Template createDrawedTemplate(final Host host, final String containerPath) {
final TemplateLayout templateLayout = new TemplateLayoutDataGen()
.withContainer("//" + defaultHost.getName() + container.getPath())
.withContainer(containerPath)
.next();

final Contentlet contentlet = new ThemeDataGen().nextPersisted();
final Template template = new TemplateDataGen()
return new TemplateDataGen()
.title("PageContextBuilderTemplate"+System.currentTimeMillis())
.host(site)
.host(host)
.drawedBody(templateLayout)
.theme(contentlet)
.nextPersisted();

PublishFactory.publishAsset(template, systemUser, false, false);

final String pageName = "testPage-"+System.currentTimeMillis();
final HTMLPageAsset pageEnglishVersion = createHtmlPageAsset(template, pageName, 1);

createMultiTree(pageEnglishVersion.getIdentifier(), container.getIdentifier(), "1");

final HttpServletRequest mockRequest = createHttpServletRequest(pageEnglishVersion);
Mockito.when(mockRequest.getParameter(WebKeys.PAGE_MODE_PARAMETER)).thenReturn(PageMode.LIVE.toString());

final HttpServletResponse mockResponse = mock(HttpServletResponse.class);
final HttpSession session = createHttpSession(mockRequest);
Mockito.when(session.getAttribute(WebKeys.VISITOR)).thenReturn(null);
Mockito.when(session.getAttribute(WebKeys.CMS_USER)).thenReturn(systemUser);

final String html = APILocator.getHTMLPageAssetRenderedAPI().getPageHtml(
PageContextBuilder.builder()
.setUser(systemUser)
.setPageUri(pageEnglishVersion.getURI())
.setPageMode(PageMode.LIVE)
.build(),
mockRequest, mockResponse);

Assert.assertTrue(html, html.contains("content1") && html.contains("content2"));
}

//Data Provider for the Widget Pre-execute code test
@@ -1421,7 +1446,7 @@ public void test_WidgetPreExecuteCodeShowRegardlessPageMode(final WidgetPreExecu
APILocator.getContentTypeFieldAPI()
.save(FieldBuilder.builder(preExecuteField).values(preExcuteCode).build(), systemUser);
// Assert that the widget has set the pre-execute field
Assert.assertTrue(APILocator.getContentTypeFieldAPI()
assertTrue(APILocator.getContentTypeFieldAPI()
.byContentTypeIdAndVar(contentType.id(),WidgetContentType.WIDGET_PRE_EXECUTE_FIELD_VAR)
.values().equals(preExcuteCode));

@@ -1444,8 +1469,8 @@ public void test_WidgetPreExecuteCodeShowRegardlessPageMode(final WidgetPreExecu
final HttpServletRequest mockRequest = createHttpServletRequest(page);
final HttpServletResponse mockResponse = mock(HttpServletResponse.class);
final HttpSession session = createHttpSession(mockRequest);
Mockito.when(session.getAttribute(WebKeys.VISITOR)).thenReturn(null);
Mockito.when(session.getAttribute(WebKeys.CMS_USER)).thenReturn(systemUser);
when(session.getAttribute(WebKeys.VISITOR)).thenReturn(null);
when(session.getAttribute(WebKeys.CMS_USER)).thenReturn(systemUser);
final String html = APILocator.getHTMLPageAssetRenderedAPI().getPageHtml(
PageContextBuilder.builder()
.setUser(systemUser)
@@ -1455,19 +1480,19 @@ public void test_WidgetPreExecuteCodeShowRegardlessPageMode(final WidgetPreExecu
mockRequest, mockResponse);

//Page html must contains the pre-execute code
Assert.assertTrue("Page Mode: " + testCase.pageMode + " html: " + html,html.contains(preExcuteCode));
assertTrue("Page Mode: " + testCase.pageMode + " html: " + html,html.contains(preExcuteCode));
}

@NotNull
private HttpServletRequest createHttpServletRequest(HTMLPageAsset pageEnglishVersion) throws DotDataException {
final HttpServletRequest mockRequest = mock(HttpServletRequest.class);
Mockito.when(mockRequest.getParameter("host_id")).thenReturn(site.getIdentifier());
when(mockRequest.getParameter("host_id")).thenReturn(site.getIdentifier());
mockRequest.setAttribute(WebKeys.HTMLPAGE_LANGUAGE, "1");
HttpServletRequestThreadLocal.INSTANCE.setRequest(mockRequest);
Mockito.when(mockRequest.getAttribute(WebKeys.CURRENT_HOST)).thenReturn(site);
Mockito.when(mockRequest.getRequestURI()).thenReturn(pageEnglishVersion.getURI());
Mockito.when(mockRequest.getParameter(WebKeys.PAGE_MODE_PARAMETER)).thenReturn(PageMode.EDIT_MODE.toString());
Mockito.when(mockRequest.getAttribute(com.liferay.portal.util.WebKeys.USER)).thenReturn(systemUser);
when(mockRequest.getAttribute(WebKeys.CURRENT_HOST)).thenReturn(site);
when(mockRequest.getRequestURI()).thenReturn(pageEnglishVersion.getURI());
when(mockRequest.getParameter(WebKeys.PAGE_MODE_PARAMETER)).thenReturn(PageMode.EDIT_MODE.toString());
when(mockRequest.getAttribute(com.liferay.portal.util.WebKeys.USER)).thenReturn(systemUser);
return mockRequest;
}

@@ -1514,7 +1539,7 @@ public void containerTwiceIntoPage() throws Exception{
HttpServletRequest mockRequest = new MockSessionRequest(
new MockAttributeRequest(new MockHttpRequest("localhost", "/").request()).request())
.request();
Mockito.when(mockRequest.getParameter("host_id")).thenReturn(site.getIdentifier());
when(mockRequest.getParameter("host_id")).thenReturn(site.getIdentifier());
mockRequest.setAttribute(WebKeys.HTMLPAGE_LANGUAGE, "1");
HttpServletRequestThreadLocal.INSTANCE.setRequest(mockRequest);
final HttpServletResponse mockResponse = mock(HttpServletResponse.class);
@@ -1567,7 +1592,7 @@ public void containerTwiceIntoPageAndTemplateLayout() throws Exception{
HttpServletRequest mockRequest = new MockSessionRequest(
new MockAttributeRequest(new MockHttpRequest("localhost", "/").request()).request())
.request();
Mockito.when(mockRequest.getParameter("host_id")).thenReturn(site.getIdentifier());
when(mockRequest.getParameter("host_id")).thenReturn(site.getIdentifier());
mockRequest.setAttribute(WebKeys.HTMLPAGE_LANGUAGE, "1");
HttpServletRequestThreadLocal.INSTANCE.setRequest(mockRequest);
final HttpServletResponse mockResponse = mock(HttpServletResponse.class);
@@ -1578,7 +1603,54 @@ public void containerTwiceIntoPageAndTemplateLayout() throws Exception{
.setPageMode(PageMode.LIVE)
.build(),
mockRequest, mockResponse);
Assert.assertTrue(html.contains("content1content2content1content2"));
assertTrue(html.contains("content1content2content1content2"));
}

@Test
public void pageWithSidebarShouldRender() throws Exception{

final FileAssetContainer container = createFileContainer();
final TemplateLayout templateLayout = new TemplateLayoutDataGen()
.withContainer(container.getPath(), "1")
.withContainerInSidebar(container.getPath(), "2")
.next();

final Contentlet theme = new ThemeDataGen().nextPersisted();
final Template template = new TemplateDataGen()
.host(site)
.drawedBody(templateLayout)
.theme(theme)
.nextPersisted();

final String pageName = "test_page_with_sidebar-"+System.currentTimeMillis();
final HTMLPageAsset pageEnglishVersion = createHtmlPageAsset(template, pageName, 1);

createMultiTree(pageEnglishVersion.getIdentifier(), container.getIdentifier(), "1");
createMultiTree(pageEnglishVersion.getIdentifier(), container.getIdentifier(), "2");

//request page ENG version
HttpServletRequest mockRequest = new MockSessionRequest(
new MockAttributeRequest(new MockHttpRequest("localhost", "/").request()).request())
.request();
when(mockRequest.getParameter("host_id")).thenReturn(site.getIdentifier());
mockRequest.setAttribute(WebKeys.HTMLPAGE_LANGUAGE, "1");
HttpServletRequestThreadLocal.INSTANCE.setRequest(mockRequest);
final HttpServletResponse mockResponse = mock(HttpServletResponse.class);
String html = APILocator.getHTMLPageAssetRenderedAPI().getPageHtml(
PageContextBuilder.builder()
.setUser(systemUser)
.setPageUri(pageEnglishVersion.getURI())
.setPageMode(PageMode.LIVE)
.build(),
mockRequest, mockResponse);

final String toFind = "content1content2";

final int firstIndex = html.indexOf(toFind);
final int secondIndex = html.indexOf(toFind, firstIndex + toFind.length());
final int thirdIndex = html.indexOf(toFind, secondIndex + toFind.length());

assertTrue(firstIndex != -1 && secondIndex != -1 && firstIndex != secondIndex && thirdIndex == -1);
}

private static class TestContainerUUID{
@@ -1,12 +1,23 @@
package com.dotcms.rendering.velocity.services;

import com.dotcms.rendering.velocity.directive.RenderParams;
import com.dotcms.rendering.velocity.directive.TemplatePathStrategy;
import com.dotcms.rendering.velocity.directive.TemplatePathStrategyResolver;
import com.dotcms.util.IntegrationTestInitService;
import com.dotmarketing.beans.Host;
import com.dotmarketing.business.APILocator;
import com.dotmarketing.exception.DotDataException;
import com.dotmarketing.portlets.languagesmanager.model.Language;
import com.dotmarketing.util.PageMode;
import org.apache.velocity.context.Context;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;

import java.util.Optional;

import static org.mockito.Mockito.mock;

/**
* Test of {@link VelocityResourceKey}
*/
@@ -20,18 +31,28 @@ public static void prepare() throws Exception {

@Test
public void velocityResourceKey_constructor_fs_container_path_test() throws DotDataException {
final String path = "//demo.dotcms.com/application/containers/large-column";

final Host host = mock(Host.class);
final Language language = mock(Language.class);

final Context context = mock(Context.class);
final RenderParams params = new RenderParams(APILocator.systemUser(), language, host, PageMode.EDIT_MODE);
final String[] arguments = new String[]{path, "1551200983126"};

final TemplatePathStrategyResolver templatePathResolver = TemplatePathStrategyResolver.getInstance();
final Optional<TemplatePathStrategy> strategy = templatePathResolver.get(context, params, arguments);

final String path = "1/EDIT_MODE///demo.dotcms.com/application/containers/large-column/1551200983126.container";
final VelocityResourceKey velocityResourceKey =
new VelocityResourceKey(path);
new VelocityResourceKey(strategy.get().apply(context, params, arguments));

Assert.assertEquals("/EDIT_MODE///demo.dotcms.com/application/containers/large-column/1551200983126.container", velocityResourceKey.path);
Assert.assertEquals("///demo.dotcms.com/application/containers/large-column", velocityResourceKey.id1);
Assert.assertEquals("/EDIT_MODE/%%demo#dotcms#com%application%containers%large-column/1551200983126.container", velocityResourceKey.path);
Assert.assertEquals("%%demo#dotcms#com%application%containers%large-column", velocityResourceKey.id1);
Assert.assertEquals("1551200983126", velocityResourceKey.id2);
Assert.assertEquals(PageMode.EDIT_MODE, velocityResourceKey.mode);
Assert.assertEquals(VelocityType.CONTAINER, velocityResourceKey.type);
Assert.assertEquals("1", velocityResourceKey.language);
Assert.assertEquals("EDIT_MODE-///demo.dotcms.com/application/containers/large-column-1.container", velocityResourceKey.cacheKey);
Assert.assertEquals("EDIT_MODE-%%demo#dotcms#com%application%containers%large-column-1.container", velocityResourceKey.cacheKey);
}


@@ -343,6 +343,6 @@ public void testingTimeMachine () throws Exception{
velocityServlet.service(mockRequest, mockResponse);

verify(mockResponse, never()).sendError(anyInt());
verify(outputStream).write("<div> content1</div>".getBytes());
verify(outputStream).write("<div>content1</div>".getBytes());
}
}
@@ -0,0 +1,152 @@
package com.dotcms.rendering.velocity.viewtools;

import com.dotcms.datagen.ContainerDataGen;
import com.dotcms.datagen.TemplateDataGen;
import com.dotcms.datagen.TemplateLayoutDataGen;
import com.dotcms.util.IntegrationTestInitService;
import com.dotmarketing.portlets.containers.model.Container;
import com.dotmarketing.portlets.contentlet.business.web.ContentletWebAPIImpl;
import com.dotmarketing.portlets.templates.design.bean.ContainerUUID;
import com.dotmarketing.portlets.templates.design.bean.TemplateLayout;
import com.dotmarketing.portlets.templates.design.bean.TemplateLayoutColumn;
import com.dotmarketing.portlets.templates.design.bean.TemplateLayoutRow;
import com.dotmarketing.portlets.templates.model.Template;
import com.liferay.portal.model.User;
import org.junit.BeforeClass;
import org.junit.Test;

import java.util.List;
import java.util.Map;

import static org.junit.Assert.*;


public class DotTemplateToolTest {

@BeforeClass
public static void prepare() throws Exception{
//Setting web app environment
IntegrationTestInitService.getInstance().init();
}

/**
* Method to Test: {@link DotTemplateTool#getMaxUUID(Template)}
* When: Get the MaxUUID for a drawed Template
* Should: Delete it
* */
@Test
public void whenGetMaxUUIDToDrawedTemplate(){
final Container container_1 = new ContainerDataGen().nextPersisted();
final Container container_2 = new ContainerDataGen().nextPersisted();
final Container container_3 = new ContainerDataGen().nextPersisted();

final TemplateLayout templateLayout = new TemplateLayoutDataGen()
.withContainer(container_1, ContainerUUID.UUID_START_VALUE)
.withContainer(container_2, ContainerUUID.UUID_START_VALUE)
.withContainer(container_3, ContainerUUID.UUID_START_VALUE)
.withContainer(container_1, "2")
.withContainer(container_3, "2")
.withContainer(container_3, "3")
.next();

final Template template = new TemplateDataGen()
.drawedBody(templateLayout)
.nextPersisted();

final Map<String, Long> maxUUID = DotTemplateTool.getMaxUUID(template);

assertEquals(3, maxUUID.size());

assertTrue(2l == maxUUID.get(container_1.getIdentifier()));
assertTrue(1l == maxUUID.get(container_2.getIdentifier()));
assertTrue(3l == maxUUID.get(container_3.getIdentifier()));
}

@Test
public void whenGetMaxUUIDToAdvancedTemplate(){
final Container container_1 = new ContainerDataGen().nextPersisted();
final Container container_2 = new ContainerDataGen().nextPersisted();
final Container container_3 = new ContainerDataGen().nextPersisted();

final Template template = new TemplateDataGen()
.withContainer(container_1, ContainerUUID.UUID_START_VALUE)
.withContainer(container_2, ContainerUUID.UUID_START_VALUE)
.withContainer(container_3, ContainerUUID.UUID_START_VALUE)
.withContainer(container_1, "2")
.withContainer(container_3, "2")
.withContainer(container_3, "3")
.nextPersisted();

final Map<String, Long> maxUUID = DotTemplateTool.getMaxUUID(template);

assertEquals(0, maxUUID.size());

}

/**
* Method to Test: {@link DotTemplateTool#getTemplateLayout(String)}
* When: The legacy template's drawBody has a hotname with comma in it
* Should: Return the Template layout with the right UUID values
* */
@Test()
public void whenHostNameHasComma(){
final String hostName = "test this hostname, please";
final String drawBody =
"<div id=\"resp-template\" name=\"globalContainer\">" +
"<div id=\"hd-template\">" +
"<h1>Header</h1>" +
"</div>" +
"<div id=\"bd-template\">" +
"<div id=\"yui-main-template\">" +
"<div class=\"yui-b-template\" id=\"splitBody0\">" +
"<div class=\"addContainerSpan\">" +
"<a href=\"javascript: showAddContainerDialog('splitBody0');\" title=\"Add Container\">" +
"<span class=\"plusBlueIcon\"></span>Add Container" +
"</a>" +
"</div>" +
"<span class=\"titleContainerSpan\" id=\"splitBody0_span_69b3d24d-7e80-4be6-b04a-d352d16493ee_1562770692396\" title=\"container_69b3d24d-7e80-4be6-b04a-d352d16493ee\">" +
"<div class=\"removeDiv\">" +
"<a href=\"javascript: removeDrawedContainer('splitBody0','69b3d24d-7e80-4be6-b04a-d352d16493ee','1562770692396');\" title=\"Remove Container\">" +
"<span class=\"minusIcon\"></span>Remove Container" +
"</a>" +
"</div>" +
"<div class=\"clear\"></div>" +
"<h2>Container: Default</h2>" +
"<p>Lorem ipsum ...</p>" +
"</span>" +
"<div style=\"display: none;\" title=\"container_69b3d24d-7e80-4be6-b04a-d352d16493ee\" id=\"splitBody0_div_69b3d24d-7e80-4be6-b04a-d352d16493ee_1562770692396\">" +
"%s" +
"</div>" +
"</div>" +
"</div>" +
"</div>" +
"<div id=\"ft-template\"><h1>Footer</h1></div>" +
"</div>";

final String parseContainer = String.format("#parseContainer('//%s/application/containers/default/','1')",hostName);
TemplateLayout templateLayout = DotTemplateTool.getTemplateLayout(String.format(drawBody, parseContainer));
System.out.println("templateLayout = " + templateLayout);
check(hostName, templateLayout);

final String parseContainer2 = String.format("#parseContainer('//%s/application/containers/default/' ,'1')",hostName);
templateLayout = DotTemplateTool.getTemplateLayout(String.format(drawBody, parseContainer2));
check(hostName, templateLayout);
}

private void check(String hostName, TemplateLayout templateLayout) {
final List<TemplateLayoutRow> rows = templateLayout.getBody().getRows();
assertEquals(1, rows.size());

final List<TemplateLayoutColumn> columns = rows.get(0).getColumns();
assertEquals(1, columns.size());

final TemplateLayoutColumn templateLayoutColumn = columns.get(0);
final List<ContainerUUID> containers = templateLayoutColumn.getContainers();
assertEquals(1, containers.size());

final ContainerUUID containerUUID = containers.get(0);

assertEquals(String.format("//%s/application/containers/default/", hostName), containerUUID.getIdentifier());
assertEquals("1", containerUUID.getUUID());
}
}
@@ -0,0 +1,321 @@
package com.dotcms.rendering.velocity.viewtools;

import com.dotcms.IntegrationTestBase;
import com.dotcms.util.IntegrationTestInitService;
import com.dotmarketing.exception.DotDataException;
import com.dotmarketing.exception.DotSecurityException;
import org.junit.BeforeClass;
import org.junit.Test;

import java.util.List;
import java.util.Map;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

/**
* Test {@link JSONTool}
* @author jsanca
*/
public class JSONToolTest extends IntegrationTestBase {

@BeforeClass
public static void prepare() throws Exception{
//Setting web app environment
IntegrationTestInitService.getInstance().init();
}

/**
* Method to test: {@link JSONTool#generate(String)}
* Given Scenario: Parsing an plain object
* ExpectedResult: Expected a map with the properties
* @throws DotSecurityException
* @throws DotDataException
*/
@Test
public void test_Generate_simple_Map() throws Exception {

final JSONTool jsonTool = new JSONTool();
final String stringJson = "{\n" +
" \"body\": \"## Container: Blank Container\\r\\n## This is autogenerated code that cannot be changed\\r\\n#parseContainer('d71d56b4-0a8b-4bb2-be15-ffa5a23366ea','1539784124854')\\r\\n\",\n" +
" \"canPublish\": true,\n" +
" \"canRead\": true,\n" +
" \"canWrite\": true,\n" +
" \"categoryId\": \"9d11b327-9bf5-4d42-92bd-7894d56ed218\",\n" +
" \"countAddContainer\": 0,\n" +
" \"countContainers\": 0,\n" +
" \"deleted\": false,\n" +
" \"drawed\": false,\n" +
" \"drawedBody\": null,\n" +
" \"footer\": \"null\",\n" +
" \"friendlyName\": \"Blank template (No: header, footer, javascript or css)\",\n" +
" \"hasLiveVersion\": true,\n" +
" \"headCode\": null,\n" +
" \"header\": \"null\",\n" +
" \"identifier\": \"7acdb856-4bbc-41c5-8695-a39c2e4a913f\",\n" +
" \"image\": \"21ea6da6-68d0-48db-bce8-d1691abd6314\",\n" +
" \"inode\": \"9d11b327-9bf5-4d42-92bd-7894d56ed218\",\n" +
" \"live\": true,\n" +
" \"locked\": false,\n" +
" \"modDate\": 1570214794657,\n" +
" \"modUser\": \"036fd43a-6d98-46e0-b22e-bae02cb86f0c\",\n" +
" \"name\": \"Blank\",\n" +
" \"new\": false,\n" +
" \"owner\": \"null\",\n" +
" \"selectedimage\": null,\n" +
" \"showOnMenu\": true,\n" +
" \"sortOrder\": 0,\n" +
" \"theme\": null,\n" +
" \"themeName\": null,\n" +
" \"title\": \"Blank\",\n" +
" \"working\": true\n" +
" }";
final Object objectMap = jsonTool.generate(stringJson);

assertNotNull(objectMap);
assertTrue(objectMap instanceof Map);

final Map<String, Object> map = (Map<String, Object>) objectMap;
assertFalse(map.isEmpty());

assertEquals(true, map.get("canPublish"));
assertEquals(false, map.get("new"));
assertEquals("Blank", map.get("name"));
assertEquals("036fd43a-6d98-46e0-b22e-bae02cb86f0c", map.get("modUser"));
assertEquals(1570214794657l, map.get("modDate"));
}

/**
* Method to test: {@link JSONTool#generate(String)}
* Given Scenario: Parsing an composite object
* ExpectedResult: Expected a map with the inner properties parsed
* @throws DotSecurityException
* @throws DotDataException
*/
@Test
public void test_Generate_inner_Map() throws Exception {

final JSONTool jsonTool = new JSONTool();
final String stringJson = "{\n" +
" \"body\": \"## Container: Blank Container\\r\\n## This is autogenerated code that cannot be changed\\r\\n#parseContainer('d71d56b4-0a8b-4bb2-be15-ffa5a23366ea','1539784124854')\\r\\n\",\n" +
" \"canPublish\": true,\n" +
" \"canRead\": true,\n" +
" \"canWrite\": true,\n" +
" \"categoryId\": \"9d11b327-9bf5-4d42-92bd-7894d56ed218\",\n" +
" \"countAddContainer\": 0,\n" +
" \"countContainers\": 0,\n" +
" \"deleted\": false,\n" +
" \"drawed\": false,\n" +
" \"drawedBody\": null,\n" +
" \"footer\": \"null\",\n" +
" \"friendlyName\": \"Blank template (No: header, footer, javascript or css)\",\n" +
" \"hasLiveVersion\": true,\n" +
" \"headCode\": null,\n" +
" \"header\": \"null\",\n" +
" \"inner\": {\n" +
" \t\"identifier\": \"7acdb856-4bbc-41c5-8695-a39c2e4a913f\",\n" +
"\t \"image\": \"21ea6da6-68d0-48db-bce8-d1691abd6318\",\n" +
" \t \"inode\": \"9d11b327-9bf5-4d42-92bd-7894d56ed218\",\n" +
" \"modDate\": 1570214794655\n" +
" },\n" +
" \"live\": true,\n" +
" \"locked\": false,\n" +
" \"modDate\": 1570214794657,\n" +
" \"modUser\": \"036fd43a-6d98-46e0-b22e-bae02cb86f0c\",\n" +
" \"name\": \"Blank\",\n" +
" \"new\": false,\n" +
" \"owner\": \"null\",\n" +
" \"selectedimage\": null,\n" +
" \"showOnMenu\": true,\n" +
" \"sortOrder\": 0,\n" +
" \"theme\": null,\n" +
" \"themeName\": null,\n" +
" \"title\": \"Blank\",\n" +
" \"working\": true\n" +
" }";
final Object objectMap = jsonTool.generate(stringJson);

assertNotNull(objectMap);
assertTrue(objectMap instanceof Map);

final Map<String, Object> map = (Map<String, Object>) objectMap;
assertFalse(map.isEmpty());

assertEquals(true, map.get("canPublish"));
assertEquals(false, map.get("new"));
assertEquals("Blank", map.get("name"));
assertEquals("036fd43a-6d98-46e0-b22e-bae02cb86f0c", map.get("modUser"));
assertEquals(1570214794657l, map.get("modDate"));
assertTrue(map.containsKey("inner"));
assertTrue(map.get("inner") instanceof Map);

final Map<String, Object> innerMap = (Map<String, Object>) map.get("inner");
assertFalse(innerMap.isEmpty());

assertEquals(1570214794655l , innerMap.get("modDate"));
assertEquals("21ea6da6-68d0-48db-bce8-d1691abd6318" , innerMap.get("image"));
}

/**
* Method to test: {@link JSONTool#generate(String)}
* Given Scenario: Parsing an empty list
* ExpectedResult: Expected empty {@link java.util.List}
* @throws DotSecurityException
* @throws DotDataException
*/
@Test
public void test_Generate_empty_list() throws Exception {

final JSONTool jsonTool = new JSONTool();
final String stringJson = "[]";
final Object objectMap = jsonTool.generate(stringJson);

assertNotNull(objectMap);
assertTrue(objectMap instanceof List);

final List<Map<String, Object>> list = (List<Map<String, Object>>) objectMap;
assertTrue(list.isEmpty());
}

/**
* Method to test: {@link JSONTool#generate(String)}
* Given Scenario: Parsing a list with a plain map
* ExpectedResult: Expected non-empty {@link java.util.List} with a map inside
* @throws DotSecurityException
* @throws DotDataException
*/
@Test
public void test_Generate_list_plain_map() throws Exception {

final JSONTool jsonTool = new JSONTool();
final String stringJson = "[{\n" +
" \"body\": \"## Container: Blank Container\\r\\n## This is autogenerated code that cannot be changed\\r\\n#parseContainer('d71d56b4-0a8b-4bb2-be15-ffa5a23366ea','1539784124854')\\r\\n\",\n" +
" \"canPublish\": true,\n" +
" \"canRead\": true,\n" +
" \"canWrite\": true,\n" +
" \"categoryId\": \"9d11b327-9bf5-4d42-92bd-7894d56ed218\",\n" +
" \"countAddContainer\": 0,\n" +
" \"countContainers\": 0,\n" +
" \"deleted\": false,\n" +
" \"drawed\": false,\n" +
" \"drawedBody\": null,\n" +
" \"footer\": \"null\",\n" +
" \"friendlyName\": \"Blank template (No: header, footer, javascript or css)\",\n" +
" \"hasLiveVersion\": true,\n" +
" \"headCode\": null,\n" +
" \"header\": \"null\",\n" +
" \"identifier\": \"7acdb856-4bbc-41c5-8695-a39c2e4a913f\",\n" +
" \"image\": \"21ea6da6-68d0-48db-bce8-d1691abd6314\",\n" +
" \"inode\": \"9d11b327-9bf5-4d42-92bd-7894d56ed218\",\n" +
" \"live\": true,\n" +
" \"locked\": false,\n" +
" \"modDate\": 1570214794657,\n" +
" \"modUser\": \"036fd43a-6d98-46e0-b22e-bae02cb86f0c\",\n" +
" \"name\": \"Blank\",\n" +
" \"new\": false,\n" +
" \"owner\": \"null\",\n" +
" \"selectedimage\": null,\n" +
" \"showOnMenu\": true,\n" +
" \"sortOrder\": 0,\n" +
" \"theme\": null,\n" +
" \"themeName\": null,\n" +
" \"title\": \"Blank\",\n" +
" \"working\": true\n" +
" }]";
final Object objectMap = jsonTool.generate(stringJson);

assertNotNull(objectMap);
assertTrue(objectMap instanceof List);

final List<Map<String, Object>> list = (List<Map<String, Object>>) objectMap;
assertFalse(list.isEmpty());
assertEquals(1, list.size());

final Map<String, Object> map = list.get(0);
assertFalse(map.isEmpty());

assertEquals(true, map.get("canPublish"));
assertEquals(false, map.get("new"));
assertEquals("Blank", map.get("name"));
assertEquals("036fd43a-6d98-46e0-b22e-bae02cb86f0c", map.get("modUser"));
assertEquals(1570214794657l, map.get("modDate"));
}

/**
* Method to test: {@link JSONTool#generate(String)}
* Given Scenario: Parsing a list with a plain map
* ExpectedResult: Expected non-empty {@link java.util.List} with a map inside
* @throws DotSecurityException
* @throws DotDataException
*/
@Test
public void test_Generate_list_inner_map() throws Exception {

final JSONTool jsonTool = new JSONTool();
final String stringJson = "[{\n" +
" \"body\": \"## Container: Blank Container\\r\\n## This is autogenerated code that cannot be changed\\r\\n#parseContainer('d71d56b4-0a8b-4bb2-be15-ffa5a23366ea','1539784124854')\\r\\n\",\n" +
" \"canPublish\": true,\n" +
" \"canRead\": true,\n" +
" \"canWrite\": true,\n" +
" \"categoryId\": \"9d11b327-9bf5-4d42-92bd-7894d56ed218\",\n" +
" \"countAddContainer\": 0,\n" +
" \"countContainers\": 0,\n" +
" \"deleted\": false,\n" +
" \"drawed\": false,\n" +
" \"drawedBody\": null,\n" +
" \"footer\": \"null\",\n" +
" \"friendlyName\": \"Blank template (No: header, footer, javascript or css)\",\n" +
" \"hasLiveVersion\": true,\n" +
" \"headCode\": null,\n" +
" \"header\": \"null\",\n" +
" \"inner\": {\n" +
" \t\"identifier\": \"7acdb856-4bbc-41c5-8695-a39c2e4a913f\",\n" +
"\t \"image\": \"21ea6da6-68d0-48db-bce8-d1691abd6318\",\n" +
" \t \"inode\": \"9d11b327-9bf5-4d42-92bd-7894d56ed218\",\n" +
" \"modDate\": 1570214794655\n" +
" },\n" +
" \"live\": true,\n" +
" \"locked\": false,\n" +
" \"modDate\": 1570214794657,\n" +
" \"modUser\": \"036fd43a-6d98-46e0-b22e-bae02cb86f0c\",\n" +
" \"name\": \"Blank\",\n" +
" \"new\": false,\n" +
" \"owner\": \"null\",\n" +
" \"selectedimage\": null,\n" +
" \"showOnMenu\": true,\n" +
" \"sortOrder\": 0,\n" +
" \"theme\": null,\n" +
" \"themeName\": null,\n" +
" \"title\": \"Blank\",\n" +
" \"working\": true\n" +
" }]";
final Object objectMap = jsonTool.generate(stringJson);

assertNotNull(objectMap);
assertTrue(objectMap instanceof List);

final List<Map<String, Object>> list = (List<Map<String, Object>>) objectMap;

assertFalse(list.isEmpty());
assertEquals(1, list.size());

final Map<String, Object> map = list.get(0);
assertFalse(map.isEmpty());

assertEquals(true, map.get("canPublish"));
assertEquals(false, map.get("new"));
assertEquals("Blank", map.get("name"));
assertEquals("036fd43a-6d98-46e0-b22e-bae02cb86f0c", map.get("modUser"));
assertEquals(1570214794657l, map.get("modDate"));
assertTrue(map.containsKey("inner"));
assertTrue(map.get("inner") instanceof Map);

final Map<String, Object> innerMap = (Map<String, Object>) map.get("inner");
assertFalse(innerMap.isEmpty());

assertEquals(1570214794655l , innerMap.get("modDate"));
assertEquals("21ea6da6-68d0-48db-bce8-d1691abd6318" , innerMap.get("image"));
}
}
@@ -1,12 +1,14 @@
package com.dotcms.rendering.velocity.viewtools.content;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import com.dotcms.IntegrationTestBase;
import com.dotcms.api.web.HttpServletRequestThreadLocal;
import com.dotcms.contenttype.business.ContentTypeAPI;
import com.dotcms.contenttype.business.FieldAPI;
import com.dotcms.contenttype.model.field.Field;
@@ -41,6 +43,7 @@
import com.tngtech.java.junit.dataprovider.DataProviderRunner;
import com.tngtech.java.junit.dataprovider.UseDataProvider;

import io.vavr.control.Try;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
@@ -447,7 +450,7 @@ public void whenTheTimeMachineDateIsNullAndPublishDateInFutureShouldNotReturnAny
final ContentTool contentTool = getContentTool(null);

final PaginatedContentList<ContentMap> contents = contentTool.pullPerPage(query, 1, 2, null);
assertEquals(0, contents.size());
assertFalse(Try.of(()->contents.get(0).isLive()).getOrElse(false));
}

/**
@@ -488,6 +491,7 @@ private ContentTool getContentTool(Calendar timeMachine) {

final HttpSession session = mock(HttpSession.class);
final HttpServletRequest request = mock(HttpServletRequest.class);
HttpServletRequestThreadLocal.INSTANCE.setRequest(request);
when(request.getSession(false)).thenReturn(session);
when(request.getSession()).thenReturn(session);
when(request.getAttribute(WebKeys.USER)).thenReturn(APILocator.systemUser());
@@ -0,0 +1,43 @@
package com.dotcms.rest;

import com.dotcms.IntegrationTestBase;
import com.dotcms.util.IntegrationTestInitService;
import com.dotmarketing.exception.InvalidLicenseException;
import org.junit.BeforeClass;
import org.junit.Test;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


import static org.mockito.Mockito.mock;

public class BundlePublisherResourceIntegrationTest extends IntegrationTestBase {

@BeforeClass
public static void prepare() throws Exception {
//Setting web app environment
IntegrationTestInitService.getInstance().init();
}

@Test()
public void publishShouldReturnLicenseException() throws Exception {

runNoLicense(()-> {
final HttpServletRequest request = mock(HttpServletRequest.class);
final HttpServletResponse response= mock(HttpServletResponse.class);

final BundlePublisherResource bundlePublisherResource = new BundlePublisherResource();

try {
bundlePublisherResource.publish(null, null, true, request, response);
throw new AssertionError();
} catch(InvalidLicenseException e) {
//expected
} catch (Exception e) {
throw new RuntimeException(e);
}
});

}
}
@@ -1,20 +1,37 @@
package com.dotcms.rest;

import com.dotcms.mock.request.MockAttributeRequest;
import com.dotcms.mock.request.MockHeaderRequest;
import com.dotcms.mock.request.MockHttpRequest;
import com.dotcms.mock.request.MockSessionRequest;
import com.dotcms.mock.response.MockAsyncResponse;
import com.dotcms.mock.response.MockHttpResponse;
import com.dotcms.publisher.bundle.bean.Bundle;
import com.dotcms.publisher.business.DotPublisherException;
import com.dotcms.publisher.pusher.PushPublisherConfig;
import com.dotcms.publishing.FilterDescriptor;
import com.dotcms.publishing.PublisherAPIImpl;
import com.dotcms.util.IntegrationTestInitService;
import com.dotmarketing.business.APILocator;
import com.dotmarketing.business.CacheLocator;
import com.dotmarketing.business.DotCacheException;
import com.dotmarketing.exception.DotDataException;
import com.dotmarketing.portlets.contentlet.model.Contentlet;
import com.dotmarketing.portlets.rules.model.Condition;
import com.dotmarketing.portlets.rules.model.ConditionGroup;
import com.dotmarketing.portlets.structure.model.ContentletRelationships;
import com.dotmarketing.util.UUIDGenerator;
import com.google.common.collect.ImmutableMap;
import com.liferay.portal.model.User;
import com.liferay.portal.util.WebKeys;
import java.io.IOException;
import java.util.Date;
import java.util.Map;
import javax.ws.rs.container.AsyncResponse;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import org.glassfish.jersey.internal.util.Base64;
import org.glassfish.jersey.media.multipart.BodyPart;
import org.glassfish.jersey.media.multipart.ContentDisposition;
import org.glassfish.jersey.media.multipart.FormDataMultiPart;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;

@@ -25,17 +42,23 @@
import java.io.FileNotFoundException;

import static com.dotcms.util.CollectionsUtils.list;
import static com.dotcms.util.CollectionsUtils.toImmutableList;
import static org.junit.Assert.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class BundleResourceTest {

private static BundleResource bundleResource;
private static User adminUser;
static HttpServletResponse response;

@BeforeClass
public static void prepare() throws Exception {
//Setting web app environment
IntegrationTestInitService.getInstance().init();
bundleResource = new BundleResource();
adminUser = APILocator.systemUser();
response = new MockHttpResponse();
}

/**
@@ -76,8 +99,6 @@ private void publish(FileInputStream createCOntentFileInputStream) throws DotPub
final HttpServletRequest request = mock(HttpServletRequest.class);
when(request.getAttribute(WebKeys.USER)).thenReturn(APILocator.systemUser());

final HttpServletResponse response = mock(HttpServletResponse.class);

final BodyPart bodyPart = mock(BodyPart.class);
when(bodyPart.getEntity()).thenReturn(createCOntentFileInputStream);

@@ -88,7 +109,118 @@ private void publish(FileInputStream createCOntentFileInputStream) throws DotPub
final FormDataMultiPart multipart = mock(FormDataMultiPart.class);
when(multipart.getBodyParts()).thenReturn(list(bodyPart));

final BundleResource bundleResource = new BundleResource();
bundleResource.uploadBundleSync(request, response, multipart);
}

private static void createFilter(){
final Map<String,Object> filtersMap =
ImmutableMap.of("dependencies",true,"relationships",true);
final FilterDescriptor filterDescriptor =
new FilterDescriptor("filterTestAPI.yml","Filter Test Title",filtersMap,true,"Reviewer,dotcms.org.2789");

APILocator.getPublisherAPI().addFilterDescriptor(filterDescriptor);
}

private String insertPublishingBundle(final String userId, final Date publishDate)
throws DotDataException {
final String uuid = UUIDGenerator.generateUuid();
final Bundle bundle = new Bundle();
bundle.setId(uuid);
bundle.setName("testBundle"+System.currentTimeMillis());
bundle.setForcePush(false);
bundle.setOwner(userId);
bundle.setPublishDate(publishDate);
APILocator.getBundleAPI().saveBundle(bundle);

return uuid;
}

/**
* BasicAuth
*/
private HttpServletRequest getHttpRequest() {
final MockHeaderRequest request = new MockHeaderRequest(
new MockSessionRequest(
new MockAttributeRequest(new MockHttpRequest("localhost", "/").request())
.request())
.request());

request.setHeader("Authorization",
"Basic " + new String(Base64.encode("admin@dotcms.com:admin".getBytes())));

return request;
}

/**
* Method to Test: {@link BundleResource#generateBundle(HttpServletRequest, HttpServletResponse, AsyncResponse, GenerateBundleForm)}
* When: Create a bundle and generate the tar.gz file of the given bundle.
* Should: Generate the bundle without issues, 200.
*/
@Test
public void test_generateBundle_success() throws DotDataException, IOException {
//Create new bundle
final String bundleId = insertPublishingBundle(adminUser.getUserId(),new Date());

//Create a Filter since it's needed to generate the bundle
createFilter();

//Create GenerateBundleForm
final GenerateBundleForm bundleForm = new GenerateBundleForm.Builder().bundleId(bundleId).build();

//Call generate endpoint
final AsyncResponse asyncResponse = new MockAsyncResponse((arg) -> {

final Response generateBundleResponse = (Response)arg;
assertEquals(Status.OK.getStatusCode(), generateBundleResponse.getStatus());
return true;
}, arg -> {
fail("Error generating bundle");
return true;
});

bundleResource.generateBundle(getHttpRequest(),response,asyncResponse,bundleForm);
PublisherAPIImpl.class.cast(APILocator.getPublisherAPI()).getFilterDescriptorMap().clear();
}

/**
* Method to Test: {@link BundleResource#downloadBundle(HttpServletRequest, HttpServletResponse, String)}
* When: Create a bundle and try to download it, but since the tar.gz has not been generated should fail.
* Should: return 404 since the file has not been generated
*/
@Test
public void test_downloadBundle_fileNotGenerated_return404() throws DotDataException {
//Create new bundle
final String bundleId = insertPublishingBundle(adminUser.getUserId(),new Date());

//Call download endpoint
final Response responseResource = bundleResource.downloadBundle(getHttpRequest(),response,bundleId);

Assert.assertEquals(Status.NOT_FOUND.getStatusCode(),responseResource.getStatus());
}

/**
* Method to Test: {@link BundleResource#downloadBundle(HttpServletRequest, HttpServletResponse, String)}
* When: Create a bundle and try to download it, since the tar.gz has been generated should succeed.
* Should: return 200 since the file has been generated
*/
@Test
public void test_downloadBundle_success() throws DotDataException {
//Create new bundle
final String bundleId = insertPublishingBundle(adminUser.getUserId(),new Date());

//Create a Filter since it's needed to generate the bundle
createFilter();

//Generate bundle file
final Bundle bundle = APILocator.getBundleAPI().getBundleById(bundleId);
bundle.setOperation(PushPublisherConfig.Operation.PUBLISH.ordinal());
APILocator.getBundleAPI().generateTarGzipBundleFile(bundle);

PublisherAPIImpl.class.cast(APILocator.getPublisherAPI()).getFilterDescriptorMap().clear();

//Call download endpoint
final Response responseResource = bundleResource.downloadBundle(getHttpRequest(),response,bundleId);

Assert.assertEquals(Status.OK.getStatusCode(),responseResource.getStatus());
}
}
@@ -0,0 +1,52 @@
package com.dotcms.rest;

import com.dotcms.IntegrationTestBase;
import com.dotcms.util.IntegrationTestInitService;
import com.dotmarketing.exception.InvalidLicenseException;
import org.junit.BeforeClass;
import org.junit.Test;

import javax.servlet.http.HttpServletRequest;

import static org.mockito.Mockito.mock;

public class IntegrityResourceIntegrationTest extends IntegrationTestBase {

@BeforeClass
public static void prepare() throws Exception {
//Setting web app environment
IntegrationTestInitService.getInstance().init();
}

@Test(expected = InvalidLicenseException.class)
public void generateShouldReturnLicenseException() throws Exception {

runNoLicense(()-> {
final HttpServletRequest request = mock(HttpServletRequest.class);

final IntegrityResource integrityResource = new IntegrityResource();

integrityResource.generateIntegrityData(request);
});

}

@Test
public void fixConflictsShouldReturnLicenseException() throws Exception {

runNoLicense(()-> {
final HttpServletRequest request = mock(HttpServletRequest.class);

final IntegrityResource integrityResource = new IntegrityResource();

try {
integrityResource.fixConflictsFromRemote(request, null, null);
} catch(InvalidLicenseException e) {
//expected
} catch (Exception e) {
throw new RuntimeException(e);
}
});

}
}
@@ -12,6 +12,7 @@
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import com.dotcms.IntegrationTestBase;
import com.dotcms.datagen.AppDescriptorDataGen;
import com.dotcms.datagen.SiteDataGen;
@@ -65,9 +66,12 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.core.Response;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.glassfish.jersey.media.multipart.ContentDisposition;
import org.glassfish.jersey.media.multipart.FormDataBodyPart;
import org.glassfish.jersey.media.multipart.FormDataMultiPart;
import org.glassfish.jersey.message.internal.OutboundJaxrsResponse;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
@@ -93,6 +97,7 @@ public static void prepare() throws Exception {
when(user.getFullName()).thenReturn(ADMIN_NAME);
when(user.getLocale()).thenReturn(Locale.getDefault());
when(user.isBackendUser()).thenReturn(true);
when(user.isAdmin()).thenReturn(true);

final WebResource webResource = mock(WebResource.class);
final InitDataObject dataObject = mock(InitDataObject.class);
@@ -105,7 +110,7 @@ public static void prepare() throws Exception {
appsResource = new AppsResource(webResource,
appsHelper);

final AppsAPI appsAPI = new AppsAPIImpl(APILocator.getUserAPI(), APILocator.getLayoutAPI(),
final AppsAPI appsAPI = new AppsAPIImpl(APILocator.getLayoutAPI(),
APILocator.getHostAPI(), APILocator.getContentletAPI(),
SecretsStore.INSTANCE.get(), CacheLocator.getAppsCache(),
APILocator.getLocalSystemEventsAPI(),
@@ -123,15 +128,39 @@ public boolean hasValidLicense() {

}

private FormDataMultiPart createFormDataMultiPart(final String fileName,
final InputStream inputStream) {
/**
* mock a multi-part with a file
* @param fileName
* @param inputStream
* @return
*/
private FormDataMultiPart createFormDataMultiPart(final String fileName, final InputStream inputStream) {
return createFormDataMultiPart(fileName, inputStream, null);
}

/**
* mock a multipart with file and json
* @param fileName
* @param inputStream
* @param json
* @return
*/
private FormDataMultiPart createFormDataMultiPart(final String fileName, final InputStream inputStream, final String json) {
final FormDataMultiPart formDataMultiPart = mock(FormDataMultiPart.class);
final FormDataBodyPart filePart1 = mock(FormDataBodyPart.class);
when(filePart1.getEntityAs(any(Class.class))).thenReturn(inputStream);
final ContentDisposition contentDisposition = mock(ContentDisposition.class);
when(contentDisposition.getFileName()).thenReturn(fileName);
when(filePart1.getContentDisposition()).thenReturn(contentDisposition);
final FormDataMultiPart formDataMultiPart = mock(FormDataMultiPart.class);
final ContentDisposition contentDisposition1 = mock(ContentDisposition.class);
when(contentDisposition1.getFileName()).thenReturn(fileName);
when(filePart1.getContentDisposition()).thenReturn(contentDisposition1);
when(formDataMultiPart.getFields("file")).thenReturn(Collections.singletonList(filePart1));
if(null != json) {
final FormDataBodyPart filePart2 = mock(FormDataBodyPart.class);
when(filePart2.getEntityAs(any(Class.class))).thenReturn(IOUtils.toInputStream(json));
final ContentDisposition contentDisposition2 = mock(ContentDisposition.class);
when(contentDisposition2.getParameters()).thenReturn(ImmutableMap.of("name", "json"));
when(filePart2.getContentDisposition()).thenReturn(contentDisposition2);
when(formDataMultiPart.getBodyParts()).thenReturn(Collections.singletonList(filePart2));
}
return formDataMultiPart;
}

@@ -1245,6 +1274,69 @@ public void Test_Pagination_And_Sort_Then_Request_Filter_Expect_Empty_Results()

}

/**
* Given Scenario: We create secrets then export that particular one then the generated file is used as the input for the import method.
* Expected Results: The secrets are imported correctly and replace the same old exiting secrets. No additional secrets are created.
* @throws IOException
*/
@Test
public void Test_Export_Import() throws IOException {
final AppDescriptorDataGen dataGen = new AppDescriptorDataGen()
.stringParam("param1", false, true)
.stringParam("param2", false, true)
.withName("import-export-test")
.withDescription("import-export-test")
//We're indicating that extra params are allowed to test required params are still required.
.withExtraParameters(true);
final String key = dataGen.getKey();
final HttpServletRequest request = mock(HttpServletRequest.class);
final HttpServletResponse response = mock(HttpServletResponse.class);

when(request.getRequestURI()).thenReturn("/baseURL");
final String fileName = dataGen.getFileName();
final File file = dataGen.nextPersistedDescriptor();
try(InputStream inputStream = Files.newInputStream(file.toPath())){
final FormDataMultiPart formDataMultiPart = createFormDataMultiPart(fileName, inputStream);
final Response appResponseOk = appsResource.createApp(request, response,formDataMultiPart);
Assert.assertNotNull(appResponseOk);
Assert.assertEquals(HttpStatus.SC_OK, appResponseOk.getStatus());

final Response availableAppsResponse = appsResource
.listAvailableApps(request, response, null);
Assert.assertEquals(HttpStatus.SC_OK, availableAppsResponse.getStatus());
final ResponseEntityView responseEntityView1 = (ResponseEntityView) availableAppsResponse
.getEntity();
final List<AppView> integrationViewList = (List<AppView>) responseEntityView1
.getEntity();
assertFalse(integrationViewList.isEmpty());

final Host site = new SiteDataGen().nextPersisted();

final String siteId = site.getIdentifier();
final Map<String, Input> inputParamMap = ImmutableMap.of(
"param1", newInputParam("value".toCharArray()),
"param2", newInputParam("value".toCharArray())
);
final SecretForm secretForm1 = new SecretForm(inputParamMap);
final Response createSecretResponse1 = appsResource.createAppSecrets(request, response, key, siteId, secretForm1);
Assert.assertEquals(HttpStatus.SC_OK, createSecretResponse1.getStatus());

final String password = "123456789";
ExportSecretForm form = new ExportSecretForm(password,false, ImmutableMap.of(siteId, ImmutableSet.of(key)));
final OutboundJaxrsResponse exportResponse = (OutboundJaxrsResponse)appsResource.exportSecrets(request, response, form);
final String json = String.format("{ password: \"%s\" }",password);
final InputStream entity = (InputStream) exportResponse.getEntity();
final File targetFile = File.createTempFile("secrets", ".export");
FileUtils.copyInputStreamToFile(entity, targetFile);

try(InputStream exportInputStream = Files.newInputStream(targetFile.toPath())){
final Response appResponse = appsResource.importSecrets(request, response,
createFormDataMultiPart(targetFile.getName(), exportInputStream, json));
Assert.assertEquals(HttpStatus.SC_OK, appResponse.getStatus());
}
}
}

/**
* This basically tests that we can not use the list endpoint that list available apps
* Given scenario: We have a non-valid-license type of situation then we call the list available apps endpoint
@@ -2,6 +2,7 @@

import com.dotcms.datagen.SiteDataGen;
import com.dotcms.datagen.TestUserUtils;
import com.dotcms.datagen.UserDataGen;
import com.dotcms.mock.request.MockAttributeRequest;
import com.dotcms.mock.request.MockHeaderRequest;
import com.dotcms.mock.request.MockHttpRequest;
@@ -240,20 +241,20 @@ public void test_loadFolderAndSubFoldersByPath_UserNoPermissionsOverFolder_retur
//Check that the response is 200, OK
Assert.assertEquals(Status.OK.getStatusCode(),responseResource.getStatus());

final User limitedUser = new UserDataGen().roles(TestUserUtils.getFrontendRole(), TestUserUtils.getBackendRole()).nextPersisted();
final String password = "admin";
limitedUser.setPassword(password);
APILocator.getUserAPI().save(limitedUser,APILocator.systemUser(),false);

//Give Permissions Over the Host
final Permission permissions = new Permission(PermissionAPI.INDIVIDUAL_PERMISSION_TYPE,
newHost.getPermissionId(),
TestUserUtils.getOrCreatePublisherRole(newHost).getId(),
(PermissionAPI.PERMISSION_READ | PermissionAPI.PERMISSION_WRITE | PermissionAPI.PERMISSION_CAN_ADD_CHILDREN | PermissionAPI.PERMISSION_EDIT_PERMISSIONS), true);
APILocator.getPermissionAPI().save(permissions, newHost, adminUser, false);

final User chrisUser = TestUserUtils.getChrisPublisherUser(newHost);
final String password = "admin";
chrisUser.setPassword(password);
APILocator.getUserAPI().save(chrisUser,APILocator.systemUser(),false);
APILocator.getRoleAPI().loadRoleByKey(limitedUser.getUserId()).getId(),
PermissionAPI.PERMISSION_READ, true);
APILocator.getPermissionAPI().save(permissions, newHost, APILocator.systemUser(), false);

//Get all the folders and subfolders using the limited user
resource.loadFolderAndSubFoldersByPath(getHttpRequest(chrisUser.getEmailAddress(),password),response,newHost.getIdentifier(),"test_"+currentTime);
resource.loadFolderAndSubFoldersByPath(getHttpRequest(limitedUser.getEmailAddress(),password),response,newHost.getIdentifier(),"test_"+currentTime);
}

/**
@@ -276,13 +277,13 @@ public void test_loadFolderAndSubFoldersByPath_UserNoPermissionsOverHost_return4
//Check that the response is 200, OK
Assert.assertEquals(Status.OK.getStatusCode(),responseResource.getStatus());

final User chrisUser = TestUserUtils.getChrisPublisherUser(newHost);
final User limitedUser = new UserDataGen().roles(TestUserUtils.getFrontendRole(), TestUserUtils.getBackendRole()).nextPersisted();
final String password = "admin";
chrisUser.setPassword(password);
APILocator.getUserAPI().save(chrisUser,APILocator.systemUser(),false);
limitedUser.setPassword(password);
APILocator.getUserAPI().save(limitedUser,APILocator.systemUser(),false);

//Get all the folders and subfolders using the limited user
responseResource = resource.loadFolderAndSubFoldersByPath(getHttpRequest(chrisUser.getEmailAddress(),password),response,newHost.getIdentifier(),"test_"+currentTime);
responseResource = resource.loadFolderAndSubFoldersByPath(getHttpRequest(limitedUser.getEmailAddress(),password),response,newHost.getIdentifier(),"test_"+currentTime);

}

@@ -314,26 +315,27 @@ public void test_loadFolderAndSubFoldersByPath_UserNoPermissionsOverSubFolder_re
FolderView responseFolderView = FolderView.class.cast(responseEntityView.getEntity());
final Folder folder = folderAPI.find(responseFolderView.getIdentifier(),adminUser,false);

final User limitedUser = new UserDataGen().roles(TestUserUtils.getFrontendRole(), TestUserUtils.getBackendRole()).nextPersisted();
final String password = "admin";
limitedUser.setPassword(password);
APILocator.getUserAPI().save(limitedUser,APILocator.systemUser(),false);

//Give Permissions Over the Host
Permission permissions = new Permission(PermissionAPI.INDIVIDUAL_PERMISSION_TYPE,
newHost.getPermissionId(),
TestUserUtils.getOrCreatePublisherRole(newHost).getId(),
APILocator.getRoleAPI().loadRoleByKey(limitedUser.getUserId()).getId(),
PermissionAPI.PERMISSION_READ, true);
APILocator.getPermissionAPI().save(permissions, newHost, adminUser, false);
APILocator.getPermissionAPI().save(permissions, newHost, APILocator.systemUser(), false);

//Give Permissions Over the Folder
permissions = new Permission(PermissionAPI.INDIVIDUAL_PERMISSION_TYPE,
folder.getPermissionId(),
TestUserUtils.getOrCreatePublisherRole(newHost).getId(),
APILocator.getRoleAPI().loadRoleByKey(limitedUser.getUserId()).getId(),
PermissionAPI.PERMISSION_READ, true);
APILocator.getPermissionAPI().save(permissions, folder, adminUser, false);

final User chrisUser = TestUserUtils.getChrisPublisherUser(newHost);
final String password = "admin";
chrisUser.setPassword(password);
APILocator.getUserAPI().save(chrisUser,APILocator.systemUser(),false);
APILocator.getPermissionAPI().save(permissions, folder, APILocator.systemUser(), false);

//Get all the folders and subfolders using the limited user
responseResource = resource.loadFolderAndSubFoldersByPath(getHttpRequest(chrisUser.getEmailAddress(),password),response,newHost.getIdentifier(),"test_"+currentTime);
responseResource = resource.loadFolderAndSubFoldersByPath(getHttpRequest(limitedUser.getEmailAddress(),password),response,newHost.getIdentifier(),"test_"+currentTime);

//Check Results
responseEntityView = ResponseEntityView.class.cast(responseResource.getEntity());
@@ -72,7 +72,7 @@ private static void checkContent(
throws DotDataException, DotSecurityException {

for (final ContainerRaw pageContainer : pageContainers) {
final Map<String, List<Map<String, Object>>> contentlets = pageContainer.getContentlets();
final Map<String, List<Contentlet>> contentlets = pageContainer.getContentlets();
final Container container = pageContainer.getContainer();
final List<Structure> structures = APILocator.getContainerAPI().getStructuresInContainer(container);

@@ -84,7 +84,7 @@ private static void checkContent(
}

int nPageViewContents = 0;
for (final List<Map<String, Object>> pageViewContents : contentlets.values()) {
for (final List<Contentlet> pageViewContents : contentlets.values()) {
nPageViewContents += pageViewContents.size();
}

@@ -412,7 +412,7 @@ public void testRender() throws DotDataException, DotSecurityException {
assertFalse(containerIds.contains(container2.getIdentifier()));

for (final ContainerRaw pageContainer : pageContainers) {
final Map<String, List<Map<String, Object>>> contentlets = pageContainer.getContentlets();
final Map<String, List<Contentlet>> contentlets = pageContainer.getContentlets();
final Container container = pageContainer.getContainer();
final List<Structure> structures = APILocator.getContainerAPI().getStructuresInContainer(container);
if(container.getIdentifier().equals(localContainer1.getIdentifier())){
@@ -40,6 +40,7 @@
import com.google.common.collect.ImmutableSortedMap;
import com.liferay.portal.model.Portlet;
import com.liferay.portal.model.User;
import com.liferay.util.EncryptorException;
import com.tngtech.java.junit.dataprovider.DataProvider;
import com.tngtech.java.junit.dataprovider.DataProviderRunner;
import com.tngtech.java.junit.dataprovider.UseDataProvider;
@@ -48,6 +49,7 @@
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.security.Key;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -726,7 +728,7 @@ public void Test_Delete_Secrets_On_Site_Delete()

}

private final AppsAPI nonValidLicenseAppsAPI = new AppsAPIImpl(APILocator.getUserAPI(),
private final AppsAPI nonValidLicenseAppsAPI = new AppsAPIImpl(
APILocator.getLayoutAPI(),
APILocator.getHostAPI(), APILocator.getContentletAPI(), SecretsStore.INSTANCE.get(),
CacheLocator.getAppsCache(), APILocator.getLocalSystemEventsAPI(),
@@ -1021,4 +1023,55 @@ public void Test_File_Comparison_Is_Case_Sensitive()
//The one from the system-folder takes precedence.
assertEquals("system-app", impl.getDescription());
}

/**
* Given Scenario: This test creates secrets then exports them Then re-imports the file with the generated secrets.
* Expected Result: The test should be Able to recreate the secrets from the generated file.
* @throws DotDataException
* @throws DotSecurityException
* @throws IOException
* @throws EncryptorException
* @throws ClassNotFoundException
*/
@Test
public void Test_Create_Secrets_Then_Export_Them_Then_Import_Then_Save()
throws DotDataException, DotSecurityException, IOException, EncryptorException, ClassNotFoundException {
final User admin = TestUserUtils.getAdminUser();
final Host site = new SiteDataGen().nextPersisted();
final AppsAPI api = APILocator.getAppsAPI();
final AppSecrets.Builder builder1 = new AppSecrets.Builder();
final String appKey = "appKey-1-Host-1";
final AppSecrets secrets = builder1.withKey(appKey)
.withHiddenSecret("test:secret1", "secret-1")
.withHiddenSecret("test:secret2", "secret-2")
.withHiddenSecret("test:secret3", "secret3")
.withHiddenSecret("test:secret4", "secret-4")
.build();
//Save it
api.saveSecrets(secrets, site, admin);
final Optional<AppSecrets> secretsOptional = api.getSecrets(appKey, site, admin);
final AppSecrets appSecretsPostSave = secretsOptional.get();

//AES only supports key sizes of 16, 24 or 32 bytes.
final String password = RandomStringUtils.randomAlphanumeric(32);
final Key key = AppsUtil.generateKey(password);
final Path exportSecretsFile = api.exportSecrets(key, true, null, admin);
assertTrue(exportSecretsFile.toFile().exists());

//Remove so we can re import them.
api.deleteSecrets(appKey, site, admin);

final Map<String, List<AppSecrets>> secretAppsBySiteId = api.importSecrets(exportSecretsFile, key, admin);
assertFalse(secretAppsBySiteId.isEmpty());
assertTrue(secretAppsBySiteId.containsKey(site.getIdentifier()));
final List<AppSecrets> appSecretsBySite = secretAppsBySiteId.get(site.getIdentifier());
assertFalse(appSecretsBySite.isEmpty());
final AppSecrets importedSecrets = appSecretsBySite.get(0);
assertEquals(importedSecrets, appSecretsPostSave);

api.saveSecrets(importedSecrets, site, admin);
final Optional<AppSecrets> secretsOptionalPostImport = api.getSecrets(appKey, site, admin);
assertTrue(secretsOptionalPostImport.isPresent());
}

}
@@ -162,4 +162,18 @@ public void Test_Encrypt_Decrypt_Text_No_Middle_String() throws EncryptorExcepti
Assert.assertEquals(input, new String(decrypted));
}

/**
* Keys have fixed values. (32 is the minimum valid length) A key can't have a random length.
* Given scenario: We need to generate an encryption Key out of a given password.
* Expected Result: Here we simply transform any password adding or removing fill-chars to fit a 32 length
*/
@Test
public void Test_Seed_Key_Add_Remove_Padding(){
assertEquals(AppsUtil.keySeed(RandomStringUtils.randomAlphanumeric(14)).length(),32);
assertEquals(AppsUtil.keySeed(RandomStringUtils.randomAlphanumeric(140)).length(),32);
assertEquals(AppsUtil.keySeed(RandomStringUtils.randomAlphanumeric(32)).length(),32);
assertEquals(AppsUtil.keySeed(RandomStringUtils.randomAlphanumeric(3)).length(),32);
assertEquals(AppsUtil.keySeed(RandomStringUtils.randomAlphanumeric(0)).length(),32);
}

}
@@ -1,13 +1,17 @@
package com.dotcms.security.apps;

import static com.dotcms.security.apps.SecretsStoreKeyStoreImpl.SECRETS_KEYSTORE_PASSWORD_KEY;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

import com.dotcms.util.IntegrationTestInitService;
import com.dotmarketing.business.CacheLocator;
import com.dotmarketing.util.Config;
import com.dotmarketing.util.UUIDGenerator;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.util.Collection;
import java.util.Optional;
import org.apache.commons.lang.RandomStringUtils;
@@ -210,5 +214,35 @@ public void Test_Encryption() {

}

/**
* Given scenario: We ensure there's a storage file out there created on top of a password. Then
* we set a new password to simulate a conflict loading the existing file
* Expected Result: After changing password we should still be able to interact with the store
* without getting the UnrecoverableKeyException.
* But as the previous file just got wiped-out no secret previously stored will be available now.
*/
@Test
public void Test_Recovery_On_Load_Failure() {
final String password = Config.getStringProperty(SECRETS_KEYSTORE_PASSWORD_KEY);
try {
final SecretsStoreKeyStoreImpl secretsStore = (SecretsStoreKeyStoreImpl) SecretsStore.INSTANCE
.get();
final String anyKey = "anyKey-" + System.currentTimeMillis();
final String anyValue = "anyValue";
// Save something to ensure there's a file in use.
secretsStore.saveValue(anyKey, anyValue.toCharArray());
//Now we change the password.
Config.setProperty(SECRETS_KEYSTORE_PASSWORD_KEY,
RandomStringUtils.randomAlphanumeric(10));
Config.forceRefresh();
secretsStore.flushCache();
//it's a brand new store so do not expect the old key to be there.
final Optional<char[]> valueInStore = secretsStore.getValue(anyKey);
assertFalse(valueInStore.isPresent());
} finally {
Config.setProperty(SECRETS_KEYSTORE_PASSWORD_KEY, password);
}
}


}
@@ -9,24 +9,28 @@
import com.dotcms.IntegrationTestBase;
import com.dotcms.contenttype.business.ContentTypeAPI;
import com.dotcms.contenttype.model.type.ContentType;
import com.dotcms.datagen.CategoryDataGen;
import com.dotcms.datagen.ContentletDataGen;
import com.dotcms.datagen.FolderDataGen;
import com.dotcms.datagen.HTMLPageDataGen;
import com.dotcms.datagen.LanguageDataGen;
import com.dotcms.datagen.RoleDataGen;
import com.dotcms.datagen.SiteDataGen;
import com.dotcms.datagen.TestDataUtils;
import com.dotcms.datagen.UserDataGen;
import com.dotcms.repackage.org.apache.commons.io.FileUtils;
import com.dotcms.util.CollectionsUtils;
import com.dotcms.util.IntegrationTestInitService;
import com.dotmarketing.beans.Host;
import com.dotmarketing.beans.Permission;
import com.dotmarketing.business.PermissionAPI.PermissionableType;
import com.dotmarketing.business.ajax.RoleAjax;
import com.dotmarketing.cache.FieldsCache;
import com.dotmarketing.db.HibernateUtil;
import com.dotmarketing.exception.DotDataException;
import com.dotmarketing.exception.DotHibernateException;
import com.dotmarketing.exception.DotSecurityException;
import com.dotmarketing.portlets.categories.model.Category;
import com.dotmarketing.portlets.contentlet.business.DotContentletStateException;
import com.dotmarketing.portlets.contentlet.model.Contentlet;
import com.dotmarketing.portlets.contentlet.model.IndexPolicy;
@@ -186,6 +190,62 @@ public void doesUserHavePermission() throws DotDataException, DotSecurityExcepti

}

/**
* Method to test: {@link PermissionAPI#doesUserHavePermissions(String, PermissionableType, int, User)}
* Given Scenario: Give a limited user permissions to edit Categories over the System Host, check permissions against System Host.
* ExpectedResult: true since the user has permissions over the permissionable type and the asset.
*/
@Test
public void test_doesUserHavePermissions_checkSameAssetIdThatPermissionsWereGiven_returnTrue()
throws DotDataException, DotSecurityException {
//Create limited user
final User limitedUser = new UserDataGen().nextPersisted();

//Give Permissions Over the System Host
final Permission permissions = new Permission(PermissionableType.CATEGORY.getCanonicalName(),
APILocator.systemHost().getPermissionId(),
APILocator.getRoleAPI().loadRoleByKey(limitedUser.getUserId()).getId(),
PermissionAPI.PERMISSION_READ | PermissionAPI.PERMISSION_EDIT, true);
permissionAPI.save(permissions, APILocator.systemHost(), sysuser, false);

//Check Permissions
final boolean doesUserHavePermissions =
permissionAPI.doesUserHavePermissions(APILocator.systemHost().getIdentifier(),PermissionableType.CATEGORY,PermissionAPI.PERMISSION_EDIT,limitedUser);
assertTrue(doesUserHavePermissions);
}

/**
* Method to test: {@link PermissionAPI#doesUserHavePermissions(String, PermissionableType, int, User)}
* Given Scenario: Create a category as asmin, Give a limited user permissions to edit the category, check permissions against System Host.
* ExpectedResult: false since the user has permissions over the permissionable type but no the System Host.
*/
@Test
public void test_doesUserHavePermissions_checkDiffAssetIdThatPermissionsWereGiven_returnFalse()
throws DotDataException, DotSecurityException {
//Create limited user
final User limitedUser = new UserDataGen().nextPersisted();

//Create new top level Category as admin user
final long currentTime = System.currentTimeMillis();
final Category parentCategory = new CategoryDataGen()
.setCategoryName("CT-Category-Parent"+currentTime)
.setKey("parent"+currentTime)
.setCategoryVelocityVarName("parent"+currentTime)
.nextPersisted();

//Give Permissions Over the Category
final Permission permissions = new Permission(PermissionableType.CATEGORY.getCanonicalName(),
parentCategory.getPermissionId(),
APILocator.getRoleAPI().loadRoleByKey(limitedUser.getUserId()).getId(),
PermissionAPI.PERMISSION_READ | PermissionAPI.PERMISSION_EDIT, true);
permissionAPI.save(permissions, parentCategory, sysuser, false);

//Check Permissions
final boolean doesUserHavePermissions =
permissionAPI.doesUserHavePermissions(APILocator.systemHost().getIdentifier(),PermissionableType.CATEGORY,PermissionAPI.PERMISSION_EDIT,limitedUser);
assertFalse(doesUserHavePermissions);
}

/**
* Method to test: doesUserHavePermission
* Given Scenario: when calls doesUserHavePermission, to a contentlet with a diff language (not the default) with or not permission, the non-default language was throwing an exception
@@ -8,15 +8,21 @@
import com.dotmarketing.beans.MultiTree;
import com.dotmarketing.business.APILocator;
import com.dotmarketing.business.CacheLocator;
import com.dotmarketing.business.Ruleable;
import com.dotmarketing.common.db.DotConnect;
import com.dotmarketing.exception.DotDataException;
import com.dotmarketing.exception.DotSecurityException;
import com.dotmarketing.portlets.containers.model.Container;
import com.dotmarketing.portlets.containers.model.FileAssetContainer;
import com.dotmarketing.portlets.contentlet.model.Contentlet;
import com.dotmarketing.portlets.folders.model.Folder;
import com.dotmarketing.portlets.htmlpageasset.model.HTMLPageAsset;
import com.dotmarketing.portlets.htmlpageasset.model.IHTMLPage;
import com.dotmarketing.portlets.languagesmanager.model.Language;
import com.dotmarketing.portlets.personas.model.Persona;
import com.dotmarketing.portlets.structure.model.Structure;
import com.dotmarketing.portlets.templates.design.bean.ContainerUUID;
import com.dotmarketing.portlets.templates.design.bean.TemplateLayout;
import com.dotmarketing.portlets.templates.model.Template;
import com.dotmarketing.startup.runonce.Task04315UpdateMultiTreePK;
import com.dotmarketing.util.Logger;
@@ -785,6 +791,114 @@ public void shouldReplaceAllMultiTree() throws Exception {
assertFalse(multiTreeContentlets.contains(enContentlet.getIdentifier()));
}

/**
* Method to Test: {@link MultiTreeAPI#getPageMultiTrees(IHTMLPage, boolean)}
* When: Advanced Template with 2 {@link FileAssetContainer} (one empty) and 2 {@link Container} (one empty)
* Should: Return the 4 containers
* */
@Test
public void testEmptyContainersInAdvancedTemplate() throws DotDataException, DotSecurityException {
final Container container = new ContainerDataGen().nextPersisted();
FileAssetContainer fileAssetContainer = new ContainerAsFileDataGen().nextPersisted();
fileAssetContainer = (FileAssetContainer) APILocator.getContainerAPI().find(fileAssetContainer.getInode(), APILocator.systemUser(), false);

final Container emptyContainer = new ContainerDataGen().nextPersisted();
FileAssetContainer emptyFileAssetContainer = new ContainerAsFileDataGen().nextPersisted();
emptyFileAssetContainer = (FileAssetContainer) APILocator.getContainerAPI().find(emptyFileAssetContainer.getInode(), APILocator.systemUser(), false);

final Template template = new TemplateDataGen()
.withContainer(container, ContainerUUID.UUID_START_VALUE)
.withContainer(fileAssetContainer, ContainerUUID.UUID_START_VALUE)
.withContainer(emptyContainer, ContainerUUID.UUID_START_VALUE)
.withContainer(emptyFileAssetContainer, ContainerUUID.UUID_START_VALUE)
.nextPersisted();

final Folder folder = new FolderDataGen().nextPersisted();
final HTMLPageAsset page = new HTMLPageDataGen(folder, template).nextPersisted();

createContentAndMultiTree(container, fileAssetContainer, page);

final Table<String, String, Set<PersonalizedContentlet>> pageMultiTrees = APILocator.getMultiTreeAPI().getPageMultiTrees(page, false);

pageMultiTrees.rowKeySet().contains(container.getIdentifier());
pageMultiTrees.rowKeySet().contains(emptyContainer.getIdentifier());
pageMultiTrees.rowKeySet().contains(fileAssetContainer.getIdentifier());
pageMultiTrees.rowKeySet().contains(emptyFileAssetContainer.getIdentifier());
}

/**
* Method to Test: {@link MultiTreeAPI#getPageMultiTrees(IHTMLPage, boolean)}
* When: Drawed Template with 2 {@link FileAssetContainer} (one empty) and 2 {@link Container} (one empty)
* Should: Return the 4 containers
* */
@Test
public void testEmptyContainersInDrawedTemplate() throws DotDataException, DotSecurityException {
final Container container = new ContainerDataGen().nextPersisted();
FileAssetContainer fileAssetContainer = new ContainerAsFileDataGen().nextPersisted();
fileAssetContainer = (FileAssetContainer) APILocator.getContainerAPI().find(fileAssetContainer.getInode(), APILocator.systemUser(), false);

final Container emptyContainer = new ContainerDataGen().nextPersisted();
FileAssetContainer emptyFileAssetContainer = new ContainerAsFileDataGen().nextPersisted();
emptyFileAssetContainer = (FileAssetContainer) APILocator.getContainerAPI().find(emptyFileAssetContainer.getInode(), APILocator.systemUser(), false);

final TemplateLayout templateLayout = new TemplateLayoutDataGen()
.withContainer(container)
.withContainer(fileAssetContainer)
.withContainer(emptyContainer)
.withContainer(emptyFileAssetContainer)
.next();

final Template template = new TemplateDataGen()
.drawedBody(templateLayout)
.nextPersisted();

final Folder folder = new FolderDataGen().nextPersisted();
final HTMLPageAsset page = new HTMLPageDataGen(folder, template).nextPersisted();

createContentAndMultiTree(container, fileAssetContainer, page);

final Table<String, String, Set<PersonalizedContentlet>> pageMultiTrees = APILocator.getMultiTreeAPI().getPageMultiTrees(page, false);

System.out.println("multiTrees = " + pageMultiTrees);

pageMultiTrees.rowKeySet().contains(container.getIdentifier());
pageMultiTrees.rowKeySet().contains(emptyContainer.getIdentifier());
pageMultiTrees.rowKeySet().contains(fileAssetContainer.getIdentifier());
pageMultiTrees.rowKeySet().contains(emptyFileAssetContainer.getIdentifier());
}

private void createContentAndMultiTree(Container container, FileAssetContainer fileAssetContainer, HTMLPageAsset page) {
final Language defaultLanguage = APILocator.getLanguageAPI().getDefaultLanguage();

final ContentType contentType = new ContentTypeDataGen().nextPersisted();

final Contentlet contentlet_1 = new ContentletDataGen(contentType.id())
.languageId(defaultLanguage.getId())
.nextPersisted();

final Contentlet contentlet_2 = new ContentletDataGen(contentType.id())
.languageId(defaultLanguage.getId())
.nextPersisted();

final MultiTree multiTree_1 = new MultiTreeDataGen()
.setPage(page)
.setContainer(container)
.setContentlet(contentlet_1)
.setInstanceID(ContainerUUID.UUID_START_VALUE)
.setPersonalization(MultiTree.DOT_PERSONALIZATION_DEFAULT)
.setTreeOrder(1)
.nextPersisted();

final MultiTree multiTree_2 = new MultiTreeDataGen()
.setPage(page)
.setContainer(fileAssetContainer)
.setContentlet(contentlet_2)
.setInstanceID(ContainerUUID.UUID_START_VALUE)
.setPersonalization(MultiTree.DOT_PERSONALIZATION_DEFAULT)
.setTreeOrder(1)
.nextPersisted();
}

///
/**
* Method to Test: {@link MultiTreeAPI#overridesMultitreesByPersonalization(String, String, List, Optional)}}
@@ -19,11 +19,14 @@
import com.dotcms.contenttype.transform.contenttype.StructureTransformer;
import com.dotcms.datagen.CategoryDataGen;
import com.dotcms.datagen.SiteDataGen;
import com.dotcms.datagen.UserDataGen;
import com.dotcms.util.IntegrationTestInitService;
import com.dotmarketing.beans.Host;
import com.dotmarketing.beans.Permission;
import com.dotmarketing.business.APILocator;
import com.dotmarketing.business.CacheLocator;
import com.dotmarketing.business.PermissionAPI;
import com.dotmarketing.business.PermissionAPI.PermissionableType;
import com.dotmarketing.exception.DotDataException;
import com.dotmarketing.exception.DotSecurityException;
import com.dotmarketing.portlets.categories.model.Category;
@@ -55,6 +58,7 @@
private static User user;
private static Host defaultHost;
private static ContentTypeAPIImpl contentTypeApi;
private static CategoryAPI categoryAPI;

@BeforeClass
public static void prepare () throws Exception {
@@ -68,6 +72,7 @@ public static void prepare () throws Exception {
user = APILocator.getUserAPI().getSystemUser();
defaultHost = hostAPI.findDefaultHost( user, false );
contentTypeApi = (ContentTypeAPIImpl) APILocator.getContentTypeAPI(user);
categoryAPI = APILocator.getCategoryAPI();
}


@@ -81,8 +86,6 @@ public static void prepare () throws Exception {
@Test
public void findTopLevelCategories() throws DotSecurityException, DotDataException {

CategoryAPI categoryAPI = APILocator.getCategoryAPI();

//***************************************************************
int start = 0;
int count = 10;//TODO: A -1 or 0 wont work in order to request all que records
@@ -159,8 +162,6 @@ public void findChildren() throws DotSecurityException, DotDataException {
final Category root = rootCategoryDataGen.children(child1, child2).nextPersisted();
System.out.println(root);

CategoryAPI categoryAPI = APILocator.getCategoryAPI();

//Find a parent category
PaginatedCategories categories = categoryAPI.findTopLevelCategories(user, false, 0, 10, "bike", null);

@@ -233,7 +234,6 @@ public void getParents () throws Exception {

Long time = new Date().getTime();

CategoryAPI categoryAPI = APILocator.getCategoryAPI();
PermissionAPI permissionAPI = APILocator.getPermissionAPI();
ContentletAPI contentletAPI = APILocator.getContentletAPI();

@@ -369,7 +369,6 @@ public void verifyCache () throws Exception {

Long time = new Date().getTime();

CategoryAPI categoryAPI = APILocator.getCategoryAPI();
CategoryCache categoryCache = CacheLocator.getCategoryCache();

List<Category> categories = new ArrayList<Category>();
@@ -559,7 +558,6 @@ public void verifyCache () throws Exception {
@Test
public void testSortChildren() {

final CategoryAPI categoryAPI = APILocator.getCategoryAPI();
final CategoryCache categoryCache = CacheLocator.getCategoryCache();

final String categoryAKey = "categoryA";
@@ -718,8 +716,6 @@ protected static Structure createStructure ( String name, String structureVeloci
@Test
public void testDuplicatedCategories() {

final CategoryAPI categoryAPI = APILocator.getCategoryAPI();

Category category = null;

try {
@@ -779,7 +775,6 @@ public void testDuplicatedCategories() {

@Test
public void getCategoryTreeUp_hierarchyLevelThree_Success(){
final CategoryAPI categoryAPI = APILocator.getCategoryAPI();

List<Category> categoriesToDelete = Lists.newArrayList();

@@ -855,7 +850,6 @@ public void getCategoryTreeUp_hierarchyLevelThree_Success(){
*/
@Test
public void checkUniqueKey_severalCases_Success() {
final CategoryAPI categoryAPI = APILocator.getCategoryAPI();

List<Category> categoriesToDelete = Lists.newArrayList();

@@ -965,7 +959,6 @@ public void checkUniqueKey_severalCases_Success() {
public void test_Find_Categories_Within_ContentType() throws Exception {
ContentType contentType = null;
List<Category> categoriesToDelete = Lists.newArrayList();
final CategoryAPI categoryAPI = APILocator.getCategoryAPI();
try {
//Create Parent Category.
Category parentCategory = new Category();
@@ -1087,6 +1080,192 @@ private ContentType createContentTypeWithCatAndTextField(final Category parentCa
}

/**
* Method to test: {@link CategoryAPI#save(Category, Category, User, boolean)}
* Given Scenario: As a admin user create a top level category
* ExpectedResult: Category created successfully
*/
@Test
public void test_save_createTopLevelCategory_asAdmin_success()
throws DotSecurityException, DotDataException {
//Create new Top Level Category
final String categoryName = "newCategory" + System.currentTimeMillis();
final Category newCategory = new CategoryDataGen().setCategoryName(categoryName)
.setCategoryVelocityVarName(categoryName).setKey(categoryName).nextPersisted();
//Find created Category
final Category getCategory = categoryAPI.findByKey(categoryName,user,false);
//Check that the category obtained is the same as the created
assertNotNull(getCategory);
assertEquals(categoryName,getCategory.getCategoryName());
}

/**
* Method to test: {@link CategoryAPI#save(Category, Category, User, boolean)}
* Given Scenario: As a admin user create a top level category and a subcategory of it
* ExpectedResult: Categories created successfully
*/
@Test
public void test_save_createSubCategory_asAdmin_success()
throws DotSecurityException, DotDataException {
final String parentCategoryName = "newParentCategory" + System.currentTimeMillis();
final String childCategoryName = "newChildCategory" + System.currentTimeMillis();
//Create Child Category
final Category newChildCategory = new CategoryDataGen().setCategoryName(childCategoryName)
.setCategoryVelocityVarName(childCategoryName).setKey(childCategoryName).next();
//Create Parent Category
final Category newParentCategory = new CategoryDataGen().setCategoryName(parentCategoryName)
.setCategoryVelocityVarName(parentCategoryName).setKey(parentCategoryName).children(newChildCategory).nextPersisted();
//Find Parent Category
final Category getParentCategory = categoryAPI.findByKey(parentCategoryName,user,false);
//Check that was created successfully
assertNotNull(getParentCategory);
assertEquals(parentCategoryName,getParentCategory.getCategoryName());
//Find Child Category
final Category getChildCategory = categoryAPI.findByKey(childCategoryName,user,false);
//Check was created successfully
assertNotNull(getChildCategory);
assertEquals(childCategoryName,getChildCategory.getCategoryName());
assertTrue(categoryAPI.isParent(getChildCategory,getParentCategory,user));
}

/**
* Method to test: {@link CategoryAPI#save(Category, Category, User, boolean)}
* Given Scenario: As a limited user, with the required permissions (can add children, publish categories), create a top level category
* ExpectedResult: Category created successfully
*/
@Test
public void test_save_createTopLevelCategory_asLimitedUser_success()
throws DotSecurityException, DotDataException {
//Create limited user
final User limitedUser = new UserDataGen().nextPersisted();
//Give Permissions Over the SystemHost Can Add children
Permission permissions = new Permission(PermissionAPI.INDIVIDUAL_PERMISSION_TYPE,
APILocator.systemHost().getPermissionId(),
APILocator.getRoleAPI().loadRoleByKey(limitedUser.getUserId()).getId(),
PermissionAPI.PERMISSION_CAN_ADD_CHILDREN, true);
APILocator.getPermissionAPI().save(permissions, APILocator.systemHost(), user, false);
//Give Permissions Over the Categories
permissions = new Permission(PermissionableType.CATEGORY.getCanonicalName(),
APILocator.systemHost().getPermissionId(),
APILocator.getRoleAPI().loadRoleByKey(limitedUser.getUserId()).getId(),
PermissionAPI.PERMISSION_READ | PermissionAPI.PERMISSION_EDIT | PermissionAPI.PERMISSION_PUBLISH, true);
APILocator.getPermissionAPI().save(permissions, APILocator.systemHost(), user, false);
//Create new top level Category as limited user
final String categoryName = "newCategory" + System.currentTimeMillis();
final Category newCategory = new Category();
newCategory.setCategoryName(categoryName);
newCategory.setCategoryVelocityVarName(categoryName);
newCategory.setKey(categoryName);

categoryAPI.save(null,newCategory,limitedUser,false);
//Find the new Category using the limited user
final Category getCategory = categoryAPI.findByKey(categoryName,limitedUser,false);
assertNotNull(getCategory);
assertEquals(categoryName,getCategory.getCategoryName());
}

/**
* Method to test: {@link CategoryAPI#save(Category, Category, User, boolean)}
* Given Scenario: As a limited user, without the required permission, create a top level category
* ExpectedResult: Fail to create a top level category
*/
@Test(expected = DotSecurityException.class)
public void test_save_createTopLevelCategory_asLimitedUser_withoutPermissions_throwDotSecurityException()
throws DotSecurityException, DotDataException {
//Create limited user
final User limitedUser = new UserDataGen().nextPersisted();
//Create new top level Category as limited user
final String categoryName = "newCategory" + System.currentTimeMillis();
final Category newCategory = new Category();
newCategory.setCategoryName(categoryName);
newCategory.setCategoryVelocityVarName(categoryName);
newCategory.setKey(categoryName);

categoryAPI.save(null,newCategory,limitedUser,false);
}

/**
* Method to test: {@link CategoryAPI#save(Category, Category, User, boolean)}
* Given Scenario: As a limited user, with the required permissions, create a top level category
* and a subcategory of the new top level category
* ExpectedResult: Categories created successfully
*/
@Test
public void test_save_createSubCategory_asLimitedUser_success()
throws DotSecurityException, DotDataException {
//Create limited user
final User limitedUser = new UserDataGen().nextPersisted();

//Create new top level Category as admin user
final String parentCategoryName = "newParentCategory" + System.currentTimeMillis();
final Category newParentCategory = new Category();
newParentCategory.setCategoryName(parentCategoryName);
newParentCategory.setCategoryVelocityVarName(parentCategoryName);
newParentCategory.setKey(parentCategoryName);

categoryAPI.save(null,newParentCategory,user,false);
//Find the new Category using the admin user
final Category getParentCategory = categoryAPI.findByKey(parentCategoryName,user,false);
assertNotNull(getParentCategory);
assertEquals(parentCategoryName,getParentCategory.getCategoryName());

//Give Permissions Over the Category
final Permission permissions = new Permission(PermissionAPI.INDIVIDUAL_PERMISSION_TYPE,
getParentCategory.getPermissionId(),
APILocator.getRoleAPI().loadRoleByKey(limitedUser.getUserId()).getId(),
PermissionAPI.PERMISSION_READ | PermissionAPI.PERMISSION_EDIT, true);
APILocator.getPermissionAPI().save(permissions, getParentCategory, user, false);

//Create new sub Category as limited user
final String childCategoryName = "newChildCategory" + System.currentTimeMillis();
final Category newChildCategory = new Category();
newChildCategory.setCategoryName(childCategoryName);
newChildCategory.setCategoryVelocityVarName(childCategoryName);
newChildCategory.setKey(childCategoryName);

categoryAPI.save(getParentCategory,newChildCategory,limitedUser,false);
//Find the new Category using the limited user
final Category getChildCategory = categoryAPI.findByKey(childCategoryName,limitedUser,false);
assertNotNull(getChildCategory);
assertEquals(childCategoryName,getChildCategory.getCategoryName());
assertTrue(categoryAPI.isParent(getChildCategory,getParentCategory,limitedUser));
}

/**
* Method to test: {@link CategoryAPI#save(Category, Category, User, boolean)}
* Given Scenario: As a limited user, with the required permissions, create a top level category
* and a subcategory of the new top level category
* ExpectedResult: Categories created successfully
*/
@Test(expected = DotSecurityException.class)
public void test_save_createSubCategory_asLimitedUser_withoutPermissions_throwDotSecurityException()
throws DotSecurityException, DotDataException {
//Create limited user
final User limitedUser = new UserDataGen().nextPersisted();

//Create new top level Category as admin user
final String parentCategoryName = "newParentCategory" + System.currentTimeMillis();
final Category newParentCategory = new Category();
newParentCategory.setCategoryName(parentCategoryName);
newParentCategory.setCategoryVelocityVarName(parentCategoryName);
newParentCategory.setKey(parentCategoryName);

categoryAPI.save(null,newParentCategory,user,false);
//Find the new Category using the admin user
final Category getParentCategory = categoryAPI.findByKey(parentCategoryName,user,false);
assertNotNull(getParentCategory);
assertEquals(parentCategoryName,getParentCategory.getCategoryName());

//Create new sub Category as limited user
final String childCategoryName = "newChildCategory" + System.currentTimeMillis();
final Category newChildCategory = new Category();
newChildCategory.setCategoryName(childCategoryName);
newChildCategory.setCategoryVelocityVarName(childCategoryName);
newChildCategory.setKey(childCategoryName);

categoryAPI.save(getParentCategory,newChildCategory,limitedUser,false);
}

/**
* Method to test: {@link CategoryAPI#isParent(Category, Category, User, boolean)}
* Given scenario: Create a Category with 3 levels of depth:
* Parent Category
@@ -1230,5 +1409,132 @@ public void test_isParent_givenGrandChildCategoryThatNoBelongsToParentCategory_r
categoryAPI.delete(category, user, false);
}
}

}

/**
* Method to test: {@link CategoryAPI#save(Category, Category, User, boolean)}
* Given Scenario: As a limited user, with the required permissions (edit over the category), edit a category
* ExpectedResult: Categories edited successfully
*/
@Test
public void test_save_editCategory_asLimitedUser_success()
throws DotSecurityException, DotDataException {
//Create limited user
final User limitedUser = new UserDataGen().nextPersisted();

//Create new top level Category as admin user
final String parentCategoryName = "newParentCategory" + System.currentTimeMillis();
final Category newParentCategory = new Category();
newParentCategory.setCategoryName(parentCategoryName);
newParentCategory.setCategoryVelocityVarName(parentCategoryName);
newParentCategory.setKey(parentCategoryName);

categoryAPI.save(null, newParentCategory, user, false);
//Find the new Category using the admin user
final Category getCategory = categoryAPI.findByKey(parentCategoryName, user, false);
assertNotNull(getCategory);
assertEquals(parentCategoryName, getCategory.getCategoryName());

//Give Permissions Over the Category
final Permission permissions = new Permission(PermissionAPI.INDIVIDUAL_PERMISSION_TYPE,
getCategory.getPermissionId(),
APILocator.getRoleAPI().loadRoleByKey(limitedUser.getUserId()).getId(),
PermissionAPI.PERMISSION_READ | PermissionAPI.PERMISSION_EDIT, true);
APILocator.getPermissionAPI().save(permissions, getCategory, user, false);

//Make some changes over the category
final String editedCategoryName = "editedCategory" + System.currentTimeMillis();
getCategory.setCategoryName(editedCategoryName);
getCategory.setKey(editedCategoryName);
getCategory.setKeywords(editedCategoryName);
categoryAPI.save(null,getCategory,limitedUser,false);

final Category getEditedCategory = categoryAPI.findByKey(editedCategoryName, user, false);
assertNotNull(getEditedCategory);
assertEquals(editedCategoryName, getEditedCategory.getCategoryName());

}

/**
* Method to test: {@link CategoryAPI#save(Category, Category, User, boolean)}
* Given Scenario: As a limited user, with the required permissions (edit over the category), edit a category
* ExpectedResult: Failed to edit Category, DotSecurityException
*/
@Test(expected = DotSecurityException.class)
public void test_save_editCategory_asLimitedUser_withoutPermissions_throwDotSecurityException()
throws DotSecurityException, DotDataException {
//Create limited user
final User limitedUser = new UserDataGen().nextPersisted();

//Create new top level Category as admin user
final String parentCategoryName = "newParentCategory" + System.currentTimeMillis();
final Category newParentCategory = new Category();
newParentCategory.setCategoryName(parentCategoryName);
newParentCategory.setCategoryVelocityVarName(parentCategoryName);
newParentCategory.setKey(parentCategoryName);

categoryAPI.save(null, newParentCategory, user, false);
//Find the new Category using the admin user
final Category getCategory = categoryAPI.findByKey(parentCategoryName, user, false);
assertNotNull(getCategory);
assertEquals(parentCategoryName, getCategory.getCategoryName());

//Make some changes over the category
final String editedCategoryName = "editedCategory" + System.currentTimeMillis();
getCategory.setCategoryName(editedCategoryName);
getCategory.setKey(editedCategoryName);
getCategory.setKeywords(editedCategoryName);
categoryAPI.save(null,getCategory,limitedUser,false);
}

/**
* Method to test: {@link CategoryAPI#save(Category, Category, User, boolean)}
* Given Scenario: As an admin create a top level category and give a limited user permissions
* to edit it. Also give Add Children permissions over the System Host.
* Try to add a new top level category using the limited user.
* ExpectedResult: Category created failed because lack of permissions (no Edit over Category under System Host).
*/
@Test(expected = DotSecurityException.class)
public void test_save_createTopLevelCategory_asLimitedUser_fail()
throws DotSecurityException, DotDataException {
//Create limited user
final User limitedUser = new UserDataGen().nextPersisted();

//Create new top level Category as admin user
final String parentCategoryName = "newParentCategory" + System.currentTimeMillis();
final Category newParentCategory = new Category();
newParentCategory.setCategoryName(parentCategoryName);
newParentCategory.setCategoryVelocityVarName(parentCategoryName);
newParentCategory.setKey(parentCategoryName);

categoryAPI.save(null, newParentCategory, user, false);
//Find the new Category using the admin user
final Category getCategory = categoryAPI.findByKey(parentCategoryName, user, false);
assertNotNull(getCategory);
assertEquals(parentCategoryName, getCategory.getCategoryName());

//Give Permissions Over the Category
Permission permissions = new Permission(PermissionableType.CATEGORY.getCanonicalName(),
getCategory.getPermissionId(),
APILocator.getRoleAPI().loadRoleByKey(limitedUser.getUserId()).getId(),
PermissionAPI.PERMISSION_READ | PermissionAPI.PERMISSION_EDIT, true);
APILocator.getPermissionAPI().save(permissions, getCategory, user, false);

//Give Permissions Over the SystemHost Can Add children
permissions = new Permission(PermissionAPI.INDIVIDUAL_PERMISSION_TYPE,
APILocator.systemHost().getPermissionId(),
APILocator.getRoleAPI().loadRoleByKey(limitedUser.getUserId()).getId(),
PermissionAPI.PERMISSION_CAN_ADD_CHILDREN, true);
APILocator.getPermissionAPI().save(permissions, APILocator.systemHost(), user, false);

//Create new top level Category as limited user
final String categoryName = "newCategory" + System.currentTimeMillis();
final Category newCategory = new Category();
newCategory.setCategoryName(categoryName);
newCategory.setCategoryVelocityVarName(categoryName);
newCategory.setKey(categoryName);

categoryAPI.save(null, newCategory, limitedUser, false);
}
}
@@ -1,31 +1,20 @@
package com.dotmarketing.portlets.containers.business;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;

import com.dotcms.IntegrationTestBase;
import com.dotcms.api.web.HttpServletRequestThreadLocal;
import com.dotcms.contenttype.exception.NotFoundInDbException;
import com.dotcms.contenttype.model.type.ContentType;
import com.dotcms.datagen.ContainerAsFileDataGen;
import com.dotcms.datagen.ContainerDataGen;
import com.dotcms.datagen.FileAssetDataGen;
import com.dotcms.datagen.SiteDataGen;
import com.dotcms.datagen.TestDataUtils;
import com.dotcms.datagen.TestUserUtils;
import com.dotcms.datagen.TestWorkflowUtils;
import com.dotcms.datagen.*;
import com.dotcms.repackage.org.apache.commons.io.FileUtils;
import com.dotcms.util.IntegrationTestInitService;
import com.dotmarketing.beans.ContainerStructure;
import com.dotmarketing.beans.Host;
import com.dotmarketing.beans.Permission;
import com.dotmarketing.business.APILocator;
import com.dotmarketing.business.CacheLocator;
import com.dotmarketing.business.PermissionAPI;
import com.dotmarketing.db.HibernateUtil;
import com.dotmarketing.beans.VersionInfo;
import com.dotmarketing.business.*;
import com.dotmarketing.exception.DotDataException;
import com.dotmarketing.exception.DotSecurityException;
import com.dotmarketing.exception.WebAssetException;
import com.dotmarketing.portlets.containers.model.Container;
import com.dotmarketing.portlets.containers.model.FileAssetContainer;
import com.dotmarketing.portlets.contentlet.business.ContentletAPI;
@@ -36,28 +25,26 @@
import com.dotmarketing.portlets.fileassets.business.FileAssetAPI;
import com.dotmarketing.portlets.folders.business.FolderAPI;
import com.dotmarketing.portlets.folders.model.Folder;
import com.dotmarketing.portlets.structure.model.Field;
import com.dotmarketing.portlets.structure.model.Structure;
import com.dotmarketing.portlets.workflows.business.SystemWorkflowConstants;
import com.dotmarketing.portlets.workflows.business.WorkflowAPI;
import com.dotmarketing.portlets.workflows.model.WorkflowAction;
import com.dotmarketing.util.Config;
import com.dotmarketing.util.Constants;
import com.liferay.portal.model.User;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.Charset;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import org.apache.felix.framework.OSGIUtil;
import java.util.*;

import org.jetbrains.annotations.NotNull;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;

import javax.servlet.http.HttpServletRequest;

import static org.junit.Assert.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

/**
* Test of {@link ContainerAPIImpl}
*/
@@ -107,10 +94,6 @@ public void getContentTypesInContainer() throws DotDataException, DotSecurityExc
.withContentType(contentType2, "")
.nextPersisted();





ContainerAPIImpl containerAPI = new ContainerAPIImpl();
List<ContentType> contentTypesInContainer = containerAPI
.getContentTypesInContainer(APILocator.systemUser(), container);
@@ -122,8 +105,6 @@ public void getContentTypesInContainer() throws DotDataException, DotSecurityExc

assertTrue("Blog like CT was expected", optionalContentType1.isPresent());
assertTrue("Banner Like CT was expected", optionalContentType2.isPresent());


}

@Test
@@ -328,4 +309,440 @@ private Contentlet unpublish(final String containerInode) throws DotSecurityExce
.build());
}

/**
* Method to Test: {@link ContainerAPIImpl#findContainer(String, User, boolean, boolean)}
* When: Find a {@link Container} not publish by Id and live equals to false
* Should: return it
*/
@Test
public void whenFindContainerByIdAndExists() throws DotSecurityException, DotDataException {
final Container container = new ContainerDataGen().nextPersisted();
final Optional<Container> containerFromDatabase =
APILocator.getContainerAPI().findContainer(container.getIdentifier(), APILocator.systemUser(), false, false);

assertTrue(containerFromDatabase.isPresent());
assertEquals(container.getIdentifier(), containerFromDatabase.get().getIdentifier());
}

/**
* Method to Test: {@link ContainerAPIImpl#findContainer(String, User, boolean, boolean)}
* When: Find a published {@link Container} by Id and live equals to false
* Should: return the working version
*/
@Test
public void whenFindPublishContainerByIdAndExistsAndLiveFalse() throws DotSecurityException, DotDataException, WebAssetException {
final Host host = new SiteDataGen().nextPersisted();
final Container container = new ContainerDataGen().site(host).nextPersisted();
container.setTitle("Live Version");
ContainerDataGen.publish(container);

container.setTitle("Working Version");
APILocator.getContainerAPI()
.save(container, Collections.emptyList(), host, APILocator.getUserAPI().getSystemUser(), false);

final Optional<Container> containerFromDatabase =
APILocator.getContainerAPI().findContainer(container.getIdentifier(), APILocator.systemUser(), false, false);

assertTrue(containerFromDatabase.isPresent());
assertEquals(container.getIdentifier(), containerFromDatabase.get().getIdentifier());

final VersionInfo versionInfo = APILocator.getVersionableAPI().getVersionInfo(container.getIdentifier());
assertEquals(versionInfo.getWorkingInode(), containerFromDatabase.get().getInode());
}

/**
* Method to Test: {@link ContainerAPIImpl#findContainer(String, User, boolean, boolean)}
* When: Find a published {@link Container} by Id and live equals to true
* Should: return the live version
*/
@Test
public void whenFindPublishContainerByIdAndExistsAndLiveTrue() throws DotSecurityException, DotDataException, WebAssetException {
final Host host = new SiteDataGen().nextPersisted();
final Container container = new ContainerDataGen().site(host).nextPersisted();
container.setTitle("Live Version");
ContainerDataGen.publish(container);

container.setTitle("Working Version");
APILocator.getContainerAPI()
.save(container, Collections.emptyList(), host, APILocator.getUserAPI().getSystemUser(), false);

final VersionInfo versionInfo = APILocator.getVersionableAPI().getVersionInfo(container.getIdentifier());
CacheLocator.getContainerCache().remove(versionInfo);

final Optional<Container> containerFromDatabase =
APILocator.getContainerAPI().findContainer(container.getIdentifier(), APILocator.systemUser(), true, false);

assertTrue(containerFromDatabase.isPresent());
assertEquals(container.getIdentifier(), containerFromDatabase.get().getIdentifier());

assertEquals(versionInfo.getLiveInode(), containerFromDatabase.get().getInode());
}

/**
* Method to Test: {@link ContainerAPIImpl#findContainer(String, User, boolean, boolean)}
* When: Find a {@link Container} by Id and it not exists
* Should: return a empty Optional
*/
@Test
public void whenFindContainerByIdAndNotExists() throws DotSecurityException, DotDataException {
final Optional<Container> containerFromDatabase =
APILocator.getContainerAPI().findContainer("not_exists", APILocator.systemUser(), false, false);

assertFalse(containerFromDatabase.isPresent());
}

/**
* Method to Test: {@link ContainerAPIImpl#findContainer(String, User, boolean, boolean)}
* When: Find a not published {@link FileAssetContainer} by absolute path and id, wit Live equals to true and false
* Should: for all the case should return the working version
*/
@Test
public void whenFindFileContainerByAbsolutePathAndExists() throws DotDataException, DotSecurityException {
final Host host = new SiteDataGen().nextPersisted();
final FileAssetContainer fileAssetContainer = new ContainerAsFileDataGen()
.host(host)
.nextPersisted();

//Find by Id
final Optional<Container> containerFromDatabase =
APILocator.getContainerAPI().findContainer(fileAssetContainer.getIdentifier(), APILocator.systemUser(),
false, false);

assertTrue(containerFromDatabase.isPresent());
assertEquals(
fileAssetContainer.getIdentifier(),
containerFromDatabase.get().getIdentifier()
);

//Find by absolute path
final Optional<Container> containerFromDatabaseByAbsolutePath =
APILocator.getContainerAPI().findContainer(
FileAssetContainerUtil.getInstance().getFullPath((FileAssetContainer) containerFromDatabase.get()),
APILocator.systemUser(), false, false);

assertTrue(containerFromDatabaseByAbsolutePath.isPresent());
assertEquals(
fileAssetContainer.getIdentifier(),
containerFromDatabaseByAbsolutePath.get().getIdentifier()
);

//find with Live equals to true
final Optional<Container> containerInLive = APILocator.getContainerAPI().findContainer(
FileAssetContainerUtil.getInstance().getFullPath((FileAssetContainer) containerFromDatabaseByAbsolutePath.get()),
APILocator.systemUser(),
true, false);

assertTrue(containerInLive.isPresent());

final VersionInfo versionInfo = APILocator.getVersionableAPI().getVersionInfo(fileAssetContainer.getIdentifier());
assertEquals(
versionInfo.getWorkingInode(),
containerInLive.get().getInode()
);
}

/**
* Method to Test: {@link ContainerAPIImpl#findContainer(String, User, boolean, boolean)}
* When: Find a published {@link FileAssetContainer} by absolute path and id
* Should: return it
*/
@Test
public void whenFindPublishedFileContainerByAbsolutePathAndExists() throws DotDataException, DotSecurityException {
final Host host = new SiteDataGen().nextPersisted();
final FileAssetContainer fileAssetContainer = new ContainerAsFileDataGen()
.host(host)
.nextPersisted();
final Contentlet contentlet = APILocator.getContentletAPI().find(fileAssetContainer.getInode(),
APILocator.systemUser(), false);
publish(contentlet);

//find by id
final Optional<Container> containerFromDatabase =
APILocator.getContainerAPI().findContainer(fileAssetContainer.getIdentifier(), APILocator.systemUser(),
true, false);

assertTrue(containerFromDatabase.isPresent());
assertEquals(
fileAssetContainer.getIdentifier(),
containerFromDatabase.get().getIdentifier()
);

//find by absolute path
final Optional<Container> containerFromDatabaseWithAbsolutePath =
APILocator.getContainerAPI().findContainer(
FileAssetContainerUtil.getInstance().getFullPath((FileAssetContainer) containerFromDatabase.get()),
APILocator.systemUser(), true, false);

assertEquals(
fileAssetContainer.getIdentifier(),
containerFromDatabaseWithAbsolutePath.get().getIdentifier()
);

final VersionInfo versionInfo = APILocator.getVersionableAPI().getVersionInfo(fileAssetContainer.getIdentifier());

assertTrue(containerFromDatabaseWithAbsolutePath.isPresent());
assertEquals(
versionInfo.getLiveInode(),
containerFromDatabaseWithAbsolutePath.get().getInode()
);

//find with LIVE equals false, should return the working version
final Optional<Container> workingContainerFromDatabase =
APILocator.getContainerAPI().findContainer(fileAssetContainer.getIdentifier(), APILocator.systemUser(), false,
false);

assertEquals(versionInfo.getWorkingInode(), workingContainerFromDatabase.get().getInode());
}

/**
* Method to Test: {@link ContainerAPIImpl#findContainer(String, User, boolean, boolean)}
* When: Find a {@link FileAssetContainer} that exists in current host by absolute path but using another host
* Also find the container with Relative Path
* Should: return it
*/
@Test
public void whenFindFileContainerByAbsolutePathAndExistsInCurrentHost() throws DotDataException, DotSecurityException {
final Host anotherHost = new SiteDataGen().nextPersisted();
final Host currentHost = new SiteDataGen().nextPersisted();

final HttpServletRequest request = mock(HttpServletRequest.class);
HttpServletRequestThreadLocal.INSTANCE.setRequest(request);
when(request.getParameter("host_id")).thenReturn(currentHost.getIdentifier());

final FileAssetContainer fileAssetContainer = new ContainerAsFileDataGen()
.host(currentHost)
.nextPersisted();

//find by id
final Optional<Container> containerFromDatabase =
APILocator.getContainerAPI().findContainer(fileAssetContainer.getIdentifier(), APILocator.systemUser(),
false, false);

final String fullPathUsingAnotherHost = FileAssetContainerUtil.getInstance().getFullPath(
anotherHost,
((FileAssetContainer) containerFromDatabase.get()).getPath());

//find by absolute path using the another hist
final Optional<Container> containerFromDatabaseWithAbsolutePath =
APILocator.getContainerAPI().findContainer(fullPathUsingAnotherHost,
APILocator.systemUser(), true, false);

final FileAssetContainer fileAssetContainerFromDataBase = (FileAssetContainer) containerFromDatabaseWithAbsolutePath.get();
assertEquals(
fileAssetContainer.getIdentifier(),
fileAssetContainerFromDataBase.getIdentifier()
);

assertEquals(
fileAssetContainerFromDataBase.getHost().getIdentifier(),
currentHost.getIdentifier()
);

//using relative path
final Optional<Container> containerFromDatabaseWithRelativePath =
APILocator.getContainerAPI().findContainer(((FileAssetContainer) containerFromDatabase.get()).getPath(),
APILocator.systemUser(), true, false);

final FileAssetContainer fileAssetContainerFromDataBaseWithRelativePath = (FileAssetContainer) containerFromDatabaseWithRelativePath.get();
assertEquals(
fileAssetContainer.getIdentifier(),
fileAssetContainerFromDataBaseWithRelativePath.getIdentifier()
);

assertEquals(
fileAssetContainerFromDataBaseWithRelativePath.getHost().getIdentifier(),
currentHost.getIdentifier()
);
}

/**
* Method to Test: {@link ContainerAPIImpl#findContainer(String, User, boolean, boolean)}
* When: Find a {@link FileAssetContainer} that exists in current host by absolute path but using another host
* Also find the container with Relative Path
* Should: return it
*/
@Test
public void whenFindFileContainerByAbsolutePathAndExistsInDefaultHost() throws DotDataException, DotSecurityException {
final Host anotherHost = new SiteDataGen().nextPersisted();
final Host defaultHost = APILocator.getHostAPI().findDefaultHost(APILocator.systemUser(), false);

final HttpServletRequest request = mock(HttpServletRequest.class);
HttpServletRequestThreadLocal.INSTANCE.setRequest(request);
when(request.getParameter("host_id")).thenReturn(defaultHost.getIdentifier());

final FileAssetContainer fileAssetContainer = new ContainerAsFileDataGen()
.host(defaultHost)
.nextPersisted();

//find by id
final Optional<Container> containerFromDatabase =
APILocator.getContainerAPI().findContainer(fileAssetContainer.getIdentifier(), APILocator.systemUser(),
false, false);

final String fullPathUsingAnotherHost = FileAssetContainerUtil.getInstance().getFullPath(
anotherHost,
((FileAssetContainer) containerFromDatabase.get()).getPath());

//find by absolute path using the another host
final Optional<Container> containerFromDatabaseWithAbsolutePath =
APILocator.getContainerAPI().findContainer(fullPathUsingAnotherHost,
APILocator.systemUser(), true, false);

final FileAssetContainer fileAssetContainerFromDataBase = (FileAssetContainer) containerFromDatabaseWithAbsolutePath.get();
assertEquals(
fileAssetContainer.getIdentifier(),
fileAssetContainerFromDataBase.getIdentifier()
);

assertEquals(
fileAssetContainerFromDataBase.getHost().getIdentifier(),
defaultHost.getIdentifier()
);

//using relative path
final Optional<Container> containerFromDatabaseWithRelativePath =
APILocator.getContainerAPI().findContainer(((FileAssetContainer) containerFromDatabase.get()).getPath(),
APILocator.systemUser(), true, false);

final FileAssetContainer fileAssetContainerFromDataBaseWithRelativePath = (FileAssetContainer) containerFromDatabaseWithRelativePath.get();
assertEquals(
fileAssetContainer.getIdentifier(),
fileAssetContainerFromDataBaseWithRelativePath.getIdentifier()
);

assertEquals(
fileAssetContainerFromDataBaseWithRelativePath.getHost().getIdentifier(),
defaultHost.getIdentifier()
);
}


/**
* Method to Test: {@link ContainerAPIImpl#findContainer(String, User, boolean, boolean)}
* When: Find a {@link FileAssetContainer} by relative path and it not exists
* Should: return Empty Optional
*/
@Test
public void whenFindFileContainerByRelativePathAndNotExists() throws DotSecurityException, DotDataException {
final Optional<Container> containerFromDatabase =
APILocator.getContainerAPI().findContainer("/not_exists", APILocator.systemUser(), false, false);

assertFalse(containerFromDatabase.isPresent());
}

/**
* Method to Test: {@link ContainerAPIImpl#findContainer(String, User, boolean, boolean)}
* When: Find a {@link Container} by Id and it exists but the not have permission
* Should: throw a {@link DotSecurityException}
*/
@Test(expected = DotSecurityException.class)
public void whenFindContainerByIdAndUserNotHasPermission() throws DotDataException, DotSecurityException {
final User user = new UserDataGen().nextPersisted();
final Host host = new SiteDataGen().nextPersisted();
final FileAssetContainer fileAssetContainer = new ContainerAsFileDataGen()
.host(host)
.nextPersisted();

APILocator.getContainerAPI().findContainer(fileAssetContainer.getIdentifier(), user,
false, false);
throw new AssertionError("DotSecurityException expected");
}

/**
* Method to Test: {@link ContainerAPIImpl#findContainer(String, User, boolean, boolean)}
* When: the FileContainer exists but the user not have permission in Default host
* Should: return it
*/
@Test
public void whenUserNotHavePermissionInDefaultHost() throws DotDataException, DotSecurityException {
final Host host = new SiteDataGen().nextPersisted();
final Role role = new RoleDataGen().nextPersisted();
final User user = new UserDataGen()
.roles(APILocator.getRoleAPI().loadBackEndUserRole(), role)
.nextPersisted();

addPermission(role, host, PermissionLevel.READ.getType());

final FileAssetContainer fileAssetContainer = new ContainerAsFileDataGen()
.host(host)
.nextPersisted();

final Optional<Container> containerFromDatabase =
APILocator.getContainerAPI().findContainer(fileAssetContainer.getIdentifier(), APILocator.systemUser(),
false, false);

final String fullPathUsingAnotherHost = FileAssetContainerUtil.getInstance().getFullPath(
host,
((FileAssetContainer) containerFromDatabase.get()).getPath());

final Optional<Container> containerFromDatabaseWithAbsolutePath =
APILocator.getContainerAPI().findContainer(fullPathUsingAnotherHost,
user, true, false);

assertFalse(containerFromDatabaseWithAbsolutePath.isPresent());
}

/**
* Method to Test: {@link ContainerAPIImpl#findContainer(String, User, boolean, boolean)}
* When: the FileContainer exists but the user not have permission in Current host
* Should: return it
*/
@Test
public void whenUserNotHavePermissionInCurrentHost() throws DotDataException, DotSecurityException {
final Host host = new SiteDataGen().nextPersisted();
final Host currentHost = new SiteDataGen().nextPersisted();

final HttpServletRequest request = mock(HttpServletRequest.class);
HttpServletRequestThreadLocal.INSTANCE.setRequest(request);
when(request.getParameter("host_id")).thenReturn(currentHost.getIdentifier());

final Role role = new RoleDataGen().nextPersisted();
final User backEndUser = new UserDataGen()
.roles(APILocator.getRoleAPI().loadBackEndUserRole(), role)
.nextPersisted();

addPermission(role, host, PermissionLevel.READ.getType());

final FileAssetContainer fileAssetContainer = new ContainerAsFileDataGen()
.host(host)
.nextPersisted();

final Optional<Container> containerFromDatabase =
APILocator.getContainerAPI().findContainer(fileAssetContainer.getIdentifier(), APILocator.systemUser(),
false, false);

final String fullPathUsingAnotherHost = FileAssetContainerUtil.getInstance().getFullPath(
host,
((FileAssetContainer) containerFromDatabase.get()).getPath());

final Optional<Container> containerFromDatabaseWithAbsolutePath =
APILocator.getContainerAPI().findContainer(fullPathUsingAnotherHost,
backEndUser, true, false);

assertFalse(containerFromDatabaseWithAbsolutePath.isPresent());
}

@NotNull
private void addPermission(
final Role role,
final Permissionable permissionable,
final int permissionPublish) {

final Permission publishPermission = getPermission(role, permissionable, permissionPublish);

try {
APILocator.getPermissionAPI().save(publishPermission, permissionable, APILocator.systemUser(), false);
} catch (DotDataException | DotSecurityException e){
throw new RuntimeException(e);
}
}

@NotNull
private Permission getPermission(Role role, Permissionable permissionable, int permissionPublish) {
final Permission publishPermission = new Permission();
publishPermission.setInode(permissionable.getPermissionId());
publishPermission.setRoleId(role.getId());
publishPermission.setPermission(permissionPublish);
return publishPermission;
}
}
@@ -0,0 +1,90 @@
package com.dotmarketing.portlets.containers.business;

import static org.junit.Assert.*;
import java.util.List;
import org.junit.BeforeClass;
import org.junit.Test;
import com.dotcms.datagen.ContainerDataGen;
import com.dotcms.datagen.SiteDataGen;
import com.dotcms.util.IntegrationTestInitService;
import com.dotmarketing.beans.Host;
import com.dotmarketing.business.APILocator;
import com.dotmarketing.business.FactoryLocator;
import com.dotmarketing.business.UserAPI;
import com.dotmarketing.common.util.SQLUtilTest;
import com.dotmarketing.exception.DotDataException;
import com.dotmarketing.exception.DotSecurityException;
import com.dotmarketing.portlets.containers.model.Container;
import com.dotmarketing.portlets.contentlet.business.HostAPI;
import com.dotmarketing.portlets.templates.business.TemplateAPI;
import com.dotmarketing.util.UUIDGenerator;
import com.google.common.collect.ImmutableMap;
import com.liferay.portal.model.User;

public class ContainerFactoryImplTest {


private static HostAPI hostAPI;
private static TemplateAPI templateAPI;
private static User user;
private static UserAPI userAPI;
private static Host host;

@BeforeClass
public static void prepare() throws Exception {

// Setting web app environment
IntegrationTestInitService.getInstance().init();


hostAPI = APILocator.getHostAPI();
templateAPI = APILocator.getTemplateAPI();
userAPI = APILocator.getUserAPI();
user = userAPI.getSystemUser();

host = hostAPI.findDefaultHost(user, false);
}



/**
* this tests whether we properly escaping the orderby clause in the
* find containers method
* @throws DotDataException
* @throws DotSecurityException
*/

@Test
public void test_container_find_by_query_no_SQL_injection_in_orderby() throws DotDataException, DotSecurityException {

final Host daHost = new SiteDataGen().nextPersisted();


Container container = new ContainerDataGen()
.site(daHost)
.title(UUIDGenerator.generateUuid() + SQLUtilTest.MALICIOUS_SQL_CONDITION)
.nextPersisted();

assertNotNull(container);
assertNotNull(container.getInode());
Container containerFromDB = APILocator.getContainerAPI().find(container.getInode(), APILocator.systemUser(), false);


// get normally
List<Container> containers = FactoryLocator.getContainerFactory().findContainers(user, false, ImmutableMap.of("title",container.getTitle() ), daHost.getIdentifier(), null, null, null, 0, 100, "mod_date");

assert ! containers.isEmpty() && containers.contains(containerFromDB);


// get with a malicious SQL order by
containers = FactoryLocator.getContainerFactory().findContainers(user, false, ImmutableMap.of("title",container.getTitle() ), daHost.getIdentifier(), null, null, null, 0, 100, SQLUtilTest.MALICIOUS_SQL_ORDER_BY);

assert ! containers.isEmpty() && containers.contains(containerFromDB);



}



}
@@ -0,0 +1,308 @@
package com.dotmarketing.portlets.contentlet.business.web;

import com.dotcms.contenttype.model.type.ContentType;
import com.dotcms.contenttype.transform.JsonTransformer;
import com.dotcms.datagen.*;
import com.dotcms.rendering.velocity.viewtools.DotTemplateTool;
import com.dotcms.repackage.org.directwebremoting.WebContextFactory;
import com.dotcms.util.IntegrationTestInitService;
import com.dotmarketing.beans.Host;
import com.dotmarketing.business.APILocator;

import com.dotmarketing.exception.DotDataException;
import com.dotmarketing.exception.DotSecurityException;
import com.dotmarketing.portlets.containers.business.FileAssetContainerUtil;
import com.dotmarketing.portlets.containers.model.Container;
import com.dotmarketing.portlets.containers.model.FileAssetContainer;
import com.dotmarketing.portlets.contentlet.model.Contentlet;

import com.dotmarketing.portlets.templates.design.bean.TemplateLayout;
import com.dotmarketing.portlets.templates.model.Template;
import com.dotmarketing.quartz.QuartzUtils;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.liferay.portal.model.User;
import com.liferay.util.servlet.SessionMessages;
import org.junit.BeforeClass;
import org.junit.Test;

import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;

import static org.junit.Assert.*;
import static org.mockito.Mockito.mock;
import static org.powermock.api.mockito.PowerMockito.when;

import com.dotcms.repackage.org.directwebremoting.WebContext;
import org.quartz.*;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

public class ContentletWebAPIImplIntegrationTest {

final String body =
"<html>" +
"<head>" +
"#dotParse('//%1$s/application/themes/landing-page/html_head.vtl')" +
"<link rel=\"stylesheet\" type=\"text/css\" href=\"/html/css/template/reset-fonts-grids.css\" />" +
"</head>" +
"<body>" +
"<div id=\"resp-template\" name=\"globalContainer\">" +
"<div id=\"hd-template\">" +
"#dotParse('//%1$s/application/themes/landing-page/header.vtl')" +
"</div>" +
"<div id=\"bd-template\">" +
"<div id=\"yui-main-template\">" +
"<div class=\"yui-b-template\" id=\"splitBody0\">" +
"#parseContainer('//%1$s/application/containers/default/','1')" +
"</div>" +
"</div>" +
"</div>" +
"</body>" +
"</html>";

@BeforeClass
public static void prepare() throws Exception {
//Setting web app environment
IntegrationTestInitService.getInstance().init();

if (!QuartzUtils.getSequentialScheduler().isStarted()) {
QuartzUtils.getSequentialScheduler().start();
}
}

/**
* Method to Test: {@link ContentletWebAPIImpl#saveContent(Map, boolean, boolean, User)}
* When: Change a Host' name and exists any template using this
* Should: Update the container path into the template, just in the template working and live version
*
* */
@Test
public void whenHostNameChangeShouldUpdateContainerPath() throws Exception {

final User user = APILocator.systemUser();
init();

final Host host = new SiteDataGen().nextPersisted();

Container container = createContainer(user, host);
Template template = createTemplate(host, container);
final String oldVersionInode = template.getInode();

template.setFooter("Footer");
template = TemplateDataGen.save(template);
TemplateDataGen.publish(template);
final String liveVersionInode = template.getInode();

template.setFooter("Footer_2");
template = TemplateDataGen.save(template);
final String workingVersionInode = template.getInode();

final ContentletWebAPIImpl contentletWebAPI = new ContentletWebAPIImpl();
final Map<String, Object> hostMap = host.getMap();
final String newHostname = "newHostName_" + System.currentTimeMillis();
hostMap.put("text1", newHostname);
hostMap.put("contentletInode", hostMap.get("inode"));

contentletWebAPI.saveContent(hostMap, false, false, user);
waitUntilJobIsFinish();

final Host hostFromDataBse = APILocator.getHostAPI().find(host.getIdentifier(), user, false);
assertEquals(newHostname, hostFromDataBse.getHostname());

checkTemplate(oldVersionInode, user, host.getHostname(), newHostname);
checkTemplate(liveVersionInode, user, newHostname, host.getHostname());
checkTemplate(workingVersionInode, user, newHostname, host.getHostname());

final TemplateLayout templateLayout = DotTemplateTool.themeLayout(template.getInode());
final String drawedBodyJson = JsonTransformer.mapper.writeValueAsString(templateLayout);
assertFalse(drawedBodyJson.contains(host.getHostname()));
assertTrue(drawedBodyJson.contains(newHostname));

final Template workingVersion = (Template) APILocator.getVersionableAPI().findWorkingVersion(
template.getIdentifier(), APILocator.systemUser(), false);

assertFalse(workingVersion.getDrawedBody().contains(host.getHostname()));
assertTrue(workingVersion.getDrawedBody().contains(newHostname));

final FileAssetContainer containerFromDataBase = (FileAssetContainer) APILocator.getContainerAPI().getWorkingContainerByFolderPath(
FileAssetContainerUtil.getInstance().getFullPath(hostFromDataBse, ((FileAssetContainer) container).getPath()),
user, false,
null);

assertEquals(newHostname, containerFromDataBase.getHost().getName());
}

private void checkTemplate(
final String templateInode,
final User user,
final String hostInTemplate,
final String hostNotInTemplate) throws DotSecurityException, DotDataException, JsonProcessingException {

final Template templateFromDatabase = APILocator.getTemplateAPI().find(templateInode, user, false);

assertFalse(templateFromDatabase.getDrawedBody().contains(hostNotInTemplate));
assertFalse(templateFromDatabase.getBody().contains(hostNotInTemplate));

assertTrue(templateFromDatabase.getDrawedBody().contains(hostInTemplate));
assertTrue(templateFromDatabase.getBody().contains(hostInTemplate));
}


/**
* Method to Test: {@link ContentletWebAPIImpl#saveContent(Map, boolean, boolean, User)}
* When: Change a Host' name and exists any html template layout using this
* Should: Update the container path into the template
*
* */
@Test
public void whenHostNameChangeWithLegacyTemplateLayout() throws Exception {

final String drawedBodyHTML = "" +
"<div style=\"display: none;\" title=\"container_854ad819-8381-434d-a70f-6e2330985ea4\" id=\"splitBody0_div_854ad819-8381-434d-a70f-6e2330985ea4_1572981893151\">" +
"#parseContainer('//%s/','1572981893151')" +
"</div>";

final User user = APILocator.systemUser();
init();

final Host host = new SiteDataGen().nextPersisted();
final Contentlet theme = new ThemeDataGen().nextPersisted();

final Template template = new TemplateDataGen()
.drawedBody(String.format(drawedBodyHTML, host.getHostname()))
.body(String.format(body, host.getHostname()))
.host(host)
.theme(theme)
.nextPersisted();

final ContentletWebAPIImpl contentletWebAPI = new ContentletWebAPIImpl();
final Map<String, Object> hostMap = host.getMap();
final String newHostname = "newHostName_" + System.currentTimeMillis();
hostMap.put("text1", newHostname);
hostMap.put("contentletInode", hostMap.get("inode"));

contentletWebAPI.saveContent(hostMap, false, false, user);
waitUntilJobIsFinish();

final Host hostFromDataBse = APILocator.getHostAPI().find(host.getIdentifier(), user, false);
assertEquals(newHostname, hostFromDataBse.getHostname());

final Template templateFromDatabase = APILocator.getTemplateAPI().find(template.getInode(), user, false);

assertFalse(templateFromDatabase.getDrawedBody().contains(host.getHostname()));
assertFalse(templateFromDatabase.getBody().contains(host.getHostname()));

assertTrue(templateFromDatabase.getDrawedBody().contains(newHostname));
assertTrue(templateFromDatabase.getBody().contains(newHostname));

final Template workingVersion = (Template) APILocator.getVersionableAPI().findWorkingVersion(
template.getIdentifier(), APILocator.systemUser(), false);

assertFalse(workingVersion.getDrawedBody().contains(host.getHostname()));
assertTrue(workingVersion.getDrawedBody().contains(newHostname));
}

private Template createTemplate(
final Host host,
final Container container) {

final TemplateLayout templateLayout = TemplateLayoutDataGen.get()
.withContainer(container)
.next();

final Contentlet theme = new ThemeDataGen().nextPersisted();
return new TemplateDataGen()
.drawedBody(templateLayout)
.body(String.format(body, host.getHostname()))
.host(host)
.theme(theme)
.nextPersisted();
}

private Container createContainer(final User user, final Host host) throws DotSecurityException, DotDataException {
final ContentType contentType = new ContentTypeDataGen()
.host(host)
.nextPersisted();

Container container = new ContainerAsFileDataGen()
.contentType(contentType, "")
.host(host)
.nextPersisted();

container = APILocator.getContainerAPI().find(container.getInode(), user, false);
return container;
}

private void waitUntilJobIsFinish() throws InterruptedException, SchedulerException {
Thread.sleep(500);

while(true){
final String[] jobGroupNames = QuartzUtils.getSequentialScheduler().getJobGroupNames();

if ((jobGroupNames.length > 0 &&
Arrays.asList(jobGroupNames).contains("update_containers_paths_job")) &&
!QuartzUtils.getSequentialScheduler().getCurrentlyExecutingJobs().isEmpty()){
Thread.sleep(500);
} else {
break;
}
}
}

/**
* Method to Test: {@link ContentletWebAPIImpl#saveContent(Map, boolean, boolean, User)}
* When: Not Change a Host' name and exists any template using this
* Should: Not change anything into the template layout
*
* */
@Test
public void whenHostNameNotChange() throws Exception {

final User user = APILocator.systemUser();
init();

final Host host = new SiteDataGen().nextPersisted();

final Container container = createContainer(user, host);
final Template template = createTemplate(host, container);

final ContentletWebAPIImpl contentletWebAPI = new ContentletWebAPIImpl();
final Map<String, Object> hostMap = host.getMap();
hostMap.put("contentletInode", hostMap.get("inode"));

contentletWebAPI.saveContent(hostMap, false, false, user);

final String[] jobGroupNames = QuartzUtils.getSequentialScheduler().getJobGroupNames();

assertFalse(jobGroupNames.length > 0 &&
Arrays.asList(jobGroupNames).contains("update_containers_paths_job"));

final Template templateFromDatabase = APILocator.getTemplateAPI().find(template.getInode(), user, false);

assertTrue(templateFromDatabase.getDrawedBody().contains(host.getHostname()));
assertTrue(templateFromDatabase.getBody().contains(host.getHostname()));
}

private void init() {
final HttpSession session = mock(HttpSession.class);
when(session.getAttribute(SessionMessages.KEY)).thenReturn(new LinkedHashMap());

final HttpServletRequest httpServletRequest = mock(HttpServletRequest.class);
when(httpServletRequest.getSession()).thenReturn(session);

final WebContext webContext = mock(WebContext.class);
when(webContext.getHttpServletRequest()).thenReturn(httpServletRequest);

final WebContextFactory.WebContextBuilder webContextBuilderMock =
mock(WebContextFactory.WebContextBuilder.class);
when(webContextBuilderMock.get()).thenReturn(webContext);

final com.dotcms.repackage.org.directwebremoting.Container containerMock =
mock(com.dotcms.repackage.org.directwebremoting.Container.class);
when(containerMock.getBean(WebContextFactory.WebContextBuilder.class)).thenReturn(webContextBuilderMock);

WebContextFactory.attach(containerMock);
}
}
@@ -0,0 +1,98 @@
package com.dotmarketing.portlets.htmlpageasset.business.render.page;

import com.dotcms.contenttype.model.field.Field;
import com.dotcms.contenttype.model.field.KeyValueField;
import com.dotcms.contenttype.model.field.TextField;
import com.dotcms.contenttype.model.type.BaseContentType;
import com.dotcms.contenttype.model.type.ContentType;
import com.dotcms.util.IntegrationTestInitService;
import com.dotmarketing.portlets.contentlet.model.Contentlet;
import org.junit.BeforeClass;
import org.junit.Test;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

/**
* Test for the {@link PageViewSerializerTest}
* @author jsanca
*/
public class PageViewSerializerTest {

@BeforeClass
public static void prepare () throws Exception {

//Setting web app environment
IntegrationTestInitService.getInstance().init();
}

/**
* Method to test: createObjectMapKeyValue
* Given Scenario: key value but not key field
* ExpectedResult: return the key value as string
*/
@Test
public void createObjectMapUrlContent_convert_url_map_no_keyvalue() throws Exception {

final PageViewSerializer pageViewSerializer = new PageViewSerializer();
final Map<String, Object> pageViewMap = new HashMap<>();
final Contentlet baseContent = new Contentlet();
final Contentlet urlContent = mock(Contentlet.class);
final String keyValue = "{\"key1\":\"value1\", \"key2\":\"value2\"}";
baseContent.getMap().put("keyValue", keyValue);
final ContentType contentType = mock(ContentType.class);
when(contentType.baseType()).thenReturn(BaseContentType.CONTENT);
when(urlContent.getTitleImage()).thenReturn(Optional.empty());
when(urlContent.getContentType()).thenReturn(contentType);
when(urlContent.getBaseType()).thenReturn(Optional.of(BaseContentType.CONTENT));
when(urlContent.get("keyValue")).thenReturn(keyValue);
when(contentType.fields(KeyValueField.class)).thenReturn(new ArrayList<>());
pageViewSerializer.createObjectMapUrlContent(urlContent, pageViewMap);

assertTrue(pageViewMap.containsKey("urlContentMap"));
assertFalse(Map.class.cast(pageViewMap.get("urlContentMap")).containsKey("keyValue"));
}

/**
* Method to test: createObjectMapKeyValue
* Given Scenario: key value given
* ExpectedResult: return the key value as map
*/
@Test
public void createObjectMapUrlContent_convert_url_map_keyvalue() throws Exception {

final PageViewSerializer pageViewSerializer = new PageViewSerializer();
final Map<String, Object> pageViewMap = new HashMap<>();
final Contentlet baseContent = new Contentlet();
final Contentlet urlContent = mock(Contentlet.class);
final String keyValue = "{\"key1\":\"value1\", \"key2\":\"value2\"}";
baseContent.getMap().put("keyValue", keyValue);
final ContentType contentType = mock(ContentType.class);
final KeyValueField keyValueField = mock(KeyValueField.class);
when(urlContent.getTitleImage()).thenReturn(Optional.empty());
when(contentType.baseType()).thenReturn(BaseContentType.CONTENT);
when(urlContent.getContentType()).thenReturn(contentType);
when(urlContent.getBaseType()).thenReturn(Optional.of(BaseContentType.CONTENT));
when(urlContent.get("keyValue")).thenReturn(keyValue);
when(contentType.fields(KeyValueField.class)).thenReturn(Arrays.asList(keyValueField));
when(keyValueField.variable()).thenReturn("keyValue");
when(urlContent.getKeyValueProperty("keyValue")).thenReturn(baseContent.getKeyValueProperty("keyValue"));
pageViewSerializer.createObjectMapUrlContent(urlContent, pageViewMap);

assertFalse(pageViewMap.isEmpty());
assertEquals(1, pageViewMap.size());
assertTrue(pageViewMap.containsKey("urlContentMap"));
assertTrue((Map.class.cast(pageViewMap.get("urlContentMap")).containsKey("keyValue")));
assertEquals("value1", Map.class.cast(Map.class.cast(pageViewMap.get("urlContentMap")).get("keyValue")).get("key1"));
assertEquals("value2", Map.class.cast(Map.class.cast(pageViewMap.get("urlContentMap")).get("keyValue")).get("key2"));
}
}
@@ -0,0 +1,114 @@
package com.dotmarketing.portlets.templates.business;

import static org.junit.Assert.assertNull;
import java.util.List;
import org.junit.BeforeClass;
import org.junit.Test;
import com.dotcms.IntegrationTestBase;
import com.dotcms.rest.api.v1.template.TemplateHelper;
import com.dotcms.util.IntegrationTestInitService;
import com.dotcms.util.PaginationUtil;
import com.dotcms.util.pagination.OrderDirection;
import com.dotcms.util.pagination.TemplatePaginator;
import com.dotcms.util.pagination.TemplateView;
import com.dotmarketing.beans.Host;
import com.dotmarketing.business.APILocator;
import com.dotmarketing.business.UserAPI;
import com.dotmarketing.business.VersionableAPI;
import com.dotmarketing.common.util.SQLUtilTest;
import com.dotmarketing.portlets.containers.business.ContainerAPI;
import com.dotmarketing.portlets.contentlet.business.HostAPI;
import com.dotmarketing.portlets.templates.model.Template;
import com.dotmarketing.util.UUIDGenerator;
import com.google.common.collect.ImmutableMap;
import com.liferay.portal.model.User;

public class TemplateFactoryImplTest extends IntegrationTestBase {

private static HostAPI hostAPI;
private static TemplateAPI templateAPI;
private static User user;
private static UserAPI userAPI;
private static Host host;

@BeforeClass
public static void prepare() throws Exception {

// Setting web app environment
IntegrationTestInitService.getInstance().init();


hostAPI = APILocator.getHostAPI();
templateAPI = APILocator.getTemplateAPI();
userAPI = APILocator.getUserAPI();
user = userAPI.getSystemUser();

host = hostAPI.findDefaultHost(user, false);
}


@Test
public void testFindWorkingTemplateByName_SQL_INJECTIION() throws Exception {
Template template = new Template();
final TemplateFactory templateFactory = new TemplateFactoryImpl();
template.setTitle(UUIDGenerator.generateUuid() + SQLUtilTest.MALICIOUS_SQL_CONDITION );
template.setBody("<html><body> I'm mostly empty </body></html>");
template.setOwner("template's owner");

template = templateAPI.saveTemplate(template, host, user, false);

final Template result = templateFactory.findWorkingTemplateByName(template.getTitle(), host);


assert(result.getIdentifier().equals(template.getIdentifier()));

}


@Test
public void test_find_templates_by_name_uses_parameterized_queries() throws Exception {
Template template = null;
Template anotherTemplate = null;

PaginationUtil utils = new PaginationUtil(new TemplatePaginator(APILocator.getTemplateAPI(),
new TemplateHelper(APILocator.getPermissionAPI(), APILocator.getRoleAPI())));


template = new Template();
final String uniqueString = UUIDGenerator.generateUuid() + SQLUtilTest.MALICIOUS_SQL_CONDITION;
final String uniqueTitle = uniqueString + " This one will not show up";
template.setTitle(uniqueTitle);
template.setBody("<html><body> Empty Template </body></html>");
template = templateAPI.saveTemplate(template, host, user, false);

anotherTemplate = new Template();
anotherTemplate.setTitle("I am not invited");
anotherTemplate.setBody("<html><body> Empty Template </body></html>");
anotherTemplate = templateAPI.saveTemplate(anotherTemplate, host, user, false);


final TemplateFactory templateFactory = new TemplateFactoryImpl();

List<Template> templates = templateFactory.findTemplates(user, false, ImmutableMap.of("title", uniqueString), host.getIdentifier(), null, null, null, 0, 10, null);

assert templates.size() ==1;

template = new Template();
template.setTitle(uniqueTitle);
template.setBody("<html><body> Empty Template </body></html>");
template = templateAPI.saveTemplate(template, host, user, false);

anotherTemplate = new Template();
anotherTemplate.setTitle("I am not invited");
anotherTemplate.setBody("<html><body> Empty Template </body></html>");
anotherTemplate = templateAPI.saveTemplate(anotherTemplate, host, user, false);


templates = templateFactory.findTemplates(user, false, ImmutableMap.of("title", uniqueString), host.getIdentifier(), null, null, null, 0, 10, SQLUtilTest.MALICIOUS_SQL_ORDER_BY);

assert templates.size() ==2;

}


}
@@ -0,0 +1,127 @@
package com.dotmarketing.quartz.job;

import com.dotcms.IntegrationTestBase;
import com.dotcms.integritycheckers.IntegrityUtil;
import com.dotcms.publisher.endpoint.bean.PublishingEndPoint;
import com.dotcms.publisher.endpoint.bean.impl.PushPublishingEndPoint;
import com.dotcms.rest.IntegrityResource;
import com.dotcms.util.IntegrationTestInitService;
import com.dotmarketing.common.db.DotConnect;
import com.dotmarketing.exception.DotDataException;
import com.rainerhahnekamp.sneakythrow.Sneaky;
import com.tngtech.java.junit.dataprovider.DataProviderRunner;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.JobExecutionContext;
import org.quartz.SchedulerException;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
import java.util.UUID;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

/**
* @author victor
*/
@RunWith(DataProviderRunner.class)
public class IntegrityDataGenerationJobTest extends IntegrationTestBase {

private IntegrityDataGenerationJob integrityDataGenerationJob;
private PublishingEndPoint endpoint;
private String requestId;

@BeforeClass
public static void beforeClass() throws Exception {
IntegrationTestInitService.getInstance().init();
}

@Before
public void before() throws DotDataException {
integrityDataGenerationJob = new IntegrityDataGenerationJob();
endpoint = new PushPublishingEndPoint();
endpoint.setId(UUID.randomUUID().toString());
requestId = UUID.randomUUID().toString();

DotConnect dotConnect = new DotConnect();
dotConnect
.setSQL("delete from QRTZ_EXCL_SIMPLE_TRIGGERS where TRIGGER_NAME = ? and TRIGGER_GROUP = ?")
.addParam(IntegrityDataGenerationJob.TRIGGER_NAME)
.addParam(IntegrityDataGenerationJob.TRIGGER_GROUP)
.loadObjectResults();
dotConnect
.setSQL("delete from QRTZ_EXCL_TRIGGERS where TRIGGER_NAME = ? and TRIGGER_GROUP = ?")
.addParam(IntegrityDataGenerationJob.TRIGGER_NAME)
.addParam(IntegrityDataGenerationJob.TRIGGER_GROUP)
.loadObjectResults();
dotConnect
.setSQL("delete from QRTZ_EXCL_JOB_DETAILS where JOB_NAME = ? and JOB_GROUP = ?")
.addParam(IntegrityDataGenerationJob.JOB_NAME)
.addParam(IntegrityDataGenerationJob.JOB_GROUP)
.loadObjectResults();
}

/**
* Method to test: IntegrityDataGenerationJob.run() which generates integrity data generation.
* Given Scenario: Given an integrity data generation is triggered for a provided endpoint and request id.
* ExpectedResult: to have Quartz job tables populated as well as the integrity data file created with its status file.
*
*/
@Test
public void test_generateIntegrationData() throws Exception {
runJob(Sneaky.sneaked(() -> integrityDataGenerationJob.run(getJobContext())));

assertTrue(new File(
IntegrityUtil.getIntegrityDataFilePath(
endpoint.getId(),
IntegrityUtil.INTEGRITY_DATA_TO_CHECK_ZIP_FILENAME))
.exists());
assertTrue(new File(
IntegrityUtil.getIntegrityDataFilePath(endpoint.getId(), IntegrityUtil.INTEGRITY_DATA_STATUS_FILENAME))
.exists());
assertStatus(IntegrityResource.ProcessStatus.FINISHED.toString());
}

private void runJob(Runnable runnable) {
runnable.run();
}

private JobExecutionContext getJobContext() throws SchedulerException {
return new JobExecutionContext(
IntegrityDataGenerationJob.getJobScheduler(),
new TestJobExecutor.TriggerFiredBundleTest(getJobDetail(endpoint, requestId)),
integrityDataGenerationJob);
}

private JobDetail getJobDetail(PublishingEndPoint endpoint, String requestId) {
JobDataMap jobDataMap = new JobDataMap();
jobDataMap.put(IntegrityUtil.REQUESTER_KEY, endpoint.getId());
jobDataMap.put(IntegrityUtil.INTEGRITY_DATA_REQUEST_ID, requestId);

final JobDetail jobDetail = new JobDetail(
IntegrityDataGenerationJob.JOB_NAME,
IntegrityDataGenerationJob.JOB_GROUP, IntegrityDataGenerationJob.class);
jobDetail.setJobDataMap(jobDataMap);
jobDetail.setDurability(false);
jobDetail.setVolatility(false);
jobDetail.setRequestsRecovery(true);

return jobDetail;
}

private void assertStatus(String status) throws IOException {
Properties properties = new Properties();
properties.load(new FileInputStream(IntegrityUtil.getIntegrityDataFilePath(
endpoint.getId(),
IntegrityUtil.INTEGRITY_DATA_STATUS_FILENAME)));
assertEquals(status, properties.getProperty(IntegrityUtil.INTEGRITY_DATA_STATUS));
}

}
@@ -44,7 +44,7 @@ public JobDetailTest(Map<String, Object> jobMap) {
}
}

private static class TriggerFiredBundleTest extends TriggerFiredBundle {
public static class TriggerFiredBundleTest extends TriggerFiredBundle {
TriggerFiredBundleTest(JobDetail jobDetail){
super(jobDetail , new TriggerTest(), null, false,null, null, null, null);
}
@@ -25,17 +25,22 @@
import com.dotmarketing.exception.DotDataException;
import com.dotmarketing.exception.DotSecurityException;
import com.dotmarketing.portlets.contentlet.model.Contentlet;
import com.dotmarketing.portlets.contentlet.model.IndexPolicy;
import com.dotmarketing.portlets.folders.model.Folder;
import com.dotmarketing.util.FileUtil;
import com.dotmarketing.util.UtilMethods;
import com.liferay.portal.model.User;
import com.liferay.portal.util.WebKeys;
import com.tngtech.java.junit.dataprovider.DataProvider;
import com.tngtech.java.junit.dataprovider.DataProviderRunner;
import com.tngtech.java.junit.dataprovider.UseDataProvider;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -154,6 +159,45 @@ public void requestBinaryFile(

}

@Test
public void requestGifFile()
throws DotDataException, DotSecurityException, ServletException, IOException {

File gifFile = new File("src/integration-test/resources/images/issue19338.gif");

final Contentlet fileContentlet = new FileAssetDataGen(gifFile).host(host)
.setPolicy(IndexPolicy.WAIT_FOR).nextPersisted();

final String fileURI = "/contentAsset/image/"+fileContentlet.getInode()+"/fileAsset/byInode/true/quality_q/30/resize_w/200";
final HttpServletRequest request = mockServletRequest(fileURI);
request.setAttribute(WebKeys.USER, APILocator.systemUser());

TmpBinaryFile tmpTargetFile = new TmpBinaryFile(false);
final HttpServletResponse response = mockServletResponse(tmpTargetFile);

// Send servlet request
sendRequest(request, response);
final byte[] responseContent = Files.readAllBytes(tmpTargetFile.getPath());
final String expectedContent = "RIFF�\u0003\u0000\u0000WEBPVP8 �\u0003\u0000\u0000�\u001C\u0000�\u0001*�\u0000c\u0000?\u0011x�R�'?���\n"
+ "S�\"\tin�\n"
+ "k\u001F\u001D�\n"
+ "���Wh��/\u0003�k�ߏ]����\u0013q��?�~a���G{��?�~�������A��wv*\u0018��\u0013�p�Z����C$\u0010[\u0000\"���w\u000E�\u000BF���\u001A'����u�E'��\u001C\f����&��J�,�����D\u0011\u0013�n�th�\\\u0015�4\u0015\u0000�\u000Bm2\u0006���\f��\u001D�\u001E��|�^H/�{�2&�6\u0004�H�S��ƍ6����Q\u0006��)���$\u0003���y��3\u0005\"�� /Un�b}�̘\u0000\u0000��q��x�?ջt�l;\u0014N2!�\u000F��6eD���^K�g���KB\u0005Da����u(�1�Gr��C�c�\u001C����?��D�C%O��Q\u00192��́�#�d\u00014�����\u000Bd\u001Aӷ�\u0010V^\u0005h�6�d�r����ںo1\u007F��y\tJ1�h˾�o5\u007F)O�2��\u0016�ꀊ�\u0010\f\u0011\u0010�\u001D3Y�S(i�b4��K�S��Cxs+�zF\u0004{��\u0010\u0006���\u001F�=#\u0002:��\u000B�L\u001D~���IE�q����f���}���[�\u0017lP\u0012\u001D�*�\u0002|�a_C��\u0005��!�]�\u0019V{�<WvY�\"3����j�\u001B-)IңZ\u0010\b\u0012F��'\u0001���R�A�|���<�?�V w�ҹ�|fw���\u0018�B\u0010r�I�P�e����C\u0013�[�#�Y�l\u000E��B�\u0012(�17\",b\"A�-�\u0012�v���\bϢ�r��?05��\u0010=�Bh\u0010�dS�Ы�ҩ+ɀ��at���>4�2��Fp\u0005|��_�\u0007�\u000F���\b�I\u0017\u0015��YI���{ȡMK�H�`�x͜ǝFj\u001E�8�����\n"
+ "��͢'���q\u0003��\u0002�%k��86����\u0010��$��g�V��\u007F�xi�\\�\t��Ÿ�0�����-��\u001C�C\u000F!A;�\u0017��\u0000i��BϦÒ�\b\t�a����,%u�)���vn'�0|�v4��O�//\u0006��Զ��J��#?L1��\u001F�['�q\u001B��\u001C\u001A-�ȡxwY�\u0016 �L=4M�j[�\u0016tU΅j|�a\u001F\u0015��_-�`_\u0005��kv���\u0007Q&�?ӻ(�\u001C\n"
+ "e�\n"
+ "\u0006�@\tĥ5���mx1����/1��.<%��bߞ�p�E;\u0007S�l�<�\u0011�\u000F��\"\u0003��\u007FY�%�%\u0003�\"O��Ǡ�\u0096�\b'�\u000FU~x�S\u0014��b���S�9��v�Xmo�ڢ}��A��3��L~\u0000\u0000\u0000\u0000";

assertTrue(equalsIgnoreNewlineStyle(expectedContent, new String(responseContent, StandardCharsets.UTF_8)));

}

public static boolean equalsIgnoreNewlineStyle(String s1, String s2) {
return s1 != null && s2 != null && normalizeLineEnds(s1).equals(normalizeLineEnds(s2));
}

private static String normalizeLineEnds(String s) {
return s.replace("\r\n", "\n").replace('\r', '\n');
}

private Contentlet checkinFileAsset(final TmpBinaryFile tmpSourceFile, final Folder folder)
throws DotSecurityException, DotDataException {

@@ -6,6 +6,7 @@

import com.dotcms.util.IntegrationTestInitService;
import com.dotmarketing.common.db.DotConnect;
import com.dotmarketing.db.DbConnectionFactory;
import com.dotmarketing.exception.DotDataException;
import com.dotmarketing.util.Logger;
import java.sql.SQLException;
@@ -28,13 +29,24 @@ private void dropColumn(final DotConnect dotConnect) {
final String dropColumnSQL = "ALTER TABLE dot_cluster DROP COLUMN cluster_salt";
dotConnect.executeStatement(dropColumnSQL);
} catch (Exception e) {
Logger.info(Task05350AddDotSaltClusterColumnTest.class, () -> "Failed removing cluster_salt column. Maybe it didn't exist?");
Logger.error(Task05350AddDotSaltClusterColumnTest.class, "Failed removing cluster_salt column. Maybe it didn't exist?");
}
}

/**
* Given scenario: We have a database that already has the cluster_salt column. Then We drop it.
* Expected Results: The test should be able to drop the column and invoke the upgrade task to demonstrate it works
* @throws DotDataException
*/
@Test
public void test_upgradeTask_success() throws DotDataException {
public void Test_UpgradeTask_Success() throws DotDataException {
final DotConnect dotConnect = new DotConnect();
Logger.debug(this, "Prepping for testing `add` column.");
try {
DbConnectionFactory.getConnection().setAutoCommit(true);
} catch (SQLException e) {
throw new DotDataException(e.getMessage(), e);
}
dropColumn(dotConnect);
final Task05350AddDotSaltClusterColumn task = new Task05350AddDotSaltClusterColumn();
assertTrue(task.forceRun());//True because the column does not exists
@@ -50,3 +62,5 @@ public void test_upgradeTask_success() throws DotDataException {
}

}


@@ -0,0 +1,576 @@
package com.dotmarketing.startup.runonce;

import com.dotcms.datagen.ContainerDataGen;
import com.dotcms.datagen.SiteDataGen;
import com.dotcms.datagen.TemplateDataGen;
import com.dotcms.datagen.ThemeDataGen;
import com.dotcms.rendering.velocity.viewtools.DotTemplateTool;
import com.dotcms.repackage.com.google.common.base.Strings;
import com.dotcms.util.IntegrationTestInitService;
import com.dotmarketing.beans.Host;
import com.dotmarketing.business.APILocator;
import com.dotmarketing.business.CacheLocator;
import com.dotmarketing.common.db.DotConnect;
import com.dotmarketing.exception.DotDataException;
import com.dotmarketing.exception.DotSecurityException;
import com.dotmarketing.portlets.containers.model.Container;
import com.dotmarketing.portlets.contentlet.model.Contentlet;
import com.dotmarketing.portlets.templates.model.Template;
import org.junit.BeforeClass;
import org.junit.Test;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static com.dotmarketing.startup.runonce.Task05380ChangeContainerPathToAbsolute.GET_HOSTNAME_COLUMN;
import static com.dotmarketing.startup.runonce.Task05380ChangeContainerPathToAbsolute.GET_TEMPLATES_QUERY;
import static org.junit.Assert.*;

public class Task05380ChangeContainerPathToAbsoluteTest {
final String body =
"<html>" +
"<head>" +
"#dotParse('%1$s/application/themes/landing-page/html_head.vtl')" +
"<link rel=\"stylesheet\" type=\"text/css\" href=\"/html/css/template/reset-fonts-grids.css\" />" +
"</head>" +
"<body>" +
"<div id=\"resp-template\" name=\"globalContainer\">" +
"<div id=\"hd-template\">" +
"#dotParse('%1$s/application/themes/landing-page/header.vtl')" +
"</div>" +
"<div id=\"bd-template\">" +
"<div id=\"yui-main-template\">" +
"<div class=\"yui-b-template\" id=\"splitBody0\">" +
"#parseContainer('%1$s/application/containers/default/','1')" +
"</div>" +
"</div>" +
"</div>" +
"</body>" +
"</html>";

final String jsonDrawBody = "{" +
"\"title\": \"layout_test\"," +
"\"body\": {" +
"\"rows\": [" +
"{" +
"\"columns\": [" +
"{" +
"\"containers\": [" +
"{" +
"\"identifier\": \"%s/application/containers/default/\"," +
"\"uuid\": \"1\"" +
"}" +
"]," +
"\"widthPercent\": 83," +
"\"leftOffset\": 1," +
"\"width\": 10," +
"\"left\": 0" +
"}" +
"]" +
"}" +
"]" +
"}" +
"}";


final String jsonDrawBodyWithSideBar =
"{" +
"\"title\": \"layout_test\"," +
"\"body\": {" +
"\"rows\": [" +
"]" +
"}," +
"\"sidebar\": {" +
"\"containers\": [" +
"{" +
"\"identifier\": \"%s/application/containers/default/\"," +
"\"uuid\": \"2\"" +
"}" +
"]," +
"\"widthPercent\": 83," +
"\"leftOffset\": 1," +
"\"width\": 10," +
"\"left\": 0" +
"}" +
"}";
final String legacyHTMLLayout =
"<div id=\"resp-template\" name=\"globalContainer\">" +
"<div id=\"hd-template\"><h1>Header</h1></div>" +
"<div id=\"bd-template\">" +
"<div id=\"yui-main-template\">" +
"<div class=\"yui-b-template\" id=\"splitBody0\">" +
"<div class=\"addContainerSpan\">" +
"<a href=\"javascript: showAddContainerDialog('splitBody0');\" title=\"Add Container\">" +
"<span class=\"plusBlueIcon\"></span>Add Container" +
"</a>" +
"</div>" +
"<span class=\"titleContainerSpan\" id=\"splitBody0_span_69b3d24d-7e80-4be6-b04a-d352d16493ee_1562770692396\" title=\"container_69b3d24d-7e80-4be6-b04a-d352d16493ee\">" +
"<div class=\"removeDiv\">" +
"<a href=\"javascript: removeDrawedContainer('splitBody0','69b3d24d-7e80-4be6-b04a-d352d16493ee','1562770692396');\" title=\"Remove Container\">" +
"<span class=\"minusIcon\"></span>Remove Container" +
"</a>" +
"</div>" +
"<div class=\"clear\"></div>" +
"<h2>Container: Default</h2>" +
"<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>" +
"</span>" +
"<div style=\"display: none;\" title=\"container_69b3d24d-7e80-4be6-b04a-d352d16493ee\" id=\"splitBody0_div_69b3d24d-7e80-4be6-b04a-d352d16493ee_1562770692396\">" +
"#parseContainer('%s/application/containers/default/','1')\n" +
"</div>" +
"</div>" +
"</div>" +
"</div>" +
"<div id=\"ft-template\">" +
"<h1>Footer</h1>" +
"</div>" +
"</div>\n";

@BeforeClass
public static void prepare() throws Exception {
// Setting web app environment
IntegrationTestInitService.getInstance().init();
}

/**
* Method to Test: {@link Task05380ChangeContainerPathToAbsolute#executeUpgrade()}
* When: Exists A TemplateLayout with relative path container
* Should: Should turn it into a Absolute Path, using the template's host
*/
@Test
public void whenTemplateLayoutHasRelativePathShouldTurnIntoAAbsolutePath() throws IOException, DotDataException, DotSecurityException {

final String layout = String.format(jsonDrawBody, "");
final String testBody = String.format(body, "");
final Host host = new SiteDataGen().nextPersisted();

checkTemplateLayout(layout);
final Contentlet theme = new ThemeDataGen().nextPersisted();
final Template template = new TemplateDataGen()
.title("template_test_" + System.currentTimeMillis())
.theme(theme)
.drawedBody(layout)
.body(testBody)
.host(host)
.nextPersisted();


final Task05380ChangeContainerPathToAbsolute task05380ChangeContainerPathToAbsolute =
new Task05380ChangeContainerPathToAbsolute();

task05380ChangeContainerPathToAbsolute.executeUpgrade();

checkTemplateFromDataBase(host, template);
}

/**
* Method to Test: {@link Task05380ChangeContainerPathToAbsolute#executeUpgrade()}
* When: Exists A TemplateLayout with not filecontainer
* Should: Should not change
*/
@Test
public void whenTemplateLayoutHasNotFileCOntainer() throws IOException, DotDataException, DotSecurityException {
final Container container = new ContainerDataGen().nextPersisted();

final String layout = String.format(jsonDrawBody, container.getIdentifier())
.replaceAll("/application/containers/default/", "");

final String testBody = String.format(body, "");
final Host host = new SiteDataGen().nextPersisted();

checkTemplateLayout(layout);
final Contentlet theme = new ThemeDataGen().nextPersisted();
final Template template = new TemplateDataGen()
.title("template_test_" + System.currentTimeMillis())
.theme(theme)
.drawedBody(layout)
.body(testBody)
.host(host)
.nextPersisted();


final Task05380ChangeContainerPathToAbsolute task05380ChangeContainerPathToAbsolute =
new Task05380ChangeContainerPathToAbsolute();

task05380ChangeContainerPathToAbsolute.executeUpgrade();

final ArrayList templates = getTemplateFromDataBase(template);

final HashMap templateMap = (HashMap) templates.get(0);
assertEquals(1, templates.size());

final String drawedBody = templateMap.get("drawed_body").toString();
assertEquals(layout, drawedBody);
}

/**
* Method to Test: {@link Task05380ChangeContainerPathToAbsolute#executeUpgrade()}
* When: Exists A TemplateLayout with relative path container in the drawed_body fields and the body field is null
* Should: Should turn it into a Absolute Path, using the template's host
*/
@Test
public void whenTemplateLayoutHasRelativePathButBodyIsNullShouldTurnIntoAAbsolutePath() throws IOException, DotDataException {

final String layout = String.format(jsonDrawBody, "");
final Host host = new SiteDataGen().nextPersisted();

checkTemplateLayout(layout);
final Contentlet theme = new ThemeDataGen().nextPersisted();
final Template template = new TemplateDataGen()
.title("template_test_" + System.currentTimeMillis())
.theme(theme)
.drawedBody(layout)
.setBodyAsNull()
.host(host)
.nextPersisted();


final Task05380ChangeContainerPathToAbsolute task05380ChangeContainerPathToAbsolute =
new Task05380ChangeContainerPathToAbsolute();

task05380ChangeContainerPathToAbsolute.executeUpgrade();

checkTemplateFromDataBase(host, template);
}

private void checkTemplateFromDataBase(Host host, Template template) throws DotDataException {
final ArrayList templates = getTemplateFromDataBase(template);

final HashMap templateMap = (HashMap) templates.get(0);
assertEquals(1, templates.size());

final String drawedBody = templateMap.get("drawed_body").toString();
assertTrue(drawedBody.contains(String.format("//%s/application/containers/default/", host.getHostname())));

if (!Strings.isNullOrEmpty(templateMap.get("body").toString())) {
final String body = templateMap.get("body").toString();
assertTrue(body.contains(String.format("//%s/application/containers/default/", host.getHostname())));
}
}

private ArrayList getTemplateFromDataBase(Template template) throws DotDataException {
return new DotConnect()
.setSQL(String.format("select drawed_body, body from template where inode = '%s'", template.getInode()))
.loadResults();
}

/**
* Method to Test: {@link Task05380ChangeContainerPathToAbsolute#executeUpgrade()}
* When: Exists A TemplateLayout with absolute path container
* Should: Should does not anything
*/
@Test
public void whenTemplateLayoutHasAbsolutePathShouldKeepThePathWithoutChange() throws IOException, DotDataException, DotSecurityException {

final Host host = new SiteDataGen().nextPersisted();
final String layout = String.format(jsonDrawBody, "//" + host.getHostname());

checkTemplateLayout(layout);
final Contentlet theme = new ThemeDataGen().nextPersisted();
final Template template = new TemplateDataGen()
.title("template_test_" + System.currentTimeMillis())
.theme(theme)
.drawedBody(layout)
.body(String.format(body, "//" + host.getHostname()))
.host(host)
.nextPersisted();


final Task05380ChangeContainerPathToAbsolute task05380ChangeContainerPathToAbsolute =
new Task05380ChangeContainerPathToAbsolute();

task05380ChangeContainerPathToAbsolute.executeUpgrade();

checkTemplateFromDataBase(host, template);
}

/**
* Method to Test: {@link Task05380ChangeContainerPathToAbsolute#executeUpgrade()}
* When: Exists A legacy HTML TemplateLayout with relative path container
* Should: Should change the relative path by absolute path
*/
@Test
public void whenLegacyTemplateLayoutHasRelativePathShouldTurnIntoAbsolutePath() throws DotDataException {
final Host host = new SiteDataGen().nextPersisted();
final String layout = String.format(legacyHTMLLayout, "");

final Contentlet theme = new ThemeDataGen().nextPersisted();
final Template template = new TemplateDataGen()
.title("template_test_" + System.currentTimeMillis())
.theme(theme)
.drawedBody(layout)
.body(String.format(body, ""))
.host(host)
.nextPersisted();


final Task05380ChangeContainerPathToAbsolute task05380ChangeContainerPathToAbsolute =
new Task05380ChangeContainerPathToAbsolute();

task05380ChangeContainerPathToAbsolute.executeUpgrade();
CacheLocator.getTemplateCache().remove(template.getInode());

checkTemplateFromDataBase(host, template);
}

/**
* Method to Test: {@link Task05380ChangeContainerPathToAbsolute#executeUpgrade()}
* When: Exists A legacy HTML TemplateLayout with absolute path container
* Should: Should keep the absolute path
*/
@Test
public void whenLegacyTemplateLayoutHasAbsolutePath() throws DotDataException {
final Host host = new SiteDataGen().nextPersisted();
final String layout = String.format(legacyHTMLLayout, "//" + host.getHostname());

final Contentlet theme = new ThemeDataGen().nextPersisted();
final Template template = new TemplateDataGen()
.title("template_test_" + System.currentTimeMillis())
.theme(theme)
.drawedBody(layout)
.body(String.format(body, "//" + host.getHostname()))
.host(host)
.nextPersisted();


final Task05380ChangeContainerPathToAbsolute task05380ChangeContainerPathToAbsolute =
new Task05380ChangeContainerPathToAbsolute();

task05380ChangeContainerPathToAbsolute.executeUpgrade();
CacheLocator.getTemplateCache().remove(template.getInode());

checkTemplateFromDataBase(host, template);
}

/**
* Method to Test: {@link Task05380ChangeContainerPathToAbsolute#executeUpgrade()}
* When: Exists More than one template with relative path
* Should: Should change the relative path by absolute path
*/
@Test
public void whenHaveMoreThanOneTemplate() throws IOException, DotDataException {
final String layout = String.format(jsonDrawBody, "");
final String testBody = String.format(body, "");
final Host host = new SiteDataGen().nextPersisted();

checkTemplateLayout(layout);
final Contentlet theme = new ThemeDataGen().nextPersisted();
final Template template = new TemplateDataGen()
.title("template_test_" + System.currentTimeMillis())
.theme(theme)
.drawedBody(layout)
.body(testBody)
.host(host)
.nextPersisted();

final Template template2 = new TemplateDataGen()
.title("template_test2_" + System.currentTimeMillis())
.theme(theme)
.drawedBody(layout)
.body(testBody)
.host(host)
.nextPersisted();

final Task05380ChangeContainerPathToAbsolute task05380ChangeContainerPathToAbsolute =
new Task05380ChangeContainerPathToAbsolute();

task05380ChangeContainerPathToAbsolute.executeUpgrade();

checkTemplateFromDataBase(host, template);
checkTemplateFromDataBase(host, template2);
}

/**
* Method to Test: {@link Task05380ChangeContainerPathToAbsolute#executeUpgrade()}
* When: Exists A advance template with relative path container
* Should: Should not change anything
*/
@Test
public void whenTemplateAdvancedHasRelativePath() throws IOException, DotDataException, DotSecurityException {
final String body = "#parseContainer(\"/application/containers/default\")";

final Host host = new SiteDataGen().nextPersisted();

final Contentlet theme = new ThemeDataGen().nextPersisted();
final Template template = new TemplateDataGen()
.title("template_test_" + System.currentTimeMillis())
.theme(theme)
.body(body)
.host(host)
.nextPersisted();


final Task05380ChangeContainerPathToAbsolute task05380ChangeContainerPathToAbsolute =
new Task05380ChangeContainerPathToAbsolute();

task05380ChangeContainerPathToAbsolute.executeUpgrade();

final ArrayList templates = getTemplateFromDataBase(template);

final HashMap templateMap = (HashMap) templates.get(0);
assertEquals(1, templates.size());

final String templateBody = templateMap.get("body").toString();
assertEquals(templateBody, body);
}

/**
* Method to Test: {@link Task05380ChangeContainerPathToAbsolute#executeUpgrade()}
* When: Exists A TemplateLayout with absolute path container from another site
* Should: Should not change anything
*/
@Test
public void whenTemplateLayoutHasAbsolutePathToAnotherSite() throws IOException, DotDataException, DotSecurityException {

final Host host = new SiteDataGen().nextPersisted();
final Host anotherHost = new SiteDataGen().nextPersisted();

final String layout = String.format(jsonDrawBody, "//" + anotherHost.getHostname());
final String testBody = String.format(body, "//" + anotherHost.getHostname());

checkTemplateLayout(layout);
final Contentlet theme = new ThemeDataGen().nextPersisted();
final Template template = new TemplateDataGen()
.title("template_test_" + System.currentTimeMillis())
.theme(theme)
.drawedBody(layout)
.body(testBody)
.host(host)
.nextPersisted();


final Task05380ChangeContainerPathToAbsolute task05380ChangeContainerPathToAbsolute =
new Task05380ChangeContainerPathToAbsolute();

task05380ChangeContainerPathToAbsolute.executeUpgrade();

checkTemplateFromDataBase(anotherHost, template);
}

/**
* Method to Test: {@link Task05380ChangeContainerPathToAbsolute#executeUpgrade()}
* When: Exists A legacy TemplateLayout with absolute path container from another site
* Should: Should does not anything
*/
@Test
public void whenLegacyTemplateLayoutHasAbsolutePathToAnotherSite() throws IOException, DotDataException, DotSecurityException {

final Host host = new SiteDataGen().nextPersisted();
final Host anotherHost = new SiteDataGen().nextPersisted();

final String layout = String.format(legacyHTMLLayout, "//" + anotherHost.getHostname());
final String testBody = String.format(body, "//" + anotherHost.getHostname());

final Contentlet theme = new ThemeDataGen().nextPersisted();
final Template template = new TemplateDataGen()
.title("template_test_" + System.currentTimeMillis())
.theme(theme)
.drawedBody(layout)
.body(testBody)
.host(host)
.nextPersisted();


final Task05380ChangeContainerPathToAbsolute task05380ChangeContainerPathToAbsolute =
new Task05380ChangeContainerPathToAbsolute();

task05380ChangeContainerPathToAbsolute.executeUpgrade();

checkTemplateFromDataBase(anotherHost, template);
}

private void checkTemplateLayout(final String layout) throws IOException {
DotTemplateTool.getTemplateLayoutFromJSON(layout);
}

/**
* Method to Test: {@link Task05380ChangeContainerPathToAbsolute#executeUpgrade()}
* When: A site change the name before run the {@link Task05380ChangeContainerPathToAbsolute}
* Should: Should use the last name
*/
@Test
public void whenSiteChangeNameBeforeRunUT() throws IOException, DotDataException, DotSecurityException {

final String layout = String.format(jsonDrawBody, "");
final String testBody = String.format(body, "");
final Host host = new SiteDataGen().nextPersisted();
final String oldName = host.getHostname();

host.setHostname(String.format("new_host_name_%s", System.currentTimeMillis()));
APILocator.getHostAPI().save(host, APILocator.systemUser(), false);

checkTemplateLayout(layout);
final Contentlet theme = new ThemeDataGen().nextPersisted();
final Template template = new TemplateDataGen()
.title("template_test_" + System.currentTimeMillis())
.theme(theme)
.drawedBody(layout)
.body(testBody)
.host(host)
.nextPersisted();

final Task05380ChangeContainerPathToAbsolute task05380ChangeContainerPathToAbsolute =
new Task05380ChangeContainerPathToAbsolute();

task05380ChangeContainerPathToAbsolute.executeUpgrade();

checkTemplateFromDataBase(host, template);

final Map<String, Object> results = new DotConnect().setSQL(GET_HOSTNAME_COLUMN)
.loadObjectResults().get(0);

final String hostNameColumnName = (String) results.get("field_contentlet");

final boolean anyMatchWithOldName = new DotConnect()
.setSQL(String.format(GET_TEMPLATES_QUERY,hostNameColumnName))
.loadObjectResults()
.stream()
.map(templateMap -> templateMap.get("host_name"))
.anyMatch(title -> title.equals(oldName));

assertFalse(anyMatchWithOldName);

final long count = new DotConnect()
.setSQL(String.format(GET_TEMPLATES_QUERY,hostNameColumnName))
.loadObjectResults()
.stream()
.map(templateMap -> templateMap.get("inode"))
.filter(inode -> inode.equals(template.getInode()))
.count();

assertEquals(count, 1);
}

/**
* Method to Test: {@link Task05380ChangeContainerPathToAbsolute#executeUpgrade()}
* When: Exists A TemplateLayout with relative path container in Sidebar
* Should: Should turn it into a Absolute Path, using the template's host
*/
@Test
public void whenTemplateLayoutHasRelativePathAndSideBar() throws IOException, DotDataException, DotSecurityException {

final String layout = String.format(jsonDrawBodyWithSideBar, "");
final String testBody = String.format(body, "");
final Host host = new SiteDataGen().nextPersisted();

checkTemplateLayout(layout);
final Contentlet theme = new ThemeDataGen().nextPersisted();
final Template template = new TemplateDataGen()
.title("template_test_" + System.currentTimeMillis())
.theme(theme)
.drawedBody(layout)
.body(testBody)
.host(host)
.nextPersisted();


final Task05380ChangeContainerPathToAbsolute task05380ChangeContainerPathToAbsolute =
new Task05380ChangeContainerPathToAbsolute();

task05380ChangeContainerPathToAbsolute.executeUpgrade();

checkTemplateFromDataBase(host, template);
}
}
@@ -0,0 +1,39 @@
package com.dotmarketing.startup.runonce;

import com.dotcms.util.IntegrationTestInitService;
import com.dotmarketing.db.DbConnectionFactory;
import com.dotmarketing.db.DbType;
import com.dotmarketing.util.Logger;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;

public class Task05390MakeRoomForLongerJobDetailTest {

@BeforeClass
public static void prepare() throws Exception {
// Setting web app environment
IntegrationTestInitService.getInstance().init();
}

@Test
public void testExecuteUpgrade() {
final DbType dbType = DbType.getDbType(DbConnectionFactory.getDBType());
try{
final Task05390MakeRoomForLongerJobDetail upgradeTask = new Task05390MakeRoomForLongerJobDetail();
final boolean forceRun = upgradeTask.forceRun();
if(forceRun){
Assert.assertEquals("should be applied only for mySQL ", DbType.MYSQL, dbType );
upgradeTask.executeUpgrade();
} else {
Assert.assertNotEquals(String.format("should not be applied on dbs ot type `%s` ",dbType), DbType.MYSQL, dbType );
}
} catch (Exception e) {
final String errMessage = "Could not modify table on db of type: " + dbType + " Err: " + e.toString() ;
Logger.error(getClass(),errMessage, e);
Assert.fail(errMessage);
}
}


}
@@ -0,0 +1,206 @@
package com.dotmarketing.startup.runonce;

import com.dotcms.util.IntegrationTestInitService;
import com.dotmarketing.common.db.DotConnect;
import com.dotmarketing.common.db.DotDatabaseMetaData;
import com.dotmarketing.db.DbConnectionFactory;
import com.dotmarketing.exception.DotDataException;
import org.junit.BeforeClass;
import org.junit.Test;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

import static com.dotcms.util.CollectionsUtils.list;
import static com.dotcms.util.CollectionsUtils.map;
import static org.junit.Assert.assertEquals;


public class Task05395RemoveEndpointIdForeignKeyInIntegrityResolverTablesIntegrationTest {

@BeforeClass
public static void prepare() throws Exception {
// Setting web app environment
IntegrationTestInitService.getInstance().init();
}

/**
* Method to Test: {@link Task05395RemoveEndpointIdForeignKeyInIntegrityResolverTables#executeUpgrade()}
* When: Run the Upgrade Task
* Should: Should remove the foreign key to all the Integrity Resolver tables and rename the column endpoint_id
* The Integrity Resolver tables are:
* - cms_roles_ir
* - folders_ir
* - structures_ir
* - htmlpages_ir
* - fileassets_ir
*
* After remove the constraint you should be allow to insert register in this table without has any register in publishing_end_point
*/
@Test
public void constraintShouldNotExists() throws DotDataException, SQLException {

final String endpointId = insertPublishingEndPoint();
insertFolderIntegrityResolver(endpointId);
insertPageIntegrityResolver(endpointId);
insertFileAssetIntegrityResolver(endpointId);
insertRolesIntegrityResolver(endpointId);
insertStructuresIntegrityResolver(endpointId);

final Task05395RemoveEndpointIdForeignKeyInIntegrityResolverTables task05390RemoveEndpointIdForeignKeyInIntegrityResolverTables =
new Task05395RemoveEndpointIdForeignKeyInIntegrityResolverTables();

task05390RemoveEndpointIdForeignKeyInIntegrityResolverTables.executeUpgrade();

insertFolderIntegrityResolver("anyIP");
insertPageIntegrityResolver("anyIP");
insertFileAssetIntegrityResolver("anyIP");
insertRolesIntegrityResolver("anyIP");
insertStructuresIntegrityResolver("anyIP");

checkColumnsSize();
}

private void checkColumnsSize() throws SQLException {
final List<String> tables = list(
"folders_ir",
"structures_ir",
"htmlpages_ir",
"fileassets_ir",
"cms_roles_ir"
);

for (String table : tables) {
checkColumnSize(table);
}
}

private void checkColumnSize(final String tableName) throws SQLException {
final Connection connection = DbConnectionFactory.getConnection();
final ResultSet resultSet = DotDatabaseMetaData.getColumnsMetaData(connection, tableName);

while (resultSet.next()) {

final String columnName = resultSet.getString("COLUMN_NAME");

if (columnName.equals("endpoint_Id")) {
final int columnSize = resultSet.getInt("COLUMN_SIZE");
assertEquals(columnSize, 40);
}

}
}

private String insertPublishingEndPoint() throws DotDataException {

DotConnect dc = new DotConnect();
dc.setSQL("INSERT INTO publishing_end_point " +
"(id, group_id, server_name, address, port, protocol, enabled, auth_key, sending) " +
"values(?,?,?,?,?,?,?,?,?)"
);

final String id = String.valueOf(System.currentTimeMillis());

dc.addParam(id);
dc.addParam("group_id");
dc.addParam("server_name");
dc.addParam("address");
dc.addParam("port");
dc.addParam("protocol");
dc.addParam(true);
dc.addParam("auth_key");
dc.addParam(true);

dc.loadResult();

return id;
}

private void insertFolderIntegrityResolver(final String remoteIP) throws DotDataException {

DotConnect dc = new DotConnect();
dc.setSQL(
"INSERT INTO folders_ir (local_inode, remote_inode, local_identifier, remote_identifier, endpoint_id) values(?,?,?,?,?)"
);

dc.addParam("localInode" + System.currentTimeMillis());
dc.addParam("remoteInode" + System.currentTimeMillis());
dc.addParam("localIdentifier" + System.currentTimeMillis());
dc.addParam("remoteIdentifier" + System.currentTimeMillis());
dc.addParam(remoteIP);

dc.loadResult();
}

private void insertPageIntegrityResolver(final String remoteIP) throws DotDataException {

DotConnect dc = new DotConnect();
dc.setSQL("INSERT INTO htmlpages_ir " +
"(local_working_inode, remote_working_inode, local_live_inode, remote_live_inode, local_identifier, remote_identifier, html_page, endpoint_id, language_id) " +
"values(?,?,?,?,?,?,?,?,?)");

dc.addParam("local_working_inode" + System.currentTimeMillis());
dc.addParam("remote_working_inode" + System.currentTimeMillis());
dc.addParam("local_live_inode" + System.currentTimeMillis());
dc.addParam("remote_live_inode" + System.currentTimeMillis());
dc.addParam("local_identifier" + System.currentTimeMillis());
dc.addParam("remote_identifier" + System.currentTimeMillis());
dc.addParam("html_page" + System.currentTimeMillis());
dc.addParam(remoteIP);
dc.addParam(new Long("1"));

dc.loadResult();
}

private void insertFileAssetIntegrityResolver(final String remoteIP) throws DotDataException {

final DotConnect dotConnect = new DotConnect();
dotConnect.setSQL("INSERT INTO fileassets_ir " +
"(local_working_inode, remote_working_inode, local_live_inode, remote_live_inode, local_identifier, remote_identifier, file_name, endpoint_id, language_id) " +
"values(?,?,?,?,?,?,?,?,?)");

dotConnect.addParam("local_working_inode" + System.currentTimeMillis());
dotConnect.addParam("remote_working_inode" + System.currentTimeMillis());
dotConnect.addParam("local_live_inode" + System.currentTimeMillis());
dotConnect.addParam("remote_live_inode" + System.currentTimeMillis());
dotConnect.addParam("local_identifier" + System.currentTimeMillis());
dotConnect.addParam("remote_identifier" + System.currentTimeMillis());
dotConnect.addParam("file_name" + System.currentTimeMillis());
dotConnect.addParam(remoteIP);
dotConnect.addParam(Long.valueOf(1));

dotConnect.loadResult();
}

private void insertRolesIntegrityResolver(final String remoteIP) throws DotDataException {

DotConnect dc = new DotConnect();
dc.setSQL(
"INSERT INTO cms_roles_ir (name, role_key, local_role_id, remote_role_id, local_role_fqn, remote_role_fqn, endpoint_id) values(?,?,?,?,?,?,?)"
);

dc.addParam("name" + System.currentTimeMillis());
dc.addParam("role_key" + System.currentTimeMillis());
dc.addParam("local_role_id" + System.currentTimeMillis());
dc.addParam("remote_role_id" + System.currentTimeMillis());
dc.addParam("local_role_fqn" + System.currentTimeMillis());
dc.addParam("remote_role_fqn" + System.currentTimeMillis());
dc.addParam(remoteIP);

dc.loadResult();
}

private void insertStructuresIntegrityResolver(final String remoteIP) throws DotDataException {

DotConnect dc = new DotConnect();
dc.setSQL("INSERT INTO structures_ir (local_inode, remote_inode, endpoint_id) values(?,?,?)");

dc.addParam("local_inode" + System.currentTimeMillis());
dc.addParam("remote_inode" + System.currentTimeMillis() );
dc.addParam(remoteIP);

dc.loadResult();
}
}
Binary file not shown.
@@ -101,7 +101,7 @@ ES_AUTH_BASIC_USER=admin
ES_AUTH_BASIC_PASSWORD=admin

#Enable TLS for ES rest api layer. If ES_TLS_ENABLED=true, certificates must be specified (ES_AUTH_TLS_CLIENT_CERT, ES_AUTH_TLS_CLIENT_KEY, ES_AUTH_TLS_CA_CERT)
ES_TLS_ENABLED=true
ES_TLS_ENABLED=false
#Path relative to assets folder
ES_AUTH_TLS_CLIENT_CERT=certs/elasticsearch.pem
ES_AUTH_TLS_CLIENT_KEY=certs/elasticsearch.key
@@ -1,17 +1,21 @@
package com.dotcms.auth.providers.jwt;

import com.dotcms.auth.providers.jwt.beans.JWToken;
import com.dotcms.rest.exception.SecurityException;
import com.liferay.portal.model.User;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.io.Serializable;
import java.util.Optional;

/**
* Process the Authentication Credentials
* @author jsanca
*/
public interface JsonWebTokenAuthCredentialProcessor extends Serializable {


String BEARER = "Bearer ";

/**
@@ -30,4 +34,15 @@
* @return User
*/
User processAuthHeaderFromJWT(final HttpServletRequest request);

/**
* Process the authentication credentials based on the jwt authorization header (it should starts with BEARER prefix)
* this Authentication will only support {@link com.dotcms.auth.providers.jwt.beans.ApiToken},
*
* @param request {@link HttpServletRequest}
* @return JWToken the JWT token or {@link Optional#empty()} if the token is invalid
*
* @throws SecurityException if the Token is not a valid API Token or if the header not have the BEARER prefix
*/
Optional<JWToken> processJWTAuthHeader(final HttpServletRequest request);
} // E:O:F:JsonWebTokenAuthCredentialProcessor.
@@ -5,6 +5,7 @@
import com.dotcms.business.CloseDBIfOpened;
import com.dotcms.business.WrapInTransaction;
import com.dotcms.enterprise.cluster.ClusterFactory;
import com.dotcms.publisher.pusher.PushPublisher;
import com.dotcms.repackage.org.apache.commons.net.util.SubnetUtils;
import com.dotmarketing.business.APILocator;
import com.dotmarketing.business.CacheLocator;
@@ -20,6 +21,10 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.liferay.portal.model.User;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.SignatureException;
import io.vavr.control.Try;

import java.util.*;
@@ -473,4 +478,21 @@ private boolean checkPerms(final ApiToken token, final User user) {
|| APILocator.getPortletAPI().hasUserAdminRights(user))
.getOrElse(false);
}

/**
* Return true if the token was correctly constructed
* @param token
* @return
*/
public boolean isWellFormedToken(final String token){
try {
Jwts.parser().parse(token);
return true;
} catch (MalformedJwtException e){
return false;
} catch(SignatureException | ExpiredJwtException | IllegalArgumentException e){
return true;
}
}

}
@@ -220,7 +220,6 @@ public JWToken parseToken(final String jsonWebToken) {

@Override
public JWToken parseToken(final String jsonWebToken, final String requestingIp) {

final Jws<Claims> jws = Jwts.parser().setSigningKey(this.getSigningKey()).parseClaimsJws(jsonWebToken);

return validateToken(jws, resolveJWTokenType(jws), requestingIp);
@@ -48,7 +48,7 @@ protected JsonWebTokenAuthCredentialProcessorImpl(final JsonWebTokenUtils jsonWe
this.jsonWebTokenUtils = jsonWebTokenUtils;
}

protected User internalProcessAuthHeaderFromJWT(final String authorizationHeader,
protected Optional<JWToken> internalProcessAuthHeaderFromJWT(final String authorizationHeader,
final String ipAddress, final boolean rejectIfNotApiToken) {

if (StringUtils.isNotEmpty(authorizationHeader) && authorizationHeader.trim()
@@ -70,7 +70,7 @@ protected User internalProcessAuthHeaderFromJWT(final String authorizationHeader
throw new SecurityException("The Api token sent on the request header must be api token", Response.Status.BAD_REQUEST);
}

return token.isPresent() ? token.get().getActiveUser().get() : null;
return token;
} catch(SecurityException se) {

this.jsonWebTokenUtils.handleInvalidTokenExceptions(this.getClass(), se, null, null);
@@ -82,14 +82,16 @@ protected User internalProcessAuthHeaderFromJWT(final String authorizationHeader

}

return null;
return Optional.empty();
}

@Override
public User processAuthHeaderFromJWT(final String authorizationHeader,
final HttpSession session, final String ipAddress) {

final User user = internalProcessAuthHeaderFromJWT(authorizationHeader, ipAddress, false);
final Optional<JWToken> jwToken = internalProcessAuthHeaderFromJWT(authorizationHeader, ipAddress, false);
final User user = jwToken.isPresent() ? jwToken.get().getActiveUser().get() : null;

if (user != null && null != session) {
session.setAttribute(WebKeys.CMS_USER, user);
session.setAttribute(com.liferay.portal.util.WebKeys.USER_ID, user.getUserId());
@@ -103,7 +105,9 @@ public User processAuthHeaderFromJWT(final HttpServletRequest request) {

// Extract authentication credentials
final String authentication = request.getHeader(ContainerRequest.AUTHORIZATION);
final User user = internalProcessAuthHeaderFromJWT(authentication, request.getRemoteAddr(), true);

final Optional<JWToken> jwToken = internalProcessAuthHeaderFromJWT(authentication, request.getRemoteAddr(), false);
final User user = jwToken.isPresent() ? jwToken.get().getActiveUser().get() : null;

if(user != null) {

@@ -114,4 +118,23 @@ public User processAuthHeaderFromJWT(final HttpServletRequest request) {
return user;
} // processAuthCredentialsFromJWT.

@Override
public Optional<JWToken> processJWTAuthHeader(final HttpServletRequest request) {

// Extract authentication credentials
final String authentication = request.getHeader(ContainerRequest.AUTHORIZATION);

final Optional<JWToken> jwToken = internalProcessAuthHeaderFromJWT(authentication, request.getRemoteAddr(), false);
final User user = jwToken.isPresent() && jwToken.get().getActiveUser().isPresent() ?
jwToken.get().getActiveUser().get() : null;

if(user != null) {

request.setAttribute(com.liferay.portal.util.WebKeys.USER_ID, user.getUserId());
request.setAttribute(com.liferay.portal.util.WebKeys.USER, user);
}

return jwToken;
} // processAuthCredentialsFromJWT.

} // E:O:F:JsonWebTokenAuthCredentialProcessorImpl.
@@ -33,6 +33,8 @@
import javax.ws.rs.core.Response;
import java.io.IOException;
import java.io.Serializable;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;

@@ -246,7 +248,7 @@ public void processLogin(@PathParam("idpConfigId") final String idpConfigId,
@Path( "/metadata/{idpConfigId}" )
@JSONP
@NoCache
@Produces( { MediaType.APPLICATION_JSON, "application/javascript" } )
@Produces( { MediaType.APPLICATION_XML, "application/xml" } )
public void metadata( @PathParam( "idpConfigId" ) final String idpConfigId,
@Context final HttpServletRequest httpServletRequest,
@Context final HttpServletResponse httpServletResponse ) throws IOException {
@@ -267,6 +269,7 @@ public void metadata( @PathParam( "idpConfigId" ) final String idpConfigId,
if (identityProviderConfiguration != null && identityProviderConfiguration.isEnabled()) {

Logger.debug(this, () -> "Processing saml login request for idpConfig id: " + idpConfigId);
httpServletResponse.setContentType("application/xml");
this.samlAuthenticationService.renderMetadataXML(httpServletResponse.getWriter(), identityProviderConfiguration);
return;
}
@@ -285,10 +288,11 @@ public void metadata( @PathParam( "idpConfigId" ) final String idpConfigId,
@POST
@Path("/logout/{idpConfigId}")
@NoCache
@Produces({MediaType.TEXT_HTML, MediaType.APPLICATION_XHTML_XML})
// Login configuration by id
public void logout(@PathParam("idpConfigId") final String idpConfigId,
public Response logoutPost(@PathParam("idpConfigId") final String idpConfigId,
@Context final HttpServletRequest httpServletRequest,
@Context final HttpServletResponse httpServletResponse) throws IOException {
@Context final HttpServletResponse httpServletResponse) throws IOException, URISyntaxException {

if (DotSamlProxyFactory.getInstance().isAnyHostConfiguredAsSAML()) {

@@ -298,13 +302,48 @@ public void logout(@PathParam("idpConfigId") final String idpConfigId,
// If idpConfig is null, means this site does not need SAML processing
if (identityProviderConfiguration != null && identityProviderConfiguration.isEnabled()) {

Logger.debug(this, () -> "Processing saml logout request for idpConfig id: " + idpConfigId);
Logger.debug(this, () -> "Processing saml logout post request for idpConfig id: " + idpConfigId);
final String logoutPath = this.samlConfigurationService.getConfigAsString(identityProviderConfiguration,
SamlName.DOT_SAML_LOGOUT_SERVICE_ENDPOINT_URL,
()->buildBaseUrlFromRequest(httpServletRequest) + "/");
()-> "/dotAdmin/#/public/logout");

httpServletResponse.sendRedirect(logoutPath);
return;
return Response.temporaryRedirect(new URI(logoutPath)).build();
}
} finally {
if (null != identityProviderConfiguration) {
identityProviderConfiguration.destroy();
}
}
}

final String message = "No idpConfig for idpConfigId: " + idpConfigId + ". At " + httpServletRequest.getRequestURI();
Logger.debug(this, () -> message);
throw new DoesNotExistException(message);
}

@GET
@Path("/logout/{idpConfigId}")
@NoCache
@Produces({MediaType.TEXT_HTML, MediaType.APPLICATION_XHTML_XML})
// Login configuration by id
public Response logoutGet(@PathParam("idpConfigId") final String idpConfigId,
@Context final HttpServletRequest httpServletRequest,
@Context final HttpServletResponse httpServletResponse) throws IOException, URISyntaxException {

if (DotSamlProxyFactory.getInstance().isAnyHostConfiguredAsSAML()) {

final IdentityProviderConfiguration identityProviderConfiguration =
this.identityProviderConfigurationFactory.findIdentityProviderConfigurationById(idpConfigId);
try {
// If idpConfig is null, means this site does not need SAML processing
if (identityProviderConfiguration != null && identityProviderConfiguration.isEnabled()) {

Logger.debug(this, () -> "Processing saml logout get request for idpConfig id: " + idpConfigId);
final String logoutPath = this.samlConfigurationService.getConfigAsString(identityProviderConfiguration,
SamlName.DOT_SAML_LOGOUT_SERVICE_ENDPOINT_URL,
()-> this.buildBaseUrlFromRequest(httpServletRequest));

return Response.temporaryRedirect(new URI(logoutPath)).build();
}
} finally {
if (null != identityProviderConfiguration) {
@@ -324,7 +363,7 @@ public void logout(@PathParam("idpConfigId") final String idpConfigId,
private String buildBaseUrlFromRequest(final HttpServletRequest httpServletRequest) {

final String uri = httpServletRequest.getScheme() + "://" + httpServletRequest.getServerName() + ":"
+ httpServletRequest.getServerPort();
+ httpServletRequest.getServerPort() + "/dotAdmin/show-logout";

return uri;
}
@@ -0,0 +1,75 @@
package com.dotcms.cms.login;

import com.dotcms.filters.interceptor.Result;
import com.dotcms.filters.interceptor.WebInterceptor;
import com.dotmarketing.business.APILocator;
import com.dotmarketing.util.Config;
import com.dotmarketing.util.Logger;
import com.dotmarketing.util.SecurityLogger;
import com.liferay.portal.model.User;
import com.liferay.portal.util.PortalUtil;
import com.liferay.util.StringPool;

import javax.servlet.RequestDispatcher;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
* Intercepts performs the logout at the end of the call in order to give chance
* to thirdparties implementation to patch the logout process, if needed
*/
public class LogoutWebInterceptor implements WebInterceptor{

private static final String API_CALL = "/dotAdmin/logout";
private static final String API_SHOW = "/dotAdmin/show-logout";


@Override
public String[] getFilters() {
return new String[] {
API_CALL + "*",API_SHOW + "*",
};
}

@Override
public Result intercept(final HttpServletRequest request, final HttpServletResponse response) {

Logger.info(this.getClass(), "Starting Logout --> " + request.getRequestURI());

try {

if (request.getRequestURI().contains(API_CALL)) {
Logger.info(this, () -> "Doing the logout");
final User user = PortalUtil.getUser(request);

APILocator.getLoginServiceAPI().doActionLogout(request, response);

if (null != user) {
SecurityLogger.logInfo(this.getClass(), "User " +
user.getFullName() + " (" + user.getUserId() + ") has logged out from IP: " + request.getRemoteAddr());
}
}

//response.sendRedirect(Config.getStringProperty("logout.url", "/dotAdmin/#/public/logout"));
final RequestDispatcher dispatcher = Config.CONTEXT
.getRequestDispatcher("/html/portal/show-logout.jsp");
dispatcher.forward(request, response);
Logger.info(this, ()-> "Logout DONE");
} catch (Exception e) {

Logger.error(this,"Error doing the logout", e);
try {
if (!response.isCommitted()) {
response.sendError(500);
}
} catch (IOException ioException) {
Logger.error(this,"Can not redirect to error", e);
}

}

return Result.SKIP_NO_CHAIN;
}

}
@@ -74,6 +74,12 @@
private final Map<String, DotConcurrentImpl> submitterMap =
new ConcurrentHashMap<>();

/**
* Creator map
*/
private final Map<String, SubmitterConfig> submitterConfigCreatorMap =
new ConcurrentHashMap<>();

/**
* Keeps a concurrent delay queue (for subscribe or unsubscribe)
*/
@@ -269,6 +275,28 @@ public DotSubmitter getSubmitter () {
return this.getSubmitter(DOT_SYSTEM_THREAD_POOL);
} // getSubmitter.

/**
* Register a submitterConfig that will act as instantiator and gets the submitter
* @param name {@link String}
* @param creator {@link SubmitterConfig}
* @return DotSubmitter
*/
public DotSubmitter getSubmitter (final String name, final SubmitterConfig creator) {

this.registerSubmitterCreator(name, creator);
return this.getSubmitter(name);
}

/**
* Register submitter creator
* @param name
* @param creator
*/
public void registerSubmitterCreator (final String name, final SubmitterConfig creator) {

this.submitterConfigCreatorMap.putIfAbsent(name, creator);
}

/**
* Get's the submitter for a submitterName parameter
* The submitterName is used as a prefix for all these properties:
@@ -318,7 +346,16 @@ public DotSubmitter getSubmitter (final String name) {

private DotConcurrentImpl createDotConcurrent (final String name) {

DotConcurrentImpl submitter =
final DotConcurrentImpl submitter = this.submitterConfigCreatorMap.containsKey(name)?
new DotConcurrentImpl(
this.submitterConfigCreatorMap.get(name).getDefaultThreadFactory(),
this.submitterConfigCreatorMap.get(name).getRejectedExecutionHandler(),
this.submitterConfigCreatorMap.get(name).getAllowCoreThreadTimeOut(),
this.submitterConfigCreatorMap.get(name).getPoolSize(),
this.submitterConfigCreatorMap.get(name).getMaxPoolSize(),
this.submitterConfigCreatorMap.get(name).getKeepAliveMillis(),
this.submitterConfigCreatorMap.get(name).getQueueCapacity()
):
new DotConcurrentImpl(
this.getDefaultThreadFactory(name),
this.rejectedExecutionHandler,
@@ -342,6 +379,156 @@ public IdentifierStripedLock getIdentifierStripedLock(){
return this.identifierStripedLock;
}

/**
* {@link SubmitterConfig} builder
*/
public static class SubmitterConfigBuilder {

private ThreadFactory threadFactory;
private RejectedExecutionHandler rejectedExecutionHandler;
private Boolean allowCoreThreadTimeOut;
private Integer poolSize;
private Integer maxPoolSize;
private Long keepAliveMillis;
private Integer queueCapacity;

public SubmitterConfigBuilder defaultThreadFactory(final ThreadFactory threadFactory) {
this.threadFactory = threadFactory; return this;
}

public SubmitterConfigBuilder rejectedExecutionHandler(final RejectedExecutionHandler rejectedExecutionHandler) {
this.rejectedExecutionHandler = rejectedExecutionHandler; return this;
}

public SubmitterConfigBuilder allowCoreThreadTimeOut(final boolean allowCoreThreadTimeOut) {
this.allowCoreThreadTimeOut = allowCoreThreadTimeOut; return this;
}

public SubmitterConfigBuilder poolSize (final int poolSize) {
this.poolSize = poolSize; return this;
}

public SubmitterConfigBuilder maxPoolSize (final int maxPoolSize) {
this.maxPoolSize = maxPoolSize; return this;
}

public SubmitterConfigBuilder keepAliveMillis(final long keepAliveMillis) {
this.keepAliveMillis = keepAliveMillis; return this;
}

public SubmitterConfigBuilder queueCapacity(final int queueCapacity) {
this.queueCapacity = queueCapacity; return this;
}

public SubmitterConfig build () {
return new SubmitterConfig() {
@Override
public ThreadFactory getDefaultThreadFactory() {
return null != SubmitterConfigBuilder.this.threadFactory?
SubmitterConfigBuilder.this.threadFactory: SubmitterConfig.super.getDefaultThreadFactory();
}

@Override
public RejectedExecutionHandler getRejectedExecutionHandler() {
return null != SubmitterConfigBuilder.this.rejectedExecutionHandler?
SubmitterConfigBuilder.this.rejectedExecutionHandler: SubmitterConfig.super.getRejectedExecutionHandler();
}

@Override
public boolean getAllowCoreThreadTimeOut() {
return null != SubmitterConfigBuilder.this.allowCoreThreadTimeOut?
SubmitterConfigBuilder.this.allowCoreThreadTimeOut: SubmitterConfig.super.getAllowCoreThreadTimeOut();
}

@Override
public int getPoolSize() {
return null != SubmitterConfigBuilder.this.poolSize?
SubmitterConfigBuilder.this.poolSize: SubmitterConfig.super.getPoolSize();
}

@Override
public int getMaxPoolSize() {
return null != SubmitterConfigBuilder.this.maxPoolSize?
SubmitterConfigBuilder.this.maxPoolSize: SubmitterConfig.super.getMaxPoolSize();
}

@Override
public long getKeepAliveMillis() {
return null != SubmitterConfigBuilder.this.keepAliveMillis?
SubmitterConfigBuilder.this.keepAliveMillis: SubmitterConfig.super.getKeepAliveMillis();
}

@Override
public int getQueueCapacity() {
return null != SubmitterConfigBuilder.this.queueCapacity?
SubmitterConfigBuilder.this.queueCapacity: SubmitterConfig.super.getQueueCapacity();
}
};
}
}

/**
* In case you want to configure by code.
*/
public interface SubmitterConfig {

/**
* Returns "Executors.defaultThreadFactory()"
* @return ThreadFactory
*/
default ThreadFactory getDefaultThreadFactory () {
return Executors.defaultThreadFactory();
}

/**
* Returns AbortPolicy
* @return RejectedExecutionHandler
*/
default RejectedExecutionHandler getRejectedExecutionHandler() {
return new ThreadPoolExecutor.AbortPolicy();
}

/**
* By default does not allows allow core time out
* @return boolean
*/
default boolean getAllowCoreThreadTimeOut() {
return Config.getBooleanProperty(DOTCMS_CONCURRENT_ALLOWCORETHREADTIMEOUT, Boolean.FALSE);
}

/**
* Returns 10 as a default pool size
* @return int
*/
default int getPoolSize () {
return Config.getIntProperty(DOTCMS_CONCURRENT_POOLSIZE, POOL_SIZE_VAL);
}

/**
* Returns 50 as a default max pool size
* @return int
*/
default int getMaxPoolSize () {
return Config.getIntProperty(DOTCMS_CONCURRENT_MAXPOOLSIZE, MAXPOOL_SIZE_VAL);
}

/**
* Returns one minute as a keep alive
* @return long
*/
default long getKeepAliveMillis() {
return Config.getLongProperty(DOTCMS_CONCURRENT_KEEPALIVEMILLIS, DateUtil.MINUTE_MILLIS);
}

/**
* Returns 100 as a queue capacity
* @return int
*/
default int getQueueCapacity() {
return Config.getIntProperty(DOTCMS_CONCURRENT_QUEUECAPACITY, QUEUE_CAPACITY_VAL);
}
}

//// DelayQueueConsumer

/**
@@ -429,6 +616,7 @@ public void run() {
}

DotConcurrentFactory.this.subscribeDelayQueue (this.delayedQueue);

} // DotConcurrentImpl.

final BlockingQueue<Runnable> createQueue(final int queueCapacity) {
@@ -442,6 +630,16 @@ final ThreadPoolExecutor getThreadPoolExecutor() {
return this.threadPoolExecutor;
}

@Override
public int getPoolSize() {
return this.threadPoolExecutor.getPoolSize();
}

@Override
public int getMaxPoolSize() {
return this.threadPoolExecutor.getMaximumPoolSize();
}

@Override
public final void execute(final Runnable command) {

@@ -65,6 +65,18 @@
*/
public int getActiveCount();

/**
* Gets the pool size configured
* @return int
*/
public int getPoolSize();

/**
* Gets the Max pool size configured
* @return int
*/
public int getMaxPoolSize();

/**
* Initiates an orderly shutdown in which previously submitted
* tasks are executed, but no new tasks will be accepted.
@@ -341,16 +341,21 @@ public String toJson(final Contentlet contentlet) throws DotMappingException {
mlowered.put(lowerCaseKey + "_dotraw", lowerCaseValue);
}
}else{
mlowered.put(lowerCaseKey.replace(TEXT, "_dotraw"), lowerCaseValue);
mlowered.put(lowerCaseKey.replace(TEXT, "_dotraw"), lowerCaseValue);
}
}

mlowered.put(lowerCaseKey, lowerCaseValue);

//exclude null values and relationships because they where appended on the loadRelationships method
if(lowerCaseValue!=null && !(lowerCaseValue instanceof List)) {
sw.append(lowerCaseValue.toString()).append(' ');
}

if (lowerCaseKey.endsWith(TEXT) && !Config
.getBooleanProperty("CREATE_TEXT_INDEX_FIELD_FOR_NON_TEXT_FIELDS", false)) {
continue;
}

mlowered.put(lowerCaseKey, lowerCaseValue);
}

final Optional<Field> binaryField = this.findFirstBinaryFieldIndexable(contentlet);
@@ -452,12 +457,12 @@ private void generateBinaryMetadata(final Contentlet contentlet,

/**
* Adds the current workflow task to the contentlet in order to be reindexed.
*
*
* @param contentlet {@link Contentlet}
* @return {@link Map}
*/
protected Map<String, Object> getWorkflowInfoForContentlet(final Contentlet contentlet) {

final Map<String, Object> workflowMap = new HashMap<>();
final WorkflowAPI workflowAPI = APILocator.getWorkflowAPI();

@@ -476,7 +481,7 @@ private void generateBinaryMetadata(final Contentlet contentlet,
workflowMap.put(ESMappingConstants.WORKFLOW_MOD_DATE, elasticSearchDateTimeFormat.format(task.getModDate()));
workflowMap.put(ESMappingConstants.WORKFLOW_MOD_DATE + TEXT, datetimeFormat.format(task.getModDate()));
}

} catch (Exception e) {
Logger.debug(this.getClass(), "No workflow info for contentlet " + contentlet.getIdentifier());
}
@@ -495,7 +500,7 @@ private void generateBinaryMetadata(final Contentlet contentlet,
stepIds.add(entryStep);
}
}

workflowMap.put(ESMappingConstants.WORKFLOW_SCHEME, String.join(" ", schemeWriter));
workflowMap.put(ESMappingConstants.WORKFLOW_STEP, stepIds);
workflowMap.put(ESMappingConstants.WORKFLOW_CURRENT_STEP, ESMappingConstants.WORKFLOW_CURRENT_STEP_NOT_ASSIGNED_VALUE); // multiple steps -> not assigned.
@@ -530,12 +535,12 @@ protected void loadCategories(final Contentlet con, final Map<String,Object> m)
String::toLowerCase).collect(Collectors.toList());

m.put(ESMappingConstants.CATEGORIES, catsVarNames);


for(final com.dotcms.contenttype.model.field.Field f : catFields){
// I don't think we care if we put all the categories in each field
m.put(type.variable() + "." + f.variable(), catsVarNames);

}
}

@@ -23,10 +23,12 @@
import com.dotmarketing.portlets.workflows.business.WorkFlowFactory;
import com.dotmarketing.util.*;
import com.google.common.collect.ImmutableSet;
import io.vavr.control.Try;
import org.apache.commons.lang.time.DateUtils;

import java.util.*;
import java.util.Calendar;
import java.util.stream.Collectors;

public class ContentTypeFactoryImpl implements ContentTypeFactory {

@@ -535,14 +537,19 @@ private boolean dbDelete(ContentType type) throws DotDataException {
dc.setSQL(this.contentTypeSql.DELETE_INODE_BY_INODE).addParam(type.id()).loadResult();
return true;
}


final boolean LOAD_FROM_CACHE=Config.getBooleanProperty("LOAD_CONTENTTYPE_DETAILS_FROM_CACHE", true);

private List<ContentType> dbSearch(String search, int baseType, String orderBy, int limit, int offset)
throws DotDataException {
int bottom = (baseType == 0) ? 0 : baseType;
int top = (baseType == 0) ? 100000 : baseType;
final int bottom = (baseType == 0) ? 0 : baseType;
final int top = (baseType == 0) ? 100000 : baseType;
if (limit == 0)
throw new DotDataException("limit param must be more than 0");
limit = (limit < 0) ? 10000 : limit;



// our legacy code passes in raw sql conditions and so we need to detect
// and handle those
SearchCondition searchCondition = new SearchCondition(search);
@@ -551,7 +558,12 @@ private boolean dbDelete(ContentType type) throws DotDataException {
orderBy = "mod_date";
}
DotConnect dc = new DotConnect();
dc.setSQL( String.format( this.contentTypeSql.SELECT_QUERY_CONDITION, SQLUtil.sanitizeCondition( searchCondition.condition ), orderBy ) );

if(LOAD_FROM_CACHE) {
dc.setSQL( String.format( this.contentTypeSql.SELECT_INODE_ONLY_QUERY_CONDITION, SQLUtil.sanitizeCondition( searchCondition.condition ), orderBy ) );
}else {
dc.setSQL( String.format( this.contentTypeSql.SELECT_QUERY_CONDITION, SQLUtil.sanitizeCondition( searchCondition.condition ), orderBy ) );
}
dc.setMaxRows(limit);
dc.setStartRow(offset);
dc.addParam( searchCondition.search );
@@ -562,8 +574,17 @@ private boolean dbDelete(ContentType type) throws DotDataException {

Logger.debug(this, "QUERY " + dc.getSQL());

return new DbContentTypeTransformer(dc.loadObjectResults()).asList();

if(LOAD_FROM_CACHE) {
return dc.loadObjectResults()
.stream()
.map(m-> Try.of(()->find((String) m.get("inode")))
.onFailure(e->Logger.warnAndDebug(ContentTypeFactoryImpl.class,e))
.getOrNull())
.filter(Objects::nonNull)
.collect(Collectors.toList());
}else {
return new DbContentTypeTransformer(dc.loadObjectResults()).asList();
}
}

private int dbCount(String search, int baseType) throws DotDataException {
@@ -21,6 +21,10 @@ public static ContentTypeSql getInstance() {
+ "description, default_structure, page_detail, structuretype, system, fixed, velocity_var_name , "
+ "url_map_pattern , host, folder, expire_date_var , publish_date_var , mod_date "
+ "from inode, structure where inode.type='structure' and inode.inode = structure.inode ";


public static String SELECT_ONLY_INODE_FIELD = "select inode.inode as inode from inode, structure where inode.type='structure' and inode.inode = structure.inode ";


public static String SELECT_BY_INODE = SELECT_ALL_STRUCTURE_FIELDS + " and inode.inode = ?";
public static String SELECT_BY_VAR = SELECT_ALL_STRUCTURE_FIELDS + " and lower(structure.velocity_var_name) like ?";
@@ -57,6 +61,13 @@ public static ContentTypeSql getInstance() {
+ " %s" //if we have a condition
+ " and structuretype>=? and structuretype<= ? order by %s";

public static String SELECT_INODE_ONLY_QUERY_CONDITION = SELECT_ONLY_INODE_FIELD
+ " and (inode.inode like ? or lower(name) like ? or velocity_var_name like ?) " //search
+ " %s" //if we have a condition
+ " and structuretype>=? and structuretype<= ? order by %s";



public static String SELECT_COUNT_CONDITION = "select count(*) as test from structure, inode "
+ "where inode.type='structure' and inode.inode=structure.inode and "
+ " (inode.inode like ? or lower(name) like ? or velocity_var_name like ?) "
@@ -458,7 +458,7 @@ public User getUser(final HttpServletRequest request,
User user = null;

final HttpSession session = request.getSession(false);
if (session == null || request.isRequestedSessionIdValid()) {
if (session == null) {

Logger.error(this, "Could not retrieve user from session as the session doesn't exist!");
return null;
@@ -580,7 +580,10 @@ private boolean isFile(final String uri, final Host host, final long languageId)
uriWithoutQueryString.endsWith(".png") ||
uriWithoutQueryString.endsWith(".gif") ||
uriWithoutQueryString.endsWith(".css") ||
uriWithoutQueryString.endsWith(".js");
uriWithoutQueryString.endsWith(".js") ||
uriWithoutQueryString.endsWith(".js.map") ||
uriWithoutQueryString.endsWith("manifest.json") ||
uriWithoutQueryString.endsWith(".ttf");
}

return true;
@@ -20,6 +20,7 @@
import static graphql.Scalars.GraphQLID;
import static graphql.Scalars.GraphQLString;

import com.dotcms.graphql.datafetcher.ContentMapDataFetcher;
import com.dotcms.graphql.datafetcher.FolderFieldDataFetcher;
import com.dotcms.graphql.datafetcher.LanguageDataFetcher;
import com.dotcms.graphql.datafetcher.SiteFieldDataFetcher;
@@ -28,6 +29,7 @@
import com.dotcms.graphql.util.TypeUtil.TypeFetcher;
import com.dotmarketing.portlets.contentlet.model.Contentlet;
import com.dotmarketing.util.UtilMethods;
import graphql.schema.GraphQLArgument;
import graphql.schema.GraphQLTypeReference;
import graphql.schema.PropertyDataFetcher;
import java.util.HashMap;
@@ -45,25 +47,33 @@ private ContentFields() {}
final Map<String, TypeFetcher> contentFields = new HashMap<>();
contentFields.put(MOD_DATE, new TypeFetcher(GraphQLString));
contentFields.put(TITLE, new TypeFetcher(GraphQLString));
contentFields.put(TITLE_IMAGE_KEY, new TypeFetcher(GraphQLTypeReference.typeRef("Binary"), new TitleImageFieldDataFetcher()));
contentFields.put(TITLE_IMAGE_KEY, new TypeFetcher(GraphQLTypeReference.typeRef("Binary"),
new TitleImageFieldDataFetcher()));
contentFields.put(CONTENT_TYPE, new TypeFetcher(GraphQLString));
contentFields.put(BASE_TYPE, new TypeFetcher(GraphQLString));
contentFields.put(LIVE, new TypeFetcher(GraphQLBoolean));
contentFields.put(WORKING, new TypeFetcher(GraphQLBoolean));
contentFields.put(ARCHIVED_KEY, new TypeFetcher(GraphQLBoolean));
contentFields.put(LOCKED_KEY, new TypeFetcher(GraphQLBoolean));
contentFields.put("conLanguage", new TypeFetcher(GraphQLTypeReference.typeRef("Language"), new LanguageDataFetcher()));
contentFields.put("conLanguage", new TypeFetcher(GraphQLTypeReference.typeRef("Language"),
new LanguageDataFetcher()));
contentFields.put(IDENTIFIER, new TypeFetcher(GraphQLID));
contentFields.put(INODE, new TypeFetcher(GraphQLID));
contentFields.put(HOST_KEY, new TypeFetcher(GraphQLTypeReference.typeRef("Site"), new SiteFieldDataFetcher()));
contentFields.put(FOLDER_KEY, new TypeFetcher(GraphQLTypeReference.typeRef("Folder"), new FolderFieldDataFetcher()));
contentFields.put(HOST_KEY, new TypeFetcher(GraphQLTypeReference.typeRef("Site"),
new SiteFieldDataFetcher()));
contentFields.put(FOLDER_KEY, new TypeFetcher(GraphQLTypeReference.typeRef("Folder"),
new FolderFieldDataFetcher()));
contentFields.put(URL_MAP, new TypeFetcher(GraphQLString, PropertyDataFetcher
.fetching((Function<Contentlet, String>) (contentlet) ->
UtilMethods.isSet(contentlet.getStringProperty("urlMap"))
? contentlet.getStringProperty("urlMap")
: contentlet.getStringProperty("URL_MAP_FOR_CONTENT"))));
contentFields.put(OWNER_KEY, new TypeFetcher(GraphQLTypeReference.typeRef("User"), new UserDataFetcher()));
contentFields.put(MOD_USER_KEY, new TypeFetcher(GraphQLTypeReference.typeRef("User"), new UserDataFetcher()));
contentFields.put(OWNER_KEY, new TypeFetcher(GraphQLTypeReference.typeRef("User"),
new UserDataFetcher()));
contentFields.put(MOD_USER_KEY, new TypeFetcher(GraphQLTypeReference.typeRef("User"),
new UserDataFetcher()));
contentFields.put("map", new TypeFetcher(GraphQLString, new ContentMapDataFetcher(),
GraphQLArgument.newArgument().name("key").type(GraphQLString).build()));
return contentFields;
}

@@ -23,6 +23,7 @@ public GraphQLSchema getSchema(HandshakeRequest request) {
@Override
public GraphQLSchema getSchema() {
try {
APILocator.getGraphqlAPI().printSchema();
return APILocator.getGraphqlAPI().getSchema();
} catch(DotDataException e) {
Logger.error(this, "Error with Schema retrieval/generation", e);
@@ -74,7 +74,10 @@

CONTENT_INTERFACE_FIELDS.addAll(contentFields.keySet());

interfaceTypes.put("CONTENTLET", createInterfaceType("Contentlet", contentFields, new ContentResolver()));
GraphQLInterfaceType contentletInterface = createInterfaceType("Contentlet",
contentFields, new ContentResolver());

interfaceTypes.put("CONTENTLET", contentletInterface);

interfaceTypes.put("CONTENT", createInterfaceType(CONTENT_INTERFACE_NAME, contentFields, new ContentResolver()));

@@ -39,7 +39,8 @@
List<GraphQLFieldDefinition> fieldDefinitions = new ArrayList<>();

contentTypeList.forEach((type) -> {
fieldDefinitions.add(createCollectionField(type));
fieldDefinitions.add(createCollectionField(type, TypeUtil.collectionizedName(type.variable())));
fieldDefinitions.add(createCollectionField(type, TypeUtil.oldCollectionizedName(type.variable())));
});

// Each BaseType as query'able collection field
@@ -60,10 +61,10 @@
* @return the field definition representing the collection
*/

private GraphQLFieldDefinition createCollectionField(ContentType type) {
private GraphQLFieldDefinition createCollectionField(final ContentType type, final String name) {
try {
return newFieldDefinition()
.name(TypeUtil.collectionizedName(type.variable()))
.name(name)
.argument(GraphQLArgument.newArgument()
.name("query")
.type(GraphQLString)
@@ -0,0 +1,54 @@
package com.dotcms.graphql.business;

import com.dotmarketing.business.Cachable;
import com.dotmarketing.business.CacheLocator;
import com.dotmarketing.business.DotCacheAdministrator;
import com.dotmarketing.business.DotCacheException;
import graphql.schema.GraphQLSchema;
import java.util.Arrays;
import java.util.Optional;

/**
* Cache implementation used to store the generated {@link GraphQLSchema}
*/

public class GraphQLSchemaCache implements Cachable {
private final static String GRAPHQL_SCHEMA_GROUP = GraphQLSchemaCache.class.getSimpleName().toLowerCase();

private final static String[] GROUPS = {GRAPHQL_SCHEMA_GROUP};

private final static String SCHEMA_KEY = "graphqlschema";

final private DotCacheAdministrator cache = CacheLocator.getCacheAdministrator();

@Override
public String getPrimaryGroup() {
return GRAPHQL_SCHEMA_GROUP;
}

public Optional<GraphQLSchema> getSchema() {
try {
return Optional.ofNullable((GraphQLSchema) cache.get(SCHEMA_KEY, GRAPHQL_SCHEMA_GROUP));
} catch (DotCacheException e) {
return Optional.empty();
}
}

public void putSchema(final GraphQLSchema schema) {
cache.put(SCHEMA_KEY, schema, GRAPHQL_SCHEMA_GROUP);
}

public void removeSchema() {
cache.remove(SCHEMA_KEY, GRAPHQL_SCHEMA_GROUP);
}

@Override
public String[] getGroups() {
return GROUPS;
}

@Override
public void clearCache() {
Arrays.asList(getGroups()).forEach(cache::flushGroup);
}
}
@@ -18,4 +18,5 @@

void invalidateSchema();

void printSchema();
}
@@ -10,10 +10,12 @@
import com.dotcms.graphql.InterfaceType;
import com.dotcms.graphql.datafetcher.ContentletDataFetcher;
import com.dotcms.util.LogTime;
import com.dotmarketing.business.CacheLocator;
import com.dotmarketing.exception.DotDataException;
import com.dotmarketing.util.Config;
import com.dotmarketing.util.ConfigUtils;
import com.dotmarketing.util.Logger;
import com.google.common.annotations.VisibleForTesting;
import graphql.schema.GraphQLArgument;
import graphql.schema.GraphQLFieldDefinition;
import graphql.schema.GraphQLObjectType;
@@ -27,22 +29,29 @@
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;

public class GraphqlAPIImpl implements GraphqlAPI {

private volatile GraphQLSchema schema;

private final Set<GraphQLTypesProvider> typesProviders = new HashSet<>();
private final Set<GraphQLFieldsProvider> fieldsProviders = new HashSet<>();

public GraphqlAPIImpl() {
private GraphQLSchemaCache schemaCache;

@VisibleForTesting
protected GraphqlAPIImpl(final GraphQLSchemaCache schemaCache) {
typesProviders.add(ContentAPIGraphQLTypesProvider.INSTANCE);
typesProviders.add(PageAPIGraphQLTypesProvider.INSTANCE);
typesProviders.add(QueryMetadataTypeProvider.INSTANCE);
fieldsProviders.add(ContentAPIGraphQLFieldsProvider.INSTANCE);
fieldsProviders.add(PageAPIGraphQLFieldsProvider.INSTANCE);
fieldsProviders.add(QueryMetadataFieldProvider.INSTANCE);
this.schemaCache = schemaCache;
}

public GraphqlAPIImpl() {
this(CacheLocator.getGraphQLSchemaCache());
}

/**
@@ -60,30 +69,28 @@ public GraphqlAPIImpl() {
*/
@Override
public GraphQLSchema getSchema() throws DotDataException {
GraphQLSchema innerSchema = this.schema;
if(innerSchema == null) {
synchronized (this) {
innerSchema = this.schema;
if(innerSchema == null) {
this.schema = innerSchema = generateSchema();
}
}
Optional<GraphQLSchema> schema = schemaCache.getSchema();

if(!schema.isPresent()) {
final GraphQLSchema generatedSchema = generateSchema();
schemaCache.putSchema(generatedSchema);
return generatedSchema;
} else {
return schema.get();
}

printSchema();
return innerSchema;
}

/**
* Nullifies the schema so it is regenerated next time it is fetched
*/
@Override
public void invalidateSchema() {
this.schema = null;
schemaCache.removeSchema();
}

private void printSchema() {
if (Config.getBooleanProperty("PRINT_GRAPHQL_SCHEMA", false)) {
@Override
public void printSchema() {
if (Config.getBooleanProperty("GRAPHQL_PRINT_SCHEMA", false)) {
SchemaPrinter printer = new SchemaPrinter();
try {
File graphqlDirectory = new File(ConfigUtils.getGraphqlPath());
@@ -94,15 +101,16 @@ private void printSchema() {

File schemaFile = new File(graphqlDirectory.getPath() + File.separator + "schema.graphqls");
schemaFile.createNewFile();
Files.write(schemaFile.toPath(), printer.print(schema).getBytes());
} catch (IOException e) {
Files.write(schemaFile.toPath(), printer.print(getSchema()).getBytes());
} catch (DotDataException | IOException e) {
Logger.error(this, "Error printing schema", e);
}
}
}

@LogTime(loggingLevel = "INFO")
private GraphQLSchema generateSchema() {
@VisibleForTesting
protected GraphQLSchema generateSchema() {
final Set<GraphQLType> graphQLTypes = new HashSet<>();

for (GraphQLTypesProvider typesProvider : typesProviders) {
@@ -1,5 +1,6 @@
package com.dotcms.graphql.business;

import static graphql.Scalars.GraphQLBoolean;
import static graphql.Scalars.GraphQLString;
import static graphql.schema.GraphQLFieldDefinition.newFieldDefinition;

@@ -39,6 +40,11 @@
.name("persona")
.type(GraphQLString)
.build())
.argument(GraphQLArgument.newArgument()
.name("fireRules")
.type(GraphQLBoolean)
.defaultValue(false)
.build())
.type(PageAPIGraphQLTypesProvider.INSTANCE.getTypesMap().get("Page"))
.dataFetcher(new PageDataFetcher()).build());
}
@@ -1,6 +1,10 @@
package com.dotcms.graphql.business;

import static com.dotmarketing.portlets.contentlet.model.Contentlet.ARCHIVED_KEY;
import static com.dotmarketing.portlets.contentlet.model.Contentlet.MOD_USER_KEY;
import static com.dotmarketing.portlets.contentlet.model.Contentlet.OWNER_KEY;
import static graphql.Scalars.GraphQLBoolean;
import static graphql.Scalars.GraphQLInt;
import static graphql.Scalars.GraphQLLong;
import static graphql.Scalars.GraphQLString;
import static graphql.schema.GraphQLList.list;
@@ -10,17 +14,26 @@
import com.dotcms.graphql.datafetcher.LanguageDataFetcher;
import com.dotcms.graphql.datafetcher.MapFieldPropertiesDataFetcher;
import com.dotcms.graphql.datafetcher.UserDataFetcher;
import com.dotcms.graphql.datafetcher.page.ContainersDataFetcher;
import com.dotcms.graphql.datafetcher.page.LayoutDataFetcher;
import com.dotcms.graphql.datafetcher.page.PageRenderDataFetcher;
import com.dotcms.graphql.datafetcher.page.RenderedContainersDataFetcher;
import com.dotcms.graphql.datafetcher.page.TemplateDataFetcher;
import com.dotcms.graphql.datafetcher.page.ViewAsDataFetcher;
import com.dotcms.graphql.util.TypeUtil;
import com.dotcms.graphql.util.TypeUtil.TypeFetcher;
import com.dotcms.visitor.domain.Geolocation;
import com.dotcms.visitor.domain.Visitor;
import com.dotcms.visitor.domain.Visitor.AccruedTag;
import com.dotmarketing.beans.ContainerStructure;
import com.dotmarketing.beans.Host;
import com.dotmarketing.business.Permissionable;
import com.dotmarketing.cms.urlmap.URLMapInfo;
import com.dotmarketing.portlets.containers.business.FileAssetContainerUtil;
import com.dotmarketing.portlets.containers.model.Container;
import com.dotmarketing.portlets.containers.model.FileAssetContainer;
import com.dotmarketing.portlets.contentlet.model.Contentlet;
import com.dotmarketing.portlets.htmlpageasset.business.render.ContainerRaw;
import com.dotmarketing.portlets.htmlpageasset.business.render.page.ViewAsPageStatus;
import com.dotmarketing.portlets.templates.design.bean.ContainerHolder;
import com.dotmarketing.portlets.templates.design.bean.ContainerUUID;
@@ -36,7 +49,9 @@
import io.vavr.control.Try;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.function.Function;

@@ -109,6 +124,8 @@

pageFields.put("layout", new TypeFetcher(GraphQLTypeReference.typeRef("Layout"),
new LayoutDataFetcher()));
pageFields.put("containers", new TypeFetcher(list(GraphQLTypeReference.typeRef("Container")),
new ContainersDataFetcher()));

typeMap.put("Page", TypeUtil.createObjectType("Page", pageFields));

@@ -346,6 +363,208 @@
typeMap.put("ContainerUUID", TypeUtil.createObjectType("ContainerUUID",
containerUUIDFields));

// Container type
final Map<String, TypeFetcher> containerFields = new HashMap<>();
containerFields.put(ARCHIVED_KEY, new TypeFetcher(GraphQLBoolean,
PropertyDataFetcher.fetching((Function<ContainerRaw, Boolean>)
(containerRaw)->Try.of(()->containerRaw.getContainer().isArchived())
.getOrElse(false))));
containerFields.put("categoryId", new TypeFetcher(GraphQLString,
PropertyDataFetcher.fetching((Function<ContainerRaw, String>)
(containerRaw)->containerRaw.getContainer().getCategoryId())));
containerFields.put("identifier", new TypeFetcher(GraphQLString,
PropertyDataFetcher.fetching((Function<ContainerRaw, String>)
(containerRaw)->containerRaw.getContainer().getIdentifier())));
containerFields.put("deleted", new TypeFetcher(GraphQLBoolean,
PropertyDataFetcher.fetching((Function<ContainerRaw, Boolean>)
(containerRaw)->Try.of(()->containerRaw.getContainer().isDeleted())
.getOrElse(false))));
containerFields.put("friendlyName", new TypeFetcher(GraphQLString,
PropertyDataFetcher.fetching((Function<ContainerRaw, String>)
(containerRaw)->containerRaw.getContainer().getFriendlyName())));
containerFields.put("host", new TypeFetcher(GraphQLTypeReference.typeRef("Site"),
PropertyDataFetcher.fetching((Function<ContainerRaw, Host>)
(containerRaw)-> {
final Container container = containerRaw.getContainer();
if (FileAssetContainerUtil.getInstance().isFileAssetContainer(container)) {
final FileAssetContainer fileAssetContainer = (FileAssetContainer) container;
return fileAssetContainer.getHost();
} else {
return null;
}
})));
containerFields.put("iDate", new TypeFetcher(GraphQLString,
PropertyDataFetcher.fetching((Function<ContainerRaw, String>)
(containerRaw)->containerRaw.getContainer().getiDate().toString())));
containerFields.put("idate", new TypeFetcher(GraphQLString,
PropertyDataFetcher.fetching((Function<ContainerRaw, String>)
(containerRaw)->containerRaw.getContainer().getIDate().toString())));
containerFields.put("inode", new TypeFetcher(GraphQLString,
PropertyDataFetcher.fetching((Function<ContainerRaw, String>)
(containerRaw)->containerRaw.getContainer().getInode())));
containerFields.put("languageId", new TypeFetcher(GraphQLLong,
PropertyDataFetcher.fetching((Function<ContainerRaw, Long>)
(containerRaw)-> {
final Container container = containerRaw.getContainer();
if (FileAssetContainerUtil.getInstance().isFileAssetContainer(container)) {
final FileAssetContainer fileAssetContainer = (FileAssetContainer) container;
return fileAssetContainer.getLanguageId();
} else {
return 0L;
}
})));
containerFields.put("live", new TypeFetcher(GraphQLBoolean,
PropertyDataFetcher.fetching((Function<ContainerRaw, Boolean>)
(containerRaw)->Try.of(()->containerRaw.getContainer().isLive())
.getOrElse(false))));
containerFields.put("locked", new TypeFetcher(GraphQLBoolean,
PropertyDataFetcher.fetching((Function<ContainerRaw, Boolean>)
(containerRaw)->Try.of(()->containerRaw.getContainer().isLocked())
.getOrElse(false))));
containerFields.put("luceneQuery", new TypeFetcher(GraphQLString,
PropertyDataFetcher.fetching((Function<ContainerRaw, String>)
(containerRaw)->containerRaw.getContainer().getLuceneQuery())));
containerFields.put("maxContentlets", new TypeFetcher(GraphQLInt,
PropertyDataFetcher.fetching((Function<ContainerRaw, Integer>)
(containerRaw)->containerRaw.getContainer().getMaxContentlets())));
containerFields.put("modDate", new TypeFetcher(GraphQLString,
PropertyDataFetcher.fetching((Function<ContainerRaw, String>)
(containerRaw)->containerRaw.getContainer().getModDate().toString())));
containerFields.put(MOD_USER_KEY, new TypeFetcher(GraphQLTypeReference.typeRef("User"),
new UserDataFetcher()));
containerFields.put("name", new TypeFetcher(GraphQLString,
PropertyDataFetcher.fetching((Function<ContainerRaw, String>)
(containerRaw)->containerRaw.getContainer().getName())));
containerFields.put("new", new TypeFetcher(GraphQLBoolean,
PropertyDataFetcher.fetching((Function<ContainerRaw, Boolean>)
(containerRaw)->Try.of(()->containerRaw.getContainer().isNew())
.getOrElse(false))));
containerFields.put("notes", new TypeFetcher(GraphQLString,
PropertyDataFetcher.fetching((Function<ContainerRaw, String>)
(containerRaw)->containerRaw.getContainer().getNotes())));
containerFields.put(OWNER_KEY, new TypeFetcher(GraphQLTypeReference.typeRef("User"),
new UserDataFetcher()));
containerFields.put("parentPermissionable", new TypeFetcher(
GraphQLTypeReference.typeRef("Site"),
PropertyDataFetcher.fetching((Function<ContainerRaw, Host>)
(containerRaw)-> (Host) Try.of(()->
containerRaw.getContainer().getParentPermissionable())
.getOrElse((Permissionable) null))));
containerFields.put("path", new TypeFetcher(GraphQLString,
PropertyDataFetcher.fetching((Function<ContainerRaw, String>)
(containerRaw)-> {
final Container container = containerRaw.getContainer();
if (FileAssetContainerUtil.getInstance().isFileAssetContainer(container)) {
final FileAssetContainer fileAssetContainer = (FileAssetContainer) container;

return FileAssetContainerUtil.getInstance().getFullPath(fileAssetContainer);
} else {
return null;
}
})));
containerFields.put("permissionId", new TypeFetcher(GraphQLString,
PropertyDataFetcher.fetching((Function<ContainerRaw, String>)
(containerRaw)->containerRaw.getContainer().getPermissionId())));
containerFields.put("permissionType", new TypeFetcher(GraphQLString,
PropertyDataFetcher.fetching((Function<ContainerRaw, String>)
(containerRaw)->containerRaw.getContainer().getPermissionType())));
containerFields.put("postLoop", new TypeFetcher(GraphQLString,
PropertyDataFetcher.fetching((Function<ContainerRaw, String>)
(containerRaw)->containerRaw.getContainer().getPostLoop())));
containerFields.put("preLoop", new TypeFetcher(GraphQLString,
PropertyDataFetcher.fetching((Function<ContainerRaw, String>)
(containerRaw)->containerRaw.getContainer().getPreLoop())));
containerFields.put("showOnMenu", new TypeFetcher(GraphQLBoolean,
PropertyDataFetcher.fetching((Function<ContainerRaw, Boolean>)
(containerRaw)->Try.of(()->containerRaw.getContainer().isShowOnMenu())
.getOrElse(false))));
containerFields.put("sortOrder", new TypeFetcher(GraphQLInt,
PropertyDataFetcher.fetching((Function<ContainerRaw, Integer>)
(containerRaw)->containerRaw.getContainer().getSortOrder())));
containerFields.put("source", new TypeFetcher(GraphQLString,
PropertyDataFetcher.fetching((Function<ContainerRaw, String>)
(containerRaw)->containerRaw.getContainer().getSource().name())));
containerFields.put("staticify", new TypeFetcher(GraphQLBoolean,
PropertyDataFetcher.fetching((Function<ContainerRaw, Boolean>)
(containerRaw)->Try.of(()->containerRaw.getContainer().isStaticify())
.getOrElse(false))));
containerFields.put("title", new TypeFetcher(GraphQLString,
PropertyDataFetcher.fetching((Function<ContainerRaw, String>)
(containerRaw)->containerRaw.getContainer().getTitle())));
containerFields.put("type", new TypeFetcher(GraphQLString,
PropertyDataFetcher.fetching((Function<ContainerRaw, String>)
(containerRaw)->containerRaw.getContainer().getType())));
containerFields.put("useDiv", new TypeFetcher(GraphQLBoolean,
PropertyDataFetcher.fetching((Function<ContainerRaw, Boolean>)
(containerRaw)->Try.of(()->containerRaw.getContainer().isUseDiv())
.getOrElse(false))));
containerFields.put("versionId", new TypeFetcher(GraphQLString,
PropertyDataFetcher.fetching((Function<ContainerRaw, String>)
(containerRaw)->containerRaw.getContainer().getVersionId())));
containerFields.put("versionType", new TypeFetcher(GraphQLString,
PropertyDataFetcher.fetching((Function<ContainerRaw, String>)
(containerRaw)->containerRaw.getContainer().getVersionType())));
containerFields.put("working", new TypeFetcher(GraphQLBoolean,
PropertyDataFetcher.fetching((Function<ContainerRaw, Boolean>)
(containerRaw)->Try.of(()->containerRaw.getContainer().isWorking())
.getOrElse(false))));

containerFields.put("rendered", new TypeFetcher(list(GraphQLTypeReference.typeRef("RenderedContainer"))
, new RenderedContainersDataFetcher()));

containerFields.put("containerStructures", new TypeFetcher(
list(GraphQLTypeReference.typeRef("ContainerStructure")),
PropertyDataFetcher.fetching(ContainerRaw::getContainerStructures)));

containerFields.put("containerContentlets", new TypeFetcher(
list(GraphQLTypeReference.typeRef("ContainerContentlets")),
PropertyDataFetcher.fetching((Function<ContainerRaw, Set<Entry<String, List<Contentlet>>>>)
(containerRaw)->containerRaw.getContentlets().entrySet())));

// RenderedContainer type
final Map<String, TypeFetcher> renderedContainerFields = new HashMap<>();
renderedContainerFields.put("uuid", new TypeFetcher(GraphQLString,
PropertyDataFetcher.fetching((Function<Entry<String, String>, String>)
Entry::getKey)));
renderedContainerFields.put("render", new TypeFetcher(GraphQLString,
PropertyDataFetcher.fetching((Function<Entry<String, String>, String>)
Entry::getValue)));

typeMap.put("RenderedContainer", TypeUtil.createObjectType("RenderedContainer",
renderedContainerFields));

// ContainerStructure type
final Map<String, TypeFetcher> containerStructureFields = new HashMap<>();
containerStructureFields.put("id", new TypeFetcher(GraphQLString,
PropertyDataFetcher.fetching(ContainerStructure::getId)));
containerStructureFields.put("structureId", new TypeFetcher(GraphQLString,
PropertyDataFetcher.fetching(ContainerStructure::getStructureId)));
containerStructureFields.put("containerInode", new TypeFetcher(GraphQLString,
PropertyDataFetcher.fetching(ContainerStructure::getContainerInode)));
containerStructureFields.put("containerId", new TypeFetcher(GraphQLString,
PropertyDataFetcher.fetching(ContainerStructure::getContainerId)));
containerStructureFields.put("code", new TypeFetcher(GraphQLString,
PropertyDataFetcher.fetching(ContainerStructure::getCode)));

typeMap.put("ContainerStructure", TypeUtil.createObjectType("ContainerStructure",
containerStructureFields));

// ContainerContentlets type
final Map<String, TypeFetcher> containerContentletsFields = new HashMap<>();
containerContentletsFields.put("uuid", new TypeFetcher(GraphQLString,
PropertyDataFetcher.fetching((Function<Entry<String, List<Contentlet>>, String>)
Entry::getKey)));
containerContentletsFields.put("contentlets", new TypeFetcher(
list(GraphQLTypeReference.typeRef("Contentlet")),
PropertyDataFetcher.fetching((Function<Entry<String, List<Contentlet>>, List<Contentlet>>)
Entry::getValue)));

typeMap.put("ContainerContentlets", TypeUtil.createObjectType("ContainerContentlets",
containerContentletsFields));

typeMap.put("Container", TypeUtil.createObjectType("Container",
containerFields));

return typeMap.values();

}
@@ -0,0 +1,20 @@
package com.dotcms.graphql.datafetcher;

import com.dotmarketing.portlets.contentlet.model.Contentlet;
import com.dotmarketing.util.Logger;
import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;

public class ContentMapDataFetcher implements DataFetcher<Object> {
@Override
public Object get(final DataFetchingEnvironment environment) throws Exception {
try {
final Contentlet contentlet = environment.getSource();
final String key = environment.getArgument("key");
return contentlet.get(key);
} catch (Exception e) {
Logger.error(this, e.getMessage(), e);
throw e;
}
}
}
@@ -5,6 +5,7 @@
import com.dotmarketing.business.NoSuchUserException;
import com.dotmarketing.exception.DotSecurityException;
import com.dotmarketing.portlets.contentlet.model.Contentlet;
import com.dotmarketing.portlets.htmlpageasset.business.render.ContainerRaw;
import com.dotmarketing.util.Logger;
import com.dotmarketing.util.UtilMethods;
import com.liferay.portal.model.User;
@@ -52,15 +53,17 @@
}

private String getUserId(final DataFetchingEnvironment environment) {
String userId;
try {
final Contentlet contentlet = environment.getSource();
userId = contentlet.getStringProperty(environment.getField().getName());
} catch (ClassCastException e) {
final Map<Object, Object> map = environment.getSource();
userId = (String) map.get(environment.getField().getName());
String userId = null;
if(environment.getSource() instanceof Contentlet) {
userId = ((Contentlet)environment.getSource()).getStringProperty(environment.getField().getName());
} else if(environment.getSource() instanceof Map) {
userId = (String) ((Map<Object, Object>)environment.getSource())
.get(environment.getField().getName());
} else if(environment.getSource() instanceof ContainerRaw && environment.getField().getName().equals("modUser")) {
userId = ((ContainerRaw)environment.getSource()).getContainer().getModUser();
} else if(environment.getSource() instanceof ContainerRaw && environment.getField().getName().equals("owner")) {
userId = ((ContainerRaw)environment.getSource()).getContainer().getOwner();
}

return userId;
}
}
@@ -0,0 +1,50 @@
package com.dotcms.graphql.datafetcher.page;

import com.dotcms.graphql.DotGraphQLContext;
import com.dotcms.rendering.velocity.services.PageRenderUtil;
import com.dotmarketing.beans.Host;
import com.dotmarketing.business.APILocator;
import com.dotmarketing.portlets.contentlet.model.Contentlet;
import com.dotmarketing.portlets.htmlpageasset.business.render.ContainerRaw;
import com.dotmarketing.portlets.htmlpageasset.model.HTMLPageAsset;
import com.dotmarketing.portlets.templates.design.bean.TemplateLayout;
import com.dotmarketing.util.Logger;
import com.dotmarketing.util.PageMode;
import com.liferay.portal.model.User;
import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;
import java.util.List;
import javax.servlet.http.HttpServletRequest;

/**
* This DataFetcher returns the {@link TemplateLayout} associated to the requested {@link HTMLPageAsset}.
*/
public class ContainersDataFetcher implements DataFetcher<List<ContainerRaw>> {
@Override
public List<ContainerRaw> get(final DataFetchingEnvironment environment) throws Exception {
try {
final DotGraphQLContext context = environment.getContext();
final User user = context.getUser();
final Contentlet page = environment.getSource();
final String pageModeAsString = (String) context.getParam("pageMode");
final String languageId = (String) context.getParam("languageId");

final PageMode mode = PageMode.get(pageModeAsString);
final HttpServletRequest request = context.getHttpServletRequest();

final HTMLPageAsset pageAsset = APILocator.getHTMLPageAssetAPI()
.fromContentlet(page);

final Host site = APILocator.getHostAPI().find(page.getHost(), user, false);
PageRenderUtil pageRenderUtil = new PageRenderUtil(pageAsset, user, mode,
Long.parseLong(languageId), site);

context.addParam("pageRenderUtil", pageRenderUtil);

return pageRenderUtil.getContainersRaw();
} catch (Exception e) {
Logger.error(this, e.getMessage(), e);
throw e;
}
}
}
@@ -10,6 +10,8 @@
import com.dotmarketing.portlets.htmlpageasset.business.render.PageContext;
import com.dotmarketing.portlets.htmlpageasset.business.render.PageContextBuilder;
import com.dotmarketing.portlets.htmlpageasset.model.HTMLPageAsset;
import com.dotmarketing.portlets.rules.business.RulesEngine;
import com.dotmarketing.portlets.rules.model.Rule.FireOn;
import com.dotmarketing.util.Logger;
import com.dotmarketing.util.PageMode;
import com.dotmarketing.util.UtilMethods;
@@ -18,6 +20,7 @@
import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
* This DataFetcher returns a {@link HTMLPageAsset} given an URL. It also takes optional parameters
@@ -35,16 +38,22 @@ public Contentlet get(final DataFetchingEnvironment environment) throws Exceptio
final HttpServletRequest request = ((DotGraphQLContext) environment.getContext())
.getHttpServletRequest();

final HttpServletResponse response = ((DotGraphQLContext) environment.getContext())
.getHttpServletResponse();

final String url = environment.getArgument("url");
final String languageId = environment.getArgument("languageId");
final String pageModeAsString = environment.getArgument("pageMode")
!= null ? environment.getArgument("pageMode") : PageMode.LIVE.name();
final boolean fireRules = environment.getArgument("fireRules");

context.addParam("url", url);
context.addParam("languageId", languageId);
context.addParam("pageMode", pageModeAsString);
context.addParam("fireRules", fireRules);

final PageMode mode = PageMode.get(pageModeAsString);
PageMode.setPageMode(request, mode);

// we need to set the language to the request
if(UtilMethods.isSet(languageId)) {
@@ -68,8 +77,15 @@ public Contentlet get(final DataFetchingEnvironment environment) throws Exceptio
}

final HTMLPageAsset pageAsset = pageUrl.getHTMLPage();
context.addParam("page", pageAsset);
pageAsset.getMap().put("URLMapContent", pageUrl.getUrlMapInfo());

if(fireRules) {
Logger.info(this, "Rules will be fired");
request.setAttribute("fireRules", true);
RulesEngine.fireRules(request, response, pageAsset, FireOn.EVERY_PAGE);
}

final DotContentletTransformer transformer = new DotTransformerBuilder()
.graphQLDataFetchOptions().content(pageAsset).forUser(user).build();

@@ -0,0 +1,50 @@
package com.dotcms.graphql.datafetcher.page;

import com.dotcms.graphql.DotGraphQLContext;
import com.dotcms.rendering.velocity.services.PageRenderUtil;
import com.dotmarketing.portlets.htmlpageasset.business.render.ContainerRaw;
import com.dotmarketing.portlets.htmlpageasset.business.render.ContainerRenderedBuilder;
import com.dotmarketing.portlets.htmlpageasset.model.HTMLPageAsset;
import com.dotmarketing.portlets.templates.design.bean.TemplateLayout;
import com.dotmarketing.util.Logger;
import com.dotmarketing.util.PageMode;
import com.dotmarketing.util.VelocityUtil;
import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.velocity.context.Context;

/**
* This DataFetcher returns the {@link TemplateLayout} associated to the requested {@link HTMLPageAsset}.
*/
public class RenderedContainersDataFetcher implements DataFetcher<Set<Entry<String, String>>> {
@Override
public Set<Entry<String, String>> get(final DataFetchingEnvironment environment) throws Exception {
try {
final DotGraphQLContext context = environment.getContext();
final ContainerRaw containerRaw = environment.getSource();
final String pageModeAsString = (String) context.getParam("pageMode");

final PageMode mode = PageMode.get(pageModeAsString);
final HttpServletRequest request = context.getHttpServletRequest();
final HttpServletResponse response = context.getHttpServletResponse();

final PageRenderUtil pageRenderUtil = (PageRenderUtil) context.getParam("pageRenderUtil");

final Context velocityContext = pageRenderUtil
.addAll(VelocityUtil.getInstance().getContext(request, response));

final Map<String, String> uuidsRendered = ContainerRenderedBuilder.
render(velocityContext, mode, containerRaw);

return uuidsRendered.entrySet();
} catch (Exception e) {
Logger.error(this, e.getMessage(), e);
throw e;
}
}
}
@@ -7,6 +7,7 @@
import com.dotmarketing.util.Logger;
import com.dotmarketing.util.UtilMethods;
import graphql.GraphQLException;
import graphql.schema.GraphQLArgument;
import graphql.schema.GraphQLFieldDefinition;
import java.util.ArrayList;
import java.util.List;
@@ -60,13 +61,22 @@ public static GraphQLObjectType createObjectType(final String typeName,
List<GraphQLFieldDefinition> fieldDefinitionList = new ArrayList<>();
fieldsTypesAndFetchers.forEach((key, value) -> {
try {
fieldDefinitionList.add(newFieldDefinition()
.name(key)
.type(value.getType())
.dataFetcher(value.getDataFetcher() != null
? value.getDataFetcher()
: new PropertyDataFetcher<String>(key)).build()
);
if(value.getArgument()!=null) {
fieldDefinitionList.add(newFieldDefinition()
.name(key)
.argument(value.getArgument())
.type(value.getType())
.dataFetcher(value.getDataFetcher() != null
? value.getDataFetcher()
: new PropertyDataFetcher<String>(key)).build());
} else {
fieldDefinitionList.add(newFieldDefinition()
.name(key)
.type(value.getType())
.dataFetcher(value.getDataFetcher() != null
? value.getDataFetcher()
: new PropertyDataFetcher<String>(key)).build());
}
} catch (GraphQLException e) {
Logger.error("Error creating GraphQL Type. Type name: " + key, e);
}
@@ -79,11 +89,22 @@ public static GraphQLInterfaceType createInterfaceType(final String typeName,
final TypeResolver typeResolver) {
final GraphQLInterfaceType.Builder builder = GraphQLInterfaceType.newInterface().name(typeName);

fieldsTypesAndFetchers.forEach((key, value) -> builder.field(newFieldDefinition()
.name(key)
.type(value.getType())
.dataFetcher(value.getDataFetcher())
));
fieldsTypesAndFetchers.forEach((key, value) -> {
if(value.getArgument()!=null) {
builder.field(newFieldDefinition()
.name(key)
.argument(value.getArgument())
.type(value.getType())
.dataFetcher(value.getDataFetcher())
);
} else {
builder.field(newFieldDefinition()
.name(key)
.type(value.getType())
.dataFetcher(value.getDataFetcher())
);
}
});

builder.typeResolver(typeResolver);
return builder.build();
@@ -93,6 +114,10 @@ public static String collectionizedName(final String typeName) {
return typeName + "Collection";
}

public static String oldCollectionizedName(final String typeName) {
return typeName.substring(0, 1).toLowerCase() + typeName.substring(1) + "Collection";
}

public static String singularizeCollectionName(final String collectionName) {
return collectionName.replaceAll("Collection", "");
}
@@ -104,15 +129,21 @@ public static String singularizeBaseTypeCollectionName(final String baseTypeColl
public static class TypeFetcher {
private final GraphQLOutputType type;
private final DataFetcher dataFetcher;
private final GraphQLArgument argument;

public TypeFetcher(GraphQLOutputType type) {
this.type = type;
this.dataFetcher = new FieldDataFetcher();
this(type, new FieldDataFetcher(), null);
}

public TypeFetcher(final GraphQLOutputType type, final DataFetcher dataFetcher) {
this(type, dataFetcher, null);
}

public TypeFetcher(final GraphQLOutputType type, final DataFetcher dataFetcher,
final GraphQLArgument argument) {
this.type = type;
this.dataFetcher = dataFetcher;
this.argument = argument;
}

public GraphQLOutputType getType() {
@@ -122,5 +153,9 @@ public GraphQLOutputType getType() {
public DataFetcher getDataFetcher() {
return dataFetcher;
}

public GraphQLArgument getArgument() {
return argument;
}
}
}
@@ -72,7 +72,7 @@ public boolean generateIntegrityResults(final String endpointId) throws Exceptio
}

@Override
public void executeFix(final String endpointId) throws DotDataException, DotSecurityException {
public void executeFix(final String key) throws DotDataException, DotSecurityException {
DotConnect dc = new DotConnect();
// Get information from IR.
final String getResultsQuery = new StringBuilder("SELECT ")
@@ -81,7 +81,7 @@ public void executeFix(final String endpointId) throws DotDataException, DotSecu
.append(getIntegrityType().getResultsTableName()).append(" WHERE endpoint_id = ?")
.toString();
dc.setSQL(getResultsQuery);
dc.addParam(endpointId);
dc.addParam(key);
List<Map<String, Object>> results = dc.loadObjectResults();

// Generate counter to know how many version are for each identifier
@@ -116,7 +116,7 @@ public boolean generateIntegrityResults(final String endpointId) throws Exceptio
* changed, which will cause an issue.</li>
* </ol>
*
* @param serverId
* @param key
* - The ID of the endpoint where the data will be fixed.
* @throws DotDataException
* An error occurred when interacting with the database.
@@ -125,12 +125,12 @@ public boolean generateIntegrityResults(final String endpointId) throws Exceptio
* action.
*/
@Override
public void executeFix(final String endpointId) throws DotDataException, DotSecurityException {
public void executeFix(final String key) throws DotDataException, DotSecurityException {
// Get the information of the IR.
DotConnect dc = new DotConnect();
dc.setSQL("SELECT " + getIntegrityType().getFirstDisplayColumnLabel() + ", local_identifier, remote_identifier, local_working_inode, remote_working_inode, local_live_inode, remote_live_inode, language_id FROM "
+ getIntegrityType().getResultsTableName() + " WHERE endpoint_id = ? ORDER BY " + getIntegrityType().getFirstDisplayColumnLabel());
dc.addParam(endpointId);
dc.addParam(key);
List<Map<String, Object>> results = dc.loadObjectResults();

// We need to load the map with the affected identifiers, inodes and languages
@@ -216,12 +216,12 @@ public boolean generateIntegrityResults(String endpointId) throws Exception {
* updating it's inode and identifier with the ones received from the other
* end
*
* @param serverId
* @param remoteIP
* @throws DotDataException
* @throws DotSecurityException
*/
@Override
public void executeFix(final String serverId) throws DotDataException, DotSecurityException {
public void executeFix(final String key) throws DotDataException, DotSecurityException {

DotConnect dc = new DotConnect();

@@ -230,7 +230,7 @@ public void executeFix(final String serverId) throws DotDataException, DotSecuri
// folder
dc.setSQL("select local_inode, remote_inode, local_identifier, remote_identifier from "
+ getIntegrityType().getResultsTableName() + " where endpoint_id = ?");
dc.addParam(serverId);
dc.addParam(key);
List<Map<String, Object>> results = dc.loadObjectResults();

for (Map<String, Object> result : results) {
@@ -68,12 +68,13 @@
* When there is conflicts, this method will fix them depending of the
* implementation of every checker
*
* @param endpointId
* Server identifier were we need to fix conflicts
* @param key
* JWT Token key when using JWT token in Push Publish or
* Server identifier were we need to fix conflicts when not using JWT token
* @throws DotDataException
* @throws DotSecurityException
*/
public void executeFix(final String endpointId) throws DotDataException, DotSecurityException;
public void executeFix(final String key) throws DotDataException, DotSecurityException;

/**
* Get temporal table names for an specific endpoint id.
@@ -1,5 +1,7 @@
package com.dotcms.integritycheckers;

import com.dotcms.business.WrapInTransaction;
import com.dotcms.http.DotExecutionException;
import com.dotcms.repackage.com.csvreader.CsvReader;
import com.dotcms.repackage.com.csvreader.CsvWriter;
import com.dotcms.rest.IntegrityResource;
@@ -14,6 +16,7 @@
import com.dotmarketing.util.Logger;
import com.dotmarketing.util.MaintenanceUtil;
import com.dotmarketing.util.UtilMethods;
import org.apache.commons.lang3.StringUtils;

import java.io.*;
import java.math.BigDecimal;
@@ -27,6 +30,9 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
import java.util.function.BiConsumer;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
@@ -57,6 +63,14 @@
*/
public class IntegrityUtil {

public static final String INTEGRITY_DATA_STATUS = "status";
public static final String INTEGRITY_DATA_ERROR_MESSAGE = "error_message";
public static final String INTEGRITY_DATA_TO_CHECK_ZIP_FILENAME = "DataToCheck.zip";
public static final String INTEGRITY_DATA_TO_FIX_ZIP_FILENAME = "DataToFix.zip";
public static final String INTEGRITY_DATA_STATUS_FILENAME = "DataStatus.properties";
public static final String REQUESTER_KEY = "requesterKey";
public static final String INTEGRITY_DATA_REQUEST_ID = "integrityDataRequestId";

private File generateDataToFixCSV(String outputPath, String endpointId, IntegrityType type)
throws DotDataException, IOException {
File csvFile = null;
@@ -243,83 +257,234 @@ public static void unzipFile(InputStream zipFile, String outputDir) throws Excep
}
}

/**
* Resolves the integrity data path based on provided endpoint id.
*
* @param endpointId endpoint if
* @return integrity data path as a string
*/
public static String getIntegrityDataPath(final String endpointId) {
return ConfigUtils.getIntegrityPath() + File.separator + endpointId;
}

/**
* Generated the integrity data file path based on provided endpointId and the file name.
* File names to be used: DataToCheck.zip, DataToFix.zip and DataStatus.properties.
*
* @param endpointId endpoint gid
* @param dataFile data filename
* @return path to filename
*/
public static String getIntegrityDataFilePath(final String endpointId, final String dataFile) {
return getIntegrityDataPath(endpointId) + File.separator + dataFile;
}

/**
* Tells whether a integrity file exists based on provided endpointId and the file name.
*
* @param endpointId endpoint id
* @param dataFile data filename
* @return path to filename
*/
public static boolean doesIntegrityDataFileExist(final String endpointId, final String dataFile) {
return new File(getIntegrityDataFilePath(endpointId, dataFile)).exists();
}

/**
* Gets a {@link IntegrityDataExecutionMetadata} instance based on provided endpoint id which contains metadata of the
* integrity data generation execution.
*
* @param requesterKey endpoint id
* @return Optional wrapping the integrity generation metadata
*/
public static Optional<IntegrityDataExecutionMetadata> getIntegrityMetadata(final String requesterKey) {
final File statusFile = new File(getIntegrityDataFilePath(requesterKey, INTEGRITY_DATA_STATUS_FILENAME));
if (!statusFile.exists()) {
return Optional.empty();
}

final Properties statusData = new Properties();
try {
statusData.load(new FileInputStream(statusFile));
} catch (IOException e) {
Logger.error(
IntegrityUtil.class,
String.format("Could load status from %s", statusFile.getAbsolutePath()),
e);
return Optional.empty();
}

return Optional.of(new IntegrityDataExecutionMetadata(
statusData.getProperty(REQUESTER_KEY),
statusData.getProperty(INTEGRITY_DATA_REQUEST_ID),
statusData.getProperty(INTEGRITY_DATA_STATUS),
statusData.getProperty(INTEGRITY_DATA_ERROR_MESSAGE)));
}

/**
* Removes any file leftovers from other integrity checks at the location based on the endpoint id.
* That will be any data check and status files.
*
* @param endpointId endpoint id
*/
public static void cleanUpIntegrityData(String endpointId) {
final BiConsumer<String, String> resetConsumer = (id, filename) -> {
final File file = new File(getIntegrityDataFilePath(id, filename));
if (file.exists()) {
file.delete();
}
};

resetConsumer.accept(endpointId, INTEGRITY_DATA_STATUS_FILENAME);
resetConsumer.accept(endpointId, INTEGRITY_DATA_TO_CHECK_ZIP_FILENAME);
}

/**
* Saves a integrity data generation metadata in a to-be-discovered location so it can be read by concurrent parts
* that need to know what is the status of the integrity data generation.
* The data is saved as {@link Properties} file.
*
* @param integrityDataExecutionMetadata execution metadata
*/
private static void saveIntegrityDataStatus(IntegrityDataExecutionMetadata integrityDataExecutionMetadata) {
final String endpointId = integrityDataExecutionMetadata.getEndpointId();
final File integrityDir = new File(getIntegrityDataPath(endpointId));
if (!integrityDir.exists()) {
integrityDir.mkdir();
}

final File statusFile = new File(getIntegrityDataFilePath(endpointId, INTEGRITY_DATA_STATUS_FILENAME));
/*if (statusFile.exists()) {
statusFile.delete();
}*/

final Properties statusData = new Properties();
final BiConsumer<String, String> addData = (data, name) -> {
if (StringUtils.isNotBlank(data)) {
statusData.setProperty(name, data);
}
};

addData.accept(endpointId, REQUESTER_KEY);
addData.accept(integrityDataExecutionMetadata.getRequestId(), INTEGRITY_DATA_REQUEST_ID);
statusData.setProperty(INTEGRITY_DATA_STATUS, integrityDataExecutionMetadata.getProcessStatus().toString().toUpperCase());
addData.accept(integrityDataExecutionMetadata.getErrorMessage(), INTEGRITY_DATA_ERROR_MESSAGE);

try (FileOutputStream output = new FileOutputStream(statusFile)) {
statusData.store(output, null);
} catch (IOException e) {
throw new DotExecutionException(
String.format("Could not save status to %s", statusFile.getAbsolutePath()),
e);
}
}

/**
* Saves a integrity data generation metadata in a to-be-discovered location so it can be read by concurrent parts
* that need to know what is the status of the integrity data generation.
* Individual parameters are used to create a {@link IntegrityDataExecutionMetadata}.
*
* @param endpointId endpoint id
* @param requestId request id
* @param processStatus {@link IntegrityResource.ProcessStatus} instance to reflect the current status
* @param errorMessage error message associated to detected error
*/
public static void saveIntegrityDataStatus(final String endpointId,
final String requestId,
final IntegrityResource.ProcessStatus processStatus,
final String errorMessage) {
saveIntegrityDataStatus(new IntegrityDataExecutionMetadata(endpointId, requestId, processStatus, errorMessage));
}

/**
* Saves a integrity data generation metadata in a to-be-discovered location so it can be read by concurrent parts
* that need to know what is the status of the integrity data generation.
* Individual parameters are used to create a {@link IntegrityDataExecutionMetadata}.
*
* @param endpointId endpoint id
* @param requestId request id
* @param processStatus {@link IntegrityResource.ProcessStatus} instance to reflect the current status
*/
public static void saveIntegrityDataStatus(final String endpointId,
final String requestId,
final IntegrityResource.ProcessStatus processStatus) {
saveIntegrityDataStatus(endpointId, requestId, processStatus, null);
}

/**
* Creates all the CSV from End Point database table and store them inside
* zip file.
*
* @param endpointId
* @throws Exception
*/
public void generateDataToCheckZip(String endpointId) throws Exception {
File zipFile = null;

try {
if (!UtilMethods.isSet(endpointId))
return;

final String outputPath = ConfigUtils.getIntegrityPath() + File.separator + endpointId;
public static void generateDataToCheckZip(final String endpointId) {
if (!UtilMethods.isSet(endpointId)) {
Logger.error(IntegrityUtil.class, "Endpoint was not provided");
return;
}

File dir = new File(outputPath);
Logger.info(
IntegrityUtil.class,
String.format("Starting integrity data generation job for endpoint %s", endpointId));

File zipFile = null;
try {
final String outputPath = getIntegrityDataPath(endpointId);
final File dir = new File(outputPath);
// if file doesn't exist, create it
if (!dir.exists()) {
dir.mkdir();
}

zipFile = new File(outputPath + File.separator
+ IntegrityResource.INTEGRITY_DATA_TO_CHECK_ZIP_FILE_NAME);

try (OutputStream os = Files.newOutputStream(zipFile.toPath());
ZipOutputStream zos = new ZipOutputStream(os)) {
IntegrityType[] types = IntegrityType.values();
zipFile = new File(getIntegrityDataFilePath(endpointId, INTEGRITY_DATA_TO_CHECK_ZIP_FILENAME));
try(OutputStream outputStream = Files.newOutputStream(zipFile.toPath());
ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream)) {
final IntegrityType[] types = IntegrityType.values();
for (IntegrityType integrityType : types) {
File fileToCheckCsvFile = null;

try {
fileToCheckCsvFile = integrityType.getIntegrityChecker()
.generateCSVFile(outputPath);

addToZipFile(fileToCheckCsvFile.getAbsolutePath(), zos,
integrityType.getDataToCheckCSVName());
fileToCheckCsvFile = integrityType.getIntegrityChecker().generateCSVFile(outputPath);
addToZipFile(fileToCheckCsvFile.getAbsolutePath(), zipOutputStream, integrityType.getDataToCheckCSVName());
} finally {
if (fileToCheckCsvFile != null && fileToCheckCsvFile.exists())
if (fileToCheckCsvFile != null && fileToCheckCsvFile.exists()) {
fileToCheckCsvFile.delete();
}
}
}
}
} catch (Exception e) {
if (zipFile != null && zipFile.exists())
if (zipFile != null && zipFile.exists()) {
zipFile.delete();
}

throw new Exception(e);
throw new DotExecutionException(e);
}
}

public void generateDataToFixZip(String endpointId, IntegrityType type) {
public void generateDataToFixZip(final String endpointId, final IntegrityType type) {
if (!UtilMethods.isSet(endpointId)) {
return;
}

File dataToFixCsvFile = null;
File zipFile = null;

try {
if (!UtilMethods.isSet(endpointId))
return;

final String outputPath = ConfigUtils.getIntegrityPath() + File.separator + endpointId;

File dir = new File(outputPath);

final String outputPath = getIntegrityDataPath(endpointId);
final File dir = new File(outputPath);
// if file doesn't exist, create it
if (!dir.exists()) {
dir.mkdir();
}

zipFile = new File(outputPath + File.separator
+ IntegrityResource.INTEGRITY_DATA_TO_FIX_ZIP_FILE_NAME);
try (OutputStream os = Files.newOutputStream(zipFile.toPath());
ZipOutputStream zos = new ZipOutputStream(os)) {
zipFile = new File(getIntegrityDataFilePath(endpointId, INTEGRITY_DATA_TO_FIX_ZIP_FILENAME));
try (OutputStream outputStream = Files.newOutputStream(zipFile.toPath());
ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream)) {
// create Folders CSV
dataToFixCsvFile = generateDataToFixCSV(outputPath, endpointId, type);

addToZipFile(dataToFixCsvFile.getAbsolutePath(), zos, type.getDataToFixCSVName());
addToZipFile(dataToFixCsvFile.getAbsolutePath(), zipOutputStream, type.getDataToFixCSVName());
}
} catch (Exception e) {
Logger.error(getClass(), "Error generating fix for remote", e);
@@ -331,7 +496,7 @@ public void generateDataToFixZip(String endpointId, IntegrityType type) {
}
}

public void dropTempTables(final String endpointId) throws DotDataException {
public static void dropTempTables(final String endpointId) throws DotDataException {
DotConnect dc = new DotConnect();
try {
IntegrityType[] types = IntegrityType.values();
@@ -349,7 +514,7 @@ public void dropTempTables(final String endpointId) throws DotDataException {
}
}
} catch (SQLException e) {
Logger.error(getClass(), "Error dropping Temp tables");
Logger.error(IntegrityResource.class, "Error dropping Temp tables");
throw new DotDataException("Error dropping Temp tables", e);
}
}
@@ -382,7 +547,7 @@ public void dropTempTables(final String endpointId) throws DotDataException {
*
* @param dataToFix
* - The {@link InputStream} containing the data to fix.
* @param endpointId
* @param key
* - The ID of the end point where the data will be fixed.
* @param type
* - The type of object (Content Page, Folder, Content Type,
@@ -391,16 +556,17 @@ public void dropTempTables(final String endpointId) throws DotDataException {
* An error occurred during the integrity fix process. The
* results table must be wiped out.
*/
public void fixConflicts(InputStream dataToFix, String endpointId, IntegrityType type)
@WrapInTransaction
public void fixConflicts(InputStream dataToFix, String key, IntegrityType type)
throws Exception {
final String outputDir = ConfigUtils.getIntegrityPath() + File.separator + endpointId;
final String outputDir = ConfigUtils.getIntegrityPath() + File.separator + key;

// lets first unzip the given file
unzipFile(dataToFix, outputDir);

// lets generate the tables with the data to be fixed
generateDataToFixTable(endpointId, type);
fixConflicts(endpointId, type);
generateDataToFixTable(key, type);
fixConflicts(key, type);

HibernateUtil.addCommitListener(new FlushCacheRunnable() {
@Override
@@ -433,19 +599,19 @@ public void flushAllCache() {
* which indicates what records <b>MUST</b> be changed in the specified end
* point.
*
* @param endpointId
* @param key
* - The ID of the end point where the data will be fixed.
* @param type
* - The type of object (Content Page, Folder, Content Type,
* etc.) that will be fixed.
* @throws Exception
* An error occurred during the integrity fix process.
*/
public void generateDataToFixTable(String endpointId, IntegrityType type) throws Exception {
public void generateDataToFixTable(String key, IntegrityType type) throws Exception {

try {
CsvReader csvFile = new CsvReader(ConfigUtils.getIntegrityPath() + File.separator
+ endpointId + File.separator + type.getDataToFixCSVName(), '|',
+ key + File.separator + type.getDataToFixCSVName(), '|',
Charset.forName("UTF-8"));

final String resultsTable = type.getResultsTableName();
@@ -503,7 +669,7 @@ public void generateDataToFixTable(String endpointId, IntegrityType type) throws
}
}

dc.addParam(endpointId);
dc.addParam(key);

if (type == IntegrityType.HTMLPAGES || type == IntegrityType.FILEASSETS) {
dc.addParam(new Long(csvFile.get(7))); // languageId
@@ -531,7 +697,7 @@ public boolean doesIntegrityConflictsDataExist(String endpointId) throws Excepti
return conflictsDataExist;
}

private boolean doesTableExist(String tableName) throws DotDataException {
private static boolean doesTableExist(String tableName) throws DotDataException {
DotConnect dc = new DotConnect();

if (DbConnectionFactory.isOracle()) {
@@ -561,7 +727,7 @@ private boolean doesTableExist(String tableName) throws DotDataException {
/**
* Executes the integrity fix process according to the specified type.
*
* @param endpointId
* @param key
* - The ID of the end point where the data will be fixed.
* @param type
* - The type of object (Content Page, Folder, Content Type,
@@ -572,9 +738,10 @@ private boolean doesTableExist(String tableName) throws DotDataException {
* The specified user does not have permissions to perform the
* action.
*/
public void fixConflicts(final String endpointId, IntegrityType type) throws DotDataException,
@WrapInTransaction
public void fixConflicts(final String key, IntegrityType type) throws DotDataException,
DotSecurityException {
type.getIntegrityChecker().executeFix(endpointId);
type.getIntegrityChecker().executeFix(key);
}

/**
@@ -598,7 +765,9 @@ public void discardConflicts(final String endpointId, IntegrityType type)
* @throws DotDataException
* @throws Exception
*/
public void completeDiscardConflicts(final String endpointId) throws DotDataException {
@WrapInTransaction
public static void completeDiscardConflicts(final String endpointId) throws DotDataException {

IntegrityType[] types = IntegrityType.values();
for (IntegrityType integrityType : types) {
integrityType.getIntegrityChecker().discardConflicts(endpointId);
@@ -614,7 +783,8 @@ public void completeDiscardConflicts(final String endpointId) throws DotDataExce
* @return is there is at least one conflict returns true, otherwise false
* @throws Exception
*/
public boolean completeCheckIntegrity(final String endpointId) throws Exception {
@WrapInTransaction
public static boolean completeCheckIntegrity(final String endpointId) throws Exception {
boolean existConflicts = false;

IntegrityType[] types = IntegrityType.values();
@@ -625,4 +795,50 @@ public boolean completeCheckIntegrity(final String endpointId) throws Exception

return existConflicts;
}

/**
* Integrity data generation metadata bean to be saved.
*/
public static class IntegrityDataExecutionMetadata implements Serializable {

private final String endpointId;
private final String requestId;
private final IntegrityResource.ProcessStatus processStatus;
private final String errorMessage;

public IntegrityDataExecutionMetadata(String endpointId,
String requestId,
IntegrityResource.ProcessStatus processStatus,
String errorMessage) {
this.endpointId = endpointId;
this.requestId = requestId;
this.processStatus = processStatus;
this.errorMessage = errorMessage;
}

public IntegrityDataExecutionMetadata(String endpointId,
String requestId,
String status,
String errorMessage) {
this(endpointId, requestId, IntegrityResource.ProcessStatus.valueOf(status.toUpperCase()), errorMessage);
}

public String getEndpointId() {
return endpointId;
}

public String getRequestId() {
return requestId;
}

public IntegrityResource.ProcessStatus getProcessStatus() {
return processStatus;
}

public String getErrorMessage() {
return errorMessage;
}

}

}
@@ -182,18 +182,18 @@ public boolean generateIntegrityResults(String endpointId) throws Exception {
* Fixes role inconsistencies for a given server id. Fixing a role means
* updating its id with the one received from the other end
*
* @param endpointId
* @param remoteIP
* @throws DotDataException
* @throws DotSecurityException
*/
@Override
public void executeFix(final String endpointId) throws DotDataException, DotSecurityException {
public void executeFix(final String key) throws DotDataException, DotSecurityException {

DotConnect dc = new DotConnect();

dc.setSQL("select name, local_role_id, remote_role_id from "+ getIntegrityType().getResultsTableName() +
" where endpoint_id = ? order by name asc");
dc.addParam(endpointId);
dc.addParam(key);

for (Map<String, Object> result : dc.loadObjectResults()) {
String oldRoleId = (String) result.get("local_role_id");
@@ -169,18 +169,18 @@ public boolean generateIntegrityResults(final String endpointId) throws Exceptio
/**
* Fixes structures inconsistencies for a given server id
*
* @param serverId
* @param key
* @throws DotDataException
* @throws DotSecurityException
*/
@Override
public void executeFix(String serverId) throws DotDataException, DotSecurityException {
public void executeFix(String key) throws DotDataException, DotSecurityException {
DotConnect dc = new DotConnect();

try {
dc.setSQL("select local_inode, remote_inode from "
+ getIntegrityType().getResultsTableName() + " where endpoint_id = ?");
dc.addParam(serverId);
dc.addParam(key);
List<Map<String, Object>> results = dc.loadObjectResults();
User systemUser = APILocator.getUserAPI().getSystemUser();

@@ -1,6 +1,5 @@
package com.dotcms.publisher.ajax;

import com.dotcms.business.CloseDBIfOpened;
import com.dotcms.enterprise.publishing.staticpublishing.AWSS3Publisher;
import com.dotcms.enterprise.publishing.staticpublishing.StaticPublisher;
import com.dotcms.publisher.bundle.bean.Bundle;
@@ -13,16 +12,11 @@
import com.dotcms.publisher.business.PublisherAPI;
import com.dotcms.publisher.endpoint.bean.PublishingEndPoint;
import com.dotcms.publisher.environment.bean.Environment;
import com.dotcms.publisher.pusher.PushPublisher;
import com.dotcms.publisher.pusher.PushPublisherConfig;
import com.dotcms.publisher.pusher.PushUtils;
import com.dotcms.publisher.util.PublisherUtil;
import com.dotcms.publishing.BundlerStatus;
import com.dotcms.publishing.BundlerUtil;
import com.dotcms.publishing.DotBundleException;
import com.dotcms.publishing.DotPublishingException;
import com.dotcms.publishing.FilterDescriptor;
import com.dotcms.publishing.IBundler;
import com.dotcms.publishing.Publisher;
import com.dotcms.publishing.PublisherConfig;
import com.dotcms.publishing.PublisherConfig.DeliveryStrategy;
@@ -71,7 +65,6 @@
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -571,6 +564,8 @@ public void downloadUnpushedBundle ( HttpServletRequest request, HttpServletResp
}

final Bundle dbBundle = Try.of(()->APILocator.getBundleAPI().getBundleById(bundleId)).getOrElseThrow(e->new DotRuntimeException(e));


final String bundleName = dbBundle.getName().replaceAll("[^\\w.-]", "_");


@@ -581,12 +576,13 @@ public void downloadUnpushedBundle ( HttpServletRequest request, HttpServletResp
//set ForcePush value of the filter to the bundle
dbBundle.setForcePush(
(boolean) APILocator.getPublisherAPI().getFilterDescriptorByKey(bundleFilter).getFilters().getOrDefault(FilterDescriptor.FORCE_PUSH_KEY,false));
dbBundle.setOperation(operation.ordinal());
//Update Bundle
APILocator.getBundleAPI().updateBundle(dbBundle);

//Generate the bundle file for this given operation
final Map<String, Object> bundleData = generateBundle( bundleId, operation );
bundle = (File) bundleData.get( "file" );

bundle = APILocator.getBundleAPI().generateTarGzipBundleFile(dbBundle);
} catch ( Exception e ) {
Logger.error( this.getClass(), "Error trying to generate bundle with id: " + bundleId, e );
response.sendError( 500, "Error trying to generate bundle with id: " + bundleId );
@@ -626,84 +622,6 @@ public void downloadUnpushedBundle ( HttpServletRequest request, HttpServletResp
}
}

/**
* Generates an Unpublish bundle for a given bundle id operation (publish/unpublish)
*
* @param bundleId The Bundle id of the Bundle we want to generate
* @param operation Download for publish or un-publish
* @return The generated requested Bundle file
* @throws DotPublisherException If fails retrieving the Bundle contents
* @throws DotDataException If fails finding the system user
* @throws DotPublishingException If fails initializing the Publisher
* @throws IllegalAccessException If fails creating new Bundlers instances
* @throws InstantiationException If fails creating new Bundlers instances
* @throws DotBundleException If fails generating the Bundle
* @throws IOException If fails compressing the all the Bundle contents into the final Bundle file
*/
@CloseDBIfOpened
@SuppressWarnings ("unchecked")
private Map<String, Object> generateBundle ( String bundleId, PushPublisherConfig.Operation operation ) throws DotPublisherException, DotDataException, DotPublishingException, IllegalAccessException, InstantiationException, DotBundleException, IOException {

final PushPublisherConfig pushPublisherConfig = new PushPublisherConfig();
final PublisherAPI publisherAPI = PublisherAPI.getInstance();

final List<PublishQueueElement> tempBundleContents = publisherAPI.getQueueElementsByBundleId( bundleId );
final List<PublishQueueElement> assetsToPublish = new ArrayList<PublishQueueElement>();

for ( final PublishQueueElement publishQueueElement : tempBundleContents ) {
assetsToPublish.add( publishQueueElement );
}

pushPublisherConfig.setDownloading( true );
pushPublisherConfig.setOperation(operation);

pushPublisherConfig.setAssets( assetsToPublish );
//Queries creation
pushPublisherConfig.setLuceneQueries( PublisherUtil.prepareQueries( tempBundleContents ) );
pushPublisherConfig.setId( bundleId );
pushPublisherConfig.setUser( APILocator.getUserAPI().getSystemUser() );

//BUNDLERS

final List<Class<IBundler>> bundlers = new ArrayList<Class<IBundler>>();
final List<IBundler> confBundlers = new ArrayList<IBundler>();

final Publisher publisher = new PushPublisher();
publisher.init( pushPublisherConfig );
//Add the bundles for this publisher
for ( final Class clazz : publisher.getBundlers() ) {
if ( !bundlers.contains( clazz ) ) {
bundlers.add( clazz );
}
}
final File bundleRoot = BundlerUtil.getBundleRoot( pushPublisherConfig );

// Run bundlers
BundlerUtil.writeBundleXML( pushPublisherConfig );
for ( final Class<IBundler> clazzBundler : bundlers ) {

final IBundler bundler = clazzBundler.newInstance();
confBundlers.add( bundler );
bundler.setConfig( pushPublisherConfig );
bundler.setPublisher(publisher);
final BundlerStatus bundlerStatus = new BundlerStatus( bundler.getClass().getName() );
//Generate the bundler
Logger.info(this, "Start of Bundler: " + clazzBundler.getSimpleName());
bundler.generate( bundleRoot, bundlerStatus );
Logger.info(this, "End of Bundler: " + clazzBundler.getSimpleName());
}

pushPublisherConfig.setBundlers( confBundlers );

//Compressing bundle
final ArrayList<File> fileList = new ArrayList<File>();
fileList.add( bundleRoot );
final File bundle = new File( bundleRoot + File.separator + ".." + File.separator + pushPublisherConfig.getId() + ".tar.gz" );

final Map<String, Object> bundleData = new HashMap<String, Object>();
bundleData.put( "file", PushUtils.compressFiles( fileList, bundle, bundleRoot.getAbsolutePath() ) );
return bundleData;
}

/**
* Publish a given Bundle file
@@ -1,6 +1,9 @@
package com.dotcms.publisher.bundle.bean;

import java.io.File;
import java.util.Date;
import com.dotmarketing.util.ConfigUtils;
import io.vavr.control.Try;

public class Bundle {
private String id;
@@ -84,4 +87,17 @@ public void setFilterKey(final String filterKey) {
this.filterKey = filterKey;
}

/**
* Checks if the bundle was already generated based on the id: BUNDLE_ID.tar.gz
* @return boolean - true if the bundle exists.
*/
public boolean bundleTgzExists() {

return Try.of(()->new File( ConfigUtils.getBundlePath() + File.separator + id + ".tar.gz" ).exists()).getOrElse(false);


}



}
@@ -1,8 +1,8 @@
package com.dotcms.publisher.bundle.business;

import java.io.File;
import java.util.Date;
import java.util.List;
import java.util.Set;

import com.dotcms.publisher.bundle.bean.Bundle;
import com.dotcms.publisher.business.PublishAuditStatus;
@@ -168,4 +168,10 @@
*/
public void deleteAssetFromBundle(String assetId, String bundleId) throws DotDataException;

/**
* This takes a bundle and generates the tar.gzipped output file. The resulting file will be placed under
* the ConfigUtils.getBundlePath() + "/" + bundleId + ".tar.gz"
*/
public File generateTarGzipBundleFile(Bundle bundle);

}
@@ -25,6 +25,10 @@
import com.dotcms.business.WrapInTransaction;
import com.dotcms.publisher.bundle.bean.Bundle;
import com.dotcms.publisher.environment.bean.Environment;
import com.dotcms.publisher.pusher.PushPublisherConfig;
import com.dotcms.publisher.pusher.PushUtils;
import com.dotcms.publishing.BundlerUtil;
import com.dotcms.publishing.GenerateBundlePublisher;
import com.dotmarketing.business.APILocator;
import com.dotmarketing.business.FactoryLocator;
import com.dotmarketing.business.UserAPI;
@@ -391,4 +395,44 @@ public void deleteAssetFromBundle(String assetId, String bundleId)

}

/**
* This takes a Bundle, generates the folder/file structure and returns the resulting directory
* as a File handle. It will not delete the bundle directory if it already existed.
* @param bundle - Bundle to generate
* @return
*/
@CloseDBIfOpened
private File generateBundleDirectory(final Bundle bundle) {

final PushPublisherConfig pushPublisherConfig = new PushPublisherConfig(bundle);
pushPublisherConfig.setPublishers(Arrays.asList(GenerateBundlePublisher.class));
try {
APILocator.getPublisherAPI().publish(pushPublisherConfig);
}
catch(final Exception e) {
Logger.error(this,e.getMessage(),e);
throw new DotRuntimeException(e);
}
return BundlerUtil.getBundleRoot( pushPublisherConfig );

}

@CloseDBIfOpened
@Override
public File generateTarGzipBundleFile(final Bundle bundle) {
final File bundleRoot = generateBundleDirectory(bundle);
final File bundleFile = new File( ConfigUtils.getBundlePath() + File.separator + bundle.getId() + ".tar.gz" );
bundleFile.delete();
try {
final File tmpFile= PushUtils.tarGzipDirectory( bundleRoot );
tmpFile.renameTo(bundleFile);
return bundleFile;
}
catch(final Exception e) {
Logger.error(this,e.getMessage(),e);
throw new DotRuntimeException(e);
}
}


}
@@ -6,51 +6,54 @@


/**
* Publish Audit status POJO
* Push Publish status
* @author alberto
*
*/
public class PublishAuditStatus implements Serializable {
private static final long serialVersionUID = 1L;

public static enum Status {
BUNDLE_REQUESTED(1),
BUNDLING(2),
SENDING_TO_ENDPOINTS(3),
FAILED_TO_SEND_TO_ALL_GROUPS(4),
FAILED_TO_SEND_TO_SOME_GROUPS(5),
FAILED_TO_BUNDLE(6),
FAILED_TO_BUNDLE(6),
FAILED_TO_SENT(7),

FAILED_TO_PUBLISH(8),
SUCCESS(9),

BUNDLE_SENT_SUCCESSFULLY(10),
RECEIVED_BUNDLE(11),
PUBLISHING_BUNDLE(12),
WAITING_FOR_PUBLISHING(13),

BUNDLE_SAVED_SUCCESSFULLY(14);
BUNDLE_SAVED_SUCCESSFULLY(14),

INVALID_TOKEN(15),
LICENSE_REQUIRED(16);

private int code;
private Status(int code) {
this.code = code;
}

public int getCode() {
return code;
}

}

private String bundleId;
private Status status;
private PublishAuditHistory statusPojo;
private Date statusUpdated;
private Date createDate;

public PublishAuditStatus() {}

/**
* Build a brand new Audit Status
* @param bundleId
@@ -61,7 +64,7 @@ public PublishAuditStatus(String bundleId) {
PushPublishLogger.log(this.getClass(), "Status Update: Pending.");
this.status = Status.BUNDLE_REQUESTED;
}

/**
* Useful to build a Audit Status with new status attribute
* from an existing status
@@ -75,56 +78,56 @@ public PublishAuditStatus(PublishAuditStatus origin, Status status) {
this.createDate = origin.createDate;
this.statusPojo = origin.getStatusPojo(); //TODO manage status POJO
}

public static String getStatusByCode(int code) {
for(Status status: Status.values()) {
if(status.getCode() == code)
return status.toString();
}
return "";
}

public static Status getStatusObjectByCode(int code) {
for(Status status: Status.values()) {
if(status.getCode() == code)
return status;
}
return null;
}

public String getBundleId() {
return bundleId;
}
public void setBundleId(String bundleId) {
this.bundleId = bundleId;
}

public Status getStatus() {
return status;
}
public void setStatus(Status status) {
this.status = status;
}

public PublishAuditHistory getStatusPojo() {
return statusPojo;
}
public void setStatusPojo(PublishAuditHistory statusPojo) {
this.statusPojo = statusPojo;
}

public Date getStatusUpdated() {
return statusUpdated;
}
public void setStatusUpdated(Date statusUpdated) {
this.statusUpdated = statusUpdated;
}

public Date getCreateDate() {
return createDate;
}
public void setCreateDate(Date createDate) {
this.createDate = createDate;
}
}
}

@@ -94,10 +94,10 @@
private PublishAuditAPI pubAuditAPI = PublishAuditAPI.getInstance();
private PublishingEndPointAPI endpointAPI = APILocator.getPublisherEndPointAPI();
private PublisherAPI pubAPI = PublisherAPI.getInstance();
private EnvironmentAPI environmentAPI = APILocator.getEnvironmentAPI();
private PublishingEndPointAPI publisherEndPointAPI = APILocator.getPublisherEndPointAPI();
private EnvironmentAPI environmentAPI = APILocator.getEnvironmentAPI();
private PublishingEndPointAPI publisherEndPointAPI = APILocator.getPublisherEndPointAPI();

/**
/**
* Reads from the publishing queue table and depending of the publish date
* will send a bundle to publish (see
* {@link com.dotcms.publishing.PublisherAPI#publish(PublisherConfig)}).
@@ -133,9 +133,11 @@ public void execute(final JobExecutionContext jobExecutionContext) throws JobExe
for (final Map<String, Object> bundle : bundles) {
final Date publishDate = (Date) bundle.get("publish_date");
Logger.info(this, "Processing bundle: ID: " + bundle.get("bundle_id") + ". Status: "
+ (UtilMethods.isSet(bundle.get("status")) ? bundle.get("status") : "Starting")
+ ". Publish Date: " + publishDate);
+ (UtilMethods.isSet(bundle.get("status")) ? bundle.get("status") : "Starting")
+ ". Publish Date: " + publishDate);
final String tempBundleId = (String) bundle.get("bundle_id");
final PublishAuditStatus status = new PublishAuditStatus(tempBundleId);

ThreadContext.put(BUNDLE_ID, BUNDLE_ID + "=" + tempBundleId);

try {
@@ -160,7 +162,6 @@ public void execute(final JobExecutionContext jobExecutionContext) throws JobExe
pconf.setAssets(assetsToPublish);

// Status
final PublishAuditStatus status = new PublishAuditStatus(tempBundleId);
status.setStatusPojo(historyPojo);
// Insert in Audit table
pubAuditAPI.insertPublishAuditStatus(status);
@@ -219,12 +220,13 @@ private void updateAuditStatus(final List<Map<String, Object>> bundlesInQueue)
throws DotPublisherException, DotDataException, DotSecurityException, LanguageException {
final List<PublishAuditStatus> pendingBundleAudits = pubAuditAPI.getPendingPublishAuditStatus();
// For each bundle audit
for (final PublishAuditStatus bundleAudit : pendingBundleAudits) {
for (final PublishAuditStatus bundleAudit : pendingBundleAudits) {
ThreadContext.put(BUNDLE_ID, BUNDLE_ID + "=" + bundleAudit.getBundleId());
try {
final PublishAuditHistory localHistory = bundleAudit.getStatusPojo();

// There is no need to keep checking after MAX_NUM_TRIES.
if ( localHistory.getNumTries() <= (MAX_NUM_TRIES + 1) ) {
if (bundleAudit.getStatusPojo().getNumTries() <= (MAX_NUM_TRIES + 1)) {

final Map<String, Map<String, EndpointDetail>> endpointTrackingMap =
collectEndpointInfoFromRemote(bundleAudit);
final GroupPushStats groupPushStats = getGroupStats(endpointTrackingMap);
@@ -240,6 +242,7 @@ private void updateAuditStatus(final List<Map<String, Object>> bundlesInQueue)
}
}


/**
* Obtains the list of Endpoints inside each Push Publishing Environment and verifies the publishing status of a
* bundle that was recently pushed. Most of the times, the system can fail to obtain the status of a bundle:
@@ -320,59 +323,62 @@ private void updateBundleStatus(final PublishAuditStatus bundleAudit,
.setLife(5000);

if ( localHistory.getNumTries() >= MAX_NUM_TRIES && (groupPushStats.getCountGroupFailed() > 0
|| groupPushStats.getCountGroupPublishing() > 0) ) {
// If bundle cannot be installed after [MAX_NUM_TRIES] tries
// and some groups could not be published
List<Environment> environments = APILocator.getEnvironmentAPI().findEnvironmentsByBundleId(auditedBundleId);
for ( Environment environment : environments ) {
APILocator.getPushedAssetsAPI().deletePushedAssets(auditedBundleId, environment.getId());
}
PushPublishLogger.log(this.getClass(), "Status Update: Failed to publish");
bundleStatus = Status.FAILED_TO_PUBLISH;
pubAuditAPI.updatePublishAuditStatus(auditedBundleId, bundleStatus, localHistory);
pubAPI.deleteElementsFromPublishQueueTable(auditedBundleId);
//Update Notification Info
notificationMessage = isBundleNameGenerated ? "bundle.title.fail.notification" : "bundle.named.fail.notification";
|| groupPushStats.getCountGroupPublishing() > 0) ) {
// If bundle cannot be installed after [MAX_NUM_TRIES] tries
// and some groups could not be published
List<Environment> environments = APILocator.getEnvironmentAPI().findEnvironmentsByBundleId(auditedBundleId);
for ( Environment environment : environments ) {
APILocator.getPushedAssetsAPI().deletePushedAssets(auditedBundleId, environment.getId());
}
PushPublishLogger.log(this.getClass(), "Status Update: Failed to publish");
bundleStatus = Status.FAILED_TO_PUBLISH;
pubAuditAPI.updatePublishAuditStatus(auditedBundleId, bundleStatus, localHistory);
pubAPI.deleteElementsFromPublishQueueTable(auditedBundleId);

//Update Notification Info
notificationMessage = isBundleNameGenerated ? "bundle.title.fail.notification" : "bundle.named.fail.notification";
notificationMessageArgument = isBundleNameGenerated ? generateBundleTitle(localHistory.getAssets()) : bundle.getName();
message.setMessage(LanguageUtil.get(
notificationMessage,
notificationMessageArgument));
message.setLife(86400000);
message.setSeverity(MessageSeverity.ERROR);
} else if (groupPushStats.getCountGroupFailed() > 0 && groupPushStats.getCountGroupFailed() == endpointTrackingMap.size()) {
// If bundle cannot be installed in all groups
bundleStatus = Status.FAILED_TO_SEND_TO_ALL_GROUPS;
pubAuditAPI.updatePublishAuditStatus(auditedBundleId, bundleStatus, localHistory);
} else if (groupPushStats.getCountGroupFailed() > 0
message.setLife(86400000);
message.setSeverity(MessageSeverity.ERROR);
} else if (groupPushStats.getCountGroupFailed() > 0 && groupPushStats.getCountGroupFailed() == endpointTrackingMap.size()) {
// If bundle cannot be installed in all groups
bundleStatus = Status.FAILED_TO_SEND_TO_ALL_GROUPS;
pubAuditAPI.updatePublishAuditStatus(auditedBundleId, bundleStatus, localHistory);
} else if (groupPushStats.getCountGroupFailed() > 0
&& (groupPushStats.getCountGroupOk() + groupPushStats.getCountGroupFailed()) == endpointTrackingMap.size()) {
// If bundle was installed in some groups only
bundleStatus = Status.FAILED_TO_SEND_TO_SOME_GROUPS;
pubAuditAPI.updatePublishAuditStatus(auditedBundleId, bundleStatus, localHistory);
} else if (groupPushStats.getCountGroupOk() > 0 && groupPushStats.getCountGroupOk() == endpointTrackingMap.size()) {
// If bundle was installed in all groups
PushPublishLogger.log(this.getClass(), "Status Update: Success");
bundleStatus = Status.SUCCESS;
pubAuditAPI.updatePublishAuditStatus(auditedBundleId, bundleStatus, localHistory);
pubAPI.deleteElementsFromPublishQueueTable(auditedBundleId);
// If bundle was installed in all groups
PushPublishLogger.log(this.getClass(), "Status Update: Success");
bundleStatus = Status.SUCCESS;
pubAuditAPI.updatePublishAuditStatus(auditedBundleId, bundleStatus, localHistory);
pubAPI.deleteElementsFromPublishQueueTable(auditedBundleId);

//Update Notification Info
notificationMessage = isBundleNameGenerated ? "bundle.title.success.notification" : "bundle.named.success.notification";
notificationMessageArgument = isBundleNameGenerated ? generateBundleTitle(localHistory.getAssets()) : bundle.getName();
message.setMessage(LanguageUtil.get(
notificationMessage,
notificationMessageArgument));
} else if ( groupPushStats.getCountGroupPublishing() == endpointTrackingMap.size() ) {
// If bundle is still publishing in all groups
bundleStatus = Status.PUBLISHING_BUNDLE;
pubAuditAPI.updatePublishAuditStatus(auditedBundleId, bundleStatus, localHistory);
} else if (groupPushStats.getCountGroupSaved() > 0){
} else if ( groupPushStats.getCountGroupPublishing() == endpointTrackingMap.size() ) {
// If bundle is still publishing in all groups
bundleStatus = Status.PUBLISHING_BUNDLE;
pubAuditAPI.updatePublishAuditStatus(auditedBundleId, bundleStatus, localHistory);
} else if (groupPushStats.getCountGroupSaved() > 0){
// If the static bundle was saved but has not been sent
bundleStatus = Status.BUNDLE_SAVED_SUCCESSFULLY;
pubAuditAPI.updatePublishAuditStatus(auditedBundleId, bundleStatus, localHistory);
} else {
// Otherwise, just keep trying to publish the bundle
bundleStatus = Status.WAITING_FOR_PUBLISHING;
pubAuditAPI.updatePublishAuditStatus(auditedBundleId, bundleStatus, localHistory);
}
}

Logger.info(this, "===========================================================");
Logger.info(this, String.format("For bundle '%s':", auditedBundleId));
Logger.info(this, String.format("-> Status : %s [%d]", bundleStatus.toString(), bundleStatus.getCode()));
@@ -465,48 +471,48 @@ private GroupPushStats getGroupStats(final Map<String, Map<String, EndpointDetai
final GroupPushStats groupPushStats = new GroupPushStats();

for (final Map<String, EndpointDetail> group : endpointTrackingMap.values()) {
boolean isGroupOk = false;
boolean isGroupPublishing = false;
boolean isGroupFailed = false;
boolean isGroupSaved = false;
for (final EndpointDetail detail : group.values() ) {
if ( detail.getStatus() == Status.SUCCESS.getCode() ) {
isGroupOk = true;
} else if ( detail.getStatus() == Status.PUBLISHING_BUNDLE
.getCode() ) {
isGroupPublishing = true;
} else if ( detail.getStatus() == Status.FAILED_TO_PUBLISH
.getCode() ) {
isGroupFailed = true;
} else if ( detail.getStatus() == Status.BUNDLE_SAVED_SUCCESSFULLY
boolean isGroupOk = false;
boolean isGroupPublishing = false;
boolean isGroupFailed = false;
boolean isGroupSaved = false;
for (final EndpointDetail detail : group.values() ) {
if ( detail.getStatus() == Status.SUCCESS.getCode() ) {
isGroupOk = true;
} else if ( detail.getStatus() == Status.PUBLISHING_BUNDLE
.getCode() ) {
isGroupPublishing = true;
} else if ( detail.getStatus() == Status.FAILED_TO_PUBLISH
.getCode() ) {
isGroupFailed = true;
} else if ( detail.getStatus() == Status.BUNDLE_SAVED_SUCCESSFULLY
.getCode() ) {
isGroupSaved = true;
}
}
if ( isGroupOk ) {
groupPushStats.increaseCountGroupOk();
}
if ( isGroupPublishing ) {
groupPushStats.increaseCountGroupPublishing();
}
if ( isGroupFailed ) {
groupPushStats.increaseCountGroupFailed();
}
}
if ( isGroupOk ) {
groupPushStats.increaseCountGroupOk();
}
if ( isGroupPublishing ) {
groupPushStats.increaseCountGroupPublishing();
}
if ( isGroupFailed ) {
groupPushStats.increaseCountGroupFailed();
}
if ( isGroupSaved ) {
groupPushStats.increaseCountGroupSaved();
}
}
}

return groupPushStats;
return groupPushStats;
}

private void updateLocalEndpointDetailFromRemote(final PublishAuditHistory localHistory, final String groupID,
final String endpointID, final PublishAuditHistory remoteHistory) {
for (final Map<String, EndpointDetail> remoteGroup : remoteHistory.getEndpointsMap().values()) {
for (final EndpointDetail remoteDetail : remoteGroup.values()) {
localHistory.addOrUpdateEndpoint(groupID, endpointID, remoteDetail);
}
}
for (final EndpointDetail remoteDetail : remoteGroup.values()) {
localHistory.addOrUpdateEndpoint(groupID, endpointID, remoteDetail);
}
}
}

private void updateLocalPublishDatesFromRemote(final PublishAuditHistory localHistory,
@@ -523,21 +529,21 @@ private void updateLocalPublishDatesFromRemote(final PublishAuditHistory localHi
}
}

/**
* Obtains the bundle history that has been created in the specified end-point.
*
* @param bundleAudit - The {@link PublishAuditStatus} object containing bundle data.
* @param targetEndpoint - The {@link PublishingEndPoint} whose bundle history will be
* retrieved.
* @return The {@link PublishAuditHistory} of the bundle in the specified end-point.
*/
/**
* Obtains the bundle history that has been created in the specified end-point.
*
* @param bundleAudit - The {@link PublishAuditStatus} object containing bundle data.
* @param targetEndpoint - The {@link PublishingEndPoint} whose bundle history will be
* retrieved.
* @return The {@link PublishAuditHistory} of the bundle in the specified end-point.
*/
private PublishAuditHistory getRemoteHistoryFromEndpoint(final PublishAuditStatus bundleAudit,
final PublishingEndPoint targetEndpoint) {
final WebTarget webTarget = getRestClient().target(targetEndpoint.toURL() + "/api/auditPublishing");
final WebTarget webTarget = getRestClient().target(targetEndpoint.toURL() + "/api/auditPublishing");
return PublishAuditHistory.getObjectFromString(
webTarget
.path("get")
.path(bundleAudit.getBundleId()).request().get(String.class));
webTarget
.path("get")
.path(bundleAudit.getBundleId()).request().get(String.class));
}

/**
@@ -548,48 +554,48 @@ private PublishAuditHistory getRemoteHistoryFromEndpoint(final PublishAuditStat
* - The Id of the generated bundle.
* @return The {@link Publisher} classes for the specified bundle.
*/
private Set<Class<?>> getPublishersForBundle(String bundleId){
private Set<Class<?>> getPublishersForBundle(String bundleId){

Set<Class<?>> publishersClasses = new HashSet<>();
Set<Class<?>> publishersClasses = new HashSet<>();

try{
Map<String, Class<? extends IPublisher>> protocolPublisherMap = Maps.newConcurrentMap();
//TODO: for OSGI we need to get this list from implementations of IPublisher or something else.
final Set<Class<?>> publishers = Sets.newHashSet(
PushPublisher.class,
AWSS3Publisher.class,
try{
Map<String, Class<? extends IPublisher>> protocolPublisherMap = Maps.newConcurrentMap();
//TODO: for OSGI we need to get this list from implementations of IPublisher or something else.
final Set<Class<?>> publishers = Sets.newHashSet(
PushPublisher.class,
AWSS3Publisher.class,
StaticPublisher.class);

//Fill protocolPublisherMap with protocol -> publisher.
for (Class publisher : publishers) {
Publisher p = (Publisher)publisher.newInstance();
for (String protocol : p.getProtocols()) {
protocolPublisherMap.put(protocol, publisher);
}
}

//For each environment in the bundle we need to get the end-points.
List<Environment> environments = this.environmentAPI.findEnvironmentsByBundleId(bundleId);

for (Environment environment : environments) {
//For each end-point we choose if run static or dynamic process (Static = AWSS3Publisher, Dynamic = PushPublisher)
List<PublishingEndPoint> endpoints = this.publisherEndPointAPI.findSendingEndPointsByEnvironment(environment.getId());

//For each end-point we need include the Publisher depending on the type.
for (PublishingEndPoint endpoint : endpoints) {
//Only if the end-point is enabled.
if (endpoint.isEnabled() && protocolPublisherMap.containsKey(endpoint.getProtocol())){
publishersClasses.add(protocolPublisherMap.get(endpoint.getProtocol()));

}
}
}
} catch (Exception e){
Logger.error(this, "Error trying to get publishers from bundle id: " + bundleId, e);
}

return publishersClasses;
}
//Fill protocolPublisherMap with protocol -> publisher.
for (final Class publisherClass : publishers) {
final Publisher publisher = (Publisher) publisherClass.newInstance();
for (String protocol : publisher.getProtocols()) {
protocolPublisherMap.put(protocol, publisherClass);
}
}

//For each environment in the bundle we need to get the end-points.
List<Environment> environments = this.environmentAPI.findEnvironmentsByBundleId(bundleId);

for (Environment environment : environments) {
//For each end-point we choose if run static or dynamic process (Static = AWSS3Publisher, Dynamic = PushPublisher)
List<PublishingEndPoint> endpoints = this.publisherEndPointAPI.findSendingEndPointsByEnvironment(environment.getId());

//For each end-point we need include the Publisher depending on the type.
for (PublishingEndPoint endpoint : endpoints) {
//Only if the end-point is enabled.
if (endpoint.isEnabled() && protocolPublisherMap.containsKey(endpoint.getProtocol())){
publishersClasses.add(protocolPublisherMap.get(endpoint.getProtocol()));

}
}
}
} catch (Exception e){
Logger.error(this, "Error trying to get publishers from bundle id: " + bundleId, e);
}

return publishersClasses;
}

/**
* Sends the parameter {@link PublisherConfig} to each Publisher in use to
@@ -604,18 +610,18 @@ private PublishAuditHistory getRemoteHistoryFromEndpoint(final PublishAuditStat
* @throws InstantiationException
* The {@link Publisher} class could not be instantiated.
*/
private PublisherConfig setUpConfigForPublisher(PublisherConfig pconf)
throws IllegalAccessException, InstantiationException {
private PublisherConfig setUpConfigForPublisher(PublisherConfig pconf)
throws IllegalAccessException, InstantiationException {

final List<Class> publishers = pconf.getPublishers();
for (Class<?> publisher : publishers) {
pconf = ((Publisher)publisher.newInstance()).setUpConfig(pconf);
}
final List<Class> publishers = pconf.getPublishers();
for (Class<?> publisher : publishers) {
pconf = ((Publisher)publisher.newInstance()).setUpConfig(pconf);
}

return pconf;
}
return pconf;
}

private class GroupPushStats {
private class GroupPushStats {
private int countGroupOk = 0;
private int countGroupPublishing = 0;
private int countGroupFailed = 0;
@@ -654,17 +660,17 @@ public int getCountGroupSaved() {
}
}

/**
* Returns an instance of the REST {@link Client} used to access Push Publishing end-points and
* retrieve their information.
*
* @return The REST {@link Client}.
*/
private Client getRestClient() {
if (null == this.restClient) {
this.restClient = RestClientBuilder.newClient();
}
return this.restClient;
}
/**
* Returns an instance of the REST {@link Client} used to access Push Publishing end-points and
* retrieve their information.
*
* @return The REST {@link Client}.
*/
private Client getRestClient() {
if (null == this.restClient) {
this.restClient = RestClientBuilder.newClient();
}
return this.restClient;
}

}
@@ -141,6 +141,8 @@ public void addEndpoint(HttpServletRequest request, HttpServletResponse response
PublishingEndPointAPI peAPI = APILocator.getPublisherEndPointAPI();
peAPI.saveEndPoint(endpoint);

response.getWriter().write(endpoint.getId());

} catch (DotDataException e) {
Logger.info(getClass(), "Error saving EndPoint. Error Message: " + e.getMessage());
response.getWriter().println("FAILURE: " + e.getMessage());
@@ -1,6 +1,9 @@
package com.dotcms.publisher.endpoint.bean;

import com.dotcms.publisher.pusher.AuthCredentialPushPublishUtil;
import com.dotmarketing.exception.PublishingEndPointValidationException;
import com.dotmarketing.util.UtilMethods;

import java.io.Serializable;

/**
@@ -99,6 +102,10 @@ public void setAuthKey(StringBuilder authKey) {
this.authKey = authKey;
}

public void setAuthKey(final String authKey) {
this.authKey = new StringBuilder(authKey);
}

/**
* Returns whether this endpoint is sending or receiving bundles.
* <p>
@@ -145,4 +152,36 @@ public StringBuilder toURL() {
* translated in upper layer.
*/
public abstract void validatePublishingEndPoint() throws PublishingEndPointValidationException;

public boolean hasAuthKey() {
final StringBuilder authKey = getAuthKey();

if (UtilMethods.isEmpty(authKey.toString())) {
return false;
}

return !isTokenExpired() && !isTokenInvalid();
}

public boolean isTokenExpired() {
final String authKey = getAuthKey().toString();

if (UtilMethods.isEmpty(authKey)) {
return false;
}

final String authKeyString = authKey;
return authKeyString.equals(AuthCredentialPushPublishUtil.EXPIRED_TOKEN_ERROR_KEY);
}

public boolean isTokenInvalid() {
final StringBuilder authKey = getAuthKey();

if (UtilMethods.isEmpty(authKey.toString())) {
return false;
}

final String authKeyString = authKey.toString();
return authKeyString.equals(AuthCredentialPushPublishUtil.INVALID_TOKEN_ERROR_KEY);
}
}
@@ -148,6 +148,7 @@ public void addEnvironment(HttpServletRequest request, HttpServletResponse respo
EnvironmentAPI eAPI = APILocator.getEnvironmentAPI();
eAPI.saveEnvironment(environment, permissions);

response.getWriter().write(environment.getId());
} catch (DotDataException e) {
Logger.info(getClass(), e.getMessage());
throw new DotRuntimeException(e.getMessage(),e);

This file was deleted.

@@ -0,0 +1,206 @@
package com.dotcms.publisher.pusher;

import com.dotcms.auth.providers.jwt.JsonWebTokenAuthCredentialProcessor;
import com.dotcms.auth.providers.jwt.beans.JWToken;
import com.dotcms.auth.providers.jwt.services.JsonWebTokenAuthCredentialProcessorImpl;
import com.dotcms.publisher.endpoint.bean.PublishingEndPoint;
import com.dotmarketing.business.APILocator;
import com.dotmarketing.exception.DotDataException;
import com.dotmarketing.util.Config;
import com.liferay.portal.model.User;
import io.jsonwebtoken.*;
import org.apache.commons.lang.StringUtils;
import org.glassfish.jersey.server.ContainerRequest;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Optional;

/**
* Singleton class to provide util method to Push Publish authentication, it support two authentication methods:
* JWToken and Auth key set in the {@link com.dotcms.publisher.endpoint.bean.impl.PushPublishingEndPoint},
* if the config property 'USE_JWT_TOKEN_IN_PUSH_PUBLISH' is set to true (default value)
* it is going to use JWT token, but if the config property is set to false or the JWT token fails then auth key is going to use
* */
public enum AuthCredentialPushPublishUtil {
INSTANCE;

private final String BEARER = "Bearer ";

public static String EXPIRED_TOKEN_ERROR_KEY = "__expired_token__";
public static String INVALID_TOKEN_ERROR_KEY = "__invalid_token__";

public Optional<String> getRequestToken(final PublishingEndPoint endpoint) {
final boolean useJWTToken = isJWTAvailable();

try {
String token;

if (useJWTToken) {
final Optional<String> optionalToken = PushPublisher.retriveEndpointKey(endpoint);

if (optionalToken.isPresent() && APILocator.getApiTokenAPI().isWellFormedToken(optionalToken.get())) {
token = optionalToken.get();
} else {
token = PushPublisher.retriveEndpointKeyDigest(endpoint).get();
}

} else {
token = PushPublisher.retriveEndpointKeyDigest(endpoint).get();

}

return Optional.of(JsonWebTokenAuthCredentialProcessor.BEARER + token);
} catch (IOException e) {
return Optional.empty();
}
}

public static boolean isJWTAvailable() {
return Config.getBooleanProperty("USE_JWT_TOKEN_IN_PUSH_PUBLISH", true);
}

/**
* Proceess the request to Authenticate a Push publish request
*
* @param request
* @return If the token is invalid {@link PushPublishAuthenticationToken#INVALID_TOKEN} or {@link PushPublishAuthenticationToken#EXPIRE_TOKEN}
* otherwise return a different {@link PushPublishAuthenticationToken} instance
*/
public PushPublishAuthenticationToken processAuthHeader(final HttpServletRequest request) {
final boolean useJWTToken = isJWTAvailable();

try{
if (useJWTToken) {
final PushPublishAuthenticationToken pushPublishAuthenticationToken = getFromJWTToken(request);

if (pushPublishAuthenticationToken == PushPublishAuthenticationToken.INVALID_TOKEN) {
return getFromEndPointAuthKey(request);
} else {
return pushPublishAuthenticationToken;
}
} else {
return getFromEndPointAuthKey(request);
}
} catch (DotDataException | IOException e) {
return PushPublishAuthenticationToken.INVALID_TOKEN;
}

}

private PushPublishAuthenticationToken getFromEndPointAuthKey(HttpServletRequest request) throws DotDataException, IOException {

final Optional<PublishingEndPoint> publishingEndPointOptional = getPublishingEndPointDotCMSToken(request);
return publishingEndPointOptional.isPresent() ?
new PushPublishAuthenticationToken(publishingEndPointOptional.get()) :
PushPublishAuthenticationToken.INVALID_TOKEN;
}

private PushPublishAuthenticationToken getFromJWTToken(HttpServletRequest request) {
try {
final Optional<JWToken> jwTokenOptional =
JsonWebTokenAuthCredentialProcessorImpl.getInstance().processJWTAuthHeader(request);

if (!jwTokenOptional.isPresent()){
return PushPublishAuthenticationToken.INVALID_TOKEN;
}

final Optional<User> optionalUser = jwTokenOptional.get().getActiveUser();
if (!optionalUser.isPresent()){
return PushPublishAuthenticationToken.INVALID_TOKEN;
}

return new PushPublishAuthenticationToken(jwTokenOptional.get());
} catch(IncorrectClaimException e){
final String claimName = e.getClaimName();

return Claims.EXPIRATION.equals(claimName) ? PushPublishAuthenticationToken.EXPIRE_TOKEN :
PushPublishAuthenticationToken.INVALID_TOKEN;
}
}

private Optional<PublishingEndPoint> getPublishingEndPointDotCMSToken(final HttpServletRequest request)
throws DotDataException, IOException {
final String remoteIP = request.getRemoteHost();
final PublishingEndPoint publishingEndPoint =
APILocator.getPublisherEndPointAPI().findEnabledSendingEndPointByAddress(remoteIP);

Optional<String> key = PushPublisher.retriveEndpointKeyDigest(publishingEndPoint);
if(!key.isPresent()) {
return Optional.empty();
}

final String token = getTokenFromRequest(request);

return token.equals( key.get() ) ? Optional.of(publishingEndPoint) : Optional.empty();
}

private String getTokenFromRequest(final HttpServletRequest request) {
final String authorizationHeader = request.getHeader(ContainerRequest.AUTHORIZATION);

if (StringUtils.isNotEmpty(authorizationHeader) && authorizationHeader.trim()
.startsWith(BEARER)) {

return authorizationHeader.substring(BEARER.length());
} else {
throw new IllegalArgumentException("Bearer Authorization header expected");
}
}

public static class PushPublishAuthenticationToken {

public static final PushPublishAuthenticationToken EXPIRE_TOKEN = new PushPublishAuthenticationToken(true, false);
public static final PushPublishAuthenticationToken INVALID_TOKEN = new PushPublishAuthenticationToken(false, true);
private final boolean tokenExpired;
private final boolean tokenInvalid;

private JWToken token;
private PublishingEndPoint publishingEndPoint;

private PushPublishAuthenticationToken(boolean tokenExprired, boolean tokenInvalid) {
this(null, null, tokenExprired, tokenInvalid);
}

public PushPublishAuthenticationToken(final JWToken token) {
this(token, null, false, false);
}

public PushPublishAuthenticationToken(final PublishingEndPoint publishingEndPoint) {
this(null, publishingEndPoint, false, false);
}

private PushPublishAuthenticationToken(final JWToken token, final PublishingEndPoint publishingEndPoint,
boolean tokenExpired, boolean tokenInvalid) {
this.token = token;
this.publishingEndPoint = publishingEndPoint;
this.tokenExpired = tokenExpired;
this.tokenInvalid = tokenInvalid;
}

public JWToken getToken() {
return token;
}

public PublishingEndPoint getPublishingEndPoint() {
return publishingEndPoint;
}

public boolean isJWTTokenWay(){
return this.token != null;
}

public boolean isTokenExpired() {
return tokenExpired;
}

public boolean isTokenInvalid() {
return tokenInvalid;
}

public String getKey(){
return isJWTTokenWay() ? getToken().getId() : getPublishingEndPoint().getId();
}

}

}
@@ -21,13 +21,7 @@
import com.dotcms.enterprise.publishing.remote.bundler.UserBundler;
import com.dotcms.enterprise.publishing.remote.bundler.WorkflowBundler;
import com.dotcms.publisher.bundle.bean.Bundle;
import com.dotcms.publisher.business.DotPublisherException;
import com.dotcms.publisher.business.EndpointDetail;
import com.dotcms.publisher.business.PublishAuditAPI;
import com.dotcms.publisher.business.PublishAuditHistory;
import com.dotcms.publisher.business.PublishAuditStatus;
import com.dotcms.publisher.business.PublishQueueElement;
import com.dotcms.publisher.business.PublisherQueueJob;
import com.dotcms.publisher.business.*;
import com.dotcms.publisher.endpoint.bean.PublishingEndPoint;
import com.dotcms.publisher.endpoint.business.PublishingEndPointAPI;
import com.dotcms.publisher.environment.bean.Environment;
@@ -41,6 +35,7 @@
import com.dotcms.publishing.PublisherConfig.DeliveryStrategy;
import com.dotcms.repackage.org.apache.commons.httpclient.HttpStatus;
import com.dotcms.repackage.org.apache.commons.io.FileUtils;
import com.dotcms.rest.ResourceResponse;
import com.dotcms.rest.RestClientBuilder;
import com.dotcms.system.event.local.business.LocalSystemEventsAPI;
import com.dotcms.system.event.local.type.pushpublish.AllPushPublishEndpointsFailureEvent;
@@ -49,11 +44,25 @@
import com.dotcms.util.CloseUtils;
import com.dotmarketing.business.APILocator;
import com.dotmarketing.cms.factories.PublicEncryptionFactory;
import com.dotmarketing.exception.DotDataException;
import com.dotmarketing.exception.DotRuntimeException;
import com.dotmarketing.quartz.QuartzUtils;
import com.dotmarketing.util.Config;
import com.dotmarketing.util.Logger;
import com.dotmarketing.util.PushPublishLogger;
import com.dotmarketing.util.UtilMethods;
import com.liferay.portal.language.LanguageException;
import com.liferay.portal.language.LanguageUtil;
import org.apache.logging.log4j.ThreadContext;
import org.glassfish.jersey.client.ClientProperties;
import org.quartz.JobDetail;
import org.quartz.ObjectAlreadyExistsException;
import org.quartz.Scheduler;

import javax.ws.rs.client.Client;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
@@ -67,16 +76,6 @@
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.apache.logging.log4j.ThreadContext;
import org.glassfish.jersey.client.ClientProperties;
import org.quartz.JobDetail;
import org.quartz.ObjectAlreadyExistsException;
import org.quartz.Scheduler;

/**
* This is the main content publishing class in the Push Publishing process.
@@ -97,29 +96,29 @@
*/
public class PushPublisher extends Publisher {

private PublishAuditAPI pubAuditAPI = PublishAuditAPI.getInstance();
private PublishingEndPointAPI publishingEndPointAPI = APILocator.getPublisherEndPointAPI();
private LocalSystemEventsAPI localSystemEventsAPI = APILocator.getLocalSystemEventsAPI();
private Client restClient;
private PublishAuditAPI pubAuditAPI = PublishAuditAPI.getInstance();
private PublishingEndPointAPI publishingEndPointAPI = APILocator.getPublisherEndPointAPI();
private LocalSystemEventsAPI localSystemEventsAPI = APILocator.getLocalSystemEventsAPI();
private Client restClient;

public static final String PROTOCOL_HTTP = "http";
public static final String PROTOCOL_HTTP = "http";
public static final String PROTOCOL_HTTPS = "https";
private static final String HTTP_PORT = "80";
private static final String HTTP_PORT = "80";
private static final String HTTPS_PORT = "443";

private static final String BUNDLE_ID = "BundleId";
private static final String ENDPOINT_NAME = "EndpointName";

@Override
public PublisherConfig init ( PublisherConfig config ) throws DotPublishingException {
if ( LicenseUtil.getLevel() < LicenseLevel.PROFESSIONAL.level ) {
throw new RuntimeException( "need an enterprise pro license to run this bundler" );
}
@Override
public PublisherConfig init ( PublisherConfig config ) throws DotPublishingException {
if ( LicenseUtil.getLevel() < LicenseLevel.PROFESSIONAL.level ) {
throw new RuntimeException( "need an enterprise pro license to run this bundler" );
}

config.setStatic(false);
this.config = super.init( config );
return this.config;
}
config.setStatic(false);
this.config = super.init( config );
return this.config;
}

/**
* Final step of the Bundle Push Publishing. This method will generate the
@@ -135,35 +134,35 @@ public PublisherConfig init ( PublisherConfig config ) throws DotPublishingExcep
* An error occurred which caused the publishing process to
* stop.
*/
@Override
public PublisherConfig process ( final PublishStatus status ) throws DotPublishingException {
@Override
public PublisherConfig process ( final PublishStatus status ) throws DotPublishingException {
if(LicenseUtil.getLevel() < LicenseLevel.PROFESSIONAL.level) {
throw new RuntimeException("An Enterprise Pro License is required to run this publisher.");
}
PublishAuditHistory currentStatusHistory = null;
throw new RuntimeException("An Enterprise Pro License is required to run this publisher.");
}
PublishAuditHistory currentStatusHistory = null;
try {
//Compressing bundle
File bundleRoot = BundlerUtil.getBundleRoot(this.config);
ArrayList<File> list = new ArrayList<File>(1);
list.add(bundleRoot);
File bundle = new File(bundleRoot+File.separator+".."+File.separator+this.config.getId()+".tar.gz");
File bundleFile = new File(bundleRoot+File.separator+".."+File.separator+this.config.getId()+".tar.gz");

// If the tar.gz doesn't exist or if it the first try to push bundle
// we need to compress the bundle folder into the tar.gz file.
if (!bundle.exists() || !pubAuditAPI.isPublishRetry(config.getId())) {
PushUtils.compressFiles(list, bundle, bundleRoot.getAbsolutePath());
} else {
Logger.info(this, "Retrying bundle: " + config.getId()
+ ", we don't need to compress bundle again");
}
// If the tar.gz doesn't exist or if it the first try to push bundle
// we need to compress the bundle folder into the tar.gz file.
if (!bundleFile.exists() || !pubAuditAPI.isPublishRetry(config.getId())) {
PushUtils.compressFiles(list, bundleFile, bundleRoot.getAbsolutePath());
} else {
Logger.info(this, "Retrying bundle: " + config.getId()
+ ", we don't need to compress bundle again");
}

List<Environment> environments = APILocator.getEnvironmentAPI().findEnvironmentsByBundleId(this.config.getId());
List<Environment> environments = APILocator.getEnvironmentAPI().findEnvironmentsByBundleId(this.config.getId());

Client client = getRestClient();
client.property(ClientProperties.REQUEST_ENTITY_PROCESSING, "CHUNKED");
client.property(ClientProperties.CHUNKED_ENCODING_SIZE, 1024);

String contentDisposition = "attachment; filename=\"" + bundle.getName() + "\"";
String contentDisposition = "attachment; filename=\"" + bundleFile.getName() + "\"";

//Updating audit table
currentStatusHistory = pubAuditAPI.getPublishAuditStatus(this.config.getId()).getStatusPojo();
@@ -176,8 +175,8 @@ public PublisherConfig process ( final PublishStatus status ) throws DotPublishi
//Increment numTries
currentStatusHistory.addNumTries();
// Counters for determining the publishing status
int errorCounter = 0;
int totalEndpoints = 0;
int errorCounter = 0;
int totalEndpoints = 0;
for (Environment environment : environments) {
List<PublishingEndPoint> allEndpoints = this.publishingEndPointAPI.findSendingEndPointsByEnvironment(environment.getId());
List<PublishingEndPoint> endpoints = new ArrayList<PublishingEndPoint>();
@@ -198,8 +197,8 @@ public PublisherConfig process ( final PublishStatus status ) throws DotPublishi
// end-points whose status IS NOT success
if (DeliveryStrategy.ALL_ENDPOINTS.equals(this.config.getDeliveryStrategy())
|| (DeliveryStrategy.FAILED_ENDPOINTS.equals(this.config.getDeliveryStrategy())
&& PublishAuditStatus.Status.SUCCESS.getCode() != epDetail.getStatus()
&& PublishAuditStatus.Status.BUNDLE_SENT_SUCCESSFULLY.getCode() != epDetail.getStatus())) {
&& PublishAuditStatus.Status.SUCCESS.getCode() != epDetail.getStatus()
&& PublishAuditStatus.Status.BUNDLE_SENT_SUCCESSFULLY.getCode() != epDetail.getStatus())) {
endpoints.add(ep);
}
}
@@ -216,73 +215,82 @@ public PublisherConfig process ( final PublishStatus status ) throws DotPublishi
for (PublishingEndPoint endpoint : endpoints) {
EndpointDetail detail = new EndpointDetail();

InputStream bundleStream = new BufferedInputStream(Files.newInputStream(bundle.toPath()));

InputStream bundleStream = new BufferedInputStream(Files.newInputStream(bundleFile.toPath()));


try {
Bundle b=APILocator.getBundleAPI().getBundleById(this.config.getId());
try {
Bundle bundle = APILocator.getBundleAPI().getBundleById(this.config.getId());

//For logging purpose
ThreadContext.put(ENDPOINT_NAME, ENDPOINT_NAME + "=" + endpoint.getServerName());
ThreadContext.put(BUNDLE_ID, BUNDLE_ID + "=" + b.getName());
PushPublishLogger.log(this.getClass(), "Status Update: Sending Bundle");
WebTarget webTarget = client.target(endpoint.toURL()+"/api/bundlePublisher/publish")
.queryParam("AUTH_TOKEN", PushPublisher.retriveEndpointKeyDigest(endpoint).get())
.queryParam("GROUP_ID", UtilMethods.isSet(endpoint.getGroupId()) ? endpoint.getGroupId() : endpoint.getId())
.queryParam("BUNDLE_NAME", b.getName())
.queryParam("ENDPOINT_ID", endpoint.getId())
.queryParam("FILE_NAME", bundle.getName())
.queryParam("FORCE_PUSH", b.isForcePush())
;

Response response = webTarget.request(MediaType.APPLICATION_OCTET_STREAM_TYPE)
.header("Content-Disposition", contentDisposition)
.post(Entity.entity(bundleStream, MediaType.APPLICATION_OCTET_STREAM_TYPE));

if(response.getStatus() == HttpStatus.SC_OK)
{
PushPublishLogger.log(this.getClass(), "Status Update: Bundle sent");
detail.setStatus(PublishAuditStatus.Status.BUNDLE_SENT_SUCCESSFULLY.getCode());
detail.setInfo("Everything ok");
} else {

PushPublishLogger.log(this.getClass(), "Status Update: Failed to send bundle.");
if(currentStatusHistory.getNumTries() >= PublisherQueueJob.MAX_NUM_TRIES) {
APILocator.getPushedAssetsAPI().deletePushedAssets(this.config.getId(), environment.getId());
}
detail.setStatus(PublishAuditStatus.Status.FAILED_TO_SENT.getCode());
detail.setInfo(
"Returned " + response.getStatus() + " status code " +
"for the endpoint " + endpoint.getServerName() + " with address " + endpoint
.getAddress() + getFormattedPort(endpoint.getPort()));
failedEnvironment |= true;
}
} catch(Exception e) {
// if the bundle can't be sent after the total num of tries, delete the pushed assets for this bundle
if(currentStatusHistory.getNumTries() >= PublisherQueueJob.MAX_NUM_TRIES) {
APILocator.getPushedAssetsAPI().deletePushedAssets(this.config.getId(), environment.getId());
}
detail.setStatus(PublishAuditStatus.Status.FAILED_TO_SENT.getCode());
ThreadContext.put(BUNDLE_ID, BUNDLE_ID + "=" + bundle.getName());

if (endpoint.hasAuthKey()) {
PushPublishLogger.log(this.getClass(), "Status Update: Sending Bundle");

WebTarget webTarget = client.target(endpoint.toURL() + "/api/bundlePublisher/publish")
.queryParam("FORCE_PUSH", bundle.isForcePush());

Response response = webTarget.request(MediaType.APPLICATION_JSON)
.header("Content-Disposition", contentDisposition)
.header("Authorization", AuthCredentialPushPublishUtil.INSTANCE.getRequestToken(endpoint).get())
.post(Entity.entity(bundleStream, MediaType.APPLICATION_OCTET_STREAM_TYPE));

if (response.getStatus() == HttpStatus.SC_OK) {
PushPublishLogger.log(this.getClass(), "Status Update: Bundle sent");
detail.setStatus(PublishAuditStatus.Status.BUNDLE_SENT_SUCCESSFULLY.getCode());
detail.setInfo("Everything ok");
} else if (response.getStatus() == HttpStatus.SC_UNAUTHORIZED) {

handleInvalidTokenResponse(environment, endpoint, detail, response);
failedEnvironment = true;
errorCounter++;
} else if (response.getStatus() == HttpStatus.SC_FORBIDDEN){
markAsLicenseRequired(environment, endpoint, detail);
failedEnvironment = true;
errorCounter++;
} else {

PushPublishLogger.log(this.getClass(), "Status Update: Failed to send bundle.");
if (currentStatusHistory.getNumTries() >= PublisherQueueJob.MAX_NUM_TRIES) {
APILocator.getPushedAssetsAPI().deletePushedAssets(this.config.getId(), environment.getId());
}
detail.setStatus(PublishAuditStatus.Status.FAILED_TO_SENT.getCode());
detail.setInfo(
"Returned " + response.getStatus() + " status code " +
"for the endpoint " + endpoint.getServerName() + " with address " + endpoint
.getAddress() + getFormattedPort(endpoint.getPort()));
failedEnvironment = true;
}
} else {
markAsInValidToken(environment, endpoint, detail, AuthCredentialPushPublishUtil.INVALID_TOKEN_ERROR_KEY);
failedEnvironment = true;
errorCounter++;
}
} catch(Exception e){
// if the bundle can't be sent after the total num of tries, delete the pushed assets for this bundle
if (currentStatusHistory.getNumTries() >= PublisherQueueJob.MAX_NUM_TRIES) {
APILocator.getPushedAssetsAPI().deletePushedAssets(this.config.getId(), environment.getId());
}
detail.setStatus(PublishAuditStatus.Status.FAILED_TO_SENT.getCode());
String
error =
"An error occurred for the endpoint " + endpoint.getServerName() + " with address "
+ endpoint.getAddress() + getFormattedPort(
endpoint.getPort()) + ". Error: " + e.getMessage();
error =
"An error occurred for the endpoint " + endpoint.getServerName() + " with address "
+ endpoint.getAddress() + getFormattedPort(
endpoint.getPort()) + ". Error: " + e.getMessage();
detail.setInfo(error);
failedEnvironment |= true;
errorCounter++;
Logger.error(this.getClass(), error, e);
failedEnvironment |= true;
errorCounter++;
Logger.error(this.getClass(), error, e);

PushPublishLogger.log(this.getClass(), "Status Update: Failed to send bundle. Exception: " + e.getMessage());
} finally {
CloseUtils.closeQuietly(bundleStream);
} finally{
CloseUtils.closeQuietly(bundleStream);
ThreadContext.remove(ENDPOINT_NAME);
ThreadContext.remove(BUNDLE_ID);
}
if (isHistoryEmpty || failedEnvironment) {
currentStatusHistory.addOrUpdateEndpoint(environment.getId(), endpoint.getId(), detail);
}
}
if (isHistoryEmpty || failedEnvironment) {
currentStatusHistory.addOrUpdateEndpoint(environment.getId(), endpoint.getId(), detail);
}
}
}

@@ -332,6 +340,72 @@ public PublisherConfig process ( final PublishStatus status ) throws DotPublishi
}
}

private void handleInvalidTokenResponse(
final Environment environment,
final PublishingEndPoint endpoint,
final EndpointDetail detail,
final Response response) throws DotDataException, LanguageException {

final Map<String, String> wwwAuthenticateHeader = ResourceResponse.getWWWAuthenticateHeader(response);
final String errorKey = wwwAuthenticateHeader.get("error_key").replaceAll("\"", "");

markAsInValidToken(environment, endpoint, detail, errorKey);
}

private void markAsInValidToken(
final Environment environment,
final PublishingEndPoint endpoint,
final EndpointDetail detail,
final String errorKey) throws DotDataException, LanguageException {

final String message = LanguageUtil.get(String.format("push_publish.end_point.%s_message", errorKey));
APILocator.getPublisherEndPointAPI().updateEndPoint(endpoint);

final PublishAuditStatus.Status invalidToken = PublishAuditStatus.Status.INVALID_TOKEN;
updatingPublishingDetailStatus(environment, endpoint, detail, errorKey, invalidToken, message);
}

private void markAsLicenseRequired(
final Environment environment,
final PublishingEndPoint endpoint,
final EndpointDetail detail) throws DotDataException, LanguageException {

final String message = LanguageUtil.get(String.format("push_publish.end_point.license_required_message"));
APILocator.getPublisherEndPointAPI().updateEndPoint(endpoint);

final PublishAuditStatus.Status licenseRequired = PublishAuditStatus.Status.LICENSE_REQUIRED;
updatingPublishingDetailStatus(environment, endpoint, detail, null, licenseRequired, message);
}

private void updatingPublishingDetailStatus(
final Environment environment,
final PublishingEndPoint endpoint,
final EndpointDetail detail,
final String newAuthKey,
final PublishAuditStatus.Status status,
final String message) throws DotDataException {

APILocator.getPushedAssetsAPI().deletePushedAssets(this.config.getId(), environment.getId());

detail.setStatus(status.getCode());
detail.setInfo(message);
PushPublishLogger.log(this.getClass(), message);

if (newAuthKey != null) {
endpoint.setAuthKey(newAuthKey);
}

APILocator.getPublisherEndPointAPI().updateEndPoint(endpoint);

try {
PublisherAPI.getInstance().deleteElementsFromPublishQueueTable(this.config.getId());
} catch (DotPublisherException e) {
Logger.error(this.getClass(), e);
throw new DotRuntimeException(e);
}
}


/**
* @param port
* @return
@@ -341,23 +415,30 @@ private String getFormattedPort(String port){
if(port !=null && !port.equals(HTTP_PORT) && !port.equals(HTTPS_PORT)){
return ":" + port;
}
return "";
return "";
}

/**
*
* @param token
* @return
* @throws IOException
*/
/**
* Return a end point token
* @return
* @throws IOException
*/
//todo: I think this method can be remove
public static Optional<String> retriveEndpointKeyDigest(final PublishingEndPoint endpoint) throws IOException {

if(endpoint==null || endpoint.getAuthKey() ==null) {
Logger.warn(PushPublisher.class,"Endpoint or endpoint key is null:" + endpoint);
return Optional.empty();
}

String token = PublicEncryptionFactory.decryptString(endpoint.getAuthKey().toString());

final Optional<String> key = retriveEndpointKey(endpoint);

return key.isPresent() ? Optional.of(PublicEncryptionFactory.digestString(key.get())) : Optional.empty();
}

public static Optional<String> retriveEndpointKey(final PublishingEndPoint endpoint) throws IOException { // todo: create a method that allows to receives a key and use the com.dotcms.util.security.Encryptor instead PublicEncryptionFactory

if(endpoint==null || endpoint.getAuthKey() ==null) {
Logger.warn(PushPublisher.class,"Endpoint or endpoint key is null:" + endpoint);
return Optional.empty();
}

String token = PublicEncryptionFactory.decryptString(endpoint.getAuthKey().toString());
String key = null;
if(token.contains(File.separator)) {
File tokenFile = new File(token);
@@ -366,94 +447,94 @@ private String getFormattedPort(String port){
} else {
key = token;
}
return key==null ? Optional.empty() : Optional.of(PublicEncryptionFactory.digestString(key));
return key==null ? Optional.empty() : Optional.of(key);
}

@Override
public List<Class> getBundlers () {
boolean buildUsers = false;
boolean buildCategories = false;
boolean buildOSGIBundle = false;
boolean buildLanguages = false;
boolean buildRules = false;
boolean buildAsset = false;
List<Class> list = new ArrayList<>();
for ( PublishQueueElement element : config.getAssets() ) {
if ( element.getType().equals(PusheableAsset.CATEGORY.getType()) ) {
buildCategories = true;
} else if ( element.getType().equals(PusheableAsset.OSGI.getType()) ) {
buildOSGIBundle = true;
} else if ( element.getType().equals(PusheableAsset.USER.getType()) ) {
buildUsers = true;
} else if (element.getType().equals(PusheableAsset.LANGUAGE.getType())) {
buildLanguages = true;
} else if (element.getType().equals(PusheableAsset.RULE.getType())) {
buildRules = true;
} else {
buildAsset = true;
}
}
if(config.getLuceneQueries().size() > 0){
buildAsset = true;
}
if ( buildUsers ) {
list.add( UserBundler.class );
}
if ( buildOSGIBundle ) {
list.add( OSGIBundler.class );
}
if ( buildAsset || buildLanguages) {
list.add( DependencyBundler.class );
list.add( HostBundler.class );
list.add( ContentBundler.class );
list.add( FolderBundler.class );
list.add( TemplateBundler.class );
list.add( ContainerBundler.class );
list.add(RuleBundler.class);
list.add( LinkBundler.class );
if ( Config.getBooleanProperty("PUSH_PUBLISHING_PUSH_STRUCTURES", false) ) {
list.add( ContentTypeBundler.class );
list.add( RelationshipBundler.class );
}
list.add( LanguageVariablesBundler.class );
list.add( WorkflowBundler.class );
list.add( LanguageBundler.class );
} else {
@Override
public List<Class> getBundlers () {
boolean buildUsers = false;
boolean buildCategories = false;
boolean buildOSGIBundle = false;
boolean buildLanguages = false;
boolean buildRules = false;
boolean buildAsset = false;
List<Class> list = new ArrayList<>();
for ( PublishQueueElement element : config.getAssets() ) {
if ( element.getType().equals(PusheableAsset.CATEGORY.getType()) ) {
buildCategories = true;
} else if ( element.getType().equals(PusheableAsset.OSGI.getType()) ) {
buildOSGIBundle = true;
} else if ( element.getType().equals(PusheableAsset.USER.getType()) ) {
buildUsers = true;
} else if (element.getType().equals(PusheableAsset.LANGUAGE.getType())) {
buildLanguages = true;
} else if (element.getType().equals(PusheableAsset.RULE.getType())) {
buildRules = true;
} else {
buildAsset = true;
}
}
if(config.getLuceneQueries().size() > 0){
buildAsset = true;
}
if ( buildUsers ) {
list.add( UserBundler.class );
}
if ( buildOSGIBundle ) {
list.add( OSGIBundler.class );
}
if ( buildAsset || buildLanguages) {
list.add( DependencyBundler.class );
list.add( HostBundler.class );
list.add( ContentBundler.class );
list.add( FolderBundler.class );
list.add( TemplateBundler.class );
list.add( ContainerBundler.class );
list.add(RuleBundler.class);
list.add( LinkBundler.class );
if ( Config.getBooleanProperty("PUSH_PUBLISHING_PUSH_STRUCTURES", false) ) {
list.add( ContentTypeBundler.class );
list.add( RelationshipBundler.class );
}
list.add( LanguageVariablesBundler.class );
list.add( WorkflowBundler.class );
list.add( LanguageBundler.class );
} else {
list.add(DependencyBundler.class);
if (buildRules) {
list.add(HostBundler.class);
list.add(RuleBundler.class);
}
}
list.add( BundleXMLAsc.class );
if ( buildCategories ) { // If we are PP from the categories portlet.
list.add( CategoryFullBundler.class );
} else { // If we are PP from anywhere else, for example a contentlet, site, folder, etc.
list.add(CategoryBundler.class);
}
return list;
}

@Override
}
list.add( BundleXMLAsc.class );
if ( buildCategories ) { // If we are PP from the categories portlet.
list.add( CategoryFullBundler.class );
} else { // If we are PP from anywhere else, for example a contentlet, site, folder, etc.
list.add(CategoryBundler.class);
}
return list;
}

@Override
public Set<String> getProtocols(){
Set<String> protocols = new HashSet<>();
protocols.add(PROTOCOL_HTTP);
protocols.add(PROTOCOL_HTTPS);
return protocols;
}

/**
* Returns an instance of the REST {@link Client} used to access Push Publishing end-points and
* retrieve their information.
*
* @return The REST {@link Client}.
*/
private Client getRestClient() {
if (null == this.restClient) {
this.restClient = RestClientBuilder.newClient();
}
return this.restClient;
}
/**
* Returns an instance of the REST {@link Client} used to access Push Publishing end-points and
* retrieve their information.
*
* @return The REST {@link Client}.
*/
private Client getRestClient() {
if (null == this.restClient) {
this.restClient = RestClientBuilder.newClient();
}
return this.restClient;
}

/**
* Allows to update the delivery strategy to the PublishQueueJob
@@ -476,4 +557,4 @@ private void updateJobDataMap(DeliveryStrategy deliveryStrategy) {
}
}

}
}
@@ -4,10 +4,18 @@
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import com.dotcms.publisher.bundle.bean.Bundle;
import com.dotcms.publisher.business.PublishQueueElement;
import com.dotcms.publisher.business.PublisherAPI;
import com.dotcms.publisher.endpoint.bean.PublishingEndPoint;
import com.dotcms.publisher.util.PublisherUtil;
import com.dotcms.publishing.PublisherConfig;
import com.dotmarketing.business.APILocator;
import com.dotmarketing.portlets.rules.model.Rule;
import com.dotmarketing.util.Logger;
import com.google.common.collect.ImmutableList;
import com.liferay.portal.model.User;
import io.vavr.control.Try;

/**
* This class provides the main configuration values for the bundle that is
@@ -42,6 +50,44 @@ public PushPublisherConfig() {
super();
}

/**
* Convenience constructor for generating a PP config from a bundle
*
* @param bundle
*/
public PushPublisherConfig(final Bundle bundle) {
super();

final PublisherAPI publisherAPI = PublisherAPI.getInstance();

final List<PublishQueueElement> tempBundleContents =
Try.of(() -> publisherAPI.getQueueElementsByBundleId(bundle.getId()))
.onFailure(e -> Logger.warnAndDebug(PushPublisherConfig.class, e))
.getOrElse(ImmutableList.of());
final List<PublishQueueElement> assetsToPublish = new ArrayList<PublishQueueElement>();
assetsToPublish.addAll(tempBundleContents);


this.setDownloading(true);
this.setOperation(PushPublisherConfig.Operation.PUBLISH.ordinal() == bundle.getOperation()
? PushPublisherConfig.Operation.PUBLISH
: PushPublisherConfig.Operation.UNPUBLISH);

setAssets(assetsToPublish);
// Queries creation
setLuceneQueries(PublisherUtil.prepareQueries(tempBundleContents));
setId(bundle.getId());
// get the bundle user or system user if failure
final User user = Try.of(()->APILocator.getUserAPI().loadUserById(bundle.getOwner())).getOrElse(APILocator.systemUser());

setUser(user);


}




boolean switchIndexWhenDone = false;

public boolean switchIndexWhenDone(){
@@ -1,14 +1,19 @@
package com.dotcms.publisher.pusher;

import com.dotcms.repackage.org.apache.commons.io.IOUtils;
import com.dotmarketing.business.APILocator;
import com.dotmarketing.exception.DotRuntimeException;
import com.dotmarketing.util.Logger;
import com.dotmarketing.util.UUIDGenerator;
import com.liferay.util.FileUtil;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.util.Collection;
import java.util.List;
import java.util.zip.GZIPOutputStream;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
@@ -50,7 +55,42 @@ public static File compressFiles(Collection<File> files, File output, String bun
return output;
}



/**
* Tar and GZIPs a directory on the asset path
* @param directory
* @return
* @throws IOException
*/
public static File tarGzipDirectory(final File directory) throws IOException {
if (directory == null || !directory.exists() || !directory.isDirectory()) {
throw new DotRuntimeException("Unable to compress directory:" + directory);
}
final String tempFileId = directory.getName() + UUIDGenerator.shorty();
final File tempFile = new File(APILocator.getFileAssetAPI().getRealAssetPathTmpBinary() +File.separator + tempFileId + ".tar.gz");
final List<File> files = FileUtil.listFilesRecursively(directory);

Logger.info(PushUtils.class, "Compressing " + files.size() + " to " + tempFile.getAbsoluteFile());
// Create the output stream for the output file

// try-with-resources handles close of streams
try (final OutputStream fileOutputStream = Files.newOutputStream(tempFile.toPath());
// Wrap the output file stream in streams that will tar and gzip everything
final TarArchiveOutputStream tarArchiveOutputStream = new TarArchiveOutputStream(
new GZIPOutputStream(new BufferedOutputStream(fileOutputStream)))) {

tarArchiveOutputStream.setBigNumberMode(TarArchiveOutputStream.BIGNUMBER_STAR);
// TAR originally didn't support long file names, so enable the support for it
tarArchiveOutputStream.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU);

// Get to putting all the files in the compressed output file
for (final File file : files) {
addFilesToCompression(tarArchiveOutputStream, file, ".", directory.getAbsolutePath());
}
}

return tempFile;
}

/**
* Does the work of compression and going recursive for nested directories
@@ -65,6 +105,12 @@ public static File compressFiles(Collection<File> files, File output, String bun
private static void addFilesToCompression(TarArchiveOutputStream taos, File file, String dir, String bundleRoot)
throws IOException
{
if(!file.getAbsolutePath().contains(bundleRoot)) {
throw new DotRuntimeException("Directory Traversal Warning: You can only tar files that are under the directory:" + bundleRoot + " found " + file.getAbsolutePath() );
}



if(!file.isHidden()) {
// Create an entry for the file
if(!dir.equals("."))
@@ -0,0 +1,27 @@
package com.dotcms.publishing;

import java.io.File;
import com.dotcms.publisher.pusher.PushPublisher;
import com.dotmarketing.util.Logger;

/**
* This GenerateBundlePublisher uses the same config/bundlers as the PushPublisher - the only
* difference is that once this publisher has built the bundle it does not do anything with it. The
* GenerateBundlePublisher is meant to be used to build the bundle when a user selects to download a
* bundle
*
* @author will
*
*/
public class GenerateBundlePublisher extends PushPublisher {

@Override
public PublisherConfig process(final PublishStatus status) throws DotPublishingException {
final File bundleRoot = BundlerUtil.getBundleRoot(this.config);
Logger.info(this.getClass(), "Bundling Complete: " + bundleRoot);
return this.config;
}



}
@@ -1,30 +1,29 @@
package com.dotcms.publishing;

import java.io.File;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import com.dotcms.business.CloseDBIfOpened;
import com.dotcms.publisher.business.PublishAuditAPI;
import com.dotcms.publisher.business.PublishAuditHistory;
import com.dotcms.publisher.business.PublishAuditStatus;
import com.dotcms.system.event.local.business.LocalSystemEventsAPI;
import com.dotcms.system.event.local.type.pushpublish.PushPublishEndEvent;
import com.dotcms.system.event.local.type.pushpublish.PushPublishStartEvent;
import com.dotcms.business.CloseDBIfOpened;
import com.dotcms.publisher.business.PublishAuditAPI;
import com.dotcms.system.event.local.type.staticpublish.StaticPublishEndEvent;
import com.dotcms.system.event.local.type.staticpublish.StaticPublishStartEvent;
import com.dotmarketing.business.APILocator;
import com.dotmarketing.business.Role;
import com.dotmarketing.exception.DotDataException;
import com.dotmarketing.exception.DotSecurityException;
import com.dotmarketing.portlets.contentlet.model.Contentlet;
import com.dotmarketing.util.Logger;
import com.dotmarketing.util.PushPublishLogger;
import com.dotmarketing.util.UtilMethods;
import com.google.common.annotations.VisibleForTesting;
import com.liferay.portal.model.User;
import java.io.File;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class PublisherAPIImpl implements PublisherAPI {

@@ -34,14 +33,14 @@


@Override
public PublishStatus publish ( PublisherConfig config ) throws DotPublishingException {
final public PublishStatus publish ( PublisherConfig config ) throws DotPublishingException {

return publish( config, new PublishStatus() );
}

@CloseDBIfOpened
@Override
public PublishStatus publish ( PublisherConfig config, PublishStatus status ) throws DotPublishingException {
final public PublishStatus publish ( PublisherConfig config, PublishStatus status ) throws DotPublishingException {

PushPublishLogger.log( this.getClass(), "Started Publishing Task", config.getId() );

@@ -20,12 +20,15 @@
public void init() {

try {
//Clear filtersMap
PublisherAPIImpl.class.cast(APILocator.getPublisherAPI()).getFilterDescriptorMap().clear();
//Path where the YAML files are stored
final String filtersDirectoryString =
APILocator.getFileAssetAPI().getRealAssetsRootPath() + File.separator + "server"
+ File.separator + "publishing-filters" + File.separator;
final File basePath = new File(filtersDirectoryString);
if (!basePath.exists()) {
Logger.debug(PushPublishFiltersInitializer.class, ()->"PushPublishing Filters directory does not exists, creating under: " + filtersDirectoryString);
basePath.mkdir();
//If the directory does not exists, copy the YAML files that are ship with
//dotcms to the created directory
@@ -43,7 +46,7 @@ public void init() {
});
Logger.debug(PushPublishFiltersInitializer.class, ()->" dotcms filters files copied");
}
Logger.info(PushPublishFiltersInitializer.class, " ymlFiles are set under: " + filtersDirectoryString);
Logger.debug(PushPublishFiltersInitializer.class, ()->" PushPublishing Filters Directory: " + filtersDirectoryString);
//For each YAML file under the directory,
// read it and load the Filter to the PublisherAPI.loadedFilters
Files.list(basePath.toPath()).forEach(this::loadFilter);
@@ -54,7 +57,7 @@ public void init() {

protected void loadFilter(final Path path){
final String fileName = path.getFileName().toString();
Logger.info(PushPublishFiltersInitializer.class, " ymlFileName: " + fileName);
Logger.info(PushPublishFiltersInitializer.class, " Loading PushPublish Filter: " + fileName);
try {
final FilterDescriptor filterDescriptor = YamlUtil.parse(path, FilterDescriptor.class);
filterDescriptor.setKey(fileName);
@@ -1,5 +1,6 @@
package com.dotcms.rendering.velocity.directive;

import static com.dotcms.rendering.velocity.services.ContainerLoader.*;
import static com.dotmarketing.util.StringUtils.builder;
import static com.liferay.util.StringPool.FORWARD_SLASH;
import static com.liferay.util.StringPool.PERIOD;
@@ -139,9 +140,21 @@ private boolean isPath (final String templatePath) {

private String getPath(final RenderParams params, final String path, final String uid) {
try {
return FileAssetContainerUtil.getInstance().isFullPath(path) ?
getContainerResourcePathFromFullPath(params, path, uid) :
getContainerResourceFromRelativePath(params, path, uid);
String fileContainerPathToVelocityPath = null;

if (FileAssetContainerUtil.getInstance().isFullPath(path)) {
fileContainerPathToVelocityPath =
path.replaceAll(FORWARD_SLASH, FILE_CONTAINER_PATH_SEPARATOR_IN_VELOCITY_KEY)
.replaceAll("\\" + PERIOD, HOST_NAME_SEPARATOR_IN_VELOCITY_KEY);
} else {
final String fullPath = FileAssetContainerUtil.getInstance().getFullPath(path);
fileContainerPathToVelocityPath = RESOLVE_RELATIVE + FILE_CONTAINER_PATH_SEPARATOR_IN_VELOCITY_KEY +
fullPath.replaceAll(FORWARD_SLASH, FILE_CONTAINER_PATH_SEPARATOR_IN_VELOCITY_KEY)
.replaceAll("\\" + PERIOD, HOST_NAME_SEPARATOR_IN_VELOCITY_KEY);
}

return builder(FORWARD_SLASH, params.mode.name(), FORWARD_SLASH, fileContainerPathToVelocityPath,
FORWARD_SLASH, uid, PERIOD, VelocityType.CONTAINER.fileExtension).toString();
} catch (Exception e) {

Logger.warn(this.getClass(), " - unable to resolve " + path + " getting this: "+ e.getMessage() );
@@ -151,40 +164,6 @@ private String getPath(final RenderParams params, final String path, final Strin
throw new DotStateException(e);
}
}

private String getContainerResourceFromRelativePath(
final RenderParams params,
final String path,
final String uid) {

return builder(FORWARD_SLASH, params.mode.name(), FORWARD_SLASH, HOST_INDICATOR,
params.currentHost.getHostname(), path.startsWith(FORWARD_SLASH)? StringPool.BLANK:FORWARD_SLASH,
path, FORWARD_SLASH, uid, PERIOD, VelocityType.CONTAINER.fileExtension).toString();
}

private String getContainerResourcePathFromFullPath(
final RenderParams params,
final String path,
final String uid) {

return builder(FORWARD_SLASH, params.mode.name(), FORWARD_SLASH,
path, FORWARD_SLASH, uid, PERIOD, VelocityType.CONTAINER.fileExtension).toString();
}

private Host getHost(final Host host) {

if (null == host) {

try {
return APILocator.getHostAPI().findDefaultHost(APILocator.systemUser(), false);
} catch (DotDataException | DotSecurityException e) {

return APILocator.systemHost();
}
}

return host;
}
} // PathTemplatePathStrategyImpl.

/////////////
@@ -21,6 +21,7 @@
import com.dotmarketing.util.Logger;
import com.dotmarketing.util.PageMode;
import com.dotmarketing.util.UtilMethods;
import com.liferay.util.StringPool;
import org.apache.velocity.runtime.resource.ResourceManager;

import java.io.InputStream;
@@ -31,33 +32,39 @@
import java.util.Set;
import java.util.stream.Collectors;

import static com.liferay.util.StringPool.PERIOD;

/**
* @author will
*/
public class ContainerLoader implements DotLoader {

public static final String SHOW_PRE_POST_LOOP="SHOW_PRE_POST_LOOP";
public static final String FILE_CONTAINER_PATH_SEPARATOR_IN_VELOCITY_KEY = "%";
public static final String HOST_NAME_SEPARATOR_IN_VELOCITY_KEY = "#";
public static final String RESOLVE_RELATIVE = "resolve_relative";

@Override
public InputStream writeObject(final VelocityResourceKey key)
throws DotDataException, DotSecurityException {

final ContainerFinderStrategyResolver resolver =
ContainerFinderStrategyResolver.getInstance();
final Optional<ContainerFinderStrategy> strategy =
resolver.get(key);

try {
final Container container = strategy.isPresent() ?
strategy.get().apply(key) : resolver.getDefaultStrategy().apply(key);
final String containerIdOrPath = key.path.split(StringPool.FORWARD_SLASH)[2]
.replaceAll(RESOLVE_RELATIVE + FILE_CONTAINER_PATH_SEPARATOR_IN_VELOCITY_KEY, "")
.replaceAll(FILE_CONTAINER_PATH_SEPARATOR_IN_VELOCITY_KEY, StringPool.FORWARD_SLASH)
.replaceAll(HOST_NAME_SEPARATOR_IN_VELOCITY_KEY, PERIOD);

final Optional<Container> optionalContainer =
APILocator.getContainerAPI().findContainer(containerIdOrPath, APILocator.systemUser(), key.mode.showLive, key.mode.respectAnonPerms);

if (null == container) {
if (!optionalContainer.isPresent()) {

final DotStateException dotStateException = new DotStateException("Cannot find container for : " + key);
new ContainerExceptionNotifier(dotStateException, UtilMethods.isSet(key.id1) ? key.id1 : key.path).notifyUser();
throw dotStateException;
}

final Container container = optionalContainer.get();
if (container.isArchived()) {

throw new DotStateException("The container : " + key + " is archived");
@@ -31,6 +31,8 @@
import com.dotmarketing.portlets.contentlet.business.ContentletAPI;
import com.dotmarketing.portlets.contentlet.business.DotContentletStateException;
import com.dotmarketing.portlets.contentlet.model.Contentlet;
import com.dotmarketing.portlets.contentlet.transform.DotContentletTransformer;
import com.dotmarketing.portlets.contentlet.transform.DotTransformerBuilder;
import com.dotmarketing.portlets.contentlet.util.ContentletUtil;
import com.dotmarketing.portlets.htmlpageasset.business.render.ContainerRaw;
import com.dotmarketing.portlets.htmlpageasset.model.HTMLPageAsset;
@@ -266,7 +268,7 @@ private Container geContainerById(final String containerIdOrPath, final User use
final List<ContainerStructure> containerStructures = APILocator.getContainerAPI().getContainerStructures(container);
this.addPermissions(container);

final Map<String, List<Map<String, Object>>> contentMaps = Maps.newLinkedHashMap();
final Map<String, List<Contentlet>> contentMaps = Maps.newLinkedHashMap();
final Map<String, List<String>> containerUuidPersona = Maps.newHashMap();
for (final String uniqueId : pageContents.row(containerId).keySet()) {

@@ -278,17 +280,20 @@ private Container geContainerById(final String containerIdOrPath, final User use
}

final Collection<PersonalizedContentlet> personalizedContentletSet = pageContents.get(containerId, uniqueId);
final List<Map<String, Object>> personalizedContentletMap = Lists.newArrayList();
final List<Contentlet> personalizedContentletMap = Lists.newArrayList();
int contentletIncludedCount = 1;
for (final PersonalizedContentlet personalizedContentlet : personalizedContentletSet) {

final Contentlet contentlet = this.getContentlet(personalizedContentlet);

if (contentlet == null) {
final Contentlet nonHydratedContentlet = this.getContentlet(personalizedContentlet);

if (nonHydratedContentlet == null) {
continue;
}

final DotContentletTransformer transformer = new DotTransformerBuilder()
.defaultOptions().content(nonHydratedContentlet).build();
final Contentlet contentlet = transformer.hydrate().get(0);

if (container.getMaxContentlets() < contentletIncludedCount) {

Logger.debug(this, ()-> "Contentlet: " + contentlet.getIdentifier()
@@ -310,15 +315,8 @@ private Container geContainerById(final String containerIdOrPath, final User use

if (personalizedContentlet.getPersonalization().equals(includeContentFor)) {

final Map<String, Object> contentPrintableMap = Try.of(() -> ContentletUtil.getContentPrintableMap(user, contentlet))
.onFailure(f -> Logger.warn(this.getClass(), f.getMessage())).getOrNull();
if (contentPrintableMap == null) {

continue;
}

contentPrintableMap.put("contentType", contentlet.getContentType().variable());
personalizedContentletMap.add(contentPrintableMap);
contentlet.getMap().put("contentType", contentlet.getContentType().variable());
personalizedContentletMap.add(contentlet);
contentletIncludedCount++;
}
}
@@ -346,30 +344,9 @@ private boolean isLive(final HttpServletRequest request) {
}

private Container getContainer(final boolean live, final String containerId) throws DotSecurityException, DotDataException {

Container container = null;
final WorkingContainerFinderByIdOrPathStrategyResolver strategyResolver =
WorkingContainerFinderByIdOrPathStrategyResolver.getInstance();
final Optional<ContainerFinderByIdOrPathStrategy> strategy = strategyResolver.get(containerId);
final ContainerFinderByIdOrPathStrategy workingStrategy = strategy.isPresent() ? strategy.get() : strategyResolver.getDefaultStrategy();
final Supplier<Host> resourceHostSupplier = () -> this.site;

try {
if (live) {

container = this.getLiveContainerById(containerId);
if (null == container) {
container = workingStrategy.apply(containerId, APILocator.systemUser(), false, resourceHostSupplier);
}
} else {
container = workingStrategy.apply(containerId, APILocator.systemUser(), false, resourceHostSupplier);
}
} catch (NotFoundInDbException | DotRuntimeException e) {

new ContainerExceptionNotifier(e, containerId).notifyUser();
container = null;
}
return container;
final Optional<Container> optionalContainer =
APILocator.getContainerAPI().findContainer(containerId, APILocator.systemUser(), live, false);
return optionalContainer.isPresent() ? optionalContainer.get() : null;
}

private void addPermissions(final Container container) throws DotDataException {
@@ -417,16 +394,8 @@ private void widgetPreExecute(final Contentlet contentlet) {
}

private boolean needParseContainerPrefix(final Container container, final String uniqueId) {
String containerIdOrPath = null;

if (FileAssetContainerUtil.getInstance().isFileAssetContainer(container)) {
containerIdOrPath = FileAssetContainerUtil.getInstance().getFullPath((FileAssetContainer) container);
} else {
containerIdOrPath = container.getIdentifier();
}

return !ParseContainer.isParserContainerUUID(uniqueId) &&
(templateLayout == null || !templateLayout.existsContainer(containerIdOrPath, uniqueId));
(templateLayout == null || !templateLayout.existsContainer(container, uniqueId));
}

private Contentlet getContentlet(final PersonalizedContentlet personalizedContentlet) {
@@ -59,26 +59,13 @@ public VelocityResourceKey(final Contentlet asset, final PageMode mode, final lo
public VelocityResourceKey(final String filePath) {

path = cleanKey(filePath);
// if it is like this: 1/EDIT_MODE///demo.dotcms.com/application/containers/large-column//1551200983126.container
final boolean isFileAssetContainer = filePath.endsWith("." + VelocityType.CONTAINER.fileExtension) && filePath.contains(HOST_INDICATOR);

// if it is a container and container fs with a path as key
final Tuple2<String, String> containerPathIdAndNormalizedPathTuple = isFileAssetContainer?
this.getPathNormalizedFileAssetContainerPath(this.path):null;

final String[] pathArry = isFileAssetContainer
&& null != containerPathIdAndNormalizedPathTuple
&& UtilMethods.isSet(containerPathIdAndNormalizedPathTuple._2)?
containerPathIdAndNormalizedPathTuple._2.split("[/\\.]", 0): // getting the path
path.split("[/\\.]", 0);
final String[] pathArry = path.split("[/\\.]", 0);

this.mode = PageMode.get(pathArry[1]);

this.id1 = isFileAssetContainer && null != containerPathIdAndNormalizedPathTuple
&& UtilMethods.isSet(containerPathIdAndNormalizedPathTuple._1)?

containerPathIdAndNormalizedPathTuple._1:
pathArry[2].indexOf("_") > -1 ? pathArry[2].substring(0, pathArry[2].indexOf("_")) : pathArry[2];
this.id1 = pathArry[2].indexOf("_") > -1 ? pathArry[2].substring(0, pathArry[2].indexOf("_")) : pathArry[2];

this.language = pathArry[2].indexOf("_") > -1 ? pathArry[2].substring(pathArry[2].indexOf("_") + 1, pathArry[2].length())
: String.valueOf(APILocator.getLanguageAPI().getDefaultLanguage().getId());
@@ -1,5 +1,6 @@
package com.dotcms.rendering.velocity.viewtools;

import com.dotmarketing.portlets.templates.design.bean.ContainerUUID;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;

@@ -27,6 +28,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import javax.servlet.http.HttpServletRequest;
@@ -118,14 +120,18 @@ public static TemplateLayout themeLayout (final String themeInode,
}

TemplateLayout layout = layoutCache.getIfPresent(key);

if(layout == null) {
layout = getLayout(themeInode, isPreview, getDrawedBody(themeInode, user));
}

return layout;
}

public static void removeFromLayoutCache(final String templateInode){
layoutCache.invalidate(templateInode + false);
layoutCache.invalidate(templateInode + true);
}

private static DrawedBody getDrawedBody(String themeInode, User user) throws DotDataException, DotSecurityException {
final Identifier ident = APILocator.getIdentifierAPI().findFromInode(themeInode);
final Template template = APILocator.getTemplateAPI().findWorkingTemplate(ident.getId(), user, false);
@@ -186,15 +192,31 @@ private static TemplateLayout getLayout ( final String themeInode, final Boolean
throw new RuntimeException("Template with inode: " + themeInode + " has not drawedBody");
}

layout = getTemplateLayout(isPreview, drawedBodyAsString);

layout.setTitle(drawedBody.getTitle());
layoutCache.put(key, layout);

return layout;
}

public static TemplateLayout getTemplateLayout(String drawedBodyAsString) {
TemplateLayout layout;
try {
layout = getTemplateLayoutFromJSON(drawedBodyAsString);
} catch (IOException e) {
layout = DesignTemplateUtil.getDesignParameters(drawedBodyAsString, isPreview);
layout = DesignTemplateUtil.getDesignParameters(drawedBodyAsString, false);
}
return layout;
}

layout.setTitle(drawedBody.getTitle());
layoutCache.put(key, layout);

private static TemplateLayout getTemplateLayout(Boolean isPreview, String drawedBodyAsString) {
TemplateLayout layout;
try {
layout = getTemplateLayoutFromJSON(drawedBodyAsString);
} catch (IOException e) {
layout = DesignTemplateUtil.getDesignParameters(drawedBodyAsString, isPreview);
}
return layout;
}

@@ -330,4 +352,32 @@ public static TemplateLayout getTemplateLayoutFromJSON(String json) throws IOEx
return themeMap;
}

public static Map<String, Long> getMaxUUID(final Template template){
if (template.isDrawed()) {

if (template.getDrawedBody() == null) {
return new HashMap<>();
}

final TemplateLayout templateLayout = DotTemplateTool.getTemplateLayout(template.getDrawedBody());
final Set<ContainerUUID> containersUUID = templateLayout.getContainersUUID();

final Map<String, Long> result = new HashMap<>();

for (final ContainerUUID containerUUID : containersUUID) {
final Long maxUUID = result.get(containerUUID.getIdentifier());
final long uuid = Long.parseLong(containerUUID.getUUID());

if (maxUUID == null ) {
result.put(containerUUID.getIdentifier(), uuid);
} else {
result.put(containerUUID.getIdentifier(), maxUUID > uuid ? maxUUID : uuid);
}
}

return result;
} else {
return new HashMap<>();
}
}
}
@@ -1,9 +1,11 @@
package com.dotcms.rendering.velocity.viewtools;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.dotcms.rest.api.v1.DotObjectMapperProvider;
import org.apache.velocity.tools.view.ImportSupport;
import org.apache.velocity.tools.view.tools.ViewTool;

@@ -247,13 +249,35 @@ public Object generate(Object o) {
return new JSONObject(o);
}

private final static Class LIST_MAP_CLASS = new ArrayList<Map<String, Object>>().getClass();
private final static Class MAP_CLASS = new HashMap<String, Object>().getClass();
/**
* Returns a JSONObject as constructed from the provided string.
*
* @param s The JSON string.
* @return A JSONObject as parsed from the provided string, null in the event of an error.
*/
public Object generate(String s) {
public Object generate(final String s) {

return Config.getBooleanProperty("jsontool.generate.jackson", true)?
this.jacksonGenerate(s): this.jsonGenerate(s);
}

private Object jacksonGenerate(final String s) {

final DotObjectMapperProvider mapper = DotObjectMapperProvider.getInstance();

try {
return s.startsWith("[") && s.endsWith("]")?
mapper.getDefaultObjectMapper().readValue(s, LIST_MAP_CLASS):
mapper.getDefaultObjectMapper().readValue(s, MAP_CLASS);
} catch (Exception e) {
Logger.warnAndDebug(this.getClass(), e);
return null;
}
}

private Object jsonGenerate(final String s) {
Object result;

try {
@@ -1,34 +1,35 @@
package com.dotcms.rest;


import com.dotcms.business.WrapInTransaction;
import com.dotcms.concurrent.DotConcurrentFactory;

import com.dotcms.enterprise.license.LicenseManager;
import com.dotcms.publisher.bundle.bean.Bundle;
import com.dotcms.publisher.business.PublishAuditAPI;
import com.dotcms.publisher.business.PublishAuditStatus;
import com.dotcms.publisher.business.PublishAuditStatus.Status;
import com.dotcms.publisher.business.PublisherQueueJob;
import com.dotcms.publisher.endpoint.bean.PublishingEndPoint;
import com.dotcms.publisher.endpoint.business.PublishingEndPointAPI;
import com.dotcms.publisher.pusher.PushPublisher;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;
import com.dotcms.publisher.pusher.AuthCredentialPushPublishUtil;

import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.*;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import com.dotcms.repackage.org.apache.commons.httpclient.HttpStatus;
import com.dotcms.util.CollectionsUtils;
import com.dotmarketing.business.APILocator;
import com.dotmarketing.db.HibernateUtil;
import com.dotmarketing.exception.DotHibernateException;
import com.dotmarketing.exception.InvalidLicenseException;
import com.dotmarketing.util.ConfigUtils;
import com.dotmarketing.util.FileUtil;
import com.dotmarketing.util.Logger;
import com.dotmarketing.util.UtilMethods;
import com.liferay.portal.model.User;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

import javax.servlet.http.HttpServletRequest;
@@ -37,148 +38,133 @@
public class BundlePublisherResource {

public static String MY_TEMP = "";
private PublishingEndPointAPI endpointAPI = APILocator.getPublisherEndPointAPI();

/**
* Method that receives from a server a bundle with the intention of publish it.<br/>
* When a Bundle file is received on this end point is required to validate if the sending server is an allowed<br/>
* server on this end point and if the security tokens match. If all the validations are correct the bundle will be add it<br/>
* to the {@link PublishThread Publish Thread}.
*
* @param fileName File name to be published
* @param auth_token_enc Authentication token
* @param groupId Group who sent the Bundle
* @param endpointId End-point who sent the Bundle
* @param type response type
* @param callback response callback
* @param bundleName The name for the Bundle to publish
* @param forcePush true/false to Force the push
* @param req HttpRequest
* @return Returns a {@link Response} object with a 200 status code if success or a 500 error code if anything fails on the Publish process
* @see PublishThread
*/
@POST
@Path ("/publish")
@Consumes(MediaType.APPLICATION_OCTET_STREAM)

/**
* Method that receives from a server a bundle with the intention of publish it.<br/>
* When a Bundle file is received on this end point is required to validate if the sending server is an allowed<br/>
* server on this end point and if the security tokens match. If all the validations are correct the bundle will be add it<br/>
* to the {@link PublishThread Publish Thread}.
*
* @param type response type
* @param callback response callback
* @param forcePush true/false to Force the push
* @param request {@link HttpServletRequest}
* @param response {@link HttpServletResponse}
* @return Returns a {@link Response} object with a 200 status code if success or a 500 error code if anything fails on the Publish process
* @see PublishThread
*/
@POST
@Path("/publish")
@Consumes(MediaType.APPLICATION_OCTET_STREAM)
@Produces(MediaType.APPLICATION_JSON)
public Response publish(
@QueryParam("FILE_NAME") String fileName,
@QueryParam("AUTH_TOKEN") String auth_token_digest,
@QueryParam("GROUP_ID") String groupId,
@QueryParam("ENDPOINT_ID") String endpointId,
@QueryParam("type") String type,
@QueryParam("callback") String callback,
@QueryParam("BUNDLE_NAME") String bundleName,
@QueryParam("FORCE_PUSH") final boolean forcePush,
@Context HttpServletRequest req
) {
try {
try (InputStream bundleStream = req.getInputStream()) {
//Creating an utility response object
Map<String, String> paramsMap = new HashMap<String, String>();
paramsMap.put( "type", type );
paramsMap.put( "callback", callback );
ResourceResponse responseResource = new ResourceResponse( paramsMap );

String remoteIP = "";
try {

remoteIP = req.getRemoteHost();
if(!UtilMethods.isSet(remoteIP))
remoteIP = req.getRemoteAddr();

HibernateUtil.startTransaction();

PublishingEndPoint mySelf = endpointAPI.findEnabledSendingEndPointByAddress(remoteIP);

if(mySelf==null || !isValidToken(auth_token_digest, remoteIP, mySelf)) {
bundleStream.close();
Logger.error(this.getClass(), "Push Publishing failed from " + remoteIP + " invalid endpoint or token");

return responseResource.responseError( HttpStatus.SC_UNAUTHORIZED );
}

String bundlePath = ConfigUtils.getBundlePath()+File.separator+MY_TEMP;
String bundleFolder = fileName.substring(0, fileName.indexOf(".tar.gz"));

PublishAuditStatus status = PublishAuditAPI.getInstance().updateAuditTable( mySelf.getId(), mySelf.getId(), bundleFolder, true );

if(bundleName.trim().length()>0) {
// save bundle if it doesn't exists
Bundle foundBundle = APILocator.getBundleAPI().getBundleById( bundleFolder );
if ( foundBundle == null || foundBundle.getId() == null ) {
Bundle bundle = new Bundle();
bundle.setId(bundleFolder);
bundle.setName(bundleName);
bundle.setPublishDate(Calendar.getInstance().getTime());
bundle.setOwner(APILocator.getUserAPI().getSystemUser().getUserId());
bundle.setForcePush(forcePush);
APILocator.getBundleAPI().saveBundle(bundle);
}
}

//Write file on FS
FileUtil.writeToFile(bundleStream, bundlePath+fileName);

//Start thread
if(!status.getStatus().equals(Status.PUBLISHING_BUNDLE)) {
new Thread(new PublishThread(fileName, groupId, endpointId, status)).start();
}

HibernateUtil.commitTransaction();

return Response.status(HttpStatus.SC_OK).build();
} catch (NumberFormatException e) {
try {
HibernateUtil.rollbackTransaction();
} catch (DotHibernateException e1) {
Logger.error(this, "error rollback",e1);
}
Logger.error(PublisherQueueJob.class,e.getMessage(),e);
} catch (Exception e) {
try {
HibernateUtil.rollbackTransaction();
} catch (DotHibernateException e1) {
Logger.error(this, "error rollback",e1);
}
Logger.error(PublisherQueueJob.class, "Error caused by remote call of: "+remoteIP);
Logger.error(PublisherQueueJob.class,e.getMessage(),e);
}
finally {
try {
HibernateUtil.closeSession();
} catch (DotHibernateException e) {
Logger.error(this, "error close session",e);
}
}
}
} catch (IOException e) {
Logger.error(PublisherQueueJob.class,e.getMessage(),e);
}

return Response.status(HttpStatus.SC_INTERNAL_SERVER_ERROR).build();
@QueryParam("type") final String type,
@QueryParam("callback") final String callback,
@QueryParam("FORCE_PUSH") final boolean forcePush,
@Context final HttpServletRequest request,
@Context final HttpServletResponse response
) throws Exception {

if (LicenseManager.getInstance().isCommunity()) {
throw new InvalidLicenseException("License required");
}

final ResourceResponse responseResource = new ResourceResponse(
CollectionsUtils.map("type", type, "callback", callback));
final String remoteIP = UtilMethods.isSet(request.getRemoteHost())?
request.getRemoteHost() : request.getRemoteAddr();

if (request.getInputStream().isFinished()) {
Logger.error(this.getClass(), "Push Publishing failed from " + remoteIP + " bundle expected");
return responseResource.responseError(HttpStatus.SC_BAD_REQUEST);
}

final AuthCredentialPushPublishUtil.PushPublishAuthenticationToken pushPublishAuthenticationToken
= AuthCredentialPushPublishUtil.INSTANCE.processAuthHeader(request);

final Optional<Response> failResponse = PushPublishResourceUtil.getFailResponse(request, pushPublishAuthenticationToken);

if (failResponse.isPresent()) {
return failResponse.get();
}

final Bundle bundle = this.publishBundle(forcePush, request, remoteIP);

return Response.ok(bundle).build();
}

/**
* Validates a received token
*
* @param token Token to validate
* @param remoteIP Sender IP
* @param mySelf Current end point
* @return True if valid
* @throws IOException If fails reading the security token
*/
public static boolean isValidToken ( String token, String remoteIP, PublishingEndPoint mySelf ) throws IOException {
@WrapInTransaction
private Bundle publishBundle(final boolean forcePush,
final HttpServletRequest request,
final String remoteIP) throws Exception {

final String fileNameSent = getFileNameFromRequest(request);
final String fileName = UtilMethods.isSet(fileNameSent) ? fileNameSent : generatedBundleFileName();

//My key
Optional<String> myKey=PushPublisher.retriveEndpointKeyDigest(mySelf);
if(!myKey.isPresent()) {
return false;
}
Bundle bundle = null;

try (InputStream bundleStream = request.getInputStream()) {

return token.equals( myKey.get() );
}
final String bundlePath = ConfigUtils.getBundlePath()+ File.separator + MY_TEMP;
final String bundleFolder = fileName.substring(0, fileName.indexOf(".tar.gz"));
final PublishAuditStatus status = PublishAuditAPI.getInstance().updateAuditTable(
remoteIP, remoteIP, bundleFolder, true);

// save bundle if it doesn't exists
bundle = APILocator.getBundleAPI().getBundleById(bundleFolder);
if (bundle == null || bundle.getId() == null) {

bundle = new Bundle();
bundle.setId(bundleFolder);
bundle.setName(fileName.replace(".tar.gz", ""));
bundle.setPublishDate(Calendar.getInstance().getTime());
bundle.setOwner(APILocator.getUserAPI().getSystemUser().getUserId());
bundle.setForcePush(forcePush);
APILocator.getBundleAPI().saveBundle(bundle);
}

//Write file on FS
FileUtil.writeToFile(bundleStream, bundlePath + fileName);

//Start thread

if(!status.getStatus().equals(Status.PUBLISHING_BUNDLE)) {

DotConcurrentFactory.getInstance()
.getSubmitter()
.submit(new PublishThread(fileName, null, null, status));
}

return bundle;
} catch (Exception e) {

Logger.error(
PublisherQueueJob.class,
String.format("Error caused by remote call of: Remote IP - %s, bundle file name - %s, end point- %s",
remoteIP, fileName, remoteIP));
Logger.error(PublisherQueueJob.class,e.getMessage(),e);
throw e;
}
}

private String getFileNameFromRequest(HttpServletRequest request) {
try {
final String fileNameValue = request.getHeader("Content-Disposition")
.split(";")[1]
.trim()
.split("=")[1];
return fileNameValue.substring(1, fileNameValue.length() - 1);
} catch (Exception e) {
return null;
}
}

private String generatedBundleFileName() {
return String.format("bundle_%d.tar.gz", System.currentTimeMillis());
}

private boolean isAdmin(final User user) {

return null != user && user.isBackendUser() && user.isAdmin();
}
}
@@ -4,7 +4,9 @@
import com.dotcms.api.system.event.SystemEventType;
import com.dotcms.api.system.event.Visibility;
import com.dotcms.api.system.event.message.MessageSeverity;
import com.dotcms.api.system.event.message.MessageType;
import com.dotcms.api.system.event.message.SystemMessageEventUtil;
import com.dotcms.api.system.event.message.builder.SystemMessage;
import com.dotcms.api.system.event.message.builder.SystemMessageBuilder;
import com.dotcms.concurrent.DotConcurrentFactory;
import com.dotcms.concurrent.DotSubmitter;
@@ -16,12 +18,14 @@
import com.dotcms.publisher.business.PublishAuditStatus;
import com.dotcms.publisher.business.PublishAuditStatus.Status;
import com.dotcms.publishing.BundlerUtil;
import com.dotcms.publishing.FilterDescriptor;
import com.dotcms.publishing.PublisherConfig;
import com.dotcms.rest.annotation.NoCache;
import com.dotcms.rest.exception.mapper.ExceptionMapperUtil;
import com.dotcms.rest.param.ISODateParam;
import com.dotmarketing.business.APILocator;
import com.dotmarketing.business.DotStateException;
import com.dotmarketing.exception.DoesNotExistException;
import com.dotmarketing.exception.DotDataException;
import com.dotmarketing.util.Config;
import com.dotmarketing.util.ConfigUtils;
@@ -32,8 +36,10 @@
import com.dotmarketing.util.json.JSONArray;
import com.dotmarketing.util.json.JSONException;
import com.dotmarketing.util.json.JSONObject;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.liferay.portal.language.LanguageUtil;
import com.liferay.portal.model.User;
import com.liferay.util.LocaleUtil;
import io.vavr.Tuple;
import io.vavr.Tuple2;
@@ -53,15 +59,13 @@
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.ServerErrorException;
import javax.ws.rs.container.AsyncResponse;
import javax.ws.rs.container.Suspended;
import javax.ws.rs.core.CacheControl;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
@@ -625,6 +629,144 @@ public Response deleteAllSuccess(@Context final HttpServletRequest request,
"Removing bundles in a separated process, the result of the operation will be notified")).build();
} // deleteAllSuccess.


@Path("/_download/{bundleId}")
@GET
@JSONP
@NoCache
@Produces(MediaType.APPLICATION_OCTET_STREAM)
public final Response downloadBundle(@Context final HttpServletRequest request,
@Context final HttpServletResponse response,
@PathParam("bundleId") final String bundleId) {

final InitDataObject initData =
new WebResource.InitBuilder(webResource)
.requiredBackendUser(true)
.requestAndResponse(request, response)
.rejectWhenNoUser(true)
.requiredPortlet("publishing-queue")
.init();
try {
final Bundle bundle = APILocator.getBundleAPI().getBundleById(bundleId);
if (!UtilMethods.isSet(bundle)) {
throw new DoesNotExistException("Bundle with ID: " + bundleId + " not found");
}
final File bundleFile = new File(
ConfigUtils.getBundlePath() + File.separator + bundle.getId() + ".tar.gz");
if (!bundleFile.exists()) {
throw new DoesNotExistException(
"The bundle has not been generated for the provided bundle ID: "
+ bundleId);
}
final String bundleName = bundle.getName().replaceAll("[^\\w.-]", "_");
response.setHeader( "Content-Disposition", "attachment; filename=" +bundleName +"-"+ bundle.getId() + ".tar.gz" );
return Response.ok(bundleFile, "application/x-tgz").build();
}catch (DoesNotExistException e){
Logger.error(this,e.getMessage());
return ExceptionMapperUtil.createResponse("",e.getMessage(),Response.Status.NOT_FOUND);
}catch (Exception e){
Logger.error(this,e.getMessage());
return ExceptionMapperUtil.createResponse("",e.getMessage(),Response.Status.INTERNAL_SERVER_ERROR);
}
}

@Path("/_generate")
@POST
@JSONP
@NoCache
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_OCTET_STREAM)
public final void generateBundle(@Context final HttpServletRequest request,
@Context final HttpServletResponse response,
@Suspended final AsyncResponse asyncResponse,
final GenerateBundleForm form) {


final InitDataObject initData =
new WebResource.InitBuilder(webResource)
.requiredBackendUser(true)
.requestAndResponse(request, response)
.rejectWhenNoUser(true)
.requiredPortlet("publishing-queue")
.init();
final User user = initData.getUser();
try {
final Bundle bundle = APILocator.getBundleAPI().getBundleById(form.bundleId);
if (!UtilMethods.isSet(bundle)) {
throw new DoesNotExistException("Bundle with ID: " + form.bundleId + " not found");
}

//set Filter to the bundle
final FilterDescriptor filter = APILocator.getPublisherAPI().getFilterDescriptorByKey(form.filterKey);
bundle.setFilterKey(filter.getKey());
bundle.setOwner(user.getUserId());
//set ForcePush value of the filter to the bundle
bundle.setForcePush(
(boolean) filter.getFilters().getOrDefault(FilterDescriptor.FORCE_PUSH_KEY,false));
bundle.setOperation(form.operation.ordinal());
//Update Bundle
APILocator.getBundleAPI().updateBundle(bundle);

//Generate the bundle file for this given operation

final BundleGenerator generator = new BundleGenerator(bundle, user,asyncResponse);

final DotSubmitter submitter =
DotConcurrentFactory.getInstance().getSubmitter("generateBundle",
new DotConcurrentFactory.SubmitterConfigBuilder().poolSize(2)
.maxPoolSize(4).queueCapacity(500).build()
);
submitter.submit(generator);
final String bundleName = bundle.getName().replaceAll("[^\\w.-]", "_");
response.setContentType( "application/x-tgz" );
response.setHeader( "Content-Disposition", "attachment; filename=" + bundleName +"-"+ bundle.getId() + ".tar.gz" );
}catch (DoesNotExistException e){
Logger.error(this,e.getMessage());
asyncResponse.resume(ExceptionMapperUtil.createResponse("",e.getMessage(),Response.Status.NOT_FOUND));
}catch (Exception e){
Logger.error(this,e.getMessage());
asyncResponse.resume(ExceptionMapperUtil.createResponse("",e.getMessage(),Response.Status.INTERNAL_SERVER_ERROR));
}


} // uploadBundleSync.


class BundleGenerator implements Runnable {


public BundleGenerator(final Bundle bundle, final User user, final AsyncResponse asyncResponse) {
super();
this.bundle = bundle;
this.user = user;
this.asyncResponse = asyncResponse;

}

File bundleFile;
final Bundle bundle;
final User user;
final AsyncResponse asyncResponse;

@Override
public void run() {
bundleFile = APILocator.getBundleAPI().generateTarGzipBundleFile(bundle);

final SystemMessageBuilder systemMessageBuilder = new SystemMessageBuilder();

final String message = "Bundle <strong>" + bundle.getName() + "</strong> can be downloaded here: <a href='/api/bundle/_download/" + bundle.getId() + "'>download</a>";

final SystemMessage systemMessage = systemMessageBuilder.setMessage(message).setType(MessageType.SIMPLE_MESSAGE)
.setSeverity(MessageSeverity.SUCCESS).setLife(1000*60*12).create();

SystemMessageEventUtil.getInstance().pushMessage(systemMessage, ImmutableList.of(user.getUserId()));

final Response response = Response.ok(bundleFile, MediaType.APPLICATION_OCTET_STREAM).build();

this.asyncResponse.resume(response);
}
}

@Path("/sync")
@POST
@JSONP
@@ -1,8 +1,5 @@
package com.dotcms.rest;

import static com.dotmarketing.util.NumberUtil.toInt;
import static com.dotmarketing.util.NumberUtil.toLong;

import com.dotcms.contenttype.model.field.CategoryField;
import com.dotcms.contenttype.model.field.RelationshipField;
import com.dotcms.contenttype.model.type.BaseContentType;
@@ -55,6 +52,24 @@
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.io.xml.DomDriver;
import org.glassfish.jersey.media.multipart.BodyPart;
import org.glassfish.jersey.media.multipart.ContentDisposition;
import org.glassfish.jersey.media.multipart.FormDataMultiPart;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
@@ -65,7 +80,9 @@
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
@@ -77,24 +94,9 @@
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import org.glassfish.jersey.media.multipart.BodyPart;
import org.glassfish.jersey.media.multipart.ContentDisposition;
import org.glassfish.jersey.media.multipart.FormDataMultiPart;
import org.jetbrains.annotations.NotNull;

import static com.dotmarketing.util.NumberUtil.toInt;
import static com.dotmarketing.util.NumberUtil.toLong;

@Path("/content")
public class ContentResource {
@@ -113,6 +115,97 @@
private final WebResource webResource = new WebResource();
private final ContentHelper contentHelper = ContentHelper.getInstance();

/**
*
* Do a search, parameter are received by post and returns the json with the search info and contentlet results
*
* Example call using curl:
* curl --location --request POST 'http://localhost:8080/api/content/_search' \
* --header 'Content-Type: application/json' \
* --data-raw '{
* "query": "+structurename:webpagecontent",
* "sort":"modDate",
* "limit":20,
* "offset":0,
* "userId":"dotcms.org.1"
* }'
*
* @param request {@link HttpServletRequest} object
* @param response {@link HttpServletResponse} object
* @param searchForm {@link SearchForm}
* @return json array of objects. each object with inode and identifier
*/
@POST
@Path("/_search")
@Produces(MediaType.APPLICATION_JSON)
public Response search(@Context HttpServletRequest request,
@Context final HttpServletResponse response,
final SearchForm searchForm) throws DotSecurityException, DotDataException {

final InitDataObject initData = this.webResource.init
(null, request, response, false, null);

final User user = initData.getUser();
final String query = searchForm.getQuery(); // default value = ""
final String sort = searchForm.getSort(); // default value = ""
final int limit = searchForm.getLimit(); // default value = 20
final int offset = searchForm.getOffset(); // default value = 0
final String render = searchForm.getRender();
final int depth = searchForm.getDepth(); // default value = -1
final long language = searchForm.getLanguageId();
final PageMode pageMode = PageMode.get(request);
final String userToPullID = searchForm.getUserId();
final boolean allCategoriesInfo = searchForm.isAllCategoriesInfo();
User userForPull = user;
List<Contentlet> contentlets = Collections.emptyList();
long resultsSize = 0;
long startAPISearchPull = 0;
long afterAPISearchPull = 0;
long startAPIPull = 0;
long afterAPIPull = 0;
JSONObject resultJson = new JSONObject();
final String tmDate = (String) request.getSession().getAttribute("tm_date");

if (depth > 3) {
throw new IllegalArgumentException("Invalid depth: " + depth);
}

Logger.debug(this, ()-> "Searching contentlets by: " + searchForm);

//If the user is an admin can send an user to filter the search
if(null != user && user.isAdmin()){

if(UtilMethods.isSet(userToPullID)) {

userForPull = APILocator.getUserAPI().loadUserById(userToPullID, APILocator.systemUser(),true);
}
}

if (UtilMethods.isSet(query)) {

startAPISearchPull = Calendar.getInstance().getTimeInMillis();
resultsSize = APILocator.getContentletAPI().indexCount(query, userForPull, pageMode.respectAnonPerms);
afterAPISearchPull = Calendar.getInstance().getTimeInMillis();

startAPIPull = Calendar.getInstance().getTimeInMillis();
contentlets = ContentUtils.pull(processQuery(query), offset, limit, sort, userForPull, tmDate, pageMode.respectAnonPerms);
resultJson = getJSONObject(contentlets, request, response, render, user, depth,
pageMode.respectAnonPerms, language, pageMode.showLive, allCategoriesInfo);

afterAPIPull = Calendar.getInstance().getTimeInMillis();

if(contentlets.isEmpty() && offset <= resultsSize ){
resultsSize = 0;
}
}

final long queryTook = afterAPISearchPull-startAPISearchPull;
final long contentTook = afterAPIPull-startAPIPull;

return Response.ok(new ResponseEntityView(
new SearchView(resultsSize, queryTook, contentTook, new JsonObjectView(resultJson)))).build();
}

/**
* performs a call to APILocator.getContentletAPI().searchIndex() with the specified parameters.
* Example call using curl: curl -XGET http://localhost:8080/api/content/indexsearch/+structurename:webpagecontent/sortby/modDate/limit/20/offset/0
@@ -586,7 +679,6 @@ public Response getContent(@Context HttpServletRequest request, @Context final H
* @throws DotSecurityException
* @throws DotDataException
*/
@NotNull
private List<Contentlet> getPullRelated(User user, int limit,
String orderBy, String tmDate, String luceneQuery, String relationshipValue, long language, boolean live)
throws DotSecurityException, DotDataException {
@@ -959,6 +1051,31 @@ private String getJSON(final List<Contentlet> cons, final HttpServletRequest req
final HttpServletResponse response, final String render, final User user,
final int depth, final boolean respectFrontendRoles, final long language,
final boolean live, final boolean allCategoriesInfo){
final JSONObject json = this.getJSONObject(cons, request, response, render, user,
depth, respectFrontendRoles, language, live, allCategoriesInfo);
return json.toString();
}

/**
* Creates a json object (as string) of a list of contentlets
* @param cons
* @param request
* @param response
* @param render
* @param user
* @param depth
* @param respectFrontendRoles
* @param language
* @param live
* @param allCategoriesInfo
* @return
* @throws IOException
* @throws DotDataException
*/
private JSONObject getJSONObject(final List<Contentlet> cons, final HttpServletRequest request,
final HttpServletResponse response, final String render, final User user,
final int depth, final boolean respectFrontendRoles, final long language,
final boolean live, final boolean allCategoriesInfo){
final JSONObject json = new JSONObject();
final JSONArray jsonCons = new JSONArray();

@@ -985,7 +1102,7 @@ private String getJSON(final List<Contentlet> cons, final HttpServletRequest req
Logger.debug(this.getClass(), "unable to create JSONObject", e);
}

return json.toString();
return json;
}

/**
@@ -1983,4 +2100,4 @@ public ContentWorkflowResult(boolean save, boolean publish) {


}
}
}
@@ -0,0 +1,58 @@
package com.dotcms.rest;

import com.dotcms.publisher.pusher.PushPublisherConfig;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;

/**
* Stolen from @author jsanca
*/
@JsonDeserialize(builder = GenerateBundleForm.Builder.class)
public class GenerateBundleForm {

public final String bundleId;
public PushPublisherConfig.Operation operation;
public final String filterKey;

private GenerateBundleForm(final Builder builder) {

this.bundleId = builder.bundleId;
this.operation = builder.operation == PushPublisherConfig.Operation.PUBLISH.ordinal() ? PushPublisherConfig.Operation.PUBLISH : PushPublisherConfig.Operation.UNPUBLISH;
this.filterKey = builder.filterKey;
}


@Override
public String toString() {
return "{bundleId=" + bundleId + ", operation=" + operation + ", filter=" + filterKey + "}";
}


public static final class Builder {

private @JsonProperty String bundleId = null;
private @JsonProperty String filterKey = null;
private @JsonProperty int operation = 0;

public Builder bundleId(final String bundleId) {
this.bundleId = bundleId;
return this;
}

public Builder filter(final String filter) {
this.filterKey = filter;
return this;
}

public Builder operation(final int operation) {
this.operation = operation;
return this;
}

public GenerateBundleForm build() {
return new GenerateBundleForm(this);
}

}

}
@@ -1,23 +1,25 @@
package com.dotcms.rest;

import com.dotcms.business.CloseDBIfOpened;
import com.dotcms.enterprise.license.LicenseManager;
import com.dotcms.exception.ExceptionUtil;
import com.dotcms.integritycheckers.IntegrityType;
import com.dotcms.integritycheckers.IntegrityUtil;
import com.dotcms.publisher.endpoint.bean.PublishingEndPoint;
import com.dotcms.publisher.endpoint.business.PublishingEndPointAPI;
import com.dotcms.publisher.integrity.IntegrityDataGeneratorThread;
import com.dotcms.publisher.pusher.PushPublisher;
import com.dotcms.publisher.pusher.AuthCredentialPushPublishUtil;
import com.dotcms.repackage.com.google.common.cache.Cache;
import com.dotcms.repackage.com.google.common.cache.CacheBuilder;
import com.dotcms.repackage.org.apache.commons.httpclient.HttpStatus;
import com.dotcms.rest.api.v1.HTTPMethod;
import com.dotcms.rest.exception.ForbiddenException;
import com.dotmarketing.business.APILocator;
import com.dotmarketing.db.HibernateUtil;
import com.dotmarketing.exception.DotDataException;
import com.dotmarketing.exception.DotHibernateException;
import com.dotmarketing.exception.DotSecurityException;
import com.dotmarketing.util.ConfigUtils;
import com.dotmarketing.exception.InvalidLicenseException;
import com.dotmarketing.util.*;
import com.dotmarketing.quartz.QuartzUtils;
import com.dotmarketing.quartz.job.IntegrityDataGenerationJob;
import com.dotmarketing.util.Logger;
import com.dotmarketing.util.UUIDGenerator;
import com.dotmarketing.util.UtilMethods;
@@ -28,40 +30,31 @@
import com.liferay.portal.language.LanguageUtil;
import com.liferay.portal.model.User;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.client.Client;
import javax.ws.rs.*;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.Invocation.Builder;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Cookie;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.NewCookie;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.StreamingOutput;
import org.apache.commons.lang3.StringUtils;
import org.glassfish.jersey.media.multipart.FormDataMultiPart;
import org.glassfish.jersey.media.multipart.FormDataParam;
import org.glassfish.jersey.media.multipart.file.FileDataBodyPart;
import org.quartz.SchedulerException;

/**
* This REST end-point provides all the required mechanisms for the execution of
@@ -82,7 +75,7 @@
* <li>Legacy Pages and Content Pages.</li>
* <li>File Assets.</li>
* </ul>
*
*
* @author Daniel Silva
* @version 1.0
* @since Jun 23, 2014
@@ -94,127 +87,169 @@
private final WebResource webResource = new WebResource();

private final static Cache<String, EndpointState> endpointStateCache =
CacheBuilder.newBuilder().expireAfterAccess(30, TimeUnit.MINUTES).build();
CacheBuilder.newBuilder().expireAfterAccess(30, TimeUnit.MINUTES).build();

public enum ProcessStatus {
PROCESSING, ERROR, FINISHED, NO_CONFLICTS, CANCELED
PROCESSING, ERROR, FINISHED, NO_CONFLICTS
}

public static final String INTEGRITY_DATA_TO_CHECK_ZIP_FILE_NAME = "DataToCheck.zip";
public static final String INTEGRITY_DATA_TO_FIX_ZIP_FILE_NAME = "DataToFix.zip";
private static class EndpointState {
private final Map<String, Cookie> cookies = new ConcurrentHashMap<String, Cookie>();

public Map<String, Cookie> getCookies() {
return cookies;
}

private static class EndpointState {
private final Map<String, Cookie> cookies = new ConcurrentHashMap<String, Cookie>();
public void addCookie(String name, Cookie cookie) {
cookies.put(name, cookie);
}
}

private static void cacheEndpointState(String endpointId, Map<String, NewCookie> cookiesMap) {

public Map<String, Cookie> getCookies() {
return cookies;
}
EndpointState endpointState = endpointStateCache.getIfPresent(endpointId);
if (endpointState == null) {
endpointStateCache.put(endpointId, endpointState = new EndpointState());
}

public void addCookie(String name, Cookie cookie) {
cookies.put(name, cookie);
}
for (Map.Entry<String, NewCookie> cookieEntry : cookiesMap.entrySet()) {
endpointState.addCookie(cookieEntry.getKey(), cookieEntry.getValue());
}
}

private void cacheEndpointState(String endpointId, Map<String, NewCookie> cookiesMap) {
private static void applyEndpointState(String endpointId, Builder requestBuilder) {

EndpointState endpointState = endpointStateCache.getIfPresent(endpointId);
if (endpointState == null) {
endpointStateCache.put(endpointId, endpointState = new EndpointState());
}
final EndpointState endpointState = endpointStateCache.getIfPresent(endpointId);

for (Map.Entry<String, NewCookie> cookieEntry : cookiesMap.entrySet()) {
endpointState.addCookie(cookieEntry.getKey(), cookieEntry.getValue());
}
if (endpointState != null) {
for (Cookie cookie : endpointState.getCookies().values()) {
requestBuilder.cookie(cookie);
}
}
}

private void applyEndpointState(String endpointId, Builder requestBuilder) {
private Response postWithEndpointState(
final PublishingEndPoint endpoint,
final String url,
final MediaType mediaType) {

final EndpointState endpointState = endpointStateCache.getIfPresent(endpointId);
return postWithEndpointState(endpoint, url, mediaType, HTTPMethod.POST, null);
}

if (endpointState != null) {
for (Cookie cookie : endpointState.getCookies().values()) {
requestBuilder.cookie(cookie);
}
}
private Response postWithEndpointState(
final PublishingEndPoint endpoint,
final String url,
final MediaType mediaType,
final HTTPMethod method) {
return postWithEndpointState(endpoint, url, mediaType, method, null);
}

// https://github.com/dotCMS/core/issues/9067
private Response postWithEndpointState(String endpointId, String url, MediaType mediaType, Entity<?> entity) {
private Response postWithEndpointState(
final PublishingEndPoint endpoint,
final String url,
final MediaType mediaType,
final HTTPMethod method,
Entity<FormDataMultiPart> entity) {

final Builder requestBuilder = RestClientBuilder.newClient().target(url).request(mediaType);

final Builder requestBuilder = RestClientBuilder.newClient().target(url).request(mediaType);
applyEndpointState(endpoint.getId(), requestBuilder);

applyEndpointState(endpointId, requestBuilder);
final Optional<String> requestToken = AuthCredentialPushPublishUtil.INSTANCE.getRequestToken(endpoint);

final Response response = requestBuilder.post(entity);
if (!requestToken.isPresent()) {
Logger.warn(IntegrityResource.class, "No Auth Token set for endpoint:" + endpoint.getId());
return response("No Auth Token set for endpoint", true);
}

cacheEndpointState(endpointId, response.getCookies());
requestBuilder.header("Authorization", requestToken.get());
final Response response = method == HTTPMethod.POST ? requestBuilder.post(entity) : requestBuilder.delete();

return response;
}
cacheEndpointState(endpoint.getId(), response.getCookies());

return response;
}

/**
* <p>Returns a zip with data from structures and folders for integrity check
*
* Usage: /getdata
* Evaluates if the {@link IntegrityDataGenerationJob} is running.
*
* @return true if it does, otherwise false
*/
private boolean isJobRunning() {
try {
return QuartzUtils.isJobRunning(
IntegrityDataGenerationJob.getJobScheduler(),
IntegrityDataGenerationJob.JOB_NAME,
IntegrityDataGenerationJob.JOB_GROUP,
IntegrityDataGenerationJob.TRIGGER_NAME,
IntegrityDataGenerationJob.TRIGGER_GROUP);
} catch (SchedulerException e) {
return false;
}
}

/**
* <p>Returns a zip with data from structures and folders for integrity check
*/
@POST
@Path("/generateintegritydata/{params:.*}")
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Path("/_generateintegritydata")
@Produces("text/plain")
public Response generateIntegrityData(@Context HttpServletRequest request, @FormDataParam("AUTH_TOKEN") String auth_token_digest) {

String remoteIP = null;
try {

if ( !UtilMethods.isSet( auth_token_digest ) ) {
return Response.status( HttpStatus.SC_BAD_REQUEST ).entity( "Error: 'endpoint' is a required param." ).build();
}

public Response generateIntegrityData(@Context HttpServletRequest request) {

if (LicenseManager.getInstance().isCommunity()) {
throw new InvalidLicenseException("License required");
}

remoteIP = request.getRemoteHost();
if(!UtilMethods.isSet(remoteIP))
remoteIP = request.getRemoteAddr();
final String localAddress = RestEndPointIPUtil.getFullLocalIp(request);
final String remoteIp = RestEndPointIPUtil.resolveRemoteIp(request);

PublishingEndPointAPI endpointAPI = APILocator.getPublisherEndPointAPI();
final PublishingEndPoint requesterEndPoint = endpointAPI.findEnabledSendingEndPointByAddress(remoteIP);
final AuthCredentialPushPublishUtil.PushPublishAuthenticationToken pushPublishAuthenticationToken
= AuthCredentialPushPublishUtil.INSTANCE.processAuthHeader(request);

if(!BundlePublisherResource.isValidToken(auth_token_digest, remoteIP, requesterEndPoint)) {
return Response.status(HttpStatus.SC_UNAUTHORIZED).build();
}
final Optional<Response> failResponse = PushPublishResourceUtil.getFailResponse(request, pushPublishAuthenticationToken);

ServletContext servletContext = request.getSession().getServletContext();
if (failResponse.isPresent()) {
return failResponse.get();
}

if(servletContext.getAttribute("integrityRunning")!=null && ((Boolean) servletContext.getAttribute("integrityRunning"))) {
throw new WebApplicationException(Response.status(HttpStatus.SC_CONFLICT).entity("Already Running").build());
try {
if (isJobRunning()) {
Logger.error(
IntegrityResource.class,
String.format(
"Receiver at %s:> job is already running for remote ip: %s, so aborting generation",
localAddress, remoteIp));
throw new WebApplicationException(
Response.status(HttpStatus.SC_CONFLICT)
.entity("Already Running")
.build());
}

String transactionId = UUIDGenerator.generateUuid();
servletContext.setAttribute("integrityDataRequestID", transactionId);

// start data generation process
IntegrityDataGeneratorThread idg = new IntegrityDataGeneratorThread( requesterEndPoint, request.getSession().getServletContext() );
idg.start();
//Saving the thread on the session context for a later use
servletContext.setAttribute( "integrityDataGeneratorThread_" + transactionId, idg );
final String transactionId = UUIDGenerator.generateUuid();
IntegrityDataGenerationJob.triggerIntegrityDataGeneration(pushPublishAuthenticationToken.getKey(), transactionId);
Logger.info(
IntegrityResource.class,
String.format(
"Receiver at %s:> job triggered for endpoint id: %s and requester id: %s",
localAddress, remoteIp, transactionId));

return Response.ok(transactionId).build();

} catch (Exception e) {
Logger.error(IntegrityResource.class, "Error caused by remote call of: "+remoteIP);
Logger.error(IntegrityResource.class,e.getMessage(),e);
Logger.error(
IntegrityResource.class,
String.format("Receiver at %s:> Error caused by remote call of: %s", localAddress, remoteIp));

Logger.error(
IntegrityResource.class,
String.format("Receiver at %s:> %s", localAddress, e.getMessage()),
e);

if (ExceptionUtil.causedBy(e, DotSecurityException.class)) {
throw new ForbiddenException(e);
} else {
return Response.status(HttpStatus.SC_INTERNAL_SERVER_ERROR).build();
}
}


return Response.status(HttpStatus.SC_INTERNAL_SERVER_ERROR).build();

}

/**
@@ -227,143 +262,180 @@ public Response generateIntegrityData(@Context HttpServletRequest request, @Form
*
*/
@POST
@Path("/getintegritydata/{params:.*}")
@Path("/{requestId}/status")
@Produces("application/zip")
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Response getIntegrityData(@Context HttpServletRequest request, @FormDataParam("AUTH_TOKEN") String auth_token_digest, @FormDataParam("REQUEST_ID") String requestId) {
String remoteIP = null;
public Response getIntegrityData(@Context HttpServletRequest request, @PathParam("requestId") final String requestId) {

try {
final String remoteIp = RestEndPointIPUtil.resolveRemoteIp(request);
final String localAddress = RestEndPointIPUtil.getFullLocalIp(request);

final AuthCredentialPushPublishUtil.PushPublishAuthenticationToken pushPublishAuthenticationToken
= AuthCredentialPushPublishUtil.INSTANCE.processAuthHeader(request);

try {
final Optional<Response> failResponse = PushPublishResourceUtil.getFailResponse(request, pushPublishAuthenticationToken);

remoteIP = request.getRemoteHost();
if(!UtilMethods.isSet(remoteIP))
remoteIP = request.getRemoteAddr();
if (failResponse.isPresent()) {
return failResponse.get();
}

PublishingEndPointAPI endpointAPI = APILocator.getPublisherEndPointAPI();
final PublishingEndPoint requesterEndPoint = endpointAPI.findEnabledSendingEndPointByAddress(remoteIP);
if (isJobRunning()) {
Logger.error(
IntegrityResource.class,
String.format(
"Receiver at %s:> job is already running for endpoint id: %s, therefore it's not ready and need to wait",
localAddress,
pushPublishAuthenticationToken.getKey()));
return Response.status(HttpStatus.SC_PROCESSING).build();
}

if(!BundlePublisherResource.isValidToken(auth_token_digest, remoteIP, requesterEndPoint) || !UtilMethods.isSet(requestId)) {
final Optional<IntegrityUtil.IntegrityDataExecutionMetadata> integrityMetadata =
IntegrityUtil.getIntegrityMetadata(pushPublishAuthenticationToken.getKey());
if (!integrityMetadata.isPresent()) {
Logger.error(
IntegrityResource.class,
String.format(
"Receiver at %s:> integrity data generation metadata for endpoint id %s is not found ",
localAddress,
pushPublishAuthenticationToken.getKey()));
return Response.status(HttpStatus.SC_UNAUTHORIZED).build();
}

ServletContext servletContext = request.getSession().getServletContext();
if(!UtilMethods.isSet(servletContext.getAttribute("integrityDataRequestID"))
|| !((String) servletContext.getAttribute("integrityDataRequestID")).equals(requestId)) {
if (!requestId.equals(integrityMetadata.get().getRequestId())) {
Logger.error(
IntegrityResource.class,
String.format(
"Receiver at %s:> integrity data generation metadata for endpoint id %s has a request id %s which does not match the provided %s",
localAddress,
pushPublishAuthenticationToken.getKey(),
integrityMetadata.get().getRequestId(),
requestId));
return Response.status(HttpStatus.SC_UNAUTHORIZED).build();
}

ProcessStatus integrityDataGeneratorStatus = (ProcessStatus) servletContext.getAttribute("integrityDataGenerationStatus");

if(UtilMethods.isSet( integrityDataGeneratorStatus )) {
switch (integrityDataGeneratorStatus) {
case PROCESSING:
return Response.status(HttpStatus.SC_PROCESSING).build();
case FINISHED:
StreamingOutput output = new StreamingOutput() {
public void write(OutputStream output) throws IOException, WebApplicationException {
InputStream is = Files.newInputStream(Paths.get(
ConfigUtils.getIntegrityPath() + File.separator
+ requesterEndPoint.getId() + File.separator
+ INTEGRITY_DATA_TO_CHECK_ZIP_FILE_NAME));

byte[] buffer = new byte[1024];
int bytesRead;
//read from is to buffer
while((bytesRead = is.read(buffer)) !=-1){
output.write(buffer, 0, bytesRead);
}
is.close();
//flush OutputStream to write any buffered data to file
output.flush();
output.close();

}
};
return Response.ok(output).build();

case CANCELED:
return Response.status( HttpStatus.SC_RESET_CONTENT ).entity( servletContext.getAttribute( "integrityDataGenerationError" ) ).build();

case ERROR:
return Response.status(HttpStatus.SC_INTERNAL_SERVER_ERROR).entity(servletContext.getAttribute("integrityDataGenerationError")).build();
if (integrityMetadata.get().getProcessStatus() == ProcessStatus.PROCESSING) {
Logger.info(
IntegrityResource.class,
String.format(
"Receiver at %s:> integrity data generation for endpoint id %s still ongoing therefore it's not ready and need to wait",
localAddress,
pushPublishAuthenticationToken.getKey()));
return Response.status(HttpStatus.SC_PROCESSING).build();
} else if (integrityMetadata.get().getProcessStatus() == ProcessStatus.FINISHED &&
IntegrityUtil.doesIntegrityDataFileExist(
pushPublishAuthenticationToken.getKey(),
IntegrityUtil.INTEGRITY_DATA_TO_CHECK_ZIP_FILENAME)) {
final String zipFilePath = IntegrityUtil.getIntegrityDataFilePath(
pushPublishAuthenticationToken.getKey(),
IntegrityUtil.INTEGRITY_DATA_TO_CHECK_ZIP_FILENAME);
final StreamingOutput output = so -> {
final InputStream inputStream = Files.newInputStream(Paths.get(zipFilePath));
final byte[] buffer = new byte[1024];
int bytesRead;
//read from is to buffer
while((bytesRead = inputStream.read(buffer)) != -1){
so.write(buffer, 0, bytesRead);
}

default:
break;
}
}
inputStream.close();
//flush OutputStream to write any buffered data to file
so.flush();
so.close();
};

Logger.info(
IntegrityResource.class,
String.format(
"Receiver at %s:> integrity data generation for endpoint id %s has finished and saved at %s",
localAddress,
pushPublishAuthenticationToken.getKey(),
zipFilePath));
return Response.ok(output).build();
} else if (integrityMetadata.get().getProcessStatus() == ProcessStatus.ERROR) {
final String message = StringUtils.defaultString(
String.format(" due to '%s'", integrityMetadata.get().getErrorMessage()),
"");
Logger.error(
IntegrityResource.class,
String.format(
"Receiver at %s:> integrity data generation for endpoint id %s has failed%s",
localAddress,
pushPublishAuthenticationToken.getKey(),
message));
return Response
.status(HttpStatus.SC_INTERNAL_SERVER_ERROR)
.entity(integrityMetadata.get().getErrorMessage())
.build();
}
} catch (Exception e) {
Logger.error(IntegrityResource.class, "Error caused by remote call of: "+remoteIP, e);
Logger.error(IntegrityResource.class, "Error caused by remote call of: " + remoteIp, e);
if (ExceptionUtil.causedBy(e, DotSecurityException.class)) {
throw new ForbiddenException(e);
}
return Response.status(HttpStatus.SC_INTERNAL_SERVER_ERROR).entity(e.getMessage()).build();
}

return Response.status(HttpStatus.SC_INTERNAL_SERVER_ERROR).build();

return Response.status(HttpStatus.SC_INTERNAL_SERVER_ERROR).build();
}

/**
* This is the entry point of the Integrity Checker process. The local data
* where this process was kicked off will be compared to the data in the
* selected end-point (server), grouped by object type: HTML Page, Content
* Type, etc. This phase is made up of 3 main steps:
* <ol>
* <li>The verification data is generated in the selected end-point and
* saved in the file system as .ZIP files, separated by object type.</li>
* <li>The .ZIP file is sent from the end-point over to the local server,
* un-zipped, and stored in temporary tables. SQL queries with local data
* and those temporary tables will determine if there are any data
* conflicts between the two servers.</li>
* <li>Users will get a list of conflicts per object type. Finally, they can
* decide to solve the data conflicts by replacing the data in the local
* server or in the remote end-point.</li>
* </ol>
*
* @param httpServletRequest
* - The {@link HttpServletRequest} that started the process.
* @param params
* - The execution parameters for running the process: The
* end-point ID.
* @return The REST {@link Response} with the status of the operation.
*/
/**
* This is the entry point of the Integrity Checker process. The local data
* where this process was kicked off will be compared to the data in the
* selected end-point (server), grouped by object type: HTML Page, Content
* Type, etc. This phase is made up of 3 main steps:
* <ol>
* <li>The verification data is generated in the selected end-point and
* saved in the file system as .ZIP files, separated by object type.</li>
* <li>The .ZIP file is sent from the end-point over to the local server,
* un-zipped, and stored in temporary tables. SQL queries with local data
* and those temporary tables will determine if there are any data
* conflicts between the two servers.</li>
* <li>Users will get a list of conflicts per object type. Finally, they can
* decide to solve the data conflicts by replacing the data in the local
* server or in the remote end-point.</li>
* </ol>
*
* @param httpServletRequest
* - The {@link HttpServletRequest} that started the process.
* @param params
* - The execution parameters for running the process: The
* end-point ID.
* @return The REST {@link Response} with the status of the operation.
*/
@GET
@Path("/checkintegrity/{params:.*}")
@Produces (MediaType.APPLICATION_JSON)
public Response checkIntegrity(@Context HttpServletRequest httpServletRequest, @Context final HttpServletResponse httpServletResponse, @PathParam("params") String params) {
InitDataObject initData = webResource.init(params, httpServletRequest, httpServletResponse, true, null);

Map<String, String> paramsMap = initData.getParamsMap();

public Response checkIntegrity(@Context final HttpServletRequest httpServletRequest,
@Context final HttpServletResponse httpServletResponse,
@PathParam("params") final String params) {
final InitDataObject initData = webResource.init(params, httpServletRequest, httpServletResponse, true, null);
final Map<String, String> paramsMap = initData.getParamsMap();
final HttpSession session = httpServletRequest.getSession();
final User loggedUser = initData.getUser();

JSONObject jsonResponse = new JSONObject();
final JSONObject jsonResponse = new JSONObject();

//Validate the parameters
final String endpointId = paramsMap.get( "endpoint" );
if ( !UtilMethods.isSet( endpointId ) ) {
return Response.status( HttpStatus.SC_BAD_REQUEST ).entity( "Error: endpoint is a required Field.").build();
final String endpointId = paramsMap.get("endpoint");
if (!UtilMethods.isSet(endpointId)) {
return Response.status(HttpStatus.SC_BAD_REQUEST).entity("Error: endpoint is a required Field.").build();
}

// return if we already have the data
final IntegrityUtil integrityUtil = new IntegrityUtil();
try {
IntegrityUtil integrityUtil = new IntegrityUtil();

if(integrityUtil.doesIntegrityConflictsDataExist(endpointId)) {

jsonResponse.put( "success", true );
jsonResponse.put( "message", "Integrity Checking Initialized..." );
if (integrityUtil.doesIntegrityConflictsDataExist(endpointId)) {
jsonResponse.put("success", true );
jsonResponse.put("message", "Integrity Checking Initialized...");

//Setting the process status
setStatus( httpServletRequest, endpointId, ProcessStatus.FINISHED );
setStatus(httpServletRequest, endpointId, ProcessStatus.FINISHED);

return response( jsonResponse.toString(), false );
return response(jsonResponse.toString(), false);
}
} catch(JSONException e) {
Logger.error(IntegrityResource.class, "Error setting return message in JSON response",
Logger.error(
IntegrityResource.class,
"Error setting return message in JSON response",
e);
return response("Error setting return message in JSON response", true);
} catch (DotSecurityException e) {
@@ -373,367 +445,82 @@ public Response checkIntegrity(@Context HttpServletRequest httpServletRequest, @
return response( "Error checking existence of integrity data" , true );
}

try {

//Setting the process status
setStatus( httpServletRequest, endpointId, ProcessStatus.PROCESSING );
//Setting the process status
setStatus(httpServletRequest, endpointId, ProcessStatus.PROCESSING);

try {
final PublishingEndPoint endpoint = APILocator.getPublisherEndPointAPI().findEndPointById(endpointId);
final Optional<String> authToken = PushPublisher.retriveEndpointKeyDigest(endpoint);
if(!authToken.isPresent()) {
Logger.warn(IntegrityResource.class, "No Auth Token set for endpoint:" + endpointId);
return response("No Auth Token set for endpoint", true);
}
FormDataMultiPart form = new FormDataMultiPart();
form.field("AUTH_TOKEN",authToken.get());

//Sending bundle to endpoint
String url = endpoint.toURL()+"/api/integrity/generateintegritydata/";

Response response = postWithEndpointState(
endpoint.getId(), url, MediaType.TEXT_PLAIN_TYPE, Entity.entity(form, form.getMediaType())
);

if(response.getStatus() == HttpStatus.SC_OK) {
final String integrityDataRequestID = response.readEntity(String.class);

Thread integrityDataRequestChecker = new Thread() {

@CloseDBIfOpened
public void run(){

FormDataMultiPart form = new FormDataMultiPart();
form.field("AUTH_TOKEN",authToken.get());
form.field("REQUEST_ID",integrityDataRequestID);

String url = endpoint.toURL()+"/api/integrity/getintegritydata/";

boolean processing = true;

while(processing) {

Response response = postWithEndpointState(
endpoint.getId(), url, new MediaType("application", "zip"), Entity.entity(form, form.getMediaType())
);

if ( response.getStatus() == HttpStatus.SC_OK ) {

processing = false;

InputStream zipFile = response.readEntity(InputStream.class);
String outputDir = ConfigUtils.getIntegrityPath() + File.separator + endpoint.getId();

try {

IntegrityUtil.unzipFile(zipFile, outputDir);

} catch(Exception e) {

//Special handling if the thread was interrupted
if ( e instanceof InterruptedException ) {
//Setting the process status
setStatus( session, endpointId, ProcessStatus.CANCELED, null );
Logger.debug( IntegrityResource.class, "Requested interruption of the integrity checking process [unzipping Integrity Data] by the user.", e );
throw new RuntimeException( "Requested interruption of the integrity checking process [unzipping Integrity Data] by the user.", e );
}

//Setting the process status
setStatus( session, endpointId, ProcessStatus.ERROR, null );
Logger.error(IntegrityResource.class, "Error while unzipping Integrity Data", e);
throw new RuntimeException("Error while unzipping Integrity Data", e);
}

// set session variable
// call IntegrityChecker
boolean conflictPresent = false;

IntegrityUtil integrityUtil = new IntegrityUtil();
try {
HibernateUtil.startTransaction();
integrityUtil.completeDiscardConflicts(endpointId);
HibernateUtil.commitTransaction();

HibernateUtil.startTransaction();
conflictPresent = integrityUtil.completeCheckIntegrity(endpointId);
HibernateUtil.commitTransaction();
} catch(Exception e) {
try {
HibernateUtil.rollbackTransaction();
} catch (DotHibernateException e1) {
Logger.error(IntegrityResource.class, "Error while rolling back transaction", e);
}

//Special handling if the thread was interrupted
if ( e instanceof InterruptedException ) {
//Setting the process status
setStatus( session, endpointId, ProcessStatus.CANCELED, null );
Logger.debug( IntegrityResource.class, "Requested interruption of the integrity checking process by the user.", e );
throw new RuntimeException( "Requested interruption of the integrity checking process by the user.", e );
}

Logger.error(IntegrityResource.class, "Error checking integrity", e);

//Setting the process status
setStatus( session, endpointId, ProcessStatus.ERROR, null );
throw new RuntimeException("Error checking integrity", e);
} finally {
try {
integrityUtil.dropTempTables(endpointId);
HibernateUtil.closeSession();
} catch (DotHibernateException e) {
Logger.warn(this, e.getMessage(), e);
} catch (DotDataException e) {
Logger.error(IntegrityResource.class, "Error while deleting temp tables", e);
}
}

if(conflictPresent) {
//Setting the process status
setStatus( session, endpointId, ProcessStatus.FINISHED, null );
} else {
String noConflictMessage;
try {
noConflictMessage = LanguageUtil.get( loggedUser.getLocale(), "push_publish_integrity_conflicts_not_found" );
} catch ( LanguageException e ) {
noConflictMessage = "No Integrity Conflicts found";
}
//Setting the process status
setStatus( session, endpointId, ProcessStatus.NO_CONFLICTS, noConflictMessage );
}

} else if ( response.getStatus() == HttpStatus.SC_PROCESSING ) {

continue;
} else if ( response.getStatus() == HttpStatus.SC_RESET_CONTENT ) {
processing = false;
//Setting the process status
setStatus( session, endpointId, ProcessStatus.CANCELED, null );
} else {
setStatus( session, endpointId, ProcessStatus.ERROR, null );
Logger.error( this.getClass(), "Response indicating a " + response.getStatusInfo().getReasonPhrase() + " (" + response.getStatus() + ") Error trying to retrieve the Integrity data from the Endpoint [" + endpointId + "]." );
processing = false;
}
}
}
};
Response response = generateIntegrityCheckerRequest(endpoint);

if (response.getStatus() == HttpStatus.SC_OK) {
final String integrityDataRequestId = response.readEntity(String.class);
Thread integrityDataRequestChecker = new Thread(
new IntegrityDataRequestChecker(loggedUser, session, endpoint, integrityDataRequestId)
);
//Start the integrity check
integrityDataRequestChecker.start();
addThreadToSession( session, integrityDataRequestChecker, endpointId, integrityDataRequestID );
addThreadToSession(session, integrityDataRequestChecker, endpointId, integrityDataRequestId);
} else if (response.getStatus() == HttpStatus.SC_UNAUTHORIZED) {
return handleInvalidTokenResponse(endpoint, response, session);

} else if ( response.getStatus() == HttpStatus.SC_UNAUTHORIZED ) {
setStatus( session, endpointId, ProcessStatus.ERROR, null );
Logger.error( this.getClass(), "Response indicating Not Authorized received from Endpoint. Please check Auth Token. Endpoint Id: " + endpointId );
return response( "Response indicating Not Authorized received from Endpoint. Please check Auth Token. Endpoint Id:" + endpointId, true );
} else {
setStatus( session, endpointId, ProcessStatus.ERROR, null );
Logger.error( this.getClass(), "Response indicating a " + response.getStatusInfo().getReasonPhrase() + " (" + response.getStatus() + ") Error trying to connect with the Integrity API on the Endpoint. Endpoint Id: " + endpointId );
return response( "Response indicating a " + response.getStatusInfo().getReasonPhrase() + " (" + response.getStatus() + ") Error trying to connect with the Integrity API on the Endpoint. Endpoint Id: " + endpointId, true );
setStatus(session, endpointId, ProcessStatus.ERROR, null);
final String message = "Response indicating a " + response.getStatusInfo().getReasonPhrase() + " ("
+ response.getStatus()
+ ") Error trying to connect with the Integrity API on the Endpoint. Endpoint Id: "
+ endpointId;
Logger.error(this.getClass(), message);
return response(message, true);
}

jsonResponse.put( "success", true );
jsonResponse.put( "message", "Integrity Checking Initialized..." );

jsonResponse.put("success", true);
jsonResponse.put("message", "Integrity Checking Initialized...");
} catch(Exception e) {

if (ExceptionUtil.causedBy(e, DotSecurityException.class)) {
throw new ForbiddenException(e);
}

//Special handling if the thread was interrupted
if ( e instanceof InterruptedException || e.getCause() instanceof InterruptedException ) {
//Setting the process status
setStatus( session, endpointId, ProcessStatus.CANCELED, null );
Logger.debug( IntegrityResource.class, "Requested interruption of the integrity checking process by the user.", e );
return response( "Requested interruption of the integrity checking process by the user for End Point server: [" + endpointId + "]" , true );
}

//Setting the process status
setStatus( session, endpointId, ProcessStatus.ERROR, null );
Logger.error( this.getClass(), "Error initializing the integrity checking process for End Point server: [" + endpointId + "]", e );
return response( "Error initializing the integrity checking process for End Point server: [" + endpointId + "]" , true );
setStatus(session, endpointId, ProcessStatus.ERROR, null);
final String message = "Error initializing the integrity checking process for End Point server: ["
+ endpointId + "]";
Logger.error(this.getClass(), message, e);
return response(message, true);
}


return response( jsonResponse.toString(), false );

}

/**
* Method that will interrupt the integrity checking running processes locally and in the end point server
*
* @param httpServletRequest
* @param params
* @return
* @throws JSONException
*/
@GET
@Path ("/cancelIntegrityProcess/{params:.*}")
@Produces (MediaType.APPLICATION_JSON)
public Response cancelIntegrityProcess ( @Context HttpServletRequest httpServletRequest, @Context final HttpServletResponse httpServletResponse, @PathParam ("params") String params ) throws JSONException {

StringBuilder responseMessage = new StringBuilder();

InitDataObject initData = webResource.init(params, httpServletRequest, httpServletResponse, true, null);
Map<String, String> paramsMap = initData.getParamsMap();

//Validate the parameters
String endpointId = paramsMap.get( "endpoint" );
if ( !UtilMethods.isSet( endpointId ) ) {
Response.ResponseBuilder responseBuilder = Response.status( HttpStatus.SC_BAD_REQUEST );
responseBuilder.entity( responseMessage.append( "Error: " ).append( "endpoint" ).append( " is a required Field." ) );

return responseBuilder.build();
}
private Response handleInvalidTokenResponse(
final PublishingEndPoint endpoint,
final Response response,
final HttpSession session) throws LanguageException {

try {
JSONObject jsonResponse = new JSONObject();
final Map<String, String> wwwAuthenticateHeader = ResourceResponse.getWWWAuthenticateHeader(response);
final String errorKey = wwwAuthenticateHeader.get("error_key").replaceAll("\"", "");

HttpSession session = httpServletRequest.getSession();
//Verify if we have something set on the session
if ( session.getAttribute( "integrityCheck_" + endpointId ) == null ) {
//And prepare the response
jsonResponse.put( "success", false );
jsonResponse.put( "message", "No checking process found for End point server [" + endpointId + "]" );
} else if ( session.getAttribute( "integrityThread_" + endpointId ) == null ) {
//And prepare the response
jsonResponse.put( "success", false );
jsonResponse.put( "message", "No checking process found for End point server [" + endpointId + "]" );
} else {
final String message =
String.format(
"%s Response indicating Not Authorized received from Endpoint. Please check Auth Token. Endpoint Id: %s",
LanguageUtil.get(String.format("push_publish.end_point.%s_message", errorKey)),
endpoint.getId()
);

//Search for the status on session
ProcessStatus status = (ProcessStatus) session.getAttribute( "integrityCheck_" + endpointId );
PushPublishLogger.log(this.getClass(), message);

//And prepare the response
jsonResponse.put( "endPoint", endpointId );
if ( status == ProcessStatus.PROCESSING ) {
setStatus( session, endpoint.getId(), ProcessStatus.ERROR, null );
Logger.error( this.getClass(), message);

//Get the thread associated to this endpoint and the integrity request id
Thread runningThread = (Thread) session.getAttribute( "integrityThread_" + endpointId );
String integrityDataRequestId = (String) session.getAttribute( "integrityDataRequest_" + endpointId );

//Find the registered auth token in order to connect to the end point server
PublishingEndPoint endpoint = APILocator.getPublisherEndPointAPI().findEndPointById( endpointId );
Optional<String> authToken = PushPublisher.retriveEndpointKeyDigest(endpoint);
if(!authToken.isPresent()) {
return Response.status( HttpStatus.SC_BAD_REQUEST )
.entity( responseMessage.append( "Error: endpoint requires an authorization key" ) ).build();

}
FormDataMultiPart form = new FormDataMultiPart();
form.field( "AUTH_TOKEN", authToken.get() );
form.field( "REQUEST_ID", integrityDataRequestId );

//Prepare the connection
String url = endpoint.toURL() + "/api/integrity/cancelIntegrityProcessOnEndpoint/";

//Execute the call
Response response = postWithEndpointState(
endpoint.getId(), url, MediaType.APPLICATION_JSON_TYPE, Entity.entity(form, form.getMediaType())
);

if ( response.getStatus() == HttpStatus.SC_OK ) {
//Nothing to do here, we found no process to cancel
} else if ( response.getStatus() == HttpStatus.SC_RESET_CONTENT ) {
//Expected return status if a cancel was made on the end point server
} else {
Logger.error( this.getClass(), "Response indicating a " + response.getStatusInfo().getReasonPhrase() + " (" + response.getStatus() + ") Error trying to interrupt the running process on the Endpoint [ " + endpointId + "]." );
}

//Interrupt the Thread process
runningThread.interrupt();

//Remove the thread from the session
clearThreadInSession( httpServletRequest, endpointId );

jsonResponse.put( "success", true );
jsonResponse.put( "message", LanguageUtil.get( initData.getUser().getLocale(), "IntegrityCheckingCanceled" ) );
} else {
jsonResponse.put( "success", false );
jsonResponse.put( "message", "The integrity process for End Point server: [" + endpointId + "] was already stopped." );
}
}

responseMessage.append( jsonResponse.toString() );

} catch ( Exception e ) {
Logger.error( this.getClass(), "Error checking the integrity process status for End Point server: [" + endpointId + "]", e );
if (ExceptionUtil.causedBy(e, DotSecurityException.class)) {
throw new ForbiddenException(e);
}
return response( "Error checking the integrity process status for End Point server: [" + endpointId + "]", true );
}

return response( responseMessage.toString(), false );
return response(
message,
HttpStatus.SC_UNAUTHORIZED);
}

/**
* Method expected to run on an end point server in order to interrupt the integrity checking process if running
*
* @param request
* @param auth_token_enc
* @param requestId
* @return
*/
@POST
@Path("/cancelIntegrityProcessOnEndpoint/{params:.*}")
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Produces (MediaType.APPLICATION_JSON)
public Response cancelIntegrityProcessOnEndpoint ( @Context HttpServletRequest request, @FormDataParam ("AUTH_TOKEN") String auth_token_digest, @FormDataParam ("REQUEST_ID") String requestId ) {

String remoteIP = null;

try {

remoteIP = request.getRemoteHost();
if ( !UtilMethods.isSet( remoteIP ) ) {
remoteIP = request.getRemoteAddr();
}

//Search for the given end point
PublishingEndPointAPI endpointAPI = APILocator.getPublisherEndPointAPI();
PublishingEndPoint requesterEndPoint = endpointAPI.findEnabledSendingEndPointByAddress( remoteIP );

//Verify the authentication token
if ( !BundlePublisherResource.isValidToken( auth_token_digest, remoteIP, requesterEndPoint ) || !UtilMethods.isSet( requestId ) ) {
return Response.status( HttpStatus.SC_UNAUTHORIZED ).build();
}

ServletContext servletContext = request.getSession().getServletContext();
if ( !UtilMethods.isSet( servletContext.getAttribute( "integrityDataRequestID" ) ) || !((String) servletContext.getAttribute( "integrityDataRequestID" )).equals( requestId ) ) {
return Response.status( HttpStatus.SC_UNAUTHORIZED ).build();
}

//Verify the status and if the process it is still running we will interrupt it
ProcessStatus integrityDataGeneratorStatus = (ProcessStatus) servletContext.getAttribute( "integrityDataGenerationStatus" );
if ( UtilMethods.isSet( integrityDataGeneratorStatus ) ) {
switch ( integrityDataGeneratorStatus ) {
case PROCESSING:

//Verify if the thread is on the session for this given request id
if ( servletContext.getAttribute( "integrityDataGeneratorThread_" + requestId ) != null ) {

//If found interrupt the process
IntegrityDataGeneratorThread integrityDataGeneratorThread = (IntegrityDataGeneratorThread) servletContext.getAttribute( "integrityDataGeneratorThread_" + requestId );
integrityDataGeneratorThread.interrupt();
servletContext.removeAttribute( "integrityDataGeneratorThread_" + requestId );

return Response.status( HttpStatus.SC_RESET_CONTENT ).entity( "Interrupted checking process on End Point server ( " + remoteIP + ")." ).build();
}
default:
break;
}
}

} catch ( Exception e ) {
Logger.error( IntegrityResource.class, "Error caused by remote call of: " + remoteIP, e );
if (ExceptionUtil.causedBy(e, DotSecurityException.class)) {
throw new ForbiddenException(e);
}
return response( "Error interrupting checking process on End Point server ( " + remoteIP + "). [" + e.getMessage() + "]", true );
}
private Response generateIntegrityCheckerRequest(final PublishingEndPoint endpoint) {
String url = endpoint.toURL()+"/api/integrity/_generateintegritydata";

return response( "Interrupted checking process on End Point server ( " + remoteIP + ").", false );
return postWithEndpointState(endpoint, url, MediaType.TEXT_PLAIN_TYPE);
}

/**
@@ -791,10 +578,6 @@ public Response checkIntegrityProcessStatus ( @Context final HttpServletRequest
jsonResponse.put( "status", "noConflicts" );
jsonResponse.put( "message", session.getAttribute( "integrityCheck_message_" + endpointId ) );
clearStatus( request, endpointId );
} else if ( status == ProcessStatus.CANCELED) {
jsonResponse.put( "status", "canceled" );
jsonResponse.put( "message", LanguageUtil.get( initData.getUser().getLocale(), "IntegrityCheckingCanceled" ) );
clearStatus( request, endpointId );
} else {
jsonResponse.put( "status", "error" );
jsonResponse.put( "message", "Error checking the integrity process status for End Point server: [" + endpointId + "]" );
@@ -870,15 +653,15 @@ public Response getIntegrityResult ( @Context HttpServletRequest request, @Conte
columns.add(integrityType.getFirstDisplayColumnLabel());

switch( integrityType ) {
case HTMLPAGES:
case FILEASSETS:
case HTMLPAGES:
case FILEASSETS:
columns.add("local_working_inode");
columns.add("remote_working_inode");
columns.add("local_live_inode");
columns.add("remote_live_inode");
columns.add("language_id");
break;
case CMS_ROLES:
case CMS_ROLES:
columns.add("role_key");
columns.add("local_role_id");
columns.add("remote_role_id");
@@ -986,10 +769,10 @@ public Response discardConflicts ( @Context final HttpServletRequest request, @C
if (ExceptionUtil.causedBy(e, DotSecurityException.class)) {
throw new ForbiddenException(e);
}
Logger.error(this.getClass(), "ERROR: Table "
+ IntegrityType.valueOf(type.toUpperCase()).getResultsTableName()
+ " could not be cleared on end-point [" + endpointId
+ "]. Please truncate the table data manually.", e);
Logger.error(this.getClass(), "ERROR: Table "
+ IntegrityType.valueOf(type.toUpperCase()).getResultsTableName()
+ " could not be cleared on end-point [" + endpointId
+ "]. Please truncate the table data manually.", e);
return response( "Error discarding "+type+" conflicts for End Point server: [" + endpointId + "]" , true );
}

@@ -1001,88 +784,89 @@ public Response discardConflicts ( @Context final HttpServletRequest request, @C
*
* @param request
* @param dataToFix
* @param auth_token_enc
* @param type
* @return
* @throws JSONException
*/
@POST
@Path("/fixconflictsfromremote/{params:.*}")
@Path("/_fixconflictsfromremote")
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Produces("text/plain")
public Response fixConflictsFromRemote ( @Context final HttpServletRequest request,
@FormDataParam("DATA_TO_FIX") InputStream dataToFix, @FormDataParam("AUTH_TOKEN") String auth_token_digest,
@FormDataParam("DATA_TO_FIX") InputStream dataToFix,
@FormDataParam("TYPE") String type ) throws JSONException {

String remoteIP = null;
JSONObject jsonResponse = new JSONObject();
IntegrityUtil integrityUtil = new IntegrityUtil();
PublishingEndPointAPI endpointAPI = APILocator.getPublisherEndPointAPI();
PublishingEndPoint requesterEndPoint = null;
try {
if (LicenseManager.getInstance().isCommunity()) {
throw new InvalidLicenseException("License required");
}

remoteIP = request.getRemoteHost();
if (!UtilMethods.isSet(remoteIP))
remoteIP = request.getRemoteAddr();
final AuthCredentialPushPublishUtil.PushPublishAuthenticationToken pushPublishAuthenticationToken
= AuthCredentialPushPublishUtil.INSTANCE.processAuthHeader(request);

requesterEndPoint = endpointAPI.findEnabledSendingEndPointByAddress(remoteIP);
final Optional<Response> failResponse = PushPublishResourceUtil.getFailResponse(request, pushPublishAuthenticationToken);

if (!BundlePublisherResource.isValidToken(auth_token_digest, remoteIP, requesterEndPoint)) {
return Response.status(HttpStatus.SC_UNAUTHORIZED).build();
}
if (failResponse.isPresent()) {
return failResponse.get();
}

JSONObject jsonResponse = new JSONObject();
IntegrityUtil integrityUtil = new IntegrityUtil();
String key = null;

HibernateUtil.startTransaction();
integrityUtil.fixConflicts(dataToFix, requesterEndPoint.getId(),
try {
key = pushPublishAuthenticationToken.isJWTTokenWay() ?
pushPublishAuthenticationToken.getToken().getId() :
pushPublishAuthenticationToken.getPublishingEndPoint().getId();
integrityUtil.fixConflicts(dataToFix, key,
IntegrityType.valueOf(type.toUpperCase()));
HibernateUtil.commitTransaction();
} catch (DotSecurityException e) {
throw new ForbiddenException(e);
} catch ( Exception e ) {
try {
HibernateUtil.rollbackTransaction();
} catch (DotHibernateException e1) {
Logger.error(IntegrityResource.class, "Error while rolling back transaction", e);
}
Logger.error( this.getClass(), "Error fixing "+type+" conflicts from remote", e );
return response( "Error fixing "+type+" conflicts from remote" , true );
} finally {
try {
if (requesterEndPoint != null) {
// Discard conflicts if successful or failed
integrityUtil.discardConflicts(requesterEndPoint.getId(),
IntegrityType.valueOf(type.toUpperCase()));
}
} catch (DotDataException e) {
Logger.error(this.getClass(), "ERROR: Table "
+ IntegrityType.valueOf(type.toUpperCase()).getResultsTableName()
+ " could not be cleared on end-point [" + requesterEndPoint.getId()
+ "]. Please truncate the table data manually.", e);
}

HibernateUtil.closeSessionSilently();
}
try {
if (key != null) {
// Discard conflicts if successful or failed
integrityUtil.discardConflicts(key, IntegrityType.valueOf(type.toUpperCase()));
}
} catch (DotDataException e) {
Logger.error(this.getClass(), "ERROR: Table "
+ IntegrityType.valueOf(type.toUpperCase()).getResultsTableName()
+ " could not be cleared on request id [" + key
+ "]. Please truncate the table data manually.", e);
}
}

jsonResponse.put( "success", true );
jsonResponse.put( "message", "Conflicts fixed in Remote Endpoint" );
return response( jsonResponse.toString(), false );
}

/**
* Fixes the data conflicts between the local and remote servers. If the
* request parameter called <code>whereToFix</code> equals
* <code>"local"</code>, the data correction will take place in local
* server. If the parameter equals <code>"remote"</code>, the fix will take
* place in remote server.
*
* @param httpServletRequest
* - The {@link HttpServletRequest} that started the process.
* @param params
* - The execution parameters for running the process: The
* end-point ID.
* @return The REST {@link Response} with the status of the operation.
* @throws JSONException
* An error occurred when generating the JSON response.
*/
private String getRemoteIP(@Context HttpServletRequest request) {
String remoteIP = request.getRemoteHost();
if (!UtilMethods.isSet(remoteIP)) {
remoteIP = request.getRemoteAddr();
}
return remoteIP;
}

/**
* Fixes the data conflicts between the local and remote servers. If the
* request parameter called <code>whereToFix</code> equals
* <code>"local"</code>, the data correction will take place in local
* server. If the parameter equals <code>"remote"</code>, the fix will take
* place in remote server.
*
* @param httpServletRequest
* - The {@link HttpServletRequest} that started the process.
* @param params
* - The execution parameters for running the process: The
* end-point ID.
* @return The REST {@link Response} with the status of the operation.
* @throws JSONException
* An error occurred when generating the JSON response.
*/
@GET
@Path ("/fixconflicts/{params:.*}")
@Produces (MediaType.APPLICATION_JSON)
@@ -1110,13 +894,10 @@ public Response fixConflicts ( @Context final HttpServletRequest httpServletRequ
}

IntegrityUtil integrityUtil = new IntegrityUtil();
IntegrityType integrityTypeToFix = IntegrityType.valueOf(type.toUpperCase());
IntegrityType integrityTypeToFix = IntegrityType.valueOf(type.toUpperCase());
try {
if (whereToFix.equals("local")) {

HibernateUtil.startTransaction();
integrityUtil.fixConflicts(endpointId, integrityTypeToFix);
HibernateUtil.commitTransaction();
jsonResponse.put("success", true);
jsonResponse.put("message", "Conflicts fixed in Local Endpoint");

@@ -1142,29 +923,14 @@ public Response fixConflicts ( @Context final HttpServletRequest httpServletRequ
} else if (whereToFix.equals("remote")) {
integrityUtil.generateDataToFixZip(endpointId, integrityTypeToFix);

final Client client = RestClientBuilder.newClient();

PublishingEndPoint endpoint = APILocator.getPublisherEndPointAPI()
.findEndPointById(endpointId);
String outputPath = ConfigUtils.getIntegrityPath() + File.separator + endpointId;
File bundle = new File(
outputPath + File.separator + INTEGRITY_DATA_TO_FIX_ZIP_FILE_NAME);

FormDataMultiPart form = new FormDataMultiPart();
Optional<String> authToken = PushPublisher.retriveEndpointKeyDigest(endpoint);
if ( !authToken.isPresent() ) {
return Response.status( HttpStatus.SC_BAD_REQUEST ).entity( "Error: 'auth key' is a required param." ).build();
}

form.field("AUTH_TOKEN",authToken.get());
final File bundle = new File(IntegrityUtil.getIntegrityDataFilePath(
endpointId,
IntegrityUtil.INTEGRITY_DATA_TO_FIX_ZIP_FILENAME));

form.field("TYPE", type);
form.bodyPart(new FileDataBodyPart("DATA_TO_FIX", bundle,
MediaType.MULTIPART_FORM_DATA_TYPE));
String url = endpoint.toURL() + "/api/integrity/fixconflictsfromremote/";
WebTarget webTarget = client.target(url);
Response response = webTarget.request(MediaType.TEXT_PLAIN_TYPE)
.post(Entity.entity(form, form.getMediaType()));
final Response response = sendFixConflictsRequest(type, endpoint, bundle);

if (response.getStatus() == HttpStatus.SC_OK) {
jsonResponse.put("success", true);
@@ -1188,31 +954,39 @@ public Response fixConflicts ( @Context final HttpServletRequest httpServletRequ
} catch (DotSecurityException e) {
throw new ForbiddenException(e);
} catch ( Exception e ) {
try {
HibernateUtil.rollbackTransaction();
} catch (DotHibernateException e1) {
Logger.error(IntegrityResource.class, "Error while rolling back transaction", e);
}

Logger.error( this.getClass(), "Error fixing "+type+" conflicts for End Point server: [" + endpointId + "]", e );
return response( "Error fixing conflicts for endpoint: " + endpointId , true );
} finally {
try {
// Discard conflicts if successful or failed
integrityUtil.discardConflicts(endpointId, integrityTypeToFix);
} catch (DotDataException e) {
Logger.error(this.getClass(), "ERROR: Table " + integrityTypeToFix.getResultsTableName()
+ " could not be cleared on end-point [" + endpointId
+ "]. Please truncate the table data manually.", e);
}
HibernateUtil.closeSessionSilently();
}
} finally {
try {
// Discard conflicts if successful or failed
integrityUtil.discardConflicts(endpointId, integrityTypeToFix);
} catch (DotDataException e) {
Logger.error(this.getClass(), "ERROR: Table " + integrityTypeToFix.getResultsTableName()
+ " could not be cleared on end-point [" + endpointId
+ "]. Please truncate the table data manually.", e);
}
}

return response( jsonResponse.toString(), false );
}

private Response sendFixConflictsRequest(String type, PublishingEndPoint endpoint, File bundle) {

FormDataMultiPart form = new FormDataMultiPart();
form.field("TYPE", type);
form.bodyPart(new FileDataBodyPart("DATA_TO_FIX", bundle,
MediaType.MULTIPART_FORM_DATA_TYPE));

String url = String.format("%s/api/integrity/_fixconflictsfromremote/", endpoint.toURL());

return postWithEndpointState(
endpoint, url, MediaType.TEXT_PLAIN_TYPE,
HTTPMethod.POST, Entity.entity(form, form.getMediaType()));
}

/**
* Removes the status for the checking integrity process of a given enpoint from session
* Removes the status for the checking integrity process of a given endpoint from session
*
* @param request
* @param endpointId
@@ -1222,7 +996,7 @@ private void clearStatus ( HttpServletRequest request, String endpointId ) {
}

/**
* Removes the status for the checking integrity process of a given enpoint from session
* Removes the status for the checking integrity process of a given endpoint from session
*
* @param session
* @param endpointId
@@ -1234,37 +1008,37 @@ private void clearStatus ( HttpSession session, String endpointId ) {
}

/**
* Sets the status for the checking integrity process of a given enpoint in session
* Sets the status for the checking integrity process of a given endpoint in session
*
* @param request
* @param endpointId
* @param status
*/
private void setStatus ( HttpServletRequest request, String endpointId, ProcessStatus status ) {
private static void setStatus ( HttpServletRequest request, String endpointId, ProcessStatus status ) {
setStatus( request, endpointId, status, null );
}

/**
* Sets the status for the checking integrity process of a given enpoint in session
* Sets the status for the checking integrity process of a given endpoint in session
*
* @param request
* @param endpointId
* @param status
* @param message
*/
private void setStatus ( HttpServletRequest request, String endpointId, ProcessStatus status, String message ) {
private static void setStatus ( HttpServletRequest request, String endpointId, ProcessStatus status, String message ) {
setStatus( request.getSession(), endpointId, status, message );
}

/**
* Sets the status for the checking integrity process of a given enpoint in session
* Sets the status for the checking integrity process of a given endpoint in session
*
* @param session
* @param endpointId
* @param status
* @param message
*/
private void setStatus ( HttpSession session, String endpointId, ProcessStatus status, String message ) {
public static void setStatus ( HttpSession session, String endpointId, ProcessStatus status, String message ) {
session.setAttribute( "integrityCheck_" + endpointId, status );
if ( message != null ) {
session.setAttribute( "integrityCheck_message_" + endpointId, message );
@@ -1328,10 +1102,114 @@ private Response response ( String response, Boolean error ) {
*/
private Response response ( String response, Boolean error, String contentType ) {
if ( error ) {
return Response.status( HttpStatus.SC_INTERNAL_SERVER_ERROR ).entity( response ).build();
return response(response, HttpStatus.SC_INTERNAL_SERVER_ERROR);
} else {
return Response.ok( response, contentType ).build();
}
}

private Response response ( String response, int status ) {
return Response.status( status ).entity( response ).build();
}

private class IntegrityDataRequestChecker implements Runnable{

private final User loggedUser;
private final HttpSession session;
private final PublishingEndPoint endpoint;
private final String integrityDataRequestID;

public IntegrityDataRequestChecker(
final User loggedUser,
final HttpSession session,
final PublishingEndPoint endpoint,
final String integrityDataRequestID) {

this.loggedUser = loggedUser;
this.session = session;
this.endpoint = endpoint;
this.integrityDataRequestID = integrityDataRequestID;
}

@CloseDBIfOpened
public void run(){

boolean processing = true;

while(processing) {

Response response = null;

response = statusIntegrityCheckerRequest();

if (response.getStatus() == HttpStatus.SC_OK) {

processing = false;

InputStream zipFile = response.readEntity(InputStream.class);
String outputDir = ConfigUtils.getIntegrityPath() + File.separator + endpoint.getId();

try {
IntegrityUtil.unzipFile(zipFile, outputDir);
} catch (Exception e) {
setStatus(session, endpoint.getId(), ProcessStatus.ERROR, null);
Logger.error(IntegrityResource.class, "Error while unzipping Integrity Data", e);
throw new RuntimeException("Error while unzipping Integrity Data", e);
}

// set session variable
// call IntegrityChecker
boolean conflictPresent;

IntegrityUtil integrityUtil = new IntegrityUtil();
try {
integrityUtil.completeDiscardConflicts(endpoint.getId());
conflictPresent = integrityUtil.completeCheckIntegrity(endpoint.getId());
} catch (Exception e) {
Logger.error(IntegrityResource.class, "Error checking integrity", e);

//Setting the process status
setStatus(session, endpoint.getId(), ProcessStatus.ERROR, null);
throw new RuntimeException("Error checking integrity", e);
} finally {
try {
integrityUtil.dropTempTables(endpoint.getId());
} catch (DotHibernateException e) {
Logger.warn(this, e.getMessage(), e);
} catch (DotDataException e) {
Logger.error(IntegrityResource.class, "Error while deleting temp tables", e);
}
}

if (conflictPresent) {
//Setting the process status
setStatus(session, endpoint.getId(), ProcessStatus.FINISHED, null);
} else {
String noConflictMessage;
try {
noConflictMessage = LanguageUtil.get(loggedUser.getLocale(), "push_publish_integrity_conflicts_not_found");
} catch (LanguageException e) {
noConflictMessage = "No Integrity Conflicts found";
}
//Setting the process status
setStatus(session, endpoint.getId(), ProcessStatus.NO_CONFLICTS, noConflictMessage);
}

} else if (response.getStatus() == HttpStatus.SC_PROCESSING) {
Logger.info(this.getClass(), "Integrity check is still provessinf");
} else {
setStatus(session, endpoint.getId(), ProcessStatus.ERROR, null);
Logger.error(this.getClass(), "Response indicating a " + response.getStatusInfo().getReasonPhrase() + " (" + response.getStatus() + ") Error trying to retrieve the Integrity data from the Endpoint [" + endpoint.getId() + "].");
processing = false;
}
}
}

private Response statusIntegrityCheckerRequest() {
String url = String.format("%s/api/integrity/%s/status", endpoint.toURL(), integrityDataRequestID);

return postWithEndpointState(
endpoint, url, new MediaType("application", "zip"));
}
}
}
@@ -0,0 +1,18 @@
package com.dotcms.rest;

import com.dotcms.repackage.org.codehaus.jettison.json.JSONObject;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;

@JsonSerialize(using = JsonObjectViewSerializer.class)
public class JsonObjectView {

private final JSONObject jsonObject;

public JsonObjectView(final JSONObject jsonObject) {
this.jsonObject = jsonObject;
}

public JSONObject getJsonObject() {
return jsonObject;
}
}
@@ -0,0 +1,17 @@
package com.dotcms.rest;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;

import java.io.IOException;

public class JsonObjectViewSerializer extends JsonSerializer<JsonObjectView> {

@Override
public void serialize(final JsonObjectView jsonObjectView, final JsonGenerator jsonGenerator,
final SerializerProvider serializerProvider) throws IOException {

jsonGenerator.writeRawValue(jsonObjectView.getJsonObject().toString());
}
}
@@ -0,0 +1,83 @@
package com.dotcms.rest;

import com.dotcms.publisher.pusher.AuthCredentialPushPublishUtil;
import com.dotcms.util.CollectionsUtils;
import com.dotmarketing.util.Logger;
import com.liferay.portal.model.User;

import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.core.Response;
import java.util.Optional;

/**
* Provide util method for {@link IntegrityResource} and {@link BundleResource} classes
*/
public class PushPublishResourceUtil {

public static Optional<Response> getFailResponse(
final HttpServletRequest request,
final AuthCredentialPushPublishUtil.PushPublishAuthenticationToken pushPublishAuthenticationToken) {

final ResourceResponse responseResource = new ResourceResponse(CollectionsUtils.map("type", "plain"));
final String localAddress = RestEndPointIPUtil.getFullLocalIp(request);
final String remoteIP = RestEndPointIPUtil.resolveRemoteIp(request);

if (pushPublishAuthenticationToken.isTokenInvalid()) {

final String message = String.format(
"Receiver at %s:> Authentication Token is invalid for ip: %s",
localAddress,
remoteIP);

Logger.error(IntegrityResource.class, String.format("Receiver at %s> :%s", localAddress, message));
return Optional.of(
responseResource.responseAuthenticateError(
message,
"invalid_token",
AuthCredentialPushPublishUtil.INVALID_TOKEN_ERROR_KEY
)
);
} else if (pushPublishAuthenticationToken.isTokenExpired()) {
final String message = String.format(
"Receiver at %s:> Authentication Token is expired for ip: %s",
localAddress,
remoteIP);

Logger.error(IntegrityResource.class, String.format("Receiver at %s> :%s", localAddress, message));

return Optional.of(
responseResource.responseAuthenticateError(
message,
"invalid_token",
AuthCredentialPushPublishUtil.EXPIRED_TOKEN_ERROR_KEY
)
);
}

if (pushPublishAuthenticationToken.isJWTTokenWay()) {

final Optional<User> optionalUser = pushPublishAuthenticationToken.getToken().getActiveUser();

if (optionalUser.isPresent() && optionalUser.get().isAdmin()) {
return Optional.empty();
} else {
final String message = String.format(
"Receiver at %s:> JWT Token is grant just for Admin user for ip: %s",
localAddress,
remoteIP);

Logger.error(IntegrityResource.class, String.format("Receiver at %s> :%s", localAddress, message));

return Optional.of(
responseResource.responseAuthenticateError(
message,
"invalid_token",
AuthCredentialPushPublishUtil.INVALID_TOKEN_ERROR_KEY
)
);
}
} else {
return Optional.empty();
}
}
}
@@ -7,15 +7,19 @@
import com.dotmarketing.util.UtilMethods;
import com.dotmarketing.util.json.JSONException;

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

import static com.dotcms.util.CollectionsUtils.map;

/**
* @author Jonathan Gamba
* Date: 8/22/13
*/
public class ResourceResponse {

public static final String AUTHENTICATE_RESPONSE_ERROR_HEADER_NAME = "WWW-Authenticate";
private String type;
private Map<String, String> paramsMap;

@@ -122,12 +126,12 @@ public Response response ( String response,

final Response.ResponseBuilder responseBuilder =
( contentType != null )?
Response.ok( response, contentType ):
Response.ok( response, contentType ):
/*
If the Content type of the response is null the default
will be the defined by the @Produces annotation of the RESTful method
*/
Response.ok( response );
Response.ok( response );

status.ifPresent( theStatus -> responseBuilder.status(status.get()));

@@ -147,14 +151,58 @@ public Response responseError ( int statusCode ) {
return responseError( null, statusCode );
}


public Response responseAuthenticateError ( final String message, final String errorCode, final String errorKey ) {
return responseWithWWWAuthenticate(message, HttpStatus.SC_UNAUTHORIZED, errorCode, errorKey);
}

public Response responseWithWWWAuthenticate ( final String message, final int status, final String errorCode, final String errorKey ) {
return responseError(
message,
status,
map("WWW-Authenticate",
String.format("Bearer realm=\"example\",error=\"%s\",error_key=\"%s\",error_description=\"\"", errorCode, errorKey)
)
);
}

public Response responseError ( String response, int statusCode ) {
return this.responseError(response, statusCode, null);
}

public Response responseError (
final String response, int statusCode,
final Map<String, Object> headers) {
Response.ResponseBuilder responseBuilder = Response.status( statusCode );

if ( UtilMethods.isSet( response ) ) {
responseBuilder.entity( response );
}

if ( UtilMethods.isSet( headers ) ) {
for (Map.Entry<String, Object> entry : headers.entrySet()) {
responseBuilder.header(entry.getKey(), entry.getValue());
}
}

return responseBuilder.build();
}

public Response responseUnauthorizedError(final String errorKey) {
return responseWithWWWAuthenticate(null, HttpStatus.SC_FORBIDDEN, "insufficient_scope", errorKey);
}

public static Map<String, String> getWWWAuthenticateHeader(final Response response) {
final String[] headerFields = response.getHeaderString(AUTHENTICATE_RESPONSE_ERROR_HEADER_NAME).split(",");

final Map<String, String> headerValues = new HashMap<>();

for (final String headerField : headerFields) {
final String[] fieldSplit = headerField.split("=");
headerValues.put(fieldSplit[0], fieldSplit[1]);
}

return headerValues;
}

}
@@ -25,7 +25,7 @@
*
* @return The REST {@link Client} object.
*/
public static Client newClient() {
public static Client newClient() { // todo: create a DotConfigMap that wraps the config and allows to reuse this class on non-dotcms contexts
TrustFactory tFactory = new TrustFactory();

final Client client;
@@ -0,0 +1,35 @@
package com.dotcms.rest;

import com.dotcms.util.HttpRequestDataUtil;
import com.dotmarketing.util.UtilMethods;

import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.core.Context;
import java.util.Optional;

/**
* Provide util methods to get remote or local IP from {@link HttpServletRequest}
*/
public class RestEndPointIPUtil {
/**
* Tries to get the local address plus the port in a "host:port" format
* @param request http servlet request
* @return a string representing the address plus the port
*/
public static String getFullLocalIp(@Context final HttpServletRequest request) {
final String localIp = request.getLocalName();
final Optional<String> port = HttpRequestDataUtil.getServerPort();
return (!UtilMethods.isSet(localIp) ? localIp : request.getLocalName())
+ ':' + port.orElse(String.valueOf(request.getLocalPort()));
}

/**
* Resolves remote IP address from request.
* @param request {@link HttpServletRequest}
* @return a String representing the remote IP address (or hostname)
*/
public static String resolveRemoteIp(final HttpServletRequest request) {
final String remoteIP = request.getRemoteHost();
return !UtilMethods.isSet(remoteIP) ? remoteIP : request.getRemoteAddr();
}
}
@@ -0,0 +1,142 @@
package com.dotcms.rest;

import com.dotmarketing.business.APILocator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;

/**
* Search Form to make a ES query
* @author jsanca
*/
@JsonDeserialize(builder = SearchForm.Builder.class)
public class SearchForm {

private final String query;
private final String sort;
private final int limit;
private final int offset;
private final String userId;
private final String render;
private final int depth;
private final long languageId;
private final boolean allCategoriesInfo;

private SearchForm (final Builder builder) {

this.query = builder.query;
this.sort = builder.sort;
this.limit = builder.limit;
this.offset = builder.offset;
this.userId = builder.userId;
this.render = builder.render;
this.depth = builder.depth;
this.languageId = builder.languageId;
this.allCategoriesInfo = builder.allCategoriesInfo;
}

public String getQuery() {
return query;
}

public String getSort() {
return sort;
}

public int getLimit() {
return limit;
}

public int getOffset() {
return offset;
}

public String getUserId() {
return userId;
}

public String getRender() {
return this.render;
}

public int getDepth() {
return depth;
}

public long getLanguageId() {
return languageId;
}

public boolean isAllCategoriesInfo() {
return allCategoriesInfo;
}

public static final class Builder {

private @JsonProperty String query = "";
private @JsonProperty String sort = "";
private @JsonProperty int limit = 20;
private @JsonProperty int offset = 0;
private @JsonProperty String userId;
private @JsonProperty String render;
private @JsonProperty int depth = -1;
private @JsonProperty long languageId = -1;
private @JsonProperty boolean allCategoriesInfo;

public Builder query(final String query) {
this.query = query;
return this;
}

public Builder sort(final String sort) {
this.sort = sort;
return this;
}

public Builder limit(final int limit) {
this.limit = limit;
return this;
}

public Builder offset(final int offset) {
this.offset = offset;
return this;
}

public Builder userId(final String userId) {
this.userId = userId;
return this;
}

public Builder render(final String render) {
this.render = render;
return this;
}

public Builder depth(final int depth) {
this.depth = depth;
return this;
}

public Builder languageId(final int languageId) {
this.languageId = languageId;
return this;
}

public Builder allCategoriesInfo(final boolean allCategoriesInfo) {
this.allCategoriesInfo = allCategoriesInfo;
return this;
}

public SearchForm build () {

if (-1 == this.languageId) {
this.languageId =
APILocator.getLanguageAPI().getDefaultLanguage().getId();
}

return new SearchForm(this);
}

}

}
@@ -0,0 +1,36 @@
package com.dotcms.rest;

public class SearchView {

private final long resultsSize;
private final long queryTook;
private final long contentTook;
private final JsonObjectView jsonObjectView;

public SearchView(final long resultsSize,
final long queryTook,
final long contentTook,
final JsonObjectView jsonObjectView) {

this.resultsSize = resultsSize;
this.queryTook = queryTook;
this.contentTook = contentTook;
this.jsonObjectView = jsonObjectView;
}

public long getResultsSize() {
return resultsSize;
}

public long getQueryTook() {
return queryTook;
}

public long getContentTook() {
return contentTook;
}

public JsonObjectView getJsonObjectView() {
return jsonObjectView;
}
}
@@ -1,14 +1,17 @@
package com.dotcms.rest.api.v1.apps;

import static com.dotmarketing.util.UtilMethods.isNotSet;
import static com.dotmarketing.util.UtilMethods.isSet;

import com.dotcms.repackage.org.codehaus.jettison.json.JSONException;
import com.dotcms.rest.api.MultiPartUtils;
import com.dotcms.rest.api.v1.apps.view.AppView;
import com.dotcms.rest.api.v1.apps.view.SecretView;
import com.dotcms.rest.api.v1.apps.view.SiteView;
import com.dotcms.security.apps.AppDescriptor;
import com.dotcms.security.apps.AppSecrets;
import com.dotcms.security.apps.AppsAPI;
import com.dotcms.security.apps.AppsUtil;
import com.dotcms.security.apps.ParamDescriptor;
import com.dotcms.security.apps.Secret;
import com.dotcms.security.apps.Type;
@@ -29,9 +32,13 @@
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.liferay.portal.model.User;
import com.liferay.util.EncryptorException;
import io.vavr.Tuple;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.security.Key;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
@@ -700,4 +707,72 @@ private boolean isAllFilledWithAsters(final char [] chars){
return true;
}

/**
* Secrets export
* @param form
* @param user
* @return
* @throws DotSecurityException
* @throws IOException
* @throws DotDataException
*/
InputStream exportSecrets(final ExportSecretForm form, final User user)
throws DotSecurityException, IOException, DotDataException {

Logger.info(AppsHelper.class,"Secrets export: "+form);

if(isNotSet(form.getPassword())){
throw new DotDataException("Unable to locate password param.");
}
final String password = form.getPassword();
final Key key = AppsUtil.generateKey(password);
return Files.newInputStream(appsAPI
.exportSecrets(key, form.isExportAll(), form.getAppKeysBySite(), user));
}

/**
* Secrets import
* @param multipart
* @param user
* @throws IOException
* @throws DotDataException
* @throws JSONException
* @throws DotSecurityException
* @throws EncryptorException
* @throws ClassNotFoundException
*/
void importSecrets(final FormDataMultiPart multipart, final User user)
throws IOException, DotDataException, JSONException, DotSecurityException, EncryptorException, ClassNotFoundException {
final MultiPartUtils multiPartUtils = new MultiPartUtils();
final List<File> files = multiPartUtils.getBinariesFromMultipart(multipart);
if(!UtilMethods.isSet(files)){
throw new DotDataException("Unable to extract any files from multi-part request.");
}

final Map<String, Object> bodyMapFromMultipart = multiPartUtils
.getBodyMapFromMultipart(multipart);
final Object object = bodyMapFromMultipart.get("password");

if(null == object){
throw new DotDataException("Unable to locate password param.");
}

final String password = object.toString();
final Key key = AppsUtil.generateKey(password);
final Map<String, List<AppSecrets>> importedSecretsBySiteId = appsAPI
.importSecrets(files.get(0).toPath(), key, user);
Logger.info(AppsHelper.class,"Number of secrets found: "+importedSecretsBySiteId.size());
for (final Entry<String, List<AppSecrets>> entry : importedSecretsBySiteId.entrySet()) {
final String siteId = entry.getKey();
final List<AppSecrets> secrets = entry.getValue();
final Host site = hostAPI.find(siteId, user, false);
if(null != site && isSet(site.getIdentifier())){
for (final AppSecrets appSecrets : secrets) {
Logger.info(AppsHelper.class,String.format("Importing secret `%s` ",appSecrets));
appsAPI.saveSecrets(appSecrets, site, user);
}
}
}
}

}
@@ -433,4 +433,78 @@ public final Response deleteApp(
}
}

/**
* Secrets export
* @param request
* @param response
* @param exportSecretForm
* @return
*/
@POST
@Path("/export")
@JSONP
@NoCache
@Produces(MediaType.APPLICATION_OCTET_STREAM)
public final Response exportSecrets(
@Context final HttpServletRequest request,
@Context final HttpServletResponse response,
final ExportSecretForm exportSecretForm
) {
exportSecretForm.checkValid();
try {
final InitDataObject initData =
new WebResource.InitBuilder(webResource)
.requiredBackendUser(true)
.requiredFrontendUser(false)
.requestAndResponse(request, response)
.rejectWhenNoUser(true)
.init();
final User user = initData.getUser();
//no need to close i'll get closed upon writing the response
return Response.ok(helper.exportSecrets(exportSecretForm, user), MediaType.APPLICATION_OCTET_STREAM)
.header("content-disposition", "attachment; filename=appSecrets.export")
.build(); // 200
} catch (Exception e) {
//By doing this mapping here. The resource becomes integration test friendly.
Logger.error(this.getClass(),"Exception exporting secrets.", e);
return ResponseUtil.mapExceptionResponse(e);
}
}

/**
* Secrets import
* @param request
* @param response
* @param form
* @return
*/
@POST
@Path("/import")
@JSONP
@NoCache
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.MULTIPART_FORM_DATA)
public final Response importSecrets(
@Context final HttpServletRequest request,
@Context final HttpServletResponse response,
final FormDataMultiPart form
) {
try {
final InitDataObject initData =
new WebResource.InitBuilder(webResource)
.requiredBackendUser(true)
.requiredFrontendUser(false)
.requestAndResponse(request, response)
.rejectWhenNoUser(true)
.init();
final User user = initData.getUser();
helper.importSecrets(form, user);
return Response.ok(new ResponseEntityView(OK)).build(); // 200
} catch (Exception e) {
//By doing this mapping here. The resource becomes integration test friendly.
Logger.error(this.getClass(),"Exception importing secrets.", e);
return ResponseUtil.mapExceptionResponse(e);
}
}

}
@@ -23,6 +23,7 @@

@JsonCreator
public DeleteSecretForm(@JsonProperty("key") final String key, @JsonProperty("siteId") final String siteId, @JsonProperty("params") final Set<String> params) {
super();
this.key = key;
this.siteId = siteId;
this.params = params;
@@ -0,0 +1,57 @@
package com.dotcms.rest.api.v1.apps;

import com.dotcms.rest.api.Validated;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;

public class ExportSecretForm extends Validated {

@NotNull
@Min(14)
@Max(32)
private final String password;

@NotNull
private final Map<String, Set<String>> appKeysBySite;

@NotNull
private final boolean exportAll;

@JsonCreator
public ExportSecretForm(@JsonProperty("password") final String password, @JsonProperty("exportAll") final boolean exportAll, @JsonProperty("appKeys") final Map<String, Set<String>> appKeysBySite) {
super();
this.password = password;
this.exportAll = exportAll;
this.appKeysBySite = appKeysBySite;
}

public String getPassword() {
return password;
}

public boolean isExportAll() {
return exportAll;
}

public Map<String, Set<String>> getAppKeysBySite() {
return appKeysBySite;
}

@Override
public String toString() {
final List<String> stringsList = appKeysBySite.entrySet().stream()
.map(entry -> "Site " + entry.getKey() + " keys: " + String
.join(",", entry.getValue())).collect(
Collectors.toList());

return String.format("ExportSecretForm{ all={%s} {%s}}", exportAll,
String.join("\n", stringsList));
}
}
@@ -17,6 +17,7 @@

@JsonCreator
public SecretForm(final Map<String, Input> inputParams) {
super();
this.inputParams = inputParams;
}

@@ -6,12 +6,15 @@

import javax.servlet.http.HttpServletRequest;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.*;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import com.dotcms.rest.WebResource;
import com.dotmarketing.business.Role;
import com.dotmarketing.util.Config;
import org.glassfish.jersey.server.JSONP;
import com.dotcms.rest.ResponseEntityView;
import com.dotcms.rest.annotation.NoCache;
@@ -70,4 +73,32 @@ public Response list(@Context final HttpServletRequest request) {
}
}

/**
* Set value to config properties in runtime
*
* @param request
* @return
*/
@PUT
@JSONP
@NoCache
@Consumes(MediaType.APPLICATION_JSON)
public Response set(
@Context final HttpServletRequest request,
@Context final HttpServletResponse response,
Map<String, String> properties) {

new WebResource
.InitBuilder(request, response)
.requiredRoles(Role.CMS_ADMINISTRATOR_ROLE)
.requiredPortlet("maintenance")
.rejectWhenNoUser(true)
.init();

for (Map.Entry<String, String> entry : properties.entrySet()) {
Config.setProperty(entry.getKey(), entry.getValue());
}

return Response.ok().build();
}
}
@@ -7,13 +7,15 @@
import com.dotcms.rest.ResponseEntityView;
import com.dotcms.rest.WebResource;
import com.dotcms.rest.annotation.NoCache;
import com.dotcms.rest.exception.ValidationException;
import com.dotcms.util.CollectionsUtils;
import com.dotcms.util.PaginationUtil;
import com.dotcms.util.pagination.ContainerPaginator;
import com.dotcms.util.pagination.OrderDirection;
import com.dotcms.util.pagination.TemplatePaginator;
import com.dotmarketing.beans.Host;
import com.dotmarketing.business.APILocator;
import com.dotmarketing.business.DotStateException;
import com.dotmarketing.business.PermissionAPI;
import com.dotmarketing.business.RoleAPI;
import com.dotmarketing.business.VersionableAPI;
@@ -346,7 +348,7 @@ public final Response save(@Context final HttpServletRequest request,
@JSONP
@NoCache
@Produces({MediaType.APPLICATION_JSON, "application/javascript"})
public final Response publish(@Context final HttpServletRequest request,
public final Response PUBLISH(@Context final HttpServletRequest request,
@Context final HttpServletResponse response,
final List<String> templatesToPublish) throws DotDataException, DotSecurityException {

@@ -669,7 +671,7 @@ public final Response delete(@Context final HttpServletRequest request,
));

return this.canTemplateBeDeleted(template, user, error)?
Response.ok(new ResponseEntityView(this.templateAPI.delete(template,user, pageMode.respectAnonPerms))).build():
Response.ok(new ResponseEntityView(this.templateAPI.deleteTemplate(template,user, pageMode.respectAnonPerms))).build():
Response.status(Response.Status.BAD_REQUEST).entity(map("message", error)).build();
}

@@ -0,0 +1,24 @@
package com.dotcms.rest.exception.mapper;

import com.dotcms.rest.api.v1.authentication.ResponseUtil;
import com.dotmarketing.util.Logger;

import javax.ws.rs.core.Response;
import javax.ws.rs.ext.Provider;

/**
* Generic runtime exception handler to avoid cyclical issues with the stack trace mapping
* @author jsanca
*/
@Provider
public class RuntimeExceptionMapper implements javax.ws.rs.ext.ExceptionMapper<RuntimeException> {

@Override
public Response toResponse(RuntimeException exception) {

//Log into our logs first.
Logger.warn(this.getClass(), exception.getMessage(), exception);

return ResponseUtil.mapExceptionResponse(exception);
}
}
@@ -185,7 +185,8 @@ private static ResourceConfig configureResourceConfig(ResourceConfig config) {
.register(DotSecurityExceptionMapper.class)
.register(DotDataExceptionMapper.class)
.register(ElasticsearchStatusExceptionMapper.class)
.register((new DotBadRequestExceptionMapper<InvalidFolderNameException>(){}).getClass());
.register((new DotBadRequestExceptionMapper<InvalidFolderNameException>(){}).getClass())
.register(RuntimeExceptionMapper.class);
//.register(ExceptionMapper.class); // temporaly unregister since some services are expecting just a plain message as an error instead of a json, so to keep the compatibility we won't apply this change yet.
}
}
}
@@ -2,6 +2,7 @@

import com.dotmarketing.util.UtilMethods;
import com.fasterxml.jackson.annotation.JsonIgnore;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -11,7 +12,9 @@
* its a super class that serves as the base form both Params and Secrets.
* @param <T>
*/
public abstract class AbstractProperty<T> {
public abstract class AbstractProperty<T> implements Serializable {

private static final long serialVersionUID = 1L;

protected final T value;
protected final Boolean hidden;
Loading