solid-community-client-simple 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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: []