distributed-press-api-client 0.2.4 → 0.3.0rc1

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: 2552a7612cdbbf7fe48afacf1327764e8f3f25a888d7bfd9f852b54a3fb5fa67
4
+ data.tar.gz: 59b252609b9d442aee543d1e5eabb642a6321c749c4936d35ad5d5e40038df3e
5
5
  SHA512:
6
- metadata.gz: 0a348613279cf9cd9753561136bcebaae7729ff112ad20be435e966b62336a899da72fcd5fd736318103174442b1e931ba7c566b01fdcea876954a6a2e922785
7
- data.tar.gz: 9f2504559bf4c6b7542ec50f649dbeb290a8fe8aa2add595a26a84ef45015d38801526a9d765ba7269a26f2f68f00373346951cbd3b165b684431c39383989ad
6
+ metadata.gz: 7d81f9c8bbdfa947f9c08b20e9e6214c0b1852987384eb7b5d4c5c757d91914f83812b781bf567122ad7790403137c3c1a8459f19ef5331e1b90bf7910887cbd
7
+ data.tar.gz: 909d13d6fba811597be93e00aecd24cb6961b65baa7cd0b669e7029fbdb34b9dc7e899dee6dfc8b2ec4575c6a4599d746b9a3182aa6ac50ad509ed51d0dbb5f2
@@ -0,0 +1,181 @@
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
+ # @param :logger [Logger]
49
+ def initialize(url: 'https://social.distributed.press', public_key_url:, private_key_pem: nil, key_size: 2048, logger: nil)
50
+ self.class.default_options[:base_uri] = @url = HTTParty.normalize_base_uri(url)
51
+ self.class.default_options[:logger] = logger
52
+ self.class.default_options[:log_level] = :debug
53
+
54
+ @public_key_url = public_key_url
55
+ @key_size = key_size
56
+ @private_key_pem = private_key_pem
57
+ end
58
+
59
+ # POST request
60
+ #
61
+ # @todo Use DRY-schemas
62
+ # @param endpoint [String]
63
+ # @param body [Hash]
64
+ # @return [Hash]
65
+ def post(endpoint:, body:)
66
+ body = body.to_json
67
+ headers = default_headers
68
+
69
+ add_request_specific_headers! headers, 'post', endpoint
70
+ checksum_body! body, headers
71
+ sign_headers! headers
72
+
73
+ self.class.post(endpoint, body: body, headers: headers)
74
+ end
75
+
76
+ # Loads or generates a private key
77
+ #
78
+ # @return [OpenSSL::PKey::RSA]
79
+ def private_key
80
+ @private_key ||= OpenSSL::PKey::RSA.new(@private_key_pem || key_size)
81
+ end
82
+
83
+ # Public key
84
+ #
85
+ # @return [OpenSSL::PKey::RSA]
86
+ def public_key
87
+ private_key.public_key
88
+ end
89
+
90
+ # Host
91
+ #
92
+ # @return [String]
93
+ def host
94
+ @host ||= URI.parse(url).host
95
+ end
96
+
97
+ private
98
+
99
+ def default_headers
100
+ {
101
+ 'User-Agent' => "DistributedPress/#{DistributedPress::VERSION}",
102
+ 'Content-Type' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
103
+ 'Host' => host
104
+ }
105
+ end
106
+
107
+ # HTTP Signatures
108
+ #
109
+ # @see {https://docs.joinmastodon.org/spec/security/}
110
+ # @param :headers [Hash]
111
+ def sign_headers!(headers)
112
+ headers['Signature'] = {
113
+ 'keyId' => public_key_url,
114
+ 'algorithm' => ALGORITHM,
115
+ 'headers' => signable_headers(headers),
116
+ 'signature' => signed_headers(headers)
117
+ }.map do |key, value|
118
+ "#{key}=\"#{value}\""
119
+ end.join(',')
120
+
121
+ headers.delete REQUEST_TARGET
122
+
123
+ nil
124
+ end
125
+
126
+ # List of headers to be signed, removing headers that don't
127
+ # exist on the request.
128
+ #
129
+ # @return [String]
130
+ def signable_headers(headers)
131
+ (SIGNABLE_HEADERS & headers.keys).join(' ').downcase
132
+ end
133
+
134
+ # Sign headers
135
+ #
136
+ # @param :headers [Hash]
137
+ # @return [String]
138
+ def signed_headers(headers)
139
+ Base64.strict_encode64(
140
+ private_key.sign(
141
+ OpenSSL::Digest.new('SHA256'),
142
+ signature_content(headers)
143
+ )
144
+ )
145
+ end
146
+
147
+ # Generates a string to be signed
148
+ #
149
+ # @param :headers [Hash]
150
+ # @return [String]
151
+ def signature_content(headers)
152
+ headers.slice(*SIGNABLE_HEADERS).map do |key, value|
153
+ "#{key.downcase}: #{value}"
154
+ end.join("\n")
155
+ end
156
+
157
+ # Generate a checksum for the request body
158
+ #
159
+ # @param :body [String]
160
+ # @param :headers [Hash]
161
+ def checksum_body!(body, headers)
162
+ headers['Digest'] = "SHA-256=#{Digest::SHA256.base64digest body}"
163
+
164
+ nil
165
+ end
166
+
167
+ # Headers specific to a single request
168
+ #
169
+ # @param :headers [Hash] Headers
170
+ # @param :verb [String] HTTP verb
171
+ # @param :endpoint [String] Path
172
+ def add_request_specific_headers!(headers, verb, endpoint)
173
+ headers['Date'] = Time.now.utc.httpdate
174
+ headers[REQUEST_TARGET] = "#{verb} #{endpoint}"
175
+
176
+ nil
177
+ end
178
+ end
179
+ end
180
+ end
181
+ 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.0rc1'
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.0rc1
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-31 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: