nomis-api-client 0.0.4 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|