distributed-press-api-client 0.2.4 → 0.3.0rc0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9655c1c4310b5c3d7b121b9984ba13eff6989deaf1056bfe3a0dd8b2c0ae493b
4
- data.tar.gz: 4933a217789771803d88f9c6b92d09119846730ab61543e2789c9a1348796ef9
3
+ metadata.gz: 25fa00e1c0a4044f03953f30018830d9cc3e934445be8bd1e72004e0c1f5b1bf
4
+ data.tar.gz: 0ddb5921a185fb2172669957b3a22d8619fa6a0d2d53fe33a5bb10372706a1b4
5
5
  SHA512:
6
- metadata.gz: 0a348613279cf9cd9753561136bcebaae7729ff112ad20be435e966b62336a899da72fcd5fd736318103174442b1e931ba7c566b01fdcea876954a6a2e922785
7
- data.tar.gz: 9f2504559bf4c6b7542ec50f649dbeb290a8fe8aa2add595a26a84ef45015d38801526a9d765ba7269a26f2f68f00373346951cbd3b165b684431c39383989ad
6
+ metadata.gz: 895ce13e70b28366fa412be512eeb075a179239a5eb7e45e302b58147baa55bccaba37505a93fdc97cef127d63faa655163eaa33c740bf2e5c334a3ab7879a5a
7
+ data.tar.gz: dcd6f8f7a6471ae979199de943350cff2b37ad5c148dbe8d143c6234cbb93e48dbc83b2794476bb0e7d193aff828dd591f96e747822bd085d89c9ef9c0a995e7
@@ -0,0 +1,178 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'httparty'
4
+ require 'openssl'
5
+ require 'time'
6
+ require 'digest/sha2'
7
+ require 'uri'
8
+
9
+ class DistributedPress
10
+ module V1
11
+ module Social
12
+ # Social Distributed Press APIv1 client
13
+ #
14
+ # Inspired by Mastodon's Request
15
+ #
16
+ # @todo It'd be nice to implement this on HTTParty itself
17
+ # @see {https://github.com/mastodon/mastodon/blob/main/app/lib/request.rb}
18
+ class Client
19
+ include ::HTTParty
20
+
21
+ # Signing algorithm
22
+ #
23
+ # @todo is it possible to use other algorithms?
24
+ ALGORITHM = 'rsa-sha256'
25
+ # Required by HTTP Signatures
26
+ REQUEST_TARGET = '(request-target)'
27
+ # Headers included in the signature
28
+ SIGNABLE_HEADERS = [REQUEST_TARGET, 'Host', 'Date', 'Digest']
29
+
30
+ # API URL
31
+ # @return [String]
32
+ attr_reader :url
33
+
34
+ # RSA key size
35
+ #
36
+ # @return [Integer]
37
+ attr_reader :key_size
38
+
39
+ # Public key URL
40
+ #
41
+ # @return [String]
42
+ attr_reader :public_key_url
43
+
44
+ # @param :url [String] Social Distributed Press URL
45
+ # @param :public_key [String] URL where public key is available
46
+ # @param :private_key_pem [String] RSA Private key, PEM encoded
47
+ # @param :key_size [Integer]
48
+ def initialize(url: 'https://social.distributed.press', public_key_url:, private_key_pem: nil, key_size: 2048)
49
+ self.class.default_options[:base_uri] = @url = HTTParty.normalize_base_uri(url)
50
+
51
+ @public_key_url = public_key_url
52
+ @key_size = key_size
53
+ @private_key_pem = private_key_pem
54
+ end
55
+
56
+ # POST request
57
+ #
58
+ # @todo Use DRY-schemas
59
+ # @param endpoint [String]
60
+ # @param body [Hash]
61
+ # @return [Hash]
62
+ def post(endpoint:, body:)
63
+ body = body.to_json
64
+ headers = default_headers
65
+
66
+ add_request_specific_headers! headers, 'post', endpoint
67
+ checksum_body! body, headers
68
+ sign_headers! headers
69
+
70
+ self.class.post(endpoint, body: body, headers: headers)
71
+ end
72
+
73
+ # Loads or generates a private key
74
+ #
75
+ # @return [OpenSSL::PKey::RSA]
76
+ def private_key
77
+ @private_key ||= OpenSSL::PKey::RSA.new(@private_key_pem || key_size)
78
+ end
79
+
80
+ # Public key
81
+ #
82
+ # @return [OpenSSL::PKey::RSA]
83
+ def public_key
84
+ private_key.public_key
85
+ end
86
+
87
+ # Host
88
+ #
89
+ # @return [String]
90
+ def host
91
+ @host ||= URI.parse(url).host
92
+ end
93
+
94
+ private
95
+
96
+ def default_headers
97
+ {
98
+ 'User-Agent' => "DistributedPress/#{DistributedPress::VERSION}",
99
+ 'Content-Type' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
100
+ 'Host' => host
101
+ }
102
+ end
103
+
104
+ # HTTP Signatures
105
+ #
106
+ # @see {https://docs.joinmastodon.org/spec/security/}
107
+ # @param :headers [Hash]
108
+ def sign_headers!(headers)
109
+ headers['Signature'] = {
110
+ 'keyId' => public_key_url,
111
+ 'algorithm' => ALGORITHM,
112
+ 'headers' => signable_headers(headers),
113
+ 'signature' => signed_headers(headers)
114
+ }.map do |key, value|
115
+ "#{key}=\"#{value}\""
116
+ end.join(',')
117
+
118
+ headers.delete REQUEST_TARGET
119
+
120
+ nil
121
+ end
122
+
123
+ # List of headers to be signed, removing headers that don't
124
+ # exist on the request.
125
+ #
126
+ # @return [String]
127
+ def signable_headers(headers)
128
+ (SIGNABLE_HEADERS & headers.keys).join(' ').downcase
129
+ end
130
+
131
+ # Sign headers
132
+ #
133
+ # @param :headers [Hash]
134
+ # @return [String]
135
+ def signed_headers(headers)
136
+ Base64.strict_encode64(
137
+ private_key.sign(
138
+ OpenSSL::Digest.new('SHA256'),
139
+ signature_content(headers)
140
+ )
141
+ )
142
+ end
143
+
144
+ # Generates a string to be signed
145
+ #
146
+ # @param :headers [Hash]
147
+ # @return [String]
148
+ def signature_content(headers)
149
+ headers.slice(*SIGNABLE_HEADERS).map do |key, value|
150
+ "#{key.downcase}: #{value}"
151
+ end.join("\n")
152
+ end
153
+
154
+ # Generate a checksum for the request body
155
+ #
156
+ # @param :body [String]
157
+ # @param :headers [Hash]
158
+ def checksum_body!(body, headers)
159
+ headers['Digest'] = "SHA-256=#{Digest::SHA256.base64digest body}"
160
+
161
+ nil
162
+ end
163
+
164
+ # Headers specific to a single request
165
+ #
166
+ # @param :headers [Hash] Headers
167
+ # @param :verb [String] HTTP verb
168
+ # @param :endpoint [String] Path
169
+ def add_request_specific_headers!(headers, verb, endpoint)
170
+ headers['Date'] = Time.now.utc.httpdate
171
+ headers[REQUEST_TARGET] = "#{verb} #{endpoint}"
172
+
173
+ nil
174
+ end
175
+ end
176
+ end
177
+ end
178
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'social/client'
4
+
5
+ class DistributedPress
6
+ module V1
7
+ # Social Distributed Press APIv1
8
+ module Social
9
+ end
10
+ end
11
+ end
@@ -3,5 +3,5 @@
3
3
  # API client
4
4
  class DistributedPress
5
5
  # Version
6
- VERSION = '0.2.4'
6
+ VERSION = '0.3.0rc0'
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: distributed-press-api-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.4
4
+ version: 0.3.0rc0
5
5
  platform: ruby
6
6
  authors:
7
7
  - f
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-05-30 00:00:00.000000000 Z
11
+ date: 2023-08-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: addressable
@@ -277,6 +277,8 @@ files:
277
277
  - lib/distributed_press/v1/schemas/token_header.rb
278
278
  - lib/distributed_press/v1/schemas/token_payload.rb
279
279
  - lib/distributed_press/v1/schemas/update_site.rb
280
+ - lib/distributed_press/v1/social.rb
281
+ - lib/distributed_press/v1/social/client.rb
280
282
  - lib/distributed_press/v1/token.rb
281
283
  - lib/distributed_press/version.rb
282
284
  - lib/jekyll-distributed-press-v0.rb
@@ -307,9 +309,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
307
309
  version: '2.7'
308
310
  required_rubygems_version: !ruby/object:Gem::Requirement
309
311
  requirements:
310
- - - ">="
312
+ - - ">"
311
313
  - !ruby/object:Gem::Version
312
- version: '0'
314
+ version: 1.3.1
313
315
  requirements: []
314
316
  rubygems_version: 3.3.26
315
317
  signing_key: