epb-auth-tools 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 782e5a8d8575cc614987e76c1475c6e3bb21126a5f1d69d35534151d9812a861
4
+ data.tar.gz: 3cb5ed246374496bc8170503bc77c0557c80cb7b6d8f44e57c09548555b9ef6b
5
+ SHA512:
6
+ metadata.gz: dc06e83c6313dccbae073da2adbc20d694279b7a99ae7e4232b4dad54ca9fa07411e6bc78cfa9c23a3de78ec8285df89da31d7b3c585eafc5312bc44fe7ef85e
7
+ data.tar.gz: e9899d2035a1955efb170ad2379207d9b3fdd69126f8f7a2d5655719cdb712a163987756332c7172e84234f2cde1c59957b82f210659ebb3d741aea640e1718a
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Auth
4
+ require_relative 'errors'
5
+ require_relative 'http_client'
6
+ require_relative 'token'
7
+ require_relative 'token_processor'
8
+
9
+ require_relative 'sinatra/conditional'
10
+ end
data/lib/errors.rb ADDED
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Auth
4
+ module Errors
5
+ class Error < RuntimeError; end
6
+
7
+ class Processor < Auth::Errors::Error; end
8
+ class ProcessorHasNoSecret < Auth::Errors::Error; end
9
+ class ProcessorHasNoIssuer < Auth::Errors::Error; end
10
+
11
+ class Token < Auth::Errors::Error; end
12
+
13
+ class TokenMissing < Auth::Errors::Token; end
14
+ class TokenPayloadError < Auth::Errors::Token; end
15
+ class TokenExpired < Auth::Errors::TokenPayloadError; end
16
+ class TokenNotYetValid < Auth::Errors::TokenPayloadError; end
17
+ class TokenHasNoIssuer < Auth::Errors::TokenPayloadError; end
18
+ class TokenHasNoSubject < Auth::Errors::TokenPayloadError; end
19
+ class TokenHasNoIssuedAt < Auth::Errors::TokenPayloadError; end
20
+ class TokenHasNoExpiry < Auth::Errors::TokenPayloadError; end
21
+ class TokenIssuerIncorrect < Auth::Errors::TokenPayloadError; end
22
+
23
+ class TokenDecodeError < Auth::Errors::Token; end
24
+ class TokenTamperDetected < Auth::Errors::TokenDecodeError; end
25
+
26
+ class Client < Auth::Errors::Error; end
27
+
28
+ class ClientHasNoAuthServer < Auth::Errors::Client; end
29
+ class ClientHasNoClientId < Auth::Errors::Client; end
30
+ class ClientHasNoClientSecret < Auth::Errors::Client; end
31
+ class ClientHasNoBaseUri < Auth::Errors::Client; end
32
+
33
+ class Network < Auth::Errors::Error; end
34
+ class NetworkConnectionFailed < Auth::Errors::Network; end
35
+ end
36
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'oauth2'
4
+
5
+ module Auth
6
+ class HttpClient
7
+ attr_reader :authenticated_client
8
+
9
+ def initialize(
10
+ client_id = nil,
11
+ client_secret = nil,
12
+ auth_server = nil,
13
+ base_uri = nil,
14
+ auth_client = OAuth2::Client
15
+ )
16
+ raise Auth::Errors::ClientHasNoClientId if client_id.nil?
17
+ raise Auth::Errors::ClientHasNoClientSecret if client_secret.nil?
18
+ raise Auth::Errors::ClientHasNoAuthServer if auth_server.nil?
19
+ raise Auth::Errors::ClientHasNoBaseUri if base_uri.nil?
20
+
21
+ @authenticated_client = nil
22
+
23
+ site_url = URI.parse(auth_server)
24
+ token_url = site_url.path + '/oauth/token'
25
+ authorisation_url = site_url.path + '/oauth/token'
26
+ site_url = "#{site_url.scheme}://#{site_url.host}:#{site_url.port}"
27
+
28
+
29
+ @base_uri = base_uri
30
+ @client =
31
+ auth_client.new client_id,
32
+ client_secret,
33
+ site: site_url,
34
+ token_url: token_url,
35
+ authorisation_url: authorisation_url,
36
+ raise_errors: false
37
+ end
38
+
39
+ def refresh
40
+ @authenticated_client = @client.client_credentials.get_token
41
+ end
42
+
43
+ def refresh?
44
+ @authenticated_client.nil? || @authenticated_client.expired?
45
+ end
46
+
47
+ def self.delegate(*methods)
48
+ methods.each do |method_name|
49
+ define_method(method_name) do |*args, &block|
50
+ request method_name, *args, &block
51
+ end
52
+ end
53
+ end
54
+
55
+ delegate :get, :post, :put
56
+
57
+ def request(method_name, *args, &block)
58
+ refresh? && refresh
59
+
60
+ args[0] = @base_uri + args[0]
61
+
62
+ if @authenticated_client.respond_to? method_name
63
+ response = @authenticated_client.send method_name, *args, &block
64
+ if response.body.is_a?(::Hash) &&
65
+ response.body[:error] == 'Auth::Errors::TokenExpired'
66
+ refresh
67
+ response = @authenticated_client.send method_name, *args, &block
68
+ end
69
+
70
+ response
71
+ end
72
+ rescue Faraday::ConnectionFailed
73
+ raise Auth::Errors::NetworkConnectionFailed
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Auth
4
+ module Sinatra
5
+ class Conditional
6
+ def self.process_request(env)
7
+ jwt_token = env.fetch('HTTP_AUTHORIZATION', '').slice(7..-1)
8
+ processor =
9
+ Auth::TokenProcessor.new ENV['JWT_SECRET'], ENV['JWT_ISSUER']
10
+ processor.process jwt_token
11
+ end
12
+ end
13
+ end
14
+ end
data/lib/token.rb ADDED
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Auth
4
+ class Token
5
+ def initialize(payload)
6
+ @payload = JSON.parse payload.to_json
7
+ validate_payload
8
+ end
9
+
10
+ def scope?(scope)
11
+ @payload['scopes'].include? scope
12
+ end
13
+
14
+ def scopes?(scopes)
15
+ scopes.all? { |scope| @payload['scopes'].include? scope }
16
+ end
17
+
18
+ def supplemental(property = nil)
19
+ unless property.nil? || @payload['sup'][property].nil?
20
+ return @payload['sup'][property]
21
+ end
22
+
23
+ @payload['sup']
24
+ end
25
+
26
+ def encode(jwt_secret)
27
+ JWT.encode @payload, jwt_secret, 'HS256'
28
+ end
29
+
30
+ private
31
+
32
+ def validate_payload
33
+ raise Auth::Errors::TokenHasNoIssuer unless @payload.key?('iss')
34
+ raise Auth::Errors::TokenHasNoIssuedAt unless @payload.key?('iat')
35
+ unless @payload['iat'] <= Time.now.to_i
36
+ raise Auth::Errors::TokenNotYetValid
37
+ end
38
+ raise Auth::Errors::TokenHasNoSubject unless @payload.key?('sub')
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'jwt'
4
+
5
+ module Auth
6
+ class TokenProcessor
7
+ def initialize(jwt_secret = nil, jwt_issuer = nil)
8
+ raise Auth::Errors::ProcessorHasNoSecret if jwt_secret.nil?
9
+ raise Auth::Errors::ProcessorHasNoIssuer if jwt_issuer.nil?
10
+
11
+ @jwt_secret = jwt_secret
12
+ @jwt_issuer = jwt_issuer
13
+ end
14
+
15
+ def process(token = nil)
16
+ raise Auth::Errors::TokenMissing if token.nil?
17
+
18
+ payload, _header = jwt_process token
19
+
20
+ raise Auth::Errors::TokenHasNoIssuer unless payload.key?('iss')
21
+ unless payload['iss'] == @jwt_issuer
22
+ raise Auth::Errors::TokenIssuerIncorrect
23
+ end
24
+
25
+ Auth::Token.new payload
26
+ end
27
+
28
+ private
29
+
30
+ def jwt_process(token)
31
+ options = { algorithm: 'HS256', iss: @jwt_issuer }
32
+
33
+ JWT.decode token, @jwt_secret, true, options
34
+ rescue JWT::ExpiredSignature
35
+ raise Auth::Errors::TokenExpired
36
+ rescue JWT::VerificationError
37
+ raise Auth::Errors::TokenTamperDetected
38
+ rescue JWT::DecodeError
39
+ raise Auth::Errors::TokenDecodeError
40
+ end
41
+ end
42
+ end
metadata ADDED
@@ -0,0 +1,78 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: epb-auth-tools
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Lawrence Goldstien <lawrence.goldstien@madetech.com>
8
+ - Yusuf Sheikh <yusuf@madetech.com>
9
+ - Jaseera <jaseera@madetech.com>
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2020-03-11 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: jwt
17
+ requirement: !ruby/object:Gem::Requirement
18
+ requirements:
19
+ - - "~>"
20
+ - !ruby/object:Gem::Version
21
+ version: '2.2'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - "~>"
27
+ - !ruby/object:Gem::Version
28
+ version: '2.2'
29
+ - !ruby/object:Gem::Dependency
30
+ name: oauth2
31
+ requirement: !ruby/object:Gem::Requirement
32
+ requirements:
33
+ - - "~>"
34
+ - !ruby/object:Gem::Version
35
+ version: '1.4'
36
+ type: :runtime
37
+ prerelease: false
38
+ version_requirements: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - "~>"
41
+ - !ruby/object:Gem::Version
42
+ version: '1.4'
43
+ description:
44
+ email:
45
+ executables: []
46
+ extensions: []
47
+ extra_rdoc_files: []
48
+ files:
49
+ - lib/epb_auth_tools.rb
50
+ - lib/errors.rb
51
+ - lib/http_client.rb
52
+ - lib/sinatra/conditional.rb
53
+ - lib/token.rb
54
+ - lib/token_processor.rb
55
+ homepage: https://github.com/communitiesuk/epb-auth-tools
56
+ licenses:
57
+ - MIT
58
+ metadata: {}
59
+ post_install_message:
60
+ rdoc_options: []
61
+ require_paths:
62
+ - lib
63
+ required_ruby_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ required_rubygems_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: '0'
73
+ requirements: []
74
+ rubygems_version: 3.0.6
75
+ signing_key:
76
+ specification_version: 4
77
+ summary: Tools for authentication and authorisation with JWTs and OAuth
78
+ test_files: []