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 +4 -4
- data/LICENSE +20 -0
- data/README.md +118 -0
- data/bin/generate_bearer_token +34 -0
- data/lib/nomis/api/auth_token.rb +86 -0
- data/lib/nomis/api/get.rb +47 -0
- data/lib/nomis/api/parsed_response.rb +23 -0
- data/lib/nomis/api/post.rb +56 -0
- data/lib/nomis/api.rb +4 -0
- data/lib/nomis_api_client_ruby.rb +1 -1
- metadata +11 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 17e5a8b25e6bfe75a4301b5f7824cbc211fd83e6
|
4
|
+
data.tar.gz: 80503af449a148c65b7b2c7da2f10cfa38d4a61b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
@@ -1 +1 @@
|
|
1
|
-
|
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
|
+
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:
|