nomis-api-client 0.0.4 → 0.0.6

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
  SHA1:
3
- metadata.gz: 266bd69217438b142dec36a1aaae5d7d9dbbc19b
4
- data.tar.gz: 5a5c0b5b759c25144d0af22780e2c2f5307e773f
3
+ metadata.gz: 17e5a8b25e6bfe75a4301b5f7824cbc211fd83e6
4
+ data.tar.gz: 80503af449a148c65b7b2c7da2f10cfa38d4a61b
5
5
  SHA512:
6
- metadata.gz: 5664287bec674a1d475109cfee4510335015892122a201a5558e69795696f58a8a70f49e134438f244ff52dd5be0320eae73e5581fff43761599298f231f084c
7
- data.tar.gz: 65ad9e1fe13074f48fbfd980a389eae209b919335bc77169c346bf19e6bb076b81105d788dfaea9e8d2c6972914e02cc3079f60651266c1af4f5c0d39d0449c0
6
+ metadata.gz: 3ac008741956f3b6d65180152683b2db74c66a2eacd30606be6668f2f353c2536711136d02594cac95d213f72d3d6d3149a3a2d61b6ddad145f1854d41a6d4a6
7
+ data.tar.gz: d45d4fd65889266c38d73d3522e5cc20dae26d01372ad50dae5882ed225b020e7be9142f7a7b5c6786d82af61e0713c093f6536d16c286baf1c332aa1592dffa
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 Crown Copyright (Ministry of Justice)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ 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, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,118 @@
1
+ NOMIS API Client (Ruby)
2
+ =======================
3
+
4
+ A minimal client for the [NOMIS API](http://ministryofjustice.github.io/nomis-api/)
5
+
6
+ ## Installation
7
+
8
+ 1. In your Gemfile, add:
9
+ ```ruby
10
+ gem 'nomis-api-client'
11
+ ```
12
+
13
+ 2. From the console:
14
+ ```bash
15
+ bundle install
16
+ ```
17
+
18
+ ## Usage
19
+
20
+ ### Authentication
21
+
22
+ The NOMIS API uses JSON Web Token authentication, and requires all requests to provide a Bearer token in the 'Authentication' header. For example:
23
+
24
+ ```bash
25
+ Authentication: Bearer eyJ(...rest of big long base64-encoded string removed...)LdRw
26
+ ```
27
+
28
+ #### Generating a bearer token automatically
29
+
30
+ The NOMIS API Client gem can generate a suitable token for you, given your Client Key and Client Token.
31
+ You can provide these either directly:
32
+
33
+ ```ruby
34
+ # direct key & token parameters
35
+ NOMIS::API::Get.new(client_key: 'your client key', client_token: 'your client token')
36
+ ```
37
+
38
+ or as paths to local files:
39
+
40
+ ```ruby
41
+ # client key & token file parameters
42
+ NOMIS::API::Get.new(client_key_file: 'path to your client key file', client_token_file: 'path to your client token file')
43
+ ```
44
+
45
+ or as environment variables:
46
+ ```bash
47
+ export NOMIS_API_CLIENT_KEY_FILE=/path/to/your/client/key/file
48
+ export NOMIS_API_CLIENT_TOKEN_FILE=/path/to/your/client/token/file
49
+ ```
50
+
51
+ #### Specifying an explicit bearer token
52
+
53
+ If you'd rather provide an explicit token yourself, you can do that as follows:
54
+ ```ruby
55
+ # explicit auth_token parameter
56
+ NOMIS::API::Get.new(auth_token:'your bearer token')
57
+ ```
58
+
59
+ #### Manually generating a bearer token
60
+
61
+ You can generate a bearer token without making a request as follows:
62
+
63
+ ```ruby
64
+ # Manually generating a bearer token
65
+ NOMIS::API::AuthToken.new(client_key: 'your client key', client_token: 'your client token').bearer_token
66
+
67
+ ### Environment (preprod/prod)
68
+
69
+ The NOMIS API has two endpoints avaiable:
70
+ - production ('prod') at https://noms-api.service.justice.gov.uk/nomisapi/
71
+ - pre-production ('preprod') at https://noms-api-preprod.dsd.io/nomisapi/.
72
+
73
+ To tell the API client to use one or the other, either provide a base_url parameter:
74
+ ```ruby
75
+ # direct base_url parameter
76
+ NOMIS::API::Get.new(base_url: 'https://noms-api.service.justice.gov.uk/nomisapi/', ...)
77
+ ```
78
+
79
+ or the environment variable NOMIS_API_BASE_URL:
80
+ ```ruby
81
+ # base URL environment variable
82
+ export NOMIS_API_BASE_URL='https://noms-api.service.justice.gov.uk/nomisapi/'
83
+ ```
84
+
85
+
86
+ ## Making a request
87
+
88
+ To make an API request, first construct a Get or Post object, providing:
89
+
90
+ - path: the path of the endpoint you are requesting (required)
91
+
92
+ - params: a hash of params you are passing (optional)
93
+ - any authentication parameters (optional) - see ['Authentication'](#Authentication) above
94
+ - base_url: the base URL (optional) - see ['Environment'](#Environment) above
95
+
96
+ ```ruby
97
+ # construct a 'lookup active offender' request
98
+ req = NOMIS::API::Get.new(path: 'lookup/active_offender', params: {noms_id:'A12345BC', date_of_birth:'1966-05-29'})
99
+
100
+ # make the request
101
+ response = req.execute
102
+ ```
103
+
104
+ The response will be a ParsedResponse object, encapsulating the raw response, the HTTP status, and the data parsed as JSON:
105
+
106
+ ```ruby
107
+ => #<NOMIS::API::ParsedResponse:0x007ffbf35a1238 @raw_response=#<Net::HTTPOK 200 OK readbody=true>, @data={"found"=>true, "offender"=>{"id"=>1234567}}>
108
+
109
+ bundle :027 > response.status
110
+ => "200"
111
+
112
+ bundle :028 > response.data
113
+ => {"found"=>true, "offender"=>{"id"=>1820518}}
114
+ ```
115
+
116
+ ## API Documentation
117
+
118
+ For full details on the supported endpoints, see the [API documentation](http://ministryofjustice.github.io/nomis-api/)
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Usage:
4
+ # generate_bearer_token client_key_file client_token_file
5
+ #
6
+
7
+ require 'nomis_api_client_ruby'
8
+
9
+ def usage
10
+ output = <<-END
11
+ Usage:
12
+ generate_bearer_token client_key_file client_token_file
13
+
14
+ Useful environment variables:
15
+
16
+ NOMIS_API_IAT_FUDGE_FACTOR
17
+ - a positive/negative integer adjustment which will be added to
18
+ the current time to generate the 'iat' timestamp for the
19
+ token
20
+ If you recieve an error message from the api saying
21
+ 'iat skew too large', you can provide this to bring your system
22
+ time within +/-10s of the API gateway.
23
+ e.g.
24
+ NOMIS_API_IAT_FUDGE_FACTOR=-5 generate_bearer_token ~/my.key ~/my.token
25
+
26
+ END
27
+ end
28
+
29
+ raise usage unless $ARGV.size == 2
30
+
31
+ token = NOMIS::API::AuthToken.new(client_key_file: $1, client_token_file: $2).bearer_token
32
+ puts token
33
+
34
+
@@ -0,0 +1,86 @@
1
+ require 'base64'
2
+ require 'jwt'
3
+ require 'openssl'
4
+
5
+ module NOMIS
6
+ module API
7
+ # Encapsulates the complexity of generating a JWT bearer token
8
+ class AuthToken
9
+ attr_accessor :client_token, :client_key, :iat_fudge_factor
10
+
11
+ # iat_fudge_factor allows you to correct for time drift between your
12
+ # client and the target server.
13
+ # For instance, if the server time is more than 10s in the future, it
14
+ # will reject any client-generated bearer tokens on the grounds of
15
+ # 'iat skew too large' (the timestamp in your payload is too old)
16
+ # In that case, you can pass an iat_fudge_factor of, say, 5, to generate a
17
+ # timestamp tagged 5s into the future and bring it back within the
18
+ # acceptable range.
19
+ def initialize(params = {})
20
+ self.client_key = OpenSSL::PKey::EC.new( params[:client_key] \
21
+ || default_client_key(params)
22
+ )
23
+ self.client_token = params[:client_token] \
24
+ || default_client_token(params)
25
+
26
+ self.iat_fudge_factor = default_iat_fudge_factor(params)
27
+ end
28
+
29
+ def bearer_token
30
+ validate_keys!
31
+
32
+ auth_token = JWT.encode(payload, client_key, 'ES256')
33
+
34
+ "Bearer #{auth_token}"
35
+ end
36
+
37
+ def payload
38
+ {
39
+ iat: Time.now.to_i + iat_fudge_factor,
40
+ token: client_token
41
+ }
42
+ end
43
+
44
+ # Validate that the supplied private key matches the token's public key.
45
+ # Obviously this step is optional, but when testing locally it's
46
+ # easy to get one's private keys in a muddle, and the API gateway's
47
+ # error message can only say that the generated JWT token does not
48
+ # validate.
49
+ def validate_keys!
50
+ client_pub = OpenSSL::PKey::EC.new client_key
51
+ client_pub.private_key = nil
52
+ client_pub_base64 = Base64.strict_encode64(client_pub.to_der)
53
+
54
+ expected_client_pub = JWT.decode(client_token, nil, nil)[0]['key']
55
+
56
+ unless client_pub_base64 == expected_client_pub
57
+ raise 'Incorrect private key supplied ' \
58
+ + '(does not match public key within token)'
59
+ end
60
+ end
61
+
62
+ protected
63
+
64
+ def default_client_key(params={})
65
+ read_client_key_file(params[:client_key_file] || ENV['NOMIS_API_CLIENT_KEY_FILE'])
66
+ end
67
+
68
+ def default_client_token(params={})
69
+ read_client_key_file(params[:client_token_file] || ENV['NOMIS_API_CLIENT_TOKEN_FILE'])
70
+ end
71
+
72
+ def default_iat_fudge_factor(params={})
73
+ ENV['NOMIS_API_IAT_FUDGE_FACTOR'].to_i || 0
74
+ end
75
+
76
+ def read_client_token_file(path)
77
+ File.open(File.expand_path(path), 'r').read.chomp('')
78
+ end
79
+
80
+ def read_client_key_file(path)
81
+ File.open(File.expand_path(path), 'r').read
82
+ end
83
+
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,47 @@
1
+ require 'uri'
2
+ require 'net/http'
3
+ require 'pp'
4
+ require 'byebug'
5
+
6
+ require 'nomis/api/auth_token'
7
+ require 'nomis/api/parsed_response'
8
+
9
+ module NOMIS
10
+ module API
11
+ # Convenience wrapper around an API call
12
+ # Manages defaulting of params from env vars,
13
+ # and parsing the returned JSON
14
+ class Get
15
+ attr_accessor :params, :auth_token, :base_url, :path
16
+
17
+ def initialize(opts={})
18
+ self.auth_token = opts[:auth_token] || default_auth_token(opts)
19
+ self.base_url = opts[:base_url] || ENV['NOMIS_API_BASE_URL']
20
+ self.params = opts[:params] || {}
21
+ self.path = opts[:path]
22
+ end
23
+
24
+ def execute
25
+ uri = URI.join(base_url, path)
26
+ uri.query = URI.encode_www_form(params)
27
+
28
+ req = Net::HTTP::Get.new(uri)
29
+ req['Authorization'] = auth_token
30
+
31
+ ParsedResponse.new(get_response(req))
32
+ end
33
+
34
+ protected
35
+
36
+ def default_auth_token(params={})
37
+ ENV['NOMIS_API_AUTH_TOKEN'] || NOMIS::API::AuthToken.new(params).bearer_token
38
+ end
39
+
40
+ def get_response(req)
41
+ http = Net::HTTP.new(req.uri.hostname, req.uri.port)
42
+ http.use_ssl = (req.uri.scheme == "https")
43
+ http.request(req)
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,23 @@
1
+ require 'json'
2
+
3
+ module NOMIS
4
+ module API
5
+ # decorates a Net::HTTP response with a data method,
6
+ # which parses the JSON in the response body
7
+ class ParsedResponse
8
+ attr_accessor :raw_response, :body, :status, :data
9
+
10
+ def initialize(raw_response)
11
+ self.raw_response = raw_response
12
+ self.data = JSON.parse(raw_response.body)
13
+ end
14
+
15
+ def body
16
+ raw_response.body
17
+ end
18
+ def status
19
+ raw_response.code
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,56 @@
1
+ require 'uri'
2
+ require 'net/http'
3
+ require 'pp'
4
+
5
+ require 'nomis/api/auth_token'
6
+ require 'nomis/api/parsed_response'
7
+
8
+ module NOMIS
9
+ module API
10
+ # Convenience wrapper around an API call
11
+ # Manages defaulting of params from env vars,
12
+ # and parsing the returned JSON
13
+ class Post
14
+ attr_accessor :params, :auth_token, :base_url, :path
15
+
16
+ def initialize(opts={})
17
+ self.auth_token = opts[:auth_token] || default_auth_token(opts)
18
+ self.base_url = opts[:base_url] || ENV['NOMIS_API_BASE_URL']
19
+ self.params = opts[:params]
20
+ self.path = opts[:path]
21
+ end
22
+
23
+ def execute
24
+ uri = URI.join(base_url, path)
25
+
26
+ req = Net::HTTP::Post.new(uri)
27
+ req['Authorization'] = auth_token
28
+ req['Accept'] = 'application/json, */*'
29
+ req['Content-type'] = 'application/json'
30
+
31
+ ParsedResponse.new(post_response(req))
32
+ end
33
+
34
+ protected
35
+
36
+ def default_auth_token(params={})
37
+ ENV['NOMIS_API_AUTH_TOKEN'] || NOMIS::API::AuthToken.new(params).bearer_token
38
+ end
39
+
40
+
41
+ def post_response(req)
42
+ http = Net::HTTP.new(req.uri.hostname, req.uri.port)
43
+ http.use_ssl = (req.uri.scheme == 'https')
44
+ req.body = params.to_json
45
+ http.request(req)
46
+ end
47
+
48
+ def stringify_hash(data)
49
+ h={}
50
+ data.each{|k,v| h[k.to_s] = v }
51
+ h
52
+ end
53
+
54
+ end
55
+ end
56
+ end
data/lib/nomis/api.rb ADDED
@@ -0,0 +1,4 @@
1
+ require 'nomis/api/auth_token'
2
+ require 'nomis/api/get'
3
+ require 'nomis/api/parsed_response'
4
+ require 'nomis/api/post'
@@ -1 +1 @@
1
- require_relative './nomis/api'
1
+ require 'nomis/api'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nomis-api-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Al Davidson
@@ -40,10 +40,19 @@ dependencies:
40
40
  version: '0'
41
41
  description: A minimal Ruby client for the [NOMIS API](http://ministryofjustice.github.io/nomis-api/)
42
42
  email: alistair.davidson@digital.justice.gov.uk
43
- executables: []
43
+ executables:
44
+ - generate_bearer_token
44
45
  extensions: []
45
46
  extra_rdoc_files: []
46
47
  files:
48
+ - LICENSE
49
+ - README.md
50
+ - bin/generate_bearer_token
51
+ - lib/nomis/api.rb
52
+ - lib/nomis/api/auth_token.rb
53
+ - lib/nomis/api/get.rb
54
+ - lib/nomis/api/parsed_response.rb
55
+ - lib/nomis/api/post.rb
47
56
  - lib/nomis_api_client_ruby.rb
48
57
  homepage: http://rubygems.org/gems/nomis_api_client_ruby
49
58
  licenses: