tilia-dav 3.1.0.pre.alpha6 → 3.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/CHANGELOG.sabre.md +97 -1
- data/Gemfile +1 -16
- data/Gemfile.lock +44 -41
- data/LICENSE +1 -1
- data/LICENSE.sabre +1 -1
- data/examples/addressbookserver.rb +52 -0
- data/examples/calendarserver.rb +58 -0
- data/examples/fileserver.rb +57 -0
- data/examples/groupwareserver.rb +69 -0
- data/examples/sql/mysql.addressbook.sql +28 -0
- data/examples/sql/mysql.calendars.sql +64 -0
- data/examples/sql/mysql.locks.sql +13 -0
- data/examples/sql/mysql.principals.sql +21 -0
- data/examples/sql/mysql.propertystorage.sql +8 -0
- data/examples/sql/mysql.users.sql +9 -0
- data/examples/sql/pgsql.addressbook.sql +52 -0
- data/examples/sql/pgsql.calendars.sql +93 -0
- data/examples/sql/pgsql.locks.sql +19 -0
- data/examples/sql/pgsql.principals.sql +38 -0
- data/examples/sql/pgsql.propertystorage.sql +13 -0
- data/examples/sql/pgsql.users.sql +14 -0
- data/examples/sql/sqlite.addressbooks.sql +28 -0
- data/examples/sql/sqlite.calendars.sql +64 -0
- data/examples/sql/sqlite.locks.sql +12 -0
- data/examples/sql/sqlite.principals.sql +20 -0
- data/examples/sql/sqlite.propertystorage.sql +10 -0
- data/examples/sql/sqlite.users.sql +9 -0
- data/lib/tilia/cal_dav/ics_export_plugin.rb +1 -1
- data/lib/tilia/cal_dav/plugin.rb +27 -11
- data/lib/tilia/cal_dav/schedule/i_mip_plugin.rb +2 -2
- data/lib/tilia/cal_dav/schedule/plugin.rb +7 -0
- data/lib/tilia/dav/auth/backend.rb +1 -0
- data/lib/tilia/dav/auth/backend/abstract_basic.rb +3 -2
- data/lib/tilia/dav/auth/backend/abstract_bearer.rb +116 -0
- data/lib/tilia/dav/auth/backend/abstract_digest.rb +3 -2
- data/lib/tilia/dav/auth/backend/apache.rb +2 -1
- data/lib/tilia/dav/auth/backend/sequel.rb +2 -9
- data/lib/tilia/dav/client.rb +29 -3
- data/lib/tilia/dav/core_plugin.rb +1 -2
- data/lib/tilia/dav/server.rb +16 -4
- data/lib/tilia/dav/temporary_file_filter_plugin.rb +3 -0
- data/lib/tilia/dav/tree.rb +4 -3
- data/lib/tilia/dav/version.rb +1 -1
- data/lib/tilia/dav/xml/element/response.rb +20 -2
- data/lib/tilia/dav_acl/principal_backend/sequel.rb +50 -6
- data/test/cal_dav/ics_export_plugin_test.rb +1 -0
- data/test/cal_dav/plugin_test.rb +4 -4
- data/test/cal_dav/schedule/plugin_properties_test.rb +51 -0
- data/test/card_dav/backend/sequel_my_sql_test.rb +3 -3
- data/test/card_dav/vcf_export_test.rb +11 -1
- data/test/dav/auth/backend/abstract_bearer_test.rb +71 -0
- data/test/dav/client_test.rb +42 -4
- data/test/dav/core_plugin_test.rb +12 -0
- data/test/dav/fs_ext/server_test.rb +1 -1
- data/test/dav/http_copy_test.rb +185 -0
- data/test/dav/mock/collection.rb +6 -9
- data/test/dav/mock/file.rb +10 -9
- data/test/dav/mock/streaming_file.rb +1 -3
- data/test/dav/server_events_test.rb +8 -6
- data/test/dav/server_range_test.rb +135 -155
- data/test/dav/server_simple_test.rb +14 -0
- data/test/dav/xml/element/response_test.rb +56 -1
- data/test/dav/xml/property/href_test.rb +14 -0
- data/test/dav_acl/principal_backend/abstract_sequel_test.rb +16 -0
- data/test/dav_acl/principal_backend/mock.rb +1 -1
- data/test/dav_server_test.rb +1 -1
- data/tilia-dav.gemspec +2 -2
- metadata +38 -14
- data/test/dav/copy_test.rb +0 -33
- data/test/dav/server_copy_move_test.rb +0 -164
@@ -0,0 +1,28 @@
|
|
1
|
+
CREATE TABLE addressbooks (
|
2
|
+
id integer primary key asc,
|
3
|
+
principaluri text,
|
4
|
+
displayname text,
|
5
|
+
uri text,
|
6
|
+
description text,
|
7
|
+
synctoken integer
|
8
|
+
);
|
9
|
+
|
10
|
+
CREATE TABLE cards (
|
11
|
+
id integer primary key asc,
|
12
|
+
addressbookid integer,
|
13
|
+
carddata blob,
|
14
|
+
uri text,
|
15
|
+
lastmodified integer,
|
16
|
+
etag text,
|
17
|
+
size integer
|
18
|
+
);
|
19
|
+
|
20
|
+
CREATE TABLE addressbookchanges (
|
21
|
+
id integer primary key asc,
|
22
|
+
uri text,
|
23
|
+
synctoken integer,
|
24
|
+
addressbookid integer,
|
25
|
+
operation integer
|
26
|
+
);
|
27
|
+
|
28
|
+
CREATE INDEX addressbookid_synctoken ON addressbookchanges (addressbookid, synctoken);
|
@@ -0,0 +1,64 @@
|
|
1
|
+
CREATE TABLE calendarobjects (
|
2
|
+
id integer primary key asc,
|
3
|
+
calendardata blob,
|
4
|
+
uri text,
|
5
|
+
calendarid integer,
|
6
|
+
lastmodified integer,
|
7
|
+
etag text,
|
8
|
+
size integer,
|
9
|
+
componenttype text,
|
10
|
+
firstoccurence integer,
|
11
|
+
lastoccurence integer,
|
12
|
+
uid text
|
13
|
+
);
|
14
|
+
|
15
|
+
CREATE TABLE calendars (
|
16
|
+
id integer primary key asc,
|
17
|
+
principaluri text,
|
18
|
+
displayname text,
|
19
|
+
uri text,
|
20
|
+
synctoken integer,
|
21
|
+
description text,
|
22
|
+
calendarorder integer,
|
23
|
+
calendarcolor text,
|
24
|
+
timezone text,
|
25
|
+
components text,
|
26
|
+
transparent bool
|
27
|
+
);
|
28
|
+
|
29
|
+
CREATE TABLE calendarchanges (
|
30
|
+
id integer primary key asc,
|
31
|
+
uri text,
|
32
|
+
synctoken integer,
|
33
|
+
calendarid integer,
|
34
|
+
operation integer
|
35
|
+
);
|
36
|
+
|
37
|
+
CREATE INDEX calendarid_synctoken ON calendarchanges (calendarid, synctoken);
|
38
|
+
|
39
|
+
CREATE TABLE calendarsubscriptions (
|
40
|
+
id integer primary key asc,
|
41
|
+
uri text,
|
42
|
+
principaluri text,
|
43
|
+
source text,
|
44
|
+
displayname text,
|
45
|
+
refreshrate text,
|
46
|
+
calendarorder integer,
|
47
|
+
calendarcolor text,
|
48
|
+
striptodos bool,
|
49
|
+
stripalarms bool,
|
50
|
+
stripattachments bool,
|
51
|
+
lastmodified int
|
52
|
+
);
|
53
|
+
|
54
|
+
CREATE TABLE schedulingobjects (
|
55
|
+
id integer primary key asc,
|
56
|
+
principaluri text,
|
57
|
+
calendardata blob,
|
58
|
+
uri text,
|
59
|
+
lastmodified integer,
|
60
|
+
etag text,
|
61
|
+
size integer
|
62
|
+
);
|
63
|
+
|
64
|
+
CREATE INDEX principaluri_uri ON calendarsubscriptions (principaluri, uri);
|
@@ -0,0 +1,20 @@
|
|
1
|
+
CREATE TABLE principals (
|
2
|
+
id INTEGER PRIMARY KEY ASC,
|
3
|
+
uri TEXT,
|
4
|
+
email TEXT,
|
5
|
+
displayname TEXT,
|
6
|
+
UNIQUE(uri)
|
7
|
+
);
|
8
|
+
|
9
|
+
CREATE TABLE groupmembers (
|
10
|
+
id INTEGER PRIMARY KEY ASC,
|
11
|
+
principal_id INTEGER,
|
12
|
+
member_id INTEGER,
|
13
|
+
UNIQUE(principal_id, member_id)
|
14
|
+
);
|
15
|
+
|
16
|
+
|
17
|
+
INSERT INTO principals (uri,email,displayname) VALUES ('principals/admin', 'admin@example.org','Administrator');
|
18
|
+
INSERT INTO principals (uri,email,displayname) VALUES ('principals/admin/calendar-proxy-read', null, null);
|
19
|
+
INSERT INTO principals (uri,email,displayname) VALUES ('principals/admin/calendar-proxy-write', null, null);
|
20
|
+
|
@@ -223,7 +223,7 @@ module Tilia
|
|
223
223
|
calendar_time_zone = ActiveSupport::TimeZone.new('UTC')
|
224
224
|
end
|
225
225
|
|
226
|
-
merged_calendar.expand(start, ending, calendar_time_zone)
|
226
|
+
merged_calendar = merged_calendar.expand(start, ending, calendar_time_zone)
|
227
227
|
end
|
228
228
|
|
229
229
|
response.update_header('Content-Type', format)
|
data/lib/tilia/cal_dav/plugin.rb
CHANGED
@@ -56,14 +56,25 @@ module Tilia
|
|
56
56
|
# Returns the path to a principal's calendar home.
|
57
57
|
#
|
58
58
|
# The return url must not end with a slash.
|
59
|
+
# This function should return null in case a principal did not have
|
60
|
+
# a calendar home.
|
59
61
|
#
|
60
62
|
# @param string principal_url
|
61
|
-
# @return
|
63
|
+
# @return [String, nil]
|
62
64
|
def calendar_home_for_principal(principal_url)
|
63
|
-
# The default
|
64
|
-
|
65
|
+
# The default behavior for most sabre/dav servers is that there is a
|
66
|
+
# principals root node, which contains users directly under it.
|
67
|
+
#
|
68
|
+
# This function assumes that there are two components in a principal
|
69
|
+
# path. If there's more, we don't return a calendar home. This
|
70
|
+
# excludes things like the calendar-proxy-read principal (which it
|
71
|
+
# should).
|
72
|
+
parts = principal_url.gsub(%r{^/+|/+$}, '').split('/')
|
73
|
+
|
74
|
+
return nil unless parts.size == 2
|
75
|
+
return nil unless parts[0] == 'principals'
|
65
76
|
|
66
|
-
CALENDAR_ROOT + '/' +
|
77
|
+
return CALENDAR_ROOT + '/' + parts[1]
|
67
78
|
end
|
68
79
|
|
69
80
|
# Returns a list of features for the DAV: HTTP header.
|
@@ -266,8 +277,9 @@ module Tilia
|
|
266
277
|
prop_find.handle(
|
267
278
|
"{#{NS_CALDAV}}calendar-home-set",
|
268
279
|
lambda do
|
269
|
-
calendar_home_path = calendar_home_for_principal(principal_url)
|
270
|
-
return
|
280
|
+
calendar_home_path = calendar_home_for_principal(principal_url)
|
281
|
+
return nil unless calendar_home_path
|
282
|
+
return Dav::Xml::Property::Href.new(calendar_home_path + '/')
|
271
283
|
end
|
272
284
|
)
|
273
285
|
|
@@ -385,7 +397,7 @@ module Tilia
|
|
385
397
|
time_zones[calendar_path] = time_zone
|
386
398
|
end
|
387
399
|
|
388
|
-
v_object.expand(report.expand['start'], report.expand['end'], time_zones[calendar_path])
|
400
|
+
v_object = v_object.expand(report.expand['start'], report.expand['end'], time_zones[calendar_path])
|
389
401
|
end
|
390
402
|
|
391
403
|
if needs_json
|
@@ -487,7 +499,7 @@ module Tilia
|
|
487
499
|
if !requested_calendar_data
|
488
500
|
properties[200].delete('{urn:ietf:params:xml:ns:caldav}calendar-data')
|
489
501
|
else
|
490
|
-
v_object.expand(report.expand['start'], report.expand['end'], calendar_time_zone) if report.expand
|
502
|
+
v_object = v_object.expand(report.expand['start'], report.expand['end'], calendar_time_zone) if report.expand
|
491
503
|
|
492
504
|
if needs_json
|
493
505
|
properties[200]["{#{NS_CALDAV}}calendar-data"] = v_object.json_serialize.to_json
|
@@ -506,10 +518,14 @@ module Tilia
|
|
506
518
|
end
|
507
519
|
|
508
520
|
if node.is_a?(ICalendarObjectContainer) && depth == 0
|
509
|
-
if @server.http_request.header('User-Agent').to_s.index('MSFT-
|
510
|
-
#
|
521
|
+
if @server.http_request.header('User-Agent').to_s.index('MSFT-') == 0
|
522
|
+
# Microsoft clients incorrectly supplied depth as 0, when it actually
|
511
523
|
# should have set depth to 1. We're implementing a workaround here
|
512
524
|
# to deal with this.
|
525
|
+
#
|
526
|
+
# This targets at least the following clients:
|
527
|
+
# Windows 10
|
528
|
+
# Windows Phone 8, 10
|
513
529
|
depth = 1
|
514
530
|
else
|
515
531
|
fail Dav::Exception::BadRequest, 'A calendar-query REPORT on a calendar with a Depth: 0 is undefined. Set Depth to 1'
|
@@ -527,7 +543,7 @@ module Tilia
|
|
527
543
|
if needs_json || report.expand
|
528
544
|
v_object = VObject::Reader.read(properties[200]["{#{NS_CALDAV}}calendar-data"])
|
529
545
|
|
530
|
-
v_object.expand(report.expand['start'], report.expand['end'], calendar_time_zone) if report.expand
|
546
|
+
v_object = v_object.expand(report.expand['start'], report.expand['end'], calendar_time_zone) if report.expand
|
531
547
|
|
532
548
|
if needs_json
|
533
549
|
properties[200]["{#{NS_CALDAV}}calendar-data"] = v_object.json_serialize.to_json
|
@@ -71,8 +71,8 @@ module Tilia
|
|
71
71
|
|
72
72
|
summary = i_tip_message.message['VEVENT']['SUMMARY'].to_s
|
73
73
|
|
74
|
-
return nil unless Uri.parse(i_tip_message.sender)[
|
75
|
-
return nil unless Uri.parse(i_tip_message.recipient)[
|
74
|
+
return nil unless Uri.parse(i_tip_message.sender)[:scheme] == 'mailto'
|
75
|
+
return nil unless Uri.parse(i_tip_message.recipient)[:scheme] == 'mailto'
|
76
76
|
|
77
77
|
sender = i_tip_message.sender[7..-1]
|
78
78
|
recipient = i_tip_message.recipient[7..-1]
|
@@ -146,6 +146,8 @@ module Tilia
|
|
146
146
|
"{#{NS_CALDAV}}schedule-outbox-URL",
|
147
147
|
lambda do
|
148
148
|
calendar_home_path = caldav_plugin.calendar_home_for_principal(principal_url)
|
149
|
+
return nil unless calendar_home_path
|
150
|
+
|
149
151
|
outbox_path = calendar_home_path + '/outbox/'
|
150
152
|
|
151
153
|
return Dav::Xml::Property::Href.new(outbox_path)
|
@@ -157,6 +159,8 @@ module Tilia
|
|
157
159
|
"{#{NS_CALDAV}}schedule-inbox-URL",
|
158
160
|
lambda do
|
159
161
|
calendar_home_path = caldav_plugin.calendar_home_for_principal(principal_url)
|
162
|
+
return nil unless calendar_home_path
|
163
|
+
|
160
164
|
inbox_path = calendar_home_path + '/inbox/'
|
161
165
|
|
162
166
|
return Dav::Xml::Property::Href.new(inbox_path)
|
@@ -169,6 +173,7 @@ module Tilia
|
|
169
173
|
# We don't support customizing this property yet, so in the
|
170
174
|
# meantime we just grab the first calendar in the home-set.
|
171
175
|
calendar_home_path = caldav_plugin.calendar_home_for_principal(principal_url)
|
176
|
+
return nil unless calendar_home_path
|
172
177
|
|
173
178
|
sccs = "{#{NS_CALDAV}}supported-calendar-component-set"
|
174
179
|
|
@@ -196,6 +201,8 @@ module Tilia
|
|
196
201
|
return Dav::Xml::Property::Href.new(child['href'])
|
197
202
|
end
|
198
203
|
end
|
204
|
+
|
205
|
+
nil
|
199
206
|
end
|
200
207
|
)
|
201
208
|
|
@@ -4,6 +4,7 @@ module Tilia
|
|
4
4
|
module Backend
|
5
5
|
require 'tilia/dav/auth/backend/backend_interface'
|
6
6
|
require 'tilia/dav/auth/backend/abstract_basic'
|
7
|
+
require 'tilia/dav/auth/backend/abstract_bearer'
|
7
8
|
require 'tilia/dav/auth/backend/abstract_digest'
|
8
9
|
require 'tilia/dav/auth/backend/apache'
|
9
10
|
require 'tilia/dav/auth/backend/basic_call_back'
|
@@ -0,0 +1,116 @@
|
|
1
|
+
module Tilia
|
2
|
+
module Dav
|
3
|
+
module Auth
|
4
|
+
module Backend
|
5
|
+
# HTTP Bearer authentication backend class
|
6
|
+
#
|
7
|
+
# This class can be used by authentication objects wishing to use HTTP Bearer
|
8
|
+
# Most of the digest logic is handled, implementors just need to worry about
|
9
|
+
# the validateBearerToken method.
|
10
|
+
class AbstractBearer
|
11
|
+
include BackendInterface
|
12
|
+
|
13
|
+
protected
|
14
|
+
|
15
|
+
# Validates a Bearer token
|
16
|
+
#
|
17
|
+
# This method should return the full principal url, or false if the
|
18
|
+
# token was incorrect.
|
19
|
+
#
|
20
|
+
# @param string bearer_token
|
21
|
+
# @return string|false
|
22
|
+
def validate_bearer_token(bearer_token)
|
23
|
+
end
|
24
|
+
|
25
|
+
public
|
26
|
+
|
27
|
+
# Sets the authentication realm for this backend.
|
28
|
+
#
|
29
|
+
# @param string realm
|
30
|
+
# @return void
|
31
|
+
attr_writer :realm
|
32
|
+
|
33
|
+
# When this method is called, the backend must check if authentication was
|
34
|
+
# successful.
|
35
|
+
#
|
36
|
+
# The returned value must be one of the following
|
37
|
+
#
|
38
|
+
# [true, "principals/username"]
|
39
|
+
# [false, "reason for failure"]
|
40
|
+
#
|
41
|
+
# If authentication was successful, it's expected that the authentication
|
42
|
+
# backend returns a so-called principal url.
|
43
|
+
#
|
44
|
+
# Examples of a principal url:
|
45
|
+
#
|
46
|
+
# principals/admin
|
47
|
+
# principals/user1
|
48
|
+
# principals/users/joe
|
49
|
+
# principals/uid/123457
|
50
|
+
#
|
51
|
+
# If you don't use WebDAV ACL (RFC3744) we recommend that you simply
|
52
|
+
# return a string such as:
|
53
|
+
#
|
54
|
+
# principals/users/[username]
|
55
|
+
#
|
56
|
+
# @param RequestInterface request
|
57
|
+
# @param ResponseInterface response
|
58
|
+
# @return array
|
59
|
+
def check(request, response)
|
60
|
+
auth = Http::Auth::Bearer.new(
|
61
|
+
@realm,
|
62
|
+
request,
|
63
|
+
response
|
64
|
+
)
|
65
|
+
|
66
|
+
bearer_token = auth.token
|
67
|
+
if bearer_token.blank?
|
68
|
+
return [false, "No 'Authorization: Bearer' header found. Either the client didn't send one, or the server is mis-configured"]
|
69
|
+
end
|
70
|
+
|
71
|
+
principal_url = validate_bearer_token(bearer_token)
|
72
|
+
if principal_url.blank?
|
73
|
+
return [false, "Bearer token was incorrect"]
|
74
|
+
end
|
75
|
+
|
76
|
+
return [true, principal_url]
|
77
|
+
end
|
78
|
+
|
79
|
+
# This method is called when a user could not be authenticated, and
|
80
|
+
# authentication was required for the current request.
|
81
|
+
#
|
82
|
+
# This gives you the opportunity to set authentication headers. The 401
|
83
|
+
# status code will already be set.
|
84
|
+
#
|
85
|
+
# In this case of Bearer Auth, this would for example mean that the
|
86
|
+
# following header needs to be set:
|
87
|
+
#
|
88
|
+
# response.add_header('WWW-Authenticate', 'Bearer realm=SabreDAV')
|
89
|
+
#
|
90
|
+
# Keep in mind that in the case of multiple authentication backends, other
|
91
|
+
# WWW-Authenticate headers may already have been set, and you'll want to
|
92
|
+
# append your own WWW-Authenticate header instead of overwriting the
|
93
|
+
# existing one.
|
94
|
+
#
|
95
|
+
# @param RequestInterface request
|
96
|
+
# @param ResponseInterface response
|
97
|
+
# @return void
|
98
|
+
def challenge(request, response)
|
99
|
+
auth = Http::Auth::Bearer.new(
|
100
|
+
@realm,
|
101
|
+
request,
|
102
|
+
response
|
103
|
+
)
|
104
|
+
auth.require_login
|
105
|
+
end
|
106
|
+
|
107
|
+
# TODO: document
|
108
|
+
def initialize *args
|
109
|
+
@realm = 'tilia/dav'
|
110
|
+
super
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|