| 
									
										
										
										
											2021-09-04 20:57:04 +08:00
										 |  |  | <?php | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | namespace BookStack\Util; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | use Illuminate\Support\Str; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class CspService | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2022-03-07 22:27:41 +08:00
										 |  |  |     protected string $nonce; | 
					
						
							| 
									
										
										
										
											2021-09-04 20:57:04 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     public function __construct(string $nonce = '') | 
					
						
							|  |  |  |     { | 
					
						
							| 
									
										
										
										
											2021-09-12 23:19:17 +08:00
										 |  |  |         $this->nonce = $nonce ?: Str::random(24); | 
					
						
							| 
									
										
										
										
											2021-09-04 20:57:04 +08:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Get the nonce value for CSP. | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public function getNonce(): string | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         return $this->nonce; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							| 
									
										
										
										
											2022-03-09 22:30:36 +08:00
										 |  |  |      * Get the CSP headers for the application. | 
					
						
							| 
									
										
										
										
											2021-09-04 20:57:04 +08:00
										 |  |  |      */ | 
					
						
							| 
									
										
										
										
											2022-03-07 22:27:41 +08:00
										 |  |  |     public function getCspHeader(): string | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         $headers = [ | 
					
						
							|  |  |  |             $this->getFrameAncestors(), | 
					
						
							|  |  |  |             $this->getFrameSrc(), | 
					
						
							|  |  |  |             $this->getScriptSrc(), | 
					
						
							|  |  |  |             $this->getObjectSrc(), | 
					
						
							|  |  |  |             $this->getBaseUri(), | 
					
						
							|  |  |  |         ]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return implode('; ', array_filter($headers)); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Get the CSP rules for the application for a HTML meta tag. | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public function getCspMetaTagValue(): string | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         $headers = [ | 
					
						
							|  |  |  |             $this->getFrameSrc(), | 
					
						
							|  |  |  |             $this->getScriptSrc(), | 
					
						
							|  |  |  |             $this->getObjectSrc(), | 
					
						
							|  |  |  |             $this->getBaseUri(), | 
					
						
							|  |  |  |         ]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return implode('; ', array_filter($headers)); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Check if the user has configured some allowed iframe hosts. | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public function allowedIFrameHostsConfigured(): bool | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         return count($this->getAllowedIframeHosts()) > 0; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Create CSP 'script-src' rule to restrict the forms of script that can run on the page. | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     protected function getScriptSrc(): string | 
					
						
							| 
									
										
										
										
											2021-09-04 20:57:04 +08:00
										 |  |  |     { | 
					
						
							|  |  |  |         if (config('app.allow_content_scripts')) { | 
					
						
							| 
									
										
										
										
											2022-03-07 22:27:41 +08:00
										 |  |  |             return ''; | 
					
						
							| 
									
										
										
										
											2021-09-04 20:57:04 +08:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         $parts = [ | 
					
						
							| 
									
										
										
										
											2021-09-04 21:34:43 +08:00
										 |  |  |             'http:', | 
					
						
							|  |  |  |             'https:', | 
					
						
							| 
									
										
										
										
											2021-09-04 20:57:04 +08:00
										 |  |  |             '\'nonce-' . $this->nonce . '\'', | 
					
						
							|  |  |  |             '\'strict-dynamic\'', | 
					
						
							|  |  |  |         ]; | 
					
						
							| 
									
										
										
										
											2021-09-04 21:34:43 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-07 22:27:41 +08:00
										 |  |  |         return 'script-src ' . implode(' ', $parts); | 
					
						
							| 
									
										
										
										
											2021-09-04 20:57:04 +08:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							| 
									
										
										
										
											2022-03-07 22:27:41 +08:00
										 |  |  |      * Create CSP "frame-ancestors" rule to restrict the hosts that BookStack can be iframed within. | 
					
						
							| 
									
										
										
										
											2021-09-04 20:57:04 +08:00
										 |  |  |      */ | 
					
						
							| 
									
										
										
										
											2022-03-07 22:27:41 +08:00
										 |  |  |     protected function getFrameAncestors(): string | 
					
						
							| 
									
										
										
										
											2021-09-04 20:57:04 +08:00
										 |  |  |     { | 
					
						
							|  |  |  |         $iframeHosts = $this->getAllowedIframeHosts(); | 
					
						
							|  |  |  |         array_unshift($iframeHosts, "'self'"); | 
					
						
							| 
									
										
										
										
											2022-03-09 22:30:36 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-07 22:27:41 +08:00
										 |  |  |         return 'frame-ancestors ' . implode(' ', $iframeHosts); | 
					
						
							| 
									
										
										
										
											2021-09-04 20:57:04 +08:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							| 
									
										
										
										
											2022-03-07 22:27:41 +08:00
										 |  |  |      * Creates CSP "frame-src" rule to restrict what hosts/sources can be loaded | 
					
						
							|  |  |  |      * within iframes to provide an allow-list-style approach to iframe content. | 
					
						
							| 
									
										
										
										
											2021-09-04 20:57:04 +08:00
										 |  |  |      */ | 
					
						
							| 
									
										
										
										
											2022-03-07 22:27:41 +08:00
										 |  |  |     protected function getFrameSrc(): string | 
					
						
							| 
									
										
										
										
											2021-09-04 20:57:04 +08:00
										 |  |  |     { | 
					
						
							| 
									
										
										
										
											2022-03-07 22:27:41 +08:00
										 |  |  |         $iframeHosts = $this->getAllowedIframeSources(); | 
					
						
							|  |  |  |         array_unshift($iframeHosts, "'self'"); | 
					
						
							| 
									
										
										
										
											2022-03-09 22:30:36 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-07 22:27:41 +08:00
										 |  |  |         return 'frame-src ' . implode(' ', $iframeHosts); | 
					
						
							| 
									
										
										
										
											2021-09-04 20:57:04 +08:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-04 21:34:43 +08:00
										 |  |  |     /** | 
					
						
							| 
									
										
										
										
											2022-03-07 22:27:41 +08:00
										 |  |  |      * Creates CSP 'object-src' rule to restrict the types of dynamic content | 
					
						
							| 
									
										
										
										
											2021-09-04 21:34:43 +08:00
										 |  |  |      * that can be embedded on the page. | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2022-03-07 22:27:41 +08:00
										 |  |  |     protected function getObjectSrc(): string | 
					
						
							| 
									
										
										
										
											2021-09-04 21:34:43 +08:00
										 |  |  |     { | 
					
						
							|  |  |  |         if (config('app.allow_content_scripts')) { | 
					
						
							| 
									
										
										
										
											2022-03-07 22:27:41 +08:00
										 |  |  |             return ''; | 
					
						
							| 
									
										
										
										
											2021-09-04 21:34:43 +08:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-07 22:27:41 +08:00
										 |  |  |         return "object-src 'self'"; | 
					
						
							| 
									
										
										
										
											2021-09-04 21:34:43 +08:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							| 
									
										
										
										
											2022-03-07 22:27:41 +08:00
										 |  |  |      * Creates CSP 'base-uri' rule to restrict what base tags can be set on | 
					
						
							| 
									
										
										
										
											2021-09-04 21:34:43 +08:00
										 |  |  |      * the page to prevent manipulation of relative links. | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2022-03-07 22:27:41 +08:00
										 |  |  |     protected function getBaseUri(): string | 
					
						
							| 
									
										
										
										
											2021-09-04 21:34:43 +08:00
										 |  |  |     { | 
					
						
							| 
									
										
										
										
											2022-03-07 22:27:41 +08:00
										 |  |  |         return "base-uri 'self'"; | 
					
						
							| 
									
										
										
										
											2021-09-04 21:34:43 +08:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-09-04 20:57:04 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     protected function getAllowedIframeHosts(): array | 
					
						
							|  |  |  |     { | 
					
						
							| 
									
										
										
										
											2023-01-22 04:50:04 +08:00
										 |  |  |         $hosts = config('app.iframe_hosts') ?? ''; | 
					
						
							| 
									
										
										
										
											2021-09-07 05:19:06 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-04 20:57:04 +08:00
										 |  |  |         return array_filter(explode(' ', $hosts)); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-03-07 22:27:41 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     protected function getAllowedIframeSources(): array | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         $sources = config('app.iframe_sources', ''); | 
					
						
							|  |  |  |         $hosts = array_filter(explode(' ', $sources)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Extract drawing service url to allow embedding if active
 | 
					
						
							|  |  |  |         $drawioConfigValue = config('services.drawio'); | 
					
						
							|  |  |  |         if ($drawioConfigValue) { | 
					
						
							|  |  |  |             $drawioSource = is_string($drawioConfigValue) ? $drawioConfigValue : 'https://embed.diagrams.net/'; | 
					
						
							|  |  |  |             $drawioSourceParsed = parse_url($drawioSource); | 
					
						
							|  |  |  |             $drawioHost = $drawioSourceParsed['scheme'] . '://' . $drawioSourceParsed['host']; | 
					
						
							|  |  |  |             $hosts[] = $drawioHost; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return $hosts; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-09-07 05:19:06 +08:00
										 |  |  | } |