Passed
Push — master ( 92b674...05df38 )
by Morris
14:36 queued 10s
created

PublishPlugin::propFind()   A

Complexity

Conditions 6
Paths 2

Size

Total Lines 22
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 13
nc 2
nop 2
dl 0
loc 22
rs 9.2222
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016 Thomas Citharel <[email protected]>
4
 *
5
 * @author Christoph Wurst <[email protected]>
6
 * @author Georg Ehrke <[email protected]>
7
 * @author Roeland Jago Douma <[email protected]>
8
 * @author Thomas Citharel <[email protected]>
9
 * @author Thomas Müller <[email protected]>
10
 *
11
 * @license GNU AGPL version 3 or any later version
12
 *
13
 * This program is free software: you can redistribute it and/or modify
14
 * it under the terms of the GNU Affero General Public License as
15
 * published by the Free Software Foundation, either version 3 of the
16
 * License, or (at your option) any later version.
17
 *
18
 * This program is distributed in the hope that it will be useful,
19
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21
 * GNU Affero General Public License for more details.
22
 *
23
 * You should have received a copy of the GNU Affero General Public License
24
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
25
 *
26
 */
27
28
namespace OCA\DAV\CalDAV\Publishing;
29
30
use OCA\DAV\CalDAV\Calendar;
31
use OCA\DAV\CalDAV\Publishing\Xml\Publisher;
32
use OCP\IConfig;
33
use OCP\IURLGenerator;
34
use Sabre\CalDAV\Xml\Property\AllowedSharingModes;
35
use Sabre\DAV\Exception\NotFound;
36
use Sabre\DAV\INode;
37
use Sabre\DAV\PropFind;
38
use Sabre\DAV\Server;
39
use Sabre\DAV\ServerPlugin;
40
use Sabre\HTTP\RequestInterface;
41
use Sabre\HTTP\ResponseInterface;
42
43
class PublishPlugin extends ServerPlugin {
44
	public const NS_CALENDARSERVER = 'http://calendarserver.org/ns/';
45
46
	/**
47
	 * Reference to SabreDAV server object.
48
	 *
49
	 * @var \Sabre\DAV\Server
50
	 */
51
	protected $server;
52
53
	/**
54
	 * Config instance to get instance secret.
55
	 *
56
	 * @var IConfig
57
	 */
58
	protected $config;
59
60
	/**
61
	 * URL Generator for absolute URLs.
62
	 *
63
	 * @var IURLGenerator
64
	 */
65
	protected $urlGenerator;
66
67
	/**
68
	 * PublishPlugin constructor.
69
	 *
70
	 * @param IConfig $config
71
	 * @param IURLGenerator $urlGenerator
72
	 */
73
	public function __construct(IConfig $config, IURLGenerator $urlGenerator) {
74
		$this->config = $config;
75
		$this->urlGenerator = $urlGenerator;
76
	}
77
78
	/**
79
	 * This method should return a list of server-features.
80
	 *
81
	 * This is for example 'versioning' and is added to the DAV: header
82
	 * in an OPTIONS response.
83
	 *
84
	 * @return string[]
85
	 */
86
	public function getFeatures() {
87
		// May have to be changed to be detected
88
		return ['oc-calendar-publishing', 'calendarserver-sharing'];
89
	}
90
91
	/**
92
	 * Returns a plugin name.
93
	 *
94
	 * Using this name other plugins will be able to access other plugins
95
	 * using Sabre\DAV\Server::getPlugin
96
	 *
97
	 * @return string
98
	 */
99
	public function getPluginName() {
100
		return 'oc-calendar-publishing';
101
	}
102
103
	/**
104
	 * This initializes the plugin.
105
	 *
106
	 * This function is called by Sabre\DAV\Server, after
107
	 * addPlugin is called.
108
	 *
109
	 * This method should set up the required event subscriptions.
110
	 *
111
	 * @param Server $server
112
	 */
113
	public function initialize(Server $server) {
114
		$this->server = $server;
115
116
		$this->server->on('method:POST', [$this, 'httpPost']);
117
		$this->server->on('propFind',    [$this, 'propFind']);
118
	}
119
120
	public function propFind(PropFind $propFind, INode $node) {
121
		if ($node instanceof Calendar) {
122
			$propFind->handle('{'.self::NS_CALENDARSERVER.'}publish-url', function () use ($node) {
123
				if ($node->getPublishStatus()) {
124
					// We return the publish-url only if the calendar is published.
125
					$token = $node->getPublishStatus();
126
					$publishUrl = $this->urlGenerator->getAbsoluteURL($this->server->getBaseUri().'public-calendars/').$token;
127
128
					return new Publisher($publishUrl, true);
129
				}
130
			});
131
132
			$propFind->handle('{'.self::NS_CALENDARSERVER.'}allowed-sharing-modes', function () use ($node) {
133
				$canShare = (!$node->isSubscription() && $node->canWrite());
134
				$canPublish = (!$node->isSubscription() && $node->canWrite());
135
136
				if ($this->config->getAppValue('dav', 'limitAddressBookAndCalendarSharingToOwner', 'no') === 'yes') {
137
					$canShare &= ($node->getOwner() === $node->getPrincipalURI());
138
					$canPublish &= ($node->getOwner() === $node->getPrincipalURI());
139
				}
140
141
				return new AllowedSharingModes((bool)$canShare, (bool)$canPublish);
142
			});
143
		}
144
	}
145
146
	/**
147
	 * We intercept this to handle POST requests on calendars.
148
	 *
149
	 * @param RequestInterface $request
150
	 * @param ResponseInterface $response
151
	 *
152
	 * @return void|bool
153
	 */
154
	public function httpPost(RequestInterface $request, ResponseInterface $response) {
155
		$path = $request->getPath();
156
157
		// Only handling xml
158
		$contentType = $request->getHeader('Content-Type');
159
		if (strpos($contentType, 'application/xml') === false && strpos($contentType, 'text/xml') === false) {
160
			return;
161
		}
162
163
		// Making sure the node exists
164
		try {
165
			$node = $this->server->tree->getNodeForPath($path);
166
		} catch (NotFound $e) {
167
			return;
168
		}
169
170
		$requestBody = $request->getBodyAsString();
171
172
		// If this request handler could not deal with this POST request, it
173
		// will return 'null' and other plugins get a chance to handle the
174
		// request.
175
		//
176
		// However, we already requested the full body. This is a problem,
177
		// because a body can only be read once. This is why we preemptively
178
		// re-populated the request body with the existing data.
179
		$request->setBody($requestBody);
180
181
		$this->server->xml->parse($requestBody, $request->getUrl(), $documentType);
182
183
		switch ($documentType) {
184
185
			case '{'.self::NS_CALENDARSERVER.'}publish-calendar':
186
187
			// We can only deal with IShareableCalendar objects
188
			if (!$node instanceof Calendar) {
189
				return;
190
			}
191
			$this->server->transactionType = 'post-publish-calendar';
192
193
			// Getting ACL info
194
			$acl = $this->server->getPlugin('acl');
195
196
			// If there's no ACL support, we allow everything
197
			if ($acl) {
0 ignored issues
show
introduced by
$acl is of type Sabre\DAV\ServerPlugin, thus it always evaluated to true.
Loading history...
198
				/** @var \Sabre\DAVACL\Plugin $acl */
199
				$acl->checkPrivileges($path, '{DAV:}write');
200
201
				$limitSharingToOwner = $this->config->getAppValue('dav', 'limitAddressBookAndCalendarSharingToOwner', 'no') === 'yes';
202
				$isOwner = $acl->getCurrentUserPrincipal() === $node->getOwner();
203
				if ($limitSharingToOwner && !$isOwner) {
204
					return;
205
				}
206
			}
207
208
			$node->setPublishStatus(true);
209
210
			// iCloud sends back the 202, so we will too.
211
			$response->setStatus(202);
212
213
			// Adding this because sending a response body may cause issues,
214
			// and I wanted some type of indicator the response was handled.
215
			$response->setHeader('X-Sabre-Status', 'everything-went-well');
216
217
			// Breaking the event chain
218
			return false;
219
220
			case '{'.self::NS_CALENDARSERVER.'}unpublish-calendar':
221
222
			// We can only deal with IShareableCalendar objects
223
			if (!$node instanceof Calendar) {
224
				return;
225
			}
226
			$this->server->transactionType = 'post-unpublish-calendar';
227
228
			// Getting ACL info
229
			$acl = $this->server->getPlugin('acl');
230
231
			// If there's no ACL support, we allow everything
232
			if ($acl) {
0 ignored issues
show
introduced by
$acl is of type Sabre\DAV\ServerPlugin, thus it always evaluated to true.
Loading history...
233
				/** @var \Sabre\DAVACL\Plugin $acl */
234
				$acl->checkPrivileges($path, '{DAV:}write');
235
236
				$limitSharingToOwner = $this->config->getAppValue('dav', 'limitAddressBookAndCalendarSharingToOwner', 'no') === 'yes';
237
				$isOwner = $acl->getCurrentUserPrincipal() === $node->getOwner();
238
				if ($limitSharingToOwner && !$isOwner) {
239
					return;
240
				}
241
			}
242
243
			$node->setPublishStatus(false);
244
245
			$response->setStatus(200);
246
247
			// Adding this because sending a response body may cause issues,
248
			// and I wanted some type of indicator the response was handled.
249
			$response->setHeader('X-Sabre-Status', 'everything-went-well');
250
251
			// Breaking the event chain
252
			return false;
253
254
		}
255
	}
256
}
257