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.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/CHANGELOG.sabre.md +97 -1
  4. data/Gemfile +1 -16
  5. data/Gemfile.lock +44 -41
  6. data/LICENSE +1 -1
  7. data/LICENSE.sabre +1 -1
  8. data/examples/addressbookserver.rb +52 -0
  9. data/examples/calendarserver.rb +58 -0
  10. data/examples/fileserver.rb +57 -0
  11. data/examples/groupwareserver.rb +69 -0
  12. data/examples/sql/mysql.addressbook.sql +28 -0
  13. data/examples/sql/mysql.calendars.sql +64 -0
  14. data/examples/sql/mysql.locks.sql +13 -0
  15. data/examples/sql/mysql.principals.sql +21 -0
  16. data/examples/sql/mysql.propertystorage.sql +8 -0
  17. data/examples/sql/mysql.users.sql +9 -0
  18. data/examples/sql/pgsql.addressbook.sql +52 -0
  19. data/examples/sql/pgsql.calendars.sql +93 -0
  20. data/examples/sql/pgsql.locks.sql +19 -0
  21. data/examples/sql/pgsql.principals.sql +38 -0
  22. data/examples/sql/pgsql.propertystorage.sql +13 -0
  23. data/examples/sql/pgsql.users.sql +14 -0
  24. data/examples/sql/sqlite.addressbooks.sql +28 -0
  25. data/examples/sql/sqlite.calendars.sql +64 -0
  26. data/examples/sql/sqlite.locks.sql +12 -0
  27. data/examples/sql/sqlite.principals.sql +20 -0
  28. data/examples/sql/sqlite.propertystorage.sql +10 -0
  29. data/examples/sql/sqlite.users.sql +9 -0
  30. data/lib/tilia/cal_dav/ics_export_plugin.rb +1 -1
  31. data/lib/tilia/cal_dav/plugin.rb +27 -11
  32. data/lib/tilia/cal_dav/schedule/i_mip_plugin.rb +2 -2
  33. data/lib/tilia/cal_dav/schedule/plugin.rb +7 -0
  34. data/lib/tilia/dav/auth/backend.rb +1 -0
  35. data/lib/tilia/dav/auth/backend/abstract_basic.rb +3 -2
  36. data/lib/tilia/dav/auth/backend/abstract_bearer.rb +116 -0
  37. data/lib/tilia/dav/auth/backend/abstract_digest.rb +3 -2
  38. data/lib/tilia/dav/auth/backend/apache.rb +2 -1
  39. data/lib/tilia/dav/auth/backend/sequel.rb +2 -9
  40. data/lib/tilia/dav/client.rb +29 -3
  41. data/lib/tilia/dav/core_plugin.rb +1 -2
  42. data/lib/tilia/dav/server.rb +16 -4
  43. data/lib/tilia/dav/temporary_file_filter_plugin.rb +3 -0
  44. data/lib/tilia/dav/tree.rb +4 -3
  45. data/lib/tilia/dav/version.rb +1 -1
  46. data/lib/tilia/dav/xml/element/response.rb +20 -2
  47. data/lib/tilia/dav_acl/principal_backend/sequel.rb +50 -6
  48. data/test/cal_dav/ics_export_plugin_test.rb +1 -0
  49. data/test/cal_dav/plugin_test.rb +4 -4
  50. data/test/cal_dav/schedule/plugin_properties_test.rb +51 -0
  51. data/test/card_dav/backend/sequel_my_sql_test.rb +3 -3
  52. data/test/card_dav/vcf_export_test.rb +11 -1
  53. data/test/dav/auth/backend/abstract_bearer_test.rb +71 -0
  54. data/test/dav/client_test.rb +42 -4
  55. data/test/dav/core_plugin_test.rb +12 -0
  56. data/test/dav/fs_ext/server_test.rb +1 -1
  57. data/test/dav/http_copy_test.rb +185 -0
  58. data/test/dav/mock/collection.rb +6 -9
  59. data/test/dav/mock/file.rb +10 -9
  60. data/test/dav/mock/streaming_file.rb +1 -3
  61. data/test/dav/server_events_test.rb +8 -6
  62. data/test/dav/server_range_test.rb +135 -155
  63. data/test/dav/server_simple_test.rb +14 -0
  64. data/test/dav/xml/element/response_test.rb +56 -1
  65. data/test/dav/xml/property/href_test.rb +14 -0
  66. data/test/dav_acl/principal_backend/abstract_sequel_test.rb +16 -0
  67. data/test/dav_acl/principal_backend/mock.rb +1 -1
  68. data/test/dav_server_test.rb +1 -1
  69. data/tilia-dav.gemspec +2 -2
  70. metadata +38 -14
  71. data/test/dav/copy_test.rb +0 -33
  72. 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,12 @@
1
+ BEGIN TRANSACTION;
2
+ CREATE TABLE locks (
3
+ id integer primary key asc,
4
+ owner text,
5
+ timeout integer,
6
+ created integer,
7
+ token text,
8
+ scope integer,
9
+ depth integer,
10
+ uri text
11
+ );
12
+ COMMIT;
@@ -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
+
@@ -0,0 +1,10 @@
1
+ CREATE TABLE propertystorage (
2
+ id integer primary key asc,
3
+ path text,
4
+ name text,
5
+ valuetype integer,
6
+ value string
7
+ );
8
+
9
+
10
+ CREATE UNIQUE INDEX path_property ON propertystorage (path, name);
@@ -0,0 +1,9 @@
1
+ CREATE TABLE users (
2
+ id integer primary key asc,
3
+ username TEXT,
4
+ digesta1 TEXT,
5
+ UNIQUE(username)
6
+ );
7
+
8
+ INSERT INTO users (username,digesta1) VALUES
9
+ ('admin', '2bd199b750010a686f5908c2551d39b3');
@@ -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)
@@ -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 string
63
+ # @return [String, nil]
62
64
  def calendar_home_for_principal(principal_url)
63
- # The default is a bit naive, but it can be overwritten.
64
- node_name = Uri.split(principal_url).second
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 + '/' + node_name
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 Dav::Xml::Property::Href.new(calendar_home_path)
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-WP/') == 0
510
- # Windows phone incorrectly supplied depth as 0, when it actually
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)['scheme'] == 'mailto'
75
- return nil unless Uri.parse(i_tip_message.recipient)['scheme'] == 'mailto'
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'
@@ -108,8 +108,9 @@ module Tilia
108
108
  end
109
109
 
110
110
  # TODO: document
111
- def initialize
112
- @realm = 'sabre/dav'
111
+ def initialize *args
112
+ super
113
+ @realm = 'tilia/dav'
113
114
  @principal_prefix = 'principals/'
114
115
  end
115
116
  end
@@ -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
@@ -121,8 +121,9 @@ module Tilia
121
121
  end
122
122
 
123
123
  # TODO: document
124
- def initialize
125
- @realm = 'TiliaDAV'
124
+ def initialize *args
125
+ super
126
+ @realm = 'tilia/dav'
126
127
  @principal_prefix = 'principals/'
127
128
  end
128
129
  end
@@ -75,7 +75,8 @@ module Tilia
75
75
  def challenge(_request, _response)
76
76
  end
77
77
 
78
- def initialize
78
+ def initialize *args
79
+ super
79
80
  @principal_prefix = 'principals/'
80
81
  end
81
82
  end