adobe_doc_api 0.1.4 → 0.3.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c788e21a963142ba23c04928654534ab9d9641f4ed7863245f7af3f3bda19b3c
4
- data.tar.gz: b7392b28f2a5024513687e22c4769c50660525e36290cb6e829c3b8a3e94a81f
3
+ metadata.gz: 88a17a7824cbc56df0cc3b1a4fe8ce43e51ac80d2f49c8aeb2bf3bd3d3afc4b6
4
+ data.tar.gz: 4ab99d5fd436027b5a8dfa58610969ec6955cd592474e55ac0285def3c4f62ce
5
5
  SHA512:
6
- metadata.gz: 1e2e36e5893bc031cb15b97eaa4f0de0c789c09831cb5724c05fbced6c32c3662dcdd4295539391be6ba54cf1fceb51cd1c92a4ed06e49ab2a00697de89c31cd
7
- data.tar.gz: 00f64a23bed62d1254ba091bcb5035db6432bc47f3976a31697b5702dd3f58c5fc0c1d1ea9bedd5b3e2c7401cb31bd02bdd8b64380249401679a04ad688fd079
6
+ metadata.gz: a0298bd962026cc2c072239d0a8dda4fbca883b94389f40daa5efc843a2256b239a52bb3c21b4900c3098338b10f78e47d22c52745527b1df739ea80c46bcd3e
7
+ data.tar.gz: e8c44532b30fe23d98076fa67f5ce0b49d9def18f5593142fbed19f75410025d5461d7f87604a48f07f0b1a458f19b4f64283b49bfb77f666b288ba2ae6e5122
data/CHANGELOG.md CHANGED
@@ -1,4 +1,9 @@
1
- ## [Unreleased]
1
+ ## [0.3.0] - 2023-06-20
2
+ - Migrated authentication from JWT to OAuth server-to-server
3
+ - Updated configuration to accept OAuth scopes
4
+
5
+ ## [0.2.1] - 2023-06-20
6
+ - Updates to Adobe PDF Services API
2
7
 
3
8
  ## [0.1.4] - 2021-12-30
4
9
  - Fix issue with parsing file boundary that included \r\n
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # AdobeDocApi
2
2
 
3
- This is still a work in progress. Use at your own risk.
3
+ This is still a work in progress and breaking changes may be made, but I will do my best to update the README when features have been changed.
4
4
 
5
5
  ## Installation
6
6
 
@@ -18,38 +18,39 @@ Or install it yourself as:
18
18
 
19
19
  $ gem install adobe_doc_api
20
20
 
21
- ## Configuration
22
- * Configuration can be overridden if you need by passing the values to AdobeDocApi::Client.new
21
+ ## Configuration
23
22
  ```ruby
24
23
  AdobeDocApi.configure do |config|
25
- config.client_id = nil
26
- config.client_secret = nil
27
- config.org_id = nil
28
- config.tech_account_id = nil
29
- config.private_key_path = nil
24
+ config.client_id = nil
25
+ config.client_secret = nil
26
+ config.scopes = nil
27
+ end
28
+ ```
29
+ ### Recommended configuration if using Rails 6+
30
+ ```ruby
31
+ AdobeDocApi.configure do |config|
32
+ config.client_id = Rails.application.credentials.dig(:adobe_doc, :client_id)
33
+ config.client_secret = Rails.application.credentials.dig(:adobe_doc, :client_secret)
34
+ config.scopes = Rails.application.credentials.dig(:adobe_doc, :scopes)
30
35
  end
31
36
  ```
32
-
33
37
  ## Usage
34
38
 
35
39
  ```ruby
36
- key_path = "../full_path_to/private.key"
37
40
  template_path = "../full_path_to/disclosure.docx"
38
41
  output_path = "../full_path_to_output/output.docx"
39
42
  json_data = { 'DocTag': 'Value', 'DocTag2': 'Value2'}
40
43
 
41
44
  client = AdobeDocApi::Client.new
42
-
43
- # Without configuration you must pass these values
44
- # client = AdobeDocApi::Client.new(private_key: key_path, client_id: ENV['adobe_client_id'], client_secret: ENV['adobe_client_secret']org_id: ENV['adobe_org_id'], tech_account_id: ENV['adobe_tech_account_id'], access_token: nil)
45
-
46
45
  client.submit(json: json_data, template: template_path, output: output_path)
47
46
  # returns true or false if file was saved to output_path
48
47
  ```
49
-
50
- ## Todo
51
- - [x] Add multipart parsing to improve saving the file from the response
52
- - [ ] Add documentation
48
+ ### Usage without configuration
49
+ ```ruby
50
+ client = AdobeDocApi::Client.new(client_id: adobe_client_id,
51
+ client_secret: adobe_client_secret,
52
+ scopes: adobe_scopes)
53
+ ```
53
54
 
54
55
  ## Contributing
55
56
 
@@ -1,136 +1,176 @@
1
- require "faraday"
2
- require "faraday_middleware"
3
- require "jwt"
4
- require "openssl"
1
+ require "net/http"
2
+ require "json"
5
3
 
6
4
  module AdobeDocApi
7
5
  class Client
8
- JWT_URL = "https://ims-na1.adobelogin.com/ims/exchange/jwt/".freeze
9
- API_ENDPOINT_URL = "https://cpf-ue1.adobe.io".freeze
6
+ OAUTH_URL = "https://ims-na1.adobelogin.com/ims/token/v3".freeze
7
+ API_ENDPOINT_URL = "https://pdf-services-ue1.adobe.io/operation/documentgeneration"
8
+ attr_reader :location_url, :raw_response, :client_id, :client_secret, :scopes
10
9
 
11
- attr_reader :access_token, :location_url, :raw_response, :client_id, :client_secret, :org_id, :tech_account_id
12
-
13
- def initialize(private_key: nil, client_id: nil, client_secret: nil, org_id: nil, tech_account_id: nil, access_token: nil)
14
- # TODO Need to validate if any params are missing and return error
10
+ def initialize(client_id: nil, client_secret: nil, scopes: nil)
15
11
  @client_id = client_id || AdobeDocApi.configuration.client_id
16
12
  @client_secret = client_secret || AdobeDocApi.configuration.client_secret
17
- @org_id = org_id || AdobeDocApi.configuration.org_id
18
- @tech_account_id = tech_account_id || AdobeDocApi.configuration.tech_account_id
19
- @private_key_path = private_key || AdobeDocApi.configuration.private_key_path
13
+ @scopes = scopes || AdobeDocApi.configuration.scopes
20
14
  @location_url = nil
21
15
  @output_file_path = nil
22
16
  @raw_response = nil
23
- @access_token = access_token || get_access_token(@private_key_path)
17
+ @access_token = get_access_token
24
18
  end
25
19
 
26
- def get_access_token(private_key)
27
- jwt_payload = {
28
- "iss" => @org_id,
29
- "sub" => @tech_account_id,
30
- "https://ims-na1.adobelogin.com/s/ent_documentcloud_sdk" => true,
31
- "aud" => "https://ims-na1.adobelogin.com/c/#{@client_id}",
32
- "exp" => (Time.now.utc + 60).to_i
33
- }
20
+ def submit(json:, template:, output:)
21
+ @output = output
22
+ @asset_id, upload_uri = upload_presigned_uri
23
+ upload_asset(upload_uri, template: template)
24
+ document_generation(json: json)
25
+ end
34
26
 
35
- rsa_private = OpenSSL::PKey::RSA.new File.read(private_key)
27
+ private
36
28
 
37
- jwt_token = JWT.encode jwt_payload, rsa_private, "RS256"
29
+ def get_access_token
30
+ url = URI(OAUTH_URL)
31
+ https = Net::HTTP.new(url.host, url.port)
32
+ https.use_ssl = true
33
+ request = Net::HTTP::Post.new(url)
34
+ request["Content-Type"] = "application/x-www-form-urlencoded"
35
+ request.body = "grant_type=client_credentials&client_id=#{@client_id}&client_secret=#{@client_secret}&scope=#{@scopes}"
36
+ response = https.request(request)
37
+ if response.code.to_i != 200
38
+ raise Error.new(status_code: response.code, msg: "Failed to get access token: #{response.body}")
39
+ else
40
+ puts "Access token retrieved successfully"
41
+ JSON.parse(response.body)["access_token"]
42
+ end
43
+ end
38
44
 
39
- connection = Faraday.new do |conn|
40
- conn.response :json, content_type: "application/json"
45
+ def upload_presigned_uri
46
+
47
+ url = URI("https://pdf-services-ue1.adobe.io/assets")
48
+ https = Net::HTTP.new(url.host, url.port)
49
+ https.use_ssl = true
50
+ request = Net::HTTP::Post.new(url)
51
+ request["Content-Type"] = "application/json"
52
+ request["X-API-Key"] = @client_id
53
+ request["Authorization"] = "bearer #{@access_token}"
54
+ request.body ='{"mediaType": "application/vnd.openxmlformats-officedocument.wordprocessingml.document"}'
55
+ response = https.request(request)
56
+
57
+ if response.code.to_i != 200
58
+ raise Error.new(status_code: response.code, msg: "Failed to create asset: #{response.body}")
59
+ else
60
+ puts "Asset created successfully"
61
+ response_body = JSON.parse(response.body)
62
+ asset_id = response_body["assetID"]
63
+ upload_uri = response_body["uploadUri"]
64
+ return asset_id, upload_uri
41
65
  end
42
- response = connection.post JWT_URL do |req|
43
- req.params["client_id"] = @client_id
44
- req.params["client_secret"] = @client_secret
45
- req.params["jwt_token"] = jwt_token
66
+
67
+ end
68
+
69
+ def upload_asset(upload_uri, template:)
70
+
71
+ # Upload the template to the presigned URI
72
+ url = URI(upload_uri)
73
+ https = Net::HTTP.new(url.host, url.port)
74
+ https.use_ssl = true
75
+ request = Net::HTTP::Put.new(url)
76
+ request["Content-Type"] = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
77
+ request.body = File.read(template)
78
+ response = https.request(request)
79
+ if response.code.to_i != 200
80
+ raise Error.new(status_code: response.code, msg: "Failed to upload template: #{response.body}")
81
+ else
82
+ puts "Template uploaded successfully"
46
83
  end
47
84
 
48
- return response.body["access_token"]
49
85
  end
50
86
 
51
- def submit(json:, template:, output:)
52
- @output = output
53
- output_format = /docx/.match?(File.extname(@output)) ? "application/vnd.openxmlformats-officedocument.wordprocessingml.document" : "application/pdf"
54
-
55
- content_request = {
56
- "cpf:engine": {
57
- "repo:assetId": "urn:aaid:cpf:Service-52d5db6097ed436ebb96f13a4c7bf8fb"
58
- },
59
- "cpf:inputs": {
60
- documentIn: {
61
- "dc:format": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
62
- "cpf:location": "InputFile0"
63
- },
64
- params: {
65
- "cpf:inline": {
66
- outputFormat: File.extname(@output).delete("."),
67
- jsonDataForMerge: json
68
- }
69
- }
70
- },
71
- "cpf:outputs": {
72
- documentOut: {
73
- "dc:format": output_format.to_s,
74
- "cpf:location": "multipartLabel"
75
- }
76
- }
87
+ def document_generation(json:)
88
+ # Document Generation
89
+ url = URI("https://pdf-services-ue1.adobe.io/operation/documentgeneration")
90
+ https = Net::HTTP.new(url.host, url.port)
91
+ https.use_ssl = true
92
+ request = Net::HTTP::Post.new(url)
93
+ request["Content-Type"] = "application/json"
94
+ request["X-API-Key"] = @client_id
95
+ request["Authorization"] = "Bearer #{@access_token}"
96
+
97
+ request.body = {"assetID": @asset_id,
98
+ "outputFormat": "docx",
99
+ "jsonDataForMerge": json
77
100
  }.to_json
78
101
 
79
- connection = Faraday.new API_ENDPOINT_URL do |conn|
80
- conn.request :authorization, "Bearer", @access_token
81
- conn.headers["x-api-key"] = @client_id
82
- conn.request :multipart
83
- conn.request :url_encoded
84
- conn.response :json, content_type: "application/json"
102
+ response = https.request(request)
103
+
104
+ if response.code.to_i != 201
105
+ raise Error.new(status_code: response.code, msg: "Failed to submit document generation request: #{response.body}")
106
+ else
107
+ status_url = response.header["location"]
108
+ puts "Document Generation submitted successfully"
85
109
  end
86
110
 
87
- payload = {"contentAnalyzerRequests" => content_request}
88
- payload[:InputFile0] = Faraday::FilePart.new(template, "application/vnd.openxmlformats-officedocument.wordprocessingml.document")
89
- res = connection.post("/ops/:create", payload)
90
- status_code = res.body["cpf:status"]["status"].to_i
91
- @location_url = res.headers["location"]
92
- raise Error.new(status_code: status_code, msg: res.body["cpf:status"]) unless status_code == 202
93
- poll_for_file(@location_url)
111
+ # Start polling for the status of the document generation
112
+ poll_status(status_url)
94
113
  end
95
114
 
96
- private
97
-
98
- def poll_for_file(url)
99
- connection = Faraday.new do |conn|
100
- conn.request :authorization, "Bearer", @access_token
101
- conn.headers["x-api-key"] = @client_id
102
- end
103
- counter = 0
115
+ def poll_status(status_url, timeout: 30)
116
+ # Poll for the generated document
117
+ download_uri = nil
104
118
  loop do
105
- sleep(6)
106
- response = connection.get(url)
107
- counter += 1
108
- if response.body.include?('"cpf:status":{"completed":true,"type":"","status":200}')
109
- @raw_response = response
110
- return write_to_file(response.body)
119
+ timeout -= 1
120
+ break if timeout <= 0
121
+
122
+ # Wait for 5 seconds before checking the status
123
+ url = URI(status_url)
124
+ https = Net::HTTP.new(url.host, url.port)
125
+ https.use_ssl = true
126
+ request = Net::HTTP::Get.new(url)
127
+ request["Content-Type"] = "application/json"
128
+ request["X-API-Key"] = @client_id
129
+ request["Authorization"] = "Bearer #{@access_token}"
130
+ response = https.request(request)
131
+
132
+ if response.code.to_i != 200
133
+ raise Error.new(status_code: response.code, msg: "Failed to check document generation status: #{response.body}")
134
+ end
135
+
136
+ response_body = JSON.parse(response.body)
137
+ puts "Current status: #{response_body['status']}"
138
+ if response_body["status"] == "done"
139
+ download_uri = response_body["asset"]["downloadUri"]
140
+ break
141
+ elsif response_body["status"] == "failed"
142
+ raise Error.new(status_code: response.code, msg: "Document generation failed: #{response.body}")
111
143
  else
112
- status = JSON.parse(response.body)["cpf:status"]
113
- raise Error.new(status_code: status["status"], msg: status) if status["status"] != 202
144
+ puts "Document generation in progress..."
114
145
  end
115
- break if counter > 10
116
- rescue => e
117
- # Raise other exceptions
118
- raise(e)
146
+ sleep 1
119
147
  end
120
- end
121
148
 
122
- def write_to_file(response_body)
123
- line_index = []
124
- lines = response_body.split("\r\n")
125
- lines.each_with_index do |line, i|
126
- next if line.include?("--Boundary_") || line.match?(/^Content-(Type|Disposition):/) || line.empty? || JSON.parse(line.force_encoding("UTF-8").to_s)
127
- rescue
128
- line_index << i
149
+ # If the download URI is available, proceed to download the document
150
+ if download_uri
151
+ download_output(download_uri: download_uri)
152
+ else
153
+ raise Error.new(status_code: response.code, msg: "Document generation not completed: #{response.body}")
129
154
  end
130
155
 
131
- return true if File.open(@output, "wb") { |f| f.write lines.map.with_index { |l, i| lines.at(i) if line_index.include?(i) }.compact.join("\r\n")}
132
- false
133
156
  end
134
157
 
158
+ def download_output(download_uri:)
159
+ # Finally, download the generated document
160
+ url = URI(download_uri)
161
+ https = Net::HTTP.new(url.host, url.port)
162
+ https.use_ssl = true
163
+ request = Net::HTTP::Get.new(url)
164
+ response = https.request(request)
165
+ if response.code.to_i != 200
166
+ raise Error.new(status_code: response.code, msg: "Failed to download document: #{response.body}")
167
+ else
168
+ if File.open(@output, "wb") { |f| f.write response.body }
169
+ puts "Document saved successfully to #{@output}"
170
+ return true
171
+ end
172
+ end
173
+ end
135
174
  end
175
+
136
176
  end
@@ -1,13 +1,11 @@
1
1
  module AdobeDocApi
2
2
  class Configuration
3
- attr_accessor :client_id, :client_secret, :org_id, :tech_account_id, :private_key_path
3
+ attr_accessor :client_id, :client_secret, :scopes
4
4
 
5
5
  def initialize
6
6
  @client_id = nil
7
- @client_sercret = nil
8
- @org_id = nil
9
- @tech_account_id = nil
10
- @private_key_path = nil
7
+ @client_secret = nil
8
+ @scopes = nil
11
9
  end
12
10
  end
13
11
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AdobeDocApi
4
- VERSION = "0.1.4"
4
+ VERSION = "0.3.0"
5
5
  end
metadata CHANGED
@@ -1,71 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: adobe_doc_api
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Sonnier
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-01-04 00:00:00.000000000 Z
11
+ date: 2025-06-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: faraday
14
+ name: json
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '1.8'
19
+ version: '2.0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '1.8'
27
- - !ruby/object:Gem::Dependency
28
- name: faraday_middleware
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - "~>"
32
- - !ruby/object:Gem::Version
33
- version: '1.2'
34
- type: :runtime
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - "~>"
39
- - !ruby/object:Gem::Version
40
- version: '1.2'
41
- - !ruby/object:Gem::Dependency
42
- name: jwt
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - "~>"
46
- - !ruby/object:Gem::Version
47
- version: 2.3.0
48
- type: :runtime
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - "~>"
53
- - !ruby/object:Gem::Version
54
- version: 2.3.0
55
- - !ruby/object:Gem::Dependency
56
- name: openssl
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - "~>"
60
- - !ruby/object:Gem::Version
61
- version: 2.2.1
62
- type: :runtime
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - "~>"
67
- - !ruby/object:Gem::Version
68
- version: 2.2.1
26
+ version: '2.0'
69
27
  description: Ruby interface for Adobe PDF Services API Document Generation
70
28
  email:
71
29
  - christopher.sonnier@gmail.com
@@ -101,14 +59,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
101
59
  requirements:
102
60
  - - ">="
103
61
  - !ruby/object:Gem::Version
104
- version: 2.6.0
62
+ version: 3.0.0
105
63
  required_rubygems_version: !ruby/object:Gem::Requirement
106
64
  requirements:
107
65
  - - ">="
108
66
  - !ruby/object:Gem::Version
109
67
  version: '0'
110
68
  requirements: []
111
- rubygems_version: 3.2.32
69
+ rubygems_version: 3.4.10
112
70
  signing_key:
113
71
  specification_version: 4
114
72
  summary: Ruby interface for Adobe PDF Services API Document Generation