solid-community-client-simple 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f3179178af2f1aa377389010b0c70b3f5f3fbb5fd1d3f7e236eb5d1a51c14744
4
+ data.tar.gz: 6f0d2834a7ba126bca117223ebc6fda28b9ae752bc935cefa7ead948084e50eb
5
+ SHA512:
6
+ metadata.gz: f4309001b3d49ebf7dc1124f15e59999160eb060b0af21991777520809918f2010f51d9d68109f19deb6cdaf5d0c54c4e9502fdc723c29c3cfc858b3a49928dd
7
+ data.tar.gz: 4f675efc01c2a5fb9c832c3053914abed55921c06499073400584856c3b28b83575644e6f480e660375b2ac92e29171d264a0eee39c79382301ecbf4c32f79bc
data/Gemfile ADDED
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: false
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gemspec
6
+ ruby '~> 3.0.6'
7
+ gem 'debase', git: 'https://github.com/ruby-debug/debase.git', tag: 'v0.2.5.beta2'
8
+ gem 'json', '~> 2.7.1'
9
+ gem 'linkeddata', '~> 3.2.0'
10
+ gem 'rake', '~> 13.0'
11
+ gem 'rdf-raptor', '~> 3.1.0'
12
+ gem 'require_all', '~> 3.0.0'
13
+ gem 'rest-client', '~>2.1.0'
14
+ gem 'xml-simple', '~>1.1.8'
15
+
16
+ gem 'metainspector', '~> 5-11-2'
17
+
18
+ gem 'base64', '~> 0.2.0'
19
+
20
+ gem 'thin', '~> 1.8'
21
+
22
+ gem 'tux', '~> 0.3.0'
23
+
24
+ gem 'rubocop', '~> 1.62'
25
+
26
+ gem 'bcrypt', '~> 3.1'
27
+
28
+ gem 'tempfile', '~> 0.2.1'
29
+
30
+ gem 'securerandom', '~> 0.3.1'
31
+
32
+ gem 'rdf-vocab', '~> 3.3'
33
+
34
+ gem 'openssl', '~> 3.2'
35
+
36
+ gem 'activesupport', '7.0.3.1'
data/Gemfile.lock ADDED
@@ -0,0 +1,405 @@
1
+ GIT
2
+ remote: https://github.com/ruby-debug/debase.git
3
+ revision: a90f2270351ae0f48370d60809ecd82d6110c85c
4
+ tag: v0.2.5.beta2
5
+ specs:
6
+ debase (0.2.5.beta2)
7
+ debase-ruby_core_source (>= 0.10.12)
8
+
9
+ PATH
10
+ remote: .
11
+ specs:
12
+ solid-community-client-simple (1)
13
+
14
+ GEM
15
+ remote: https://rubygems.org/
16
+ specs:
17
+ activesupport (7.0.3.1)
18
+ concurrent-ruby (~> 1.0, >= 1.0.2)
19
+ i18n (>= 1.6, < 2)
20
+ minitest (>= 5.1)
21
+ tzinfo (~> 2.0)
22
+ addressable (2.8.6)
23
+ public_suffix (>= 2.0.2, < 6.0)
24
+ ast (2.4.2)
25
+ base64 (0.2.0)
26
+ bcp47_spec (0.2.1)
27
+ bcrypt (3.1.20)
28
+ bond (0.5.1)
29
+ builder (3.2.4)
30
+ cgi (0.4.1)
31
+ concurrent-ruby (1.2.3)
32
+ connection_pool (2.4.1)
33
+ daemons (1.4.1)
34
+ debase-ruby_core_source (3.3.1)
35
+ diff-lcs (1.5.1)
36
+ domain_name (0.6.20240107)
37
+ dpop (0.1.3)
38
+ activesupport
39
+ jwt
40
+ openssl
41
+ ebnf (2.4.0)
42
+ htmlentities (~> 4.3)
43
+ rdf (~> 3.3)
44
+ scanf (~> 1.0)
45
+ sxp (~> 1.3)
46
+ unicode-types (~> 1.8)
47
+ eventmachine (1.2.7)
48
+ faraday (2.9.0)
49
+ faraday-net_http (>= 2.0, < 3.2)
50
+ faraday-cookie_jar (0.0.7)
51
+ faraday (>= 0.8.0)
52
+ http-cookie (~> 1.0.0)
53
+ faraday-encoding (0.0.5)
54
+ faraday
55
+ faraday-follow_redirects (0.3.0)
56
+ faraday (>= 1, < 3)
57
+ faraday-gzip (1.0.0)
58
+ faraday (>= 1.0)
59
+ zlib (~> 2.1)
60
+ faraday-http-cache (2.5.1)
61
+ faraday (>= 0.8)
62
+ faraday-net_http (3.1.0)
63
+ net-http
64
+ faraday-retry (2.2.0)
65
+ faraday (~> 2.0)
66
+ fastimage (2.3.1)
67
+ ffi (1.16.3)
68
+ fileutils (1.5.0)
69
+ haml (6.3.0)
70
+ temple (>= 0.8.2)
71
+ thor
72
+ tilt
73
+ hamster (3.0.0)
74
+ concurrent-ruby (~> 1.0)
75
+ htmlentities (4.3.4)
76
+ http-accept (1.7.0)
77
+ http-cookie (1.0.5)
78
+ domain_name (~> 0.5)
79
+ i18n (1.14.5)
80
+ concurrent-ruby (~> 1.0)
81
+ json (2.7.2)
82
+ json-canonicalization (1.0.0)
83
+ json-ld (3.3.1)
84
+ htmlentities (~> 4.3)
85
+ json-canonicalization (~> 1.0)
86
+ link_header (~> 0.0, >= 0.0.8)
87
+ multi_json (~> 1.15)
88
+ rack (>= 2.2, < 4)
89
+ rdf (~> 3.3)
90
+ json-ld-preloaded (3.3.0)
91
+ json-ld (~> 3.3)
92
+ rdf (~> 3.3)
93
+ jwt (2.8.2)
94
+ base64
95
+ language_server-protocol (3.17.0.3)
96
+ ld-patch (3.3.0)
97
+ ebnf (~> 2.4)
98
+ rdf (~> 3.3)
99
+ rdf-xsd (~> 3.3)
100
+ sparql (~> 3.3)
101
+ sxp (~> 1.3)
102
+ link_header (0.0.8)
103
+ linkeddata (3.2.2)
104
+ json-ld (~> 3.2, >= 3.2.5)
105
+ json-ld-preloaded (~> 3.2, >= 3.2.2)
106
+ ld-patch (~> 3.2, >= 3.2.2)
107
+ nokogiri (~> 1.13, >= 1.13.8)
108
+ rdf (~> 3.2, >= 3.2.1)
109
+ rdf-aggregate-repo (~> 3.2, >= 3.2.1)
110
+ rdf-hamster-repo (~> 3.2, >= 3.2.1)
111
+ rdf-isomorphic (~> 3.2, >= 3.2.1)
112
+ rdf-json (~> 3.2)
113
+ rdf-microdata (~> 3.2, >= 3.2.1)
114
+ rdf-n3 (~> 3.2, >= 3.2.1)
115
+ rdf-normalize (~> 0.6, >= 0.6.1)
116
+ rdf-ordered-repo (~> 3.2, >= 3.2.1)
117
+ rdf-rdfa (~> 3.2, >= 3.2.3)
118
+ rdf-rdfxml (~> 3.2, >= 3.2.2)
119
+ rdf-reasoner (~> 0.8)
120
+ rdf-tabular (~> 3.2, >= 3.2.1)
121
+ rdf-trig (~> 3.2)
122
+ rdf-trix (~> 3.2)
123
+ rdf-turtle (~> 3.2, >= 3.2.1)
124
+ rdf-vocab (~> 3.2, >= 3.2.7)
125
+ rdf-xsd (~> 3.2, >= 3.2.1)
126
+ shacl (~> 0.3)
127
+ shex (~> 0.7, >= 0.7.1)
128
+ sparql (~> 3.2, >= 3.2.6)
129
+ sparql-client (~> 3.2, >= 3.2.2)
130
+ yaml-ld (~> 0.0)
131
+ logger (1.6.0)
132
+ matrix (0.4.2)
133
+ metainspector (5.15.0)
134
+ addressable (~> 2.8.4)
135
+ faraday (~> 2.5)
136
+ faraday-cookie_jar (~> 0.0)
137
+ faraday-encoding (~> 0.0)
138
+ faraday-follow_redirects (~> 0.3)
139
+ faraday-gzip (>= 0.1, < 2.0)
140
+ faraday-http-cache (~> 2.5)
141
+ faraday-retry (~> 2.0)
142
+ fastimage (~> 2.2)
143
+ nesty (~> 1.0)
144
+ nokogiri (~> 1.13)
145
+ mime-types (3.5.2)
146
+ mime-types-data (~> 3.2015)
147
+ mime-types-data (3.2024.0305)
148
+ minitest (5.24.1)
149
+ multi_json (1.15.0)
150
+ mustermann (2.0.2)
151
+ ruby2_keywords (~> 0.0.1)
152
+ nesty (1.0.2)
153
+ net-http (0.4.1)
154
+ uri
155
+ net-http-persistent (4.0.2)
156
+ connection_pool (~> 2.2)
157
+ netrc (0.11.0)
158
+ nokogiri (1.16.3-aarch64-linux)
159
+ racc (~> 1.4)
160
+ nokogiri (1.16.3-arm-linux)
161
+ racc (~> 1.4)
162
+ nokogiri (1.16.3-arm64-darwin)
163
+ racc (~> 1.4)
164
+ nokogiri (1.16.3-x86-linux)
165
+ racc (~> 1.4)
166
+ nokogiri (1.16.3-x86_64-darwin)
167
+ racc (~> 1.4)
168
+ nokogiri (1.16.3-x86_64-linux)
169
+ racc (~> 1.4)
170
+ openssl (3.2.0)
171
+ parallel (1.24.0)
172
+ parser (3.3.0.5)
173
+ ast (~> 2.4.1)
174
+ racc
175
+ psych (5.1.2)
176
+ stringio
177
+ public_suffix (5.0.5)
178
+ racc (1.7.3)
179
+ rack (2.2.9)
180
+ rack-protection (2.2.4)
181
+ rack
182
+ rack-test (0.6.3)
183
+ rack (>= 1.0)
184
+ rainbow (3.1.1)
185
+ rake (13.2.1)
186
+ rdf (3.3.1)
187
+ bcp47_spec (~> 0.2)
188
+ link_header (~> 0.0, >= 0.0.8)
189
+ rdf-aggregate-repo (3.3.0)
190
+ rdf (~> 3.3)
191
+ rdf-hamster-repo (3.3.0)
192
+ hamster (~> 3.0)
193
+ rdf (~> 3.3)
194
+ rdf-isomorphic (3.3.0)
195
+ rdf (~> 3.3)
196
+ rdf-json (3.3.0)
197
+ rdf (~> 3.3)
198
+ rdf-microdata (3.3.0)
199
+ htmlentities (~> 4.3)
200
+ nokogiri (~> 1.15, >= 1.15.4)
201
+ rdf (~> 3.3)
202
+ rdf-rdfa (~> 3.3)
203
+ rdf-xsd (~> 3.3)
204
+ rdf-n3 (3.3.0)
205
+ ebnf (~> 2.4)
206
+ rdf (~> 3.3)
207
+ sparql (~> 3.3)
208
+ sxp (~> 1.3)
209
+ rdf-normalize (0.7.0)
210
+ rdf (~> 3.3)
211
+ rdf-ordered-repo (3.3.0)
212
+ rdf (~> 3.3)
213
+ rdf-raptor (3.1.0)
214
+ ffi (~> 1.11)
215
+ rdf (~> 3.1)
216
+ rdf-rdfa (3.3.0)
217
+ haml (~> 6.1)
218
+ htmlentities (~> 4.3)
219
+ rdf (~> 3.3)
220
+ rdf-aggregate-repo (~> 3.3)
221
+ rdf-vocab (~> 3.3)
222
+ rdf-xsd (~> 3.3)
223
+ rdf-rdfxml (3.3.0)
224
+ builder (~> 3.2, >= 3.2.4)
225
+ htmlentities (~> 4.3)
226
+ rdf (~> 3.3)
227
+ rdf-xsd (~> 3.3)
228
+ rdf-reasoner (0.9.0)
229
+ rdf (~> 3.3)
230
+ rdf-xsd (~> 3.3)
231
+ rdf-tabular (3.3.0)
232
+ addressable (~> 2.8)
233
+ bcp47_spec (~> 0.2)
234
+ json-ld (~> 3.3)
235
+ rdf (~> 3.3)
236
+ rdf-vocab (~> 3.3)
237
+ rdf-xsd (~> 3.3)
238
+ rdf-trig (3.3.0)
239
+ ebnf (~> 2.4)
240
+ rdf (~> 3.3)
241
+ rdf-turtle (~> 3.3)
242
+ rdf-trix (3.3.0)
243
+ rdf (~> 3.3)
244
+ rdf-xsd (~> 3.3)
245
+ rdf-turtle (3.3.0)
246
+ ebnf (~> 2.4)
247
+ rdf (~> 3.3)
248
+ rdf-vocab (3.3.0)
249
+ rdf (~> 3.3)
250
+ rdf-xsd (3.3.0)
251
+ rdf (~> 3.3)
252
+ rexml (~> 3.2)
253
+ regexp_parser (2.9.0)
254
+ require_all (3.0.0)
255
+ rest-client (2.1.0)
256
+ http-accept (>= 1.7.0, < 2.0)
257
+ http-cookie (>= 1.0.2, < 2.0)
258
+ mime-types (>= 1.16, < 4.0)
259
+ netrc (~> 0.8)
260
+ rexml (3.2.6)
261
+ ripl (0.7.1)
262
+ bond (~> 0.5.1)
263
+ ripl-multi_line (0.3.1)
264
+ ripl (>= 0.3.6)
265
+ ripl-rack (0.2.1)
266
+ rack (>= 1.0)
267
+ rack-test (~> 0.6.2)
268
+ ripl (>= 0.7.0)
269
+ rspec (3.11.0)
270
+ rspec-core (~> 3.11.0)
271
+ rspec-expectations (~> 3.11.0)
272
+ rspec-mocks (~> 3.11.0)
273
+ rspec-core (3.11.0)
274
+ rspec-support (~> 3.11.0)
275
+ rspec-expectations (3.11.1)
276
+ diff-lcs (>= 1.2.0, < 2.0)
277
+ rspec-support (~> 3.11.0)
278
+ rspec-mocks (3.11.2)
279
+ diff-lcs (>= 1.2.0, < 2.0)
280
+ rspec-support (~> 3.11.0)
281
+ rspec-support (3.11.1)
282
+ rubocop (1.62.1)
283
+ json (~> 2.3)
284
+ language_server-protocol (>= 3.17.0)
285
+ parallel (~> 1.10)
286
+ parser (>= 3.3.0.2)
287
+ rainbow (>= 2.2.2, < 4.0)
288
+ regexp_parser (>= 1.8, < 3.0)
289
+ rexml (>= 3.2.5, < 4.0)
290
+ rubocop-ast (>= 1.31.1, < 2.0)
291
+ ruby-progressbar (~> 1.7)
292
+ unicode-display_width (>= 2.4.0, < 3.0)
293
+ rubocop-ast (1.31.2)
294
+ parser (>= 3.3.0.4)
295
+ ruby-progressbar (1.13.0)
296
+ ruby2_keywords (0.0.5)
297
+ scanf (1.0.0)
298
+ securerandom (0.3.1)
299
+ shacl (0.4.1)
300
+ json-ld (~> 3.3)
301
+ rdf (~> 3.3)
302
+ sparql (~> 3.3)
303
+ sxp (~> 1.2)
304
+ shex (0.8.0)
305
+ ebnf (~> 2.4)
306
+ htmlentities (~> 4.3)
307
+ json-ld (~> 3.3)
308
+ json-ld-preloaded (~> 3.3)
309
+ rdf (~> 3.3)
310
+ rdf-xsd (~> 3.3)
311
+ sparql (~> 3.3)
312
+ sxp (~> 1.3)
313
+ sinatra (2.2.4)
314
+ mustermann (~> 2.0)
315
+ rack (~> 2.2)
316
+ rack-protection (= 2.2.4)
317
+ tilt (~> 2.0)
318
+ sinatra-cross_origin (0.4.0)
319
+ sparql (3.3.0)
320
+ builder (~> 3.2, >= 3.2.4)
321
+ ebnf (~> 2.4)
322
+ logger (~> 1.5)
323
+ rdf (~> 3.3)
324
+ rdf-aggregate-repo (~> 3.3)
325
+ rdf-xsd (~> 3.3)
326
+ sparql-client (~> 3.3)
327
+ sxp (~> 1.3)
328
+ sparql-client (3.3.0)
329
+ net-http-persistent (~> 4.0, >= 4.0.2)
330
+ rdf (~> 3.3)
331
+ stringio (3.1.0)
332
+ swagger-blocks (3.0.0)
333
+ sxp (1.3.0)
334
+ matrix (~> 0.4)
335
+ rdf (~> 3.3)
336
+ tempfile (0.2.1)
337
+ temple (0.10.3)
338
+ thin (1.8.2)
339
+ daemons (~> 1.0, >= 1.0.9)
340
+ eventmachine (~> 1.0, >= 1.0.4)
341
+ rack (>= 1, < 3)
342
+ thor (1.3.1)
343
+ tilt (2.3.0)
344
+ tux (0.3.0)
345
+ ripl (>= 0.3.5)
346
+ ripl-multi_line (>= 0.2.4)
347
+ ripl-rack (>= 0.2.0)
348
+ sinatra (>= 1.2.1)
349
+ tzinfo (2.0.6)
350
+ concurrent-ruby (~> 1.0)
351
+ unicode-display_width (2.5.0)
352
+ unicode-types (1.9.0)
353
+ uri (0.12.2)
354
+ xml-simple (1.1.9)
355
+ rexml
356
+ yaml-ld (0.0.3)
357
+ json-ld (~> 3.3)
358
+ psych (>= 3.3)
359
+ rdf (~> 3.3)
360
+ rdf-xsd (~> 3.3)
361
+ zlib (2.1.1)
362
+
363
+ PLATFORMS
364
+ aarch64-linux
365
+ arm-linux
366
+ arm64-darwin
367
+ x86-linux
368
+ x86_64-darwin
369
+ x86_64-linux
370
+
371
+ DEPENDENCIES
372
+ activesupport (= 7.0.3.1)
373
+ base64 (~> 0.2.0)
374
+ bcrypt (~> 3.1)
375
+ cgi (~> 0.4.1)
376
+ debase!
377
+ dpop (~> 0.1.3)
378
+ fileutils (~> 1.5.0)
379
+ json (~> 2.7.1)
380
+ linkeddata (~> 3.2.0)
381
+ metainspector (~> 5.pre.11.pre.2)
382
+ openssl (~> 3.2)
383
+ rake (~> 13.0)
384
+ rdf-raptor (~> 3.1.0)
385
+ rdf-vocab (~> 3.3)
386
+ require_all (~> 3.0.0)
387
+ rest-client (~> 2.1.0)
388
+ rspec (~> 3.11.0)
389
+ rubocop (~> 1.62)
390
+ securerandom (~> 0.3.1)
391
+ sinatra (~> 2.2)
392
+ sinatra-cross_origin
393
+ solid-community-client-simple!
394
+ swagger-blocks (~> 3.0.0)
395
+ tempfile (~> 0.2.1)
396
+ thin (~> 1.8)
397
+ tux (~> 0.3.0)
398
+ uri (~> 0.12.2)
399
+ xml-simple (~> 1.1.8)
400
+
401
+ RUBY VERSION
402
+ ruby 3.0.6p216
403
+
404
+ BUNDLED WITH
405
+ 2.5.5
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Wilkinson Laboratory for Biological Informatics
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,10 @@
1
+ # SOLID Client for Community Server
2
+
3
+ Talk to SOLID Pods
4
+
5
+ # This is not guaranteed to be useful for any purpose whatsoever
6
+
7
+ If you want an idea of what's happening behind the scenes, have a look at this Gist:
8
+ https://gist.github.com/markwilkinson/c5e819ae08d3753b1ee63edb2a504401
9
+
10
+ I will get around to documentation as soon as I can. In the meantime, have a look at demo.rb for simple simple simple examples of how to use this gem.
data/demo.rb ADDED
@@ -0,0 +1,8 @@
1
+ require "./lib/solid.rb"
2
+ #require "solid-community-client-simple"
3
+ s = SOLID::CommunityClient.new(server: "http://localhost:3000/", username: "mark.wilkinson@upm.es", password: "markw")
4
+ s.login
5
+ at = s.get_current_access_token(webid: "http://localhost:3000/markw/profile/card#me", name: "codetest")
6
+ dpop = s.prepare_dpop(url: "http://localhost:3000/markw/", method: "get")
7
+ res = s.execute_with_proof(dpop: dpop, data: "", content_type: "text/turtle", current_access_token: at)
8
+ puts res.body
data/lib/account.rb ADDED
@@ -0,0 +1,25 @@
1
+ module SOLID
2
+ class Account
3
+ attr_accessor :json
4
+
5
+ def initialize(json:)
6
+ @json = json
7
+ end
8
+
9
+ def login_url
10
+ login = json['controls']['password']['login']
11
+ warn "login #{login}"
12
+ login
13
+ end
14
+
15
+ def credentials_url
16
+ json['controls']['account']['clientCredentials']
17
+ end
18
+
19
+ def webid_url
20
+ json['controls']['account']['webId']
21
+ end
22
+
23
+ end
24
+ end
25
+
data/lib/config.rb ADDED
@@ -0,0 +1,16 @@
1
+ module SOLID
2
+ class CommunityClientConfig
3
+
4
+ require 'base64'
5
+ require 'openssl'
6
+ require 'rest-client'
7
+ require 'json'
8
+ require 'jwt'
9
+ require 'securerandom'
10
+
11
+ require_relative './account'
12
+ require_relative './dpop'
13
+ require_relative './login'
14
+
15
+ end
16
+ end
data/lib/dpop.rb ADDED
@@ -0,0 +1,42 @@
1
+ module SOLID
2
+
3
+ class DPOP
4
+ attr_accessor :public_key, :private_key, :header, :current_payload, :method, :url, :proof
5
+
6
+ def initialize (public_key: nil, private_key: nil, method:, url:)
7
+ private_key ||= OpenSSL::PKey::RSA.generate 2048
8
+ public_key ||= private_key.public_key
9
+ @public_key = public_key
10
+ @private_key = private_key
11
+ @method = method
12
+ @url = url
13
+
14
+ @header = {
15
+ alg: 'RS256', # Signing algorithm
16
+ typ: 'dpop+jwt', # Token type
17
+ jwk: {
18
+ kty: 'RSA',
19
+ e: Base64.urlsafe_encode64(public_key.e.to_s(2)), # see above for explanation
20
+ n: Base64.urlsafe_encode64(public_key.n.to_s(2))
21
+ }
22
+ }
23
+
24
+ @current_payload = {
25
+ htu: @url, # Target URI
26
+ htm: @method.upcase, # HTTP method
27
+ jti: SecureRandom.uuid, # Unique token ID
28
+ iat: Time.now.to_i # Issued at time
29
+ }
30
+ end
31
+
32
+ def get_proof
33
+ proof = JWT.encode(self.current_payload, self.private_key, 'RS256', self.header) # PRIVATE key!!
34
+ warn "DPoP Proof: #{proof}"
35
+ @proof = proof
36
+ @proof
37
+ end
38
+
39
+
40
+ end
41
+ end
42
+
data/lib/login.rb ADDED
@@ -0,0 +1,19 @@
1
+
2
+ module SOLID
3
+ class Login
4
+ attr_accessor :json
5
+
6
+ def initialize(json:)
7
+ @json = json
8
+ end
9
+
10
+ def login_token
11
+ auth_token = json['authorization']
12
+ warn "auth_token #{auth_token}"
13
+ auth_token
14
+ end
15
+ end
16
+ end
17
+
18
+
19
+
@@ -0,0 +1 @@
1
+ require_relative "./solid.rb"
data/lib/solid.rb ADDED
@@ -0,0 +1,168 @@
1
+ module SOLID
2
+ class CommunityClient
3
+ require_relative './config'
4
+
5
+ attr_accessor :username, :password, :server, :account_meta, :login_meta, :credentials_url, :webid_url, :webids,
6
+ :css_account_token, :tokenid, :secret, :auth_string, :encoded_auth, :current_access_token
7
+
8
+ def initialize(server:, username: ENV['SOLIDUSER'], password: ENV['SOLIDPASSWORD'], webid: nil)
9
+ @username = username
10
+ @password = password
11
+ @server = server
12
+ abort "can't proceed without usernme password" unless @username && @password
13
+ @webid = webid
14
+ end
15
+
16
+ def login
17
+ account = server + '.account/'
18
+
19
+ resp = RestClient::Request.new({
20
+ method: :get,
21
+ url: account,
22
+ headers: { accept: 'application/json' }
23
+ }).execute
24
+
25
+ @account_meta = SOLID::Account.new(json: JSON.parse(resp.body))
26
+ login_url = account_meta.login_url
27
+
28
+ # I take my username and password from the environment
29
+ payload = { "email": username, "password": password }.to_json
30
+ resp = RestClient::Request.new({
31
+ method: :post,
32
+ url: login_url,
33
+ headers: {
34
+ content_type: 'application/json',
35
+ accept: 'application/json'
36
+ },
37
+ payload: payload
38
+ }).execute
39
+
40
+ @login_meta = SOLID::Login.new(json: JSON.parse(resp.body))
41
+ @css_account_token = login_meta.login_token
42
+
43
+ # with the authoization, the return message for the /.account GET call is richer
44
+ resp = RestClient::Request.new({
45
+ method: :get,
46
+ url: 'http://localhost:3000/.account/',
47
+ headers: {
48
+ accept: 'application/json',
49
+ authorization: "CSS-Account-Token #{css_account_token}"
50
+ }
51
+ }).execute
52
+
53
+ @account_meta = SOLID::Account.new(json: JSON.parse(resp.body))
54
+ @credentials_url = account_meta.credentials_url
55
+ @webid_url = account_meta.webid_url
56
+ true
57
+ end
58
+
59
+ def get_webids
60
+ resp = RestClient::Request.new({
61
+ method: :get,
62
+ url: webid_url,
63
+ headers: {
64
+ accept: 'application/json',
65
+ authorization: "CSS-Account-Token #{css_account_token}"
66
+ }
67
+ }).execute
68
+
69
+ j = JSON.parse(resp.body)
70
+ @webids = j['webIdLinks'].keys
71
+ webids
72
+ end
73
+
74
+ def create_access_token(webid:, name: 'my-token')
75
+ payload = { "name": name, "webId": webid }.to_json
76
+ warn "", "", credentials_url, css_account_token, payload, "", ""
77
+ resp = RestClient::Request.new({
78
+ method: :post,
79
+ url: credentials_url,
80
+ headers: { content_type: 'application/json', accept: 'application/json',
81
+ authorization: "CSS-Account-Token #{css_account_token}" },
82
+ payload: payload
83
+ }).execute
84
+ # puts resp.body
85
+ j = JSON.parse(resp.body)
86
+ @tokenid = j['id'] # this is the ID that you see on the localhost:3000/yourpod Web page....
87
+ @secret = j['secret']
88
+ # concatenate tokenid and secret with a ":"
89
+ @auth_string = "#{tokenid}:#{secret}"
90
+
91
+ # BE CAREFUL! Base64 encoders may add newline characters
92
+ # pick an encoding method that does NOT do this!
93
+ @encoded_auth = Base64.strict_encode64(auth_string)
94
+
95
+ # where do I get a token?
96
+ tokenurl = "#{server}.well-known/openid-configuration"
97
+ resp = RestClient.get(tokenurl)
98
+ j = JSON.parse(resp.body)
99
+ token_endpoint = j['token_endpoint']
100
+ warn "token endpoint #{token_endpoint}"
101
+
102
+ payload = 'grant_type=client_credentials&scope=webid'
103
+ resp = RestClient::Request.new({
104
+ method: :post,
105
+ url: token_endpoint,
106
+ headers: {
107
+ content_type: 'application/x-www-form-urlencoded',
108
+ accept: 'application/json',
109
+ authorization: "Basic #{encoded_auth}" # BASIC Auth
110
+ # 'dpop': proof
111
+ },
112
+ payload: payload
113
+ }).execute
114
+ # puts resp.body
115
+ j = JSON.parse(resp.body)
116
+ @current_access_token = j['access_token']
117
+ end
118
+
119
+ def get_current_access_token(webid:, name: "my-token")
120
+ create_access_token(webid: webid, name: name) unless current_access_token && !(current_access_token.empty?)
121
+
122
+ # by default, I find the community server to make tokens that last for 10 minutes
123
+ decoded_token = JWT.decode(current_access_token, nil, false)
124
+ # Access the payload (claims)
125
+ payload = decoded_token[0]
126
+ # Print the entire payload
127
+ puts "Token Payload: #{payload}"\
128
+ # Check the expiry time
129
+ if payload['exp']
130
+ expiry_time = Time.at(payload['exp'])
131
+ warn "Token Expiry Time: #{expiry_time} (#{expiry_time.utc})"
132
+ if (expiry_time - Time.now) < 30
133
+ puts "token will expire. Getting new one."
134
+ create_access_token(webid: webid, name: name)
135
+ else
136
+ puts "The access token is still valid."
137
+ end
138
+ else
139
+ puts "No expiration time (exp) claim found."
140
+ end
141
+ current_access_token
142
+ end
143
+
144
+ def prepare_dpop(url:, method:)
145
+ SOLID::DPOP.new(url: url, method: method)
146
+ end
147
+
148
+
149
+ def execute_with_proof(dpop:, data:, content_type:, current_access_token:)
150
+
151
+ req = RestClient::Request.new({
152
+ method: dpop.method.downcase.to_sym, # htm: 'POST',
153
+ url: dpop.url, # htu: 'http://localhost:3000/markw/'
154
+ headers: {
155
+ # "Link": "<http://www.w3.org/ns/ldp/BasicContainer>; rel='type'",
156
+ content_type: content_type, # if you're sending turtle
157
+ # content_type: 'application/json',
158
+ accept: 'text/turtle',
159
+ # slug: "container8", # I am allowed to select my preferred resource name
160
+ authorization: "Bearer #{current_access_token}", # Note switch to Bearer auth!
161
+ 'DPoP': dpop.proof
162
+ },
163
+ payload: data
164
+ })
165
+ req.execute
166
+ end
167
+ end
168
+ end
data/lib/version.rb ADDED
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SOLID
4
+ module CommunityClient
5
+ VERSION = "0.0.2"
6
+ end
7
+ end
data/test.rb ADDED
@@ -0,0 +1,280 @@
1
+ # This is written in the Ruby language, but it tries to avoid using
2
+ # too many libraries that will obfuscate what is happening.
3
+ # The REST calls are explicit so you can see all of the required
4
+ # parameters at each point. Nothing is assumed #(including your own WebID!)
5
+ # though of course, you will already know that.
6
+ # You will possibly also already have a token
7
+ #
8
+ # In this example, you are assumed to be running the Community Server
9
+ # on port 3000 (e.g. via the docker image). You have already created an account
10
+ # your username and password are in the environemnt variables
11
+ # ENV['SOLIDUSER'] and ENV['SOLIDPASS']
12
+
13
+ require 'base64'
14
+ require 'openssl'
15
+ require 'rest-client'
16
+ require 'json'
17
+ require 'jwt'
18
+ require 'securerandom'
19
+
20
+ # get login address
21
+ resp = RestClient::Request.new({
22
+ method: :get,
23
+ url: 'http://localhost:3000/.account/',
24
+ headers: { accept: 'application/json' }
25
+ }).execute
26
+
27
+ j = JSON.parse(resp.body)
28
+ login = j['controls']['password']['login']
29
+ warn "login #{login}"
30
+
31
+ # I take my username and password from the environment
32
+ payload = { "email": ENV['SOLIDUSER'].to_s, "password": ENV['SOLIDPASS'].to_s }.to_json
33
+ resp = RestClient::Request.new({
34
+ method: :post,
35
+ url: login,
36
+ headers: { content_type: 'application/json', accept: 'application/json' },
37
+ payload: payload
38
+ }).execute
39
+
40
+ # puts resp.body
41
+
42
+ # get the authorization token from response message
43
+ j = JSON.parse(resp.body)
44
+ auth_token = j['authorization']
45
+ warn "auth_token #{auth_token}"
46
+
47
+ # with the authoization, the return message for the /.account GET call is richer
48
+ resp = RestClient::Request.new({
49
+ method: :get,
50
+ url: 'http://localhost:3000/.account/',
51
+ headers: { accept: 'application/json',
52
+ authorization: "CSS-Account-Token #{auth_token}" }
53
+ }).execute
54
+ # puts resp.body
55
+
56
+ # get credentialsURL and WebID URL
57
+ j = JSON.parse(resp.body)
58
+ credurl = j['controls']['account']['clientCredentials']
59
+ warn "credurl #{credurl}"
60
+ webidurl = j['controls']['account']['webId']
61
+ warn "webIdurl #{webidurl}"
62
+
63
+ resp = RestClient::Request.new({
64
+ method: :get,
65
+ url: webidurl,
66
+ headers: { accept: 'application/json',
67
+ authorization: "CSS-Account-Token #{auth_token}" }
68
+ }).execute
69
+ # puts resp.body
70
+
71
+ # get your WebID (I am selecting the first WebID in the possible list, because I have only one)
72
+ j = JSON.parse(resp.body)
73
+ webid = j['webIdLinks'].keys.first
74
+ warn "webid #{webid}"
75
+
76
+ # Prepare to create an access token. you can name it whatever you wish
77
+ payload = { "name": 'my-token', "webId": "#{webid}" }.to_json
78
+ resp = RestClient::Request.new({
79
+ method: :post,
80
+ url: credurl,
81
+ headers: { content_type: 'application/json', accept: 'application/json',
82
+ authorization: "CSS-Account-Token #{auth_token}" },
83
+ payload: payload
84
+ }).execute
85
+ # puts resp.body
86
+ j = JSON.parse(resp.body)
87
+ tokenid = j['id'] # this is the ID that you see on the localhost:3000/yourpod Web page....
88
+ secret = j['secret']
89
+ # concatenate tokenid and secret with a ":"
90
+ authstring = "#{tokenid}:#{secret}"
91
+
92
+ # BE CAREFUL! Base64 encoders may add newline characters
93
+ # pick an encoding method that does NOT do this!
94
+ encoded_auth = Base64.strict_encode64(authstring)
95
+
96
+ # where do I get a token?
97
+ resp = RestClient.get('http://localhost:3000/.well-known/openid-configuration')
98
+ j = JSON.parse(resp.body)
99
+ token_endpoint = j['token_endpoint']
100
+ warn "token endpoint #{token_endpoint}"
101
+
102
+ # prepare to request an access token
103
+ # note switch to "Basic" auth
104
+ payload = 'grant_type=client_credentials&scope=webid'
105
+ resp = RestClient::Request.new({
106
+ method: :post,
107
+ url: token_endpoint,
108
+ headers: {
109
+ content_type: 'application/x-www-form-urlencoded',
110
+ accept: 'application/json',
111
+ authorization: "Basic #{encoded_auth}", # BASIC Auth
112
+ # 'dpop': proof
113
+ },
114
+ payload: payload
115
+ }).execute
116
+ # puts resp.body
117
+ j = JSON.parse(resp.body)
118
+ access_token = j['access_token']
119
+ warn "access_token #{access_token}"
120
+
121
+
122
+ ############################ BEGIN DPOP #################################################################
123
+
124
+ private_key = OpenSSL::PKey::RSA.generate 2048
125
+
126
+ # this is what you are creating here...
127
+ # warn private_key.to_s
128
+ # -----BEGIN RSA PRIVATE KEY-----
129
+ # MIIEpgIBAAKCAQEA3Tef264buPI0FjIOnb4MxYLJn+E0C57MXzLT7A6C+D04/YSR
130
+ # +nmiqAls+5PS7po0oYggLQ9HXAKvduE2f6PDGp0pjMWkmJKE8xQ9Jx6Ue1FzClSt
131
+ # vVzcvN6+GjVYk64DqeVWm5N4eBHTgoFkYLBq1tUyfjo6iNUU0gBLfDfBYejVqH5f
132
+ # ...
133
+ # zfjMIvzM439RqE5jTOQYI0mFv1tumwFiIEZfcm2oh9CPeDoXB26WlSbe
134
+ # -----END RSA PRIVATE KEY-----
135
+
136
+ public_key = private_key.public_key
137
+
138
+ # warn public_key.to_s
139
+ # -----BEGIN PUBLIC KEY-----
140
+ # MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5RCfc+IdrVBNTnmO7gla
141
+ # LhitaX0Ikahyt4+wDk+c3nClhUiRB+oR9M0ga+2gZ8oqkMAul1D18EegaHS0lx7t
142
+ # ...
143
+ # HwIDAQAB
144
+ # -----END PUBLIC KEY-----
145
+
146
+
147
+ # two things are happening in the next section:
148
+ # The public key needs to be represented in bigendian base 2
149
+ # it then needs to be URL-base64 encoded
150
+ # You have to figure out how to make your code do this!
151
+ # Note that you are using the PUBLIC KEY here!!!!
152
+ warn "key e" , Base64.urlsafe_encode64(public_key.e.to_s(2)) # AQAB
153
+ warn "key n", Base64.urlsafe_encode64(public_key.n.to_s(2)) # wPIlffH...h61J3w==
154
+
155
+ # the final result is like this:
156
+ # "jwk": {
157
+ # "kty": "RSA",
158
+ # "e": "AQAB",
159
+ # "n": "wPIlffH...h61J3w=="
160
+ # }
161
+
162
+ # DPoP Header
163
+ header = {
164
+ alg: 'RS256', # Signing algorithm
165
+ typ: 'dpop+jwt', # Token type
166
+ jwk: {
167
+ kty: 'RSA',
168
+ e: Base64.urlsafe_encode64(public_key.e.to_s(2)), # see above for explanation
169
+ n: Base64.urlsafe_encode64(public_key.n.to_s(2))
170
+ }
171
+ }
172
+
173
+
174
+ # Create DPoP Payload
175
+ # you need to do this FOR EVERY CALL!
176
+ # what is the URL you will call, and with what HTTP method
177
+ # then you get a "proof" for that call, and you can execute it.
178
+ payload = {
179
+ htu: 'http://localhost:3000/markw/container8/', # Target URI
180
+ htm: 'POST', # HTTP method
181
+ jti: SecureRandom.uuid, # Unique token ID
182
+ iat: Time.now.to_i # Issued at time
183
+ }
184
+
185
+ # Generate the DPoP proof
186
+ proof = JWT.encode(payload, private_key, 'RS256', header) # PRIVATE key!!
187
+
188
+ puts "DPoP Proof: #{proof}"
189
+ puts "Access Token: #{access_token}"
190
+
191
+
192
+ # JUST FYI - you can inspect the token, especially to see if it is still valid
193
+ # by default, I find the community server to make tokens that last for 10 minutes
194
+ # decoded_token = JWT.decode(access_token, nil, false)
195
+ # # Access the payload (claims)
196
+ # payload = decoded_token[0]
197
+ # # Print the entire payload
198
+ # puts "Token Payload: #{payload}"\
199
+ # # Check the expiry time
200
+ # if payload['exp']
201
+ # expiry_time = Time.at(payload['exp'])
202
+ # puts "Token Expiry Time: #{expiry_time} (#{expiry_time.utc})"
203
+ # if expiry_time < Time.now
204
+ # puts "The access token has expired."
205
+ # else
206
+ # puts "The access token is still valid."
207
+ # end
208
+ # else
209
+ # puts "No expiration time (exp) claim found."
210
+ # end
211
+ # # Check the scope
212
+ # if payload['scope']
213
+ # scopes = payload['scope'].split(' ')
214
+ # puts "Token Scopes: #{scopes.join(', ')}"
215
+ # else
216
+ # puts "No scope claim found."
217
+ # end
218
+
219
+
220
+ # Data to be posted to the Solid POD
221
+ # data = {
222
+ # name: 'Mark',
223
+ # age: 30,
224
+ # occupation: 'Software Developer'
225
+ # }
226
+
227
+ # json_data = data.to_json
228
+
229
+
230
+ data = "@prefix ldp: <http://www.w3.org/ns/ldp#>. <> a ldp:Container, ldp:BasicContainer ."
231
+
232
+ # this request matches my proof:
233
+ # it is to the same URL using the same HTTP Method
234
+
235
+ req = RestClient::Request.new({
236
+ method: :put, # htm: 'POST',
237
+ url: 'http://localhost:3000/markw/container8/', # htu: 'http://localhost:3000/markw/'
238
+ headers: {
239
+ "Link": "<http://www.w3.org/ns/ldp/BasicContainer>; rel='type'",
240
+ content_type: 'text/turtle', # if you're sending turtle
241
+ # content_type: 'application/json',
242
+ accept: 'text/turtle',
243
+ # slug: "container8", # I am allowed to select my preferred resource name
244
+ authorization: "Bearer #{access_token}", # Note switch to Bearer auth!
245
+ 'DPoP': proof
246
+ },
247
+ payload: data
248
+ })
249
+ _resp = req.execute
250
+
251
+ payload = {
252
+ htu: 'http://localhost:3000/markw/container8/', # Target URI
253
+ htm: 'GET', # HTTP method
254
+ jti: SecureRandom.uuid, # Unique token ID
255
+ iat: Time.now.to_i # Issued at time
256
+ }
257
+
258
+ # Generate the DPoP proof
259
+ proof = JWT.encode(payload, private_key, 'RS256', header) # PRIVATE key!!
260
+
261
+ req = RestClient::Request.new({
262
+ method: :get,
263
+ url: 'http://localhost:3000/markw/container8/', # htu: 'http://localhost:3000/markw/'
264
+ headers: {
265
+ content_type: 'text/turtle', # if you're sending turtle
266
+ # content_type: 'application/json',
267
+ accept: 'text/turtle',
268
+ authorization: "Bearer #{access_token}", # Note switch to Bearer auth!
269
+ 'DPoP': proof
270
+ },
271
+ payload: data
272
+ })
273
+ resp = req.execute
274
+ puts resp.inspect
275
+ puts resp.headers
276
+ puts resp.body
277
+
278
+
279
+
280
+
metadata ADDED
@@ -0,0 +1,65 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: solid-community-client-simple
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Mark Wilkinson
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2024-08-13 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: A simple client to interact with the SOLID Community Server. NOTA BENE
14
+ - this is NOT a fully functional SOLID client. It does only what I need it to do
15
+ for a specific project. It may or may not be useful to anyone else. It is badly
16
+ documented (if at all!). It has only been tested against the Docker version of the
17
+ Solid Community Server runnign on localhost. Don't blame me if it doesn't work
18
+ for you. Enough said?
19
+ email:
20
+ - mark.wilkinson@upm.es
21
+ executables: []
22
+ extensions: []
23
+ extra_rdoc_files: []
24
+ files:
25
+ - Gemfile
26
+ - Gemfile.lock
27
+ - LICENSE
28
+ - README.md
29
+ - demo.rb
30
+ - lib/account.rb
31
+ - lib/config.rb
32
+ - lib/dpop.rb
33
+ - lib/login.rb
34
+ - lib/solid-community-client-simple.rb
35
+ - lib/solid.rb
36
+ - lib/version.rb
37
+ - test.rb
38
+ homepage: https://github.com/markwilkinson/solid-community-client-simple
39
+ licenses:
40
+ - MIT
41
+ metadata:
42
+ allowed_push_host: https://rubygems.org
43
+ homepage_uri: https://github.com/markwilkinson/solid-community-client-simple
44
+ source_code_uri: https://github.com/markwilkinson/solid-community-client-simple
45
+ changelog_uri: https://github.com/markwilkinson/solid-community-client-simple/blob/master/CHANGELOG.md
46
+ post_install_message:
47
+ rdoc_options: []
48
+ require_paths:
49
+ - lib
50
+ required_ruby_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: 3.0.6
55
+ required_rubygems_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: '0'
60
+ requirements: []
61
+ rubygems_version: 3.2.33
62
+ signing_key:
63
+ specification_version: 4
64
+ summary: A simple client to interact with the SOLID Community Server.
65
+ test_files: []