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 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: