3636use OCP \Accounts \IAccountManager ;
3737use OCP \AppFramework \Controller ;
3838use OCP \AppFramework \Db \DoesNotExistException ;
39+ use OCP \AppFramework \Http \ContentSecurityPolicy ;
3940use OCP \AppFramework \Http \RedirectResponse ;
4041use OCP \AppFramework \Http \Response ;
4142use OCP \AppFramework \Http \Template \PublicTemplateResponse ;
@@ -152,46 +153,87 @@ public function internalLinkView(string $hash): Response {
152153 * @return TemplateResponse Public template.
153154 */
154155 public function publicLinkView (string $ hash ): Response {
155- // Inject style on all templates
156- Util::addStyle ($ this ->appName , 'forms ' );
156+ try {
157+ $ share = $ this ->shareMapper ->findPublicShareByHash ($ hash );
158+ $ form = $ this ->formMapper ->findById ($ share ->getFormId ());
159+ } catch (DoesNotExistException $ e ) {
160+ return $ this ->provideEmptyContent (Constants::EMPTY_NOTFOUND );
161+ }
157162
163+ return $ this ->createPublicSubmitView ($ form , $ hash );
164+ }
165+
166+ /**
167+ * @NoAdminRequired
168+ * @PublicPage
169+ * @NoCSRFRequired
170+ *
171+ * @param string $hash
172+ * @return Response
173+ */
174+ public function embeddedFormView (string $ hash ): Response {
158175 try {
159176 $ share = $ this ->shareMapper ->findPublicShareByHash ($ hash );
177+ // Check if the form is allwed to be embedded
178+ if (!in_array (Constants::PERMISSION_EMBED , $ share ->getPermissions ())) {
179+ throw new DoesNotExistException ('Shared form not allowed to be embedded ' );
180+ }
181+
160182 $ form = $ this ->formMapper ->findById ($ share ->getFormId ());
161183 } catch (DoesNotExistException $ e ) {
162184 return $ this ->provideEmptyContent (Constants::EMPTY_NOTFOUND );
185+ // We do not handle the MultipleObjectsReturnedException as this will automatically result in a 500 error as expected
163186 }
164187
188+ Util::addStyle ($ this ->appName , 'embedded ' );
189+ $ response = $ this ->createPublicSubmitView ($ form , $ hash )
190+ ->renderAs (TemplateResponse::RENDER_AS_BASE );
191+
192+ $ this ->initialState ->provideInitialState ('isEmbedded ' , true );
193+
194+ return $ this ->setEmbeddedCSP ($ response );
195+ }
196+
197+ /**
198+ * Create a TemplateResponse for a given public form
199+ * This sets all needed headers, initial state, loads scripts and styles
200+ */
201+ protected function createPublicSubmitView (Form $ form , string $ hash ): TemplateResponse {
165202 // Has form expired
166203 if ($ this ->formsService ->hasFormExpired ($ form )) {
167204 return $ this ->provideEmptyContent (Constants::EMPTY_EXPIRED , $ form );
168205 }
169206
207+ $ this ->insertHeaderOnIos ();
208+
209+ // Inject style on all templates
210+ Util::addStyle ($ this ->appName , 'forms ' );
170211 // Main Template to fill the form
171212 Util::addScript ($ this ->appName , 'forms-submit ' );
172- $ this -> insertHeaderOnIos ();
213+
173214 $ this ->initialState ->provideInitialState ('form ' , $ this ->formsService ->getPublicForm ($ form ));
174215 $ this ->initialState ->provideInitialState ('isLoggedIn ' , $ this ->userSession ->isLoggedIn ());
175216 $ this ->initialState ->provideInitialState ('shareHash ' , $ hash );
176217 $ this ->initialState ->provideInitialState ('maxStringLengths ' , Constants::MAX_STRING_LENGTHS );
177218 return $ this ->provideTemplate (self ::TEMPLATE_MAIN , $ form , ['id-app-navigation ' => null ]);
178219 }
179220
180- public function provideEmptyContent (string $ renderAs , ?Form $ form = null ): TemplateResponse {
221+ /**
222+ * Provide empty content message response for a form
223+ */
224+ protected function provideEmptyContent (string $ renderAs , ?Form $ form = null ): TemplateResponse {
181225 Util::addScript ($ this ->appName , 'forms-emptyContent ' );
182226 $ this ->initialState ->provideInitialState ('renderAs ' , $ renderAs );
183227 return $ this ->provideTemplate (self ::TEMPLATE_MAIN , $ form );
184228 }
185229
186230 /**
187- * @NoAdminRequired
188- * @NoCSRFRequired
189- * @PublicPage
231+ * Helper function to create a template response from a form
190232 * @param string $template
191233 * @param Form $form Necessary to set header on public forms, not necessary for 'notfound'-template
192234 * @return TemplateResponse
193235 */
194- public function provideTemplate (string $ template , ?Form $ form = null , array $ options = []): TemplateResponse {
236+ protected function provideTemplate (string $ template , ?Form $ form = null , array $ options = []): TemplateResponse {
195237 Util::addStyle ($ this ->appName , 'forms-style ' );
196238 // If not logged in, use PublicTemplate
197239 if (!$ this ->userSession ->isLoggedIn ()) {
@@ -217,7 +259,6 @@ public function provideTemplate(string $template, ?Form $form = null, array $opt
217259 }
218260 }
219261 }
220-
221262 return $ response ;
222263 }
223264
@@ -230,7 +271,7 @@ public function provideTemplate(string $template, ?Form $form = null, array $opt
230271 /**
231272 * Insert the extended viewport Header on iPhones to prevent automatic zooming.
232273 */
233- public function insertHeaderOnIos (): void {
274+ protected function insertHeaderOnIos (): void {
234275 $ USER_AGENT_IPHONE_SAFARI = '/^Mozilla\/5\.0 \(iPhone[^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\) Version\/[0-9.]+ Mobile\/[0-9.A-Z]+ Safari\/[0-9.A-Z]+$/ ' ;
235276 if (preg_match ($ USER_AGENT_IPHONE_SAFARI , $ this ->request ->getHeader ('User-Agent ' ))) {
236277 Util::addHeader ('meta ' , [
@@ -239,4 +280,17 @@ public function insertHeaderOnIos(): void {
239280 ]);
240281 }
241282 }
283+
284+ /**
285+ * Set CSP options to allow the page be embedded using <iframe>
286+ */
287+ protected function setEmbeddedCSP (TemplateResponse $ response ) {
288+ $ policy = new ContentSecurityPolicy ();
289+ $ policy ->addAllowedFrameAncestorDomain ('* ' );
290+
291+ $ response ->addHeader ('X-Frame-Options ' , 'ALLOW ' );
292+ $ response ->setContentSecurityPolicy ($ policy );
293+
294+ return $ response ;
295+ }
242296}
0 commit comments