soar_authentication_token 3.0.9 → 4.0.0
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/README.md +12 -12
- data/lib/soar_authentication_token.rb +5 -0
- data/lib/soar_authentication_token/providers/jwt_token_generator.rb +66 -0
- data/lib/soar_authentication_token/providers/jwt_token_validator.rb +89 -0
- data/lib/soar_authentication_token/providers/remote_token_generator.rb +44 -0
- data/lib/soar_authentication_token/providers/remote_token_validator.rb +57 -0
- data/lib/soar_authentication_token/providers/static_token_validator.rb +64 -0
- data/lib/soar_authentication_token/token_generator.rb +8 -87
- data/lib/soar_authentication_token/token_validator.rb +8 -149
- data/lib/soar_authentication_token/version.rb +1 -1
- data/spec/rack_middleware_spec.rb +3 -4
- data/spec/token_generator_spec.rb +10 -7
- data/spec/token_validator_spec.rb +7 -7
- metadata +7 -2
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA1:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 4020cefab79d1ffcef0ea47c4787412cf4c520e4
         | 
| 4 | 
            +
              data.tar.gz: dff3cc2d0e722cfbfbd9ae761694417350bfe9ba
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: a9f8caae8f97bc3b7954b9c70848f605b6b7c6ba32b90800e42955811ccdfb34cc2d9bc7632aca2ec1c5b0872146a4a2ca761340fd228afe44a238f37b2998fd
         | 
| 7 | 
            +
              data.tar.gz: 2a9aaf075ccae76090499d7bca7a7d88b663f75aecbd40393c783071a48591c2bcf842c64b86e9b04c436e6c5c36947442eadd34b0314ced9660030a5f5dc55b
         | 
    
        data/README.md
    CHANGED
    
    | @@ -24,16 +24,16 @@ gem install soar_authentication_token | |
| 24 24 |  | 
| 25 25 | 
             
            ## Configuration of generators and validators
         | 
| 26 26 |  | 
| 27 | 
            -
            There are three  | 
| 27 | 
            +
            There are three provider groups for operation: JwtToken, RemoteToken or StaticToken
         | 
| 28 28 |  | 
| 29 | 
            -
            ###  | 
| 30 | 
            -
             | 
| 29 | 
            +
            ### JwtTokenGenerator/JwtTokenValidator
         | 
| 30 | 
            +
            With the JwtTokenValidator provider the tokens are decoded, verified and meta extracted locally using configured key material. In practice the the token generation and validation services run in this mode since their roles are to generate and validate tokens.
         | 
| 31 31 |  | 
| 32 | 
            -
            ###  | 
| 33 | 
            -
             | 
| 32 | 
            +
            ### RemoteTokenGenerator/RemoteTokenValidator
         | 
| 33 | 
            +
            With the RemoteTokenValidator provider the tokens are passed to a token validation service for validation. This allows for a centralized management of tokens. The key material are therefore managed on the validation service and .  In this mode you only have to provide the url of the validation service.
         | 
| 34 34 |  | 
| 35 | 
            -
            ###  | 
| 36 | 
            -
            In this mode the  | 
| 35 | 
            +
            ### StaticTokenValidator
         | 
| 36 | 
            +
            In this mode the StaticTokenValidator is configured with a list of preconfigured static tokens.  Incoming tokens are simply checked against this list.  No extraction of meta is performed on the tokens but retrieved from the configuration.  Rotation of tokens is simple since many tokens can be configured. This mode is to be used in only two scenarios:
         | 
| 37 37 | 
             
            * Between the various authentication token services that requires authentication between themselves.  These services themselves do not the benefit of another centralized authentication service to rely on.
         | 
| 38 38 | 
             
            * In test scenarios where you do not want to pull in the authentication services to perform testing of your services.
         | 
| 39 39 |  | 
| @@ -77,7 +77,7 @@ First step is to configure the middleware. Actually you are not configuring the | |
| 77 77 |  | 
| 78 78 | 
             
            ```ruby
         | 
| 79 79 | 
             
            @iut_configuration = {
         | 
| 80 | 
            -
              ' | 
| 80 | 
            +
              'provider' => 'RemoteTokenValidator',
         | 
| 81 81 | 
             
              'validator-url' => 'http://authentication-token-validator-service:9393/validate'
         | 
| 82 82 | 
             
            }
         | 
| 83 83 | 
             
            @iut = SoarAuthenticationToken::RackMiddleware.new(@test_app, @iut_configuration)
         | 
| @@ -94,7 +94,7 @@ The class generates tokens or requests a token from a generator service as confi | |
| 94 94 | 
             
            For remote token generation, configure as such:
         | 
| 95 95 | 
             
            ```ruby
         | 
| 96 96 | 
             
            @configuration_remote = {
         | 
| 97 | 
            -
              ' | 
| 97 | 
            +
              'provider' => 'RemoteTokenGenerator',
         | 
| 98 98 | 
             
              'generator-url' => 'http://authentication-token-generator-service:9393/generate',
         | 
| 99 99 | 
             
              'generator-client-auth-token' => 'xxxx'
         | 
| 100 100 | 
             
            }
         | 
| @@ -105,7 +105,7 @@ For local token generation, configure as such: | |
| 105 105 | 
             
            generator = SoarAuthenticationToken::KeypairGenerator.new
         | 
| 106 106 | 
             
            private_key, public_key = generator.generate
         | 
| 107 107 | 
             
            @configuration_local = {
         | 
| 108 | 
            -
              ' | 
| 108 | 
            +
              'provider' => 'JwtTokenGenerator',
         | 
| 109 109 | 
             
              'private_key' => private_key
         | 
| 110 110 | 
             
            }
         | 
| 111 111 | 
             
            ```
         | 
| @@ -131,7 +131,7 @@ The class validates tokens or requests validation of a token from a validation s | |
| 131 131 | 
             
            For remote token validation, configure as such:
         | 
| 132 132 | 
             
            ```ruby
         | 
| 133 133 | 
             
            @configuration_remote = {
         | 
| 134 | 
            -
              ' | 
| 134 | 
            +
              'provider' => 'RemoteTokenGenerator',
         | 
| 135 135 | 
             
              'validator-url' => 'http://authentication-token-validator-service:9393/validate',
         | 
| 136 136 | 
             
            }
         | 
| 137 137 | 
             
            ```
         | 
| @@ -141,7 +141,7 @@ For local token validation, configure as such: | |
| 141 141 | 
             
            generator = SoarAuthenticationToken::KeypairGenerator.new
         | 
| 142 142 | 
             
            private_key, public_key = generator.generate
         | 
| 143 143 | 
             
            @configuration_local = {
         | 
| 144 | 
            -
              ' | 
| 144 | 
            +
              'provider' => 'JwtTokenValidator',
         | 
| 145 145 | 
             
              'public_key' => public_key
         | 
| 146 146 | 
             
            }
         | 
| 147 147 | 
             
            ```
         | 
| @@ -1,6 +1,11 @@ | |
| 1 1 | 
             
            module SoarAuthenticationToken
         | 
| 2 2 | 
             
            end
         | 
| 3 3 |  | 
| 4 | 
            +
            require 'soar_authentication_token/providers/jwt_token_generator'
         | 
| 5 | 
            +
            require 'soar_authentication_token/providers/jwt_token_validator'
         | 
| 6 | 
            +
            require 'soar_authentication_token/providers/remote_token_generator'
         | 
| 7 | 
            +
            require 'soar_authentication_token/providers/remote_token_validator'
         | 
| 8 | 
            +
            require 'soar_authentication_token/providers/static_token_validator'
         | 
| 4 9 | 
             
            require 'soar_authentication_token/keypair_generator'
         | 
| 5 10 | 
             
            require 'soar_authentication_token/token_generator'
         | 
| 6 11 | 
             
            require 'soar_authentication_token/token_validator'
         | 
| @@ -0,0 +1,66 @@ | |
| 1 | 
            +
            require 'soar_xt'
         | 
| 2 | 
            +
            require 'jwt'
         | 
| 3 | 
            +
            require 'securerandom'
         | 
| 4 | 
            +
            require 'time'
         | 
| 5 | 
            +
            require 'json'
         | 
| 6 | 
            +
            require 'authenticated_client'
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            module SoarAuthenticationToken
         | 
| 9 | 
            +
              class JwtTokenGenerator
         | 
| 10 | 
            +
                DEFAULT_CONFIGURATION = {
         | 
| 11 | 
            +
                  'expiry' => 604800 #a days worth of seconds
         | 
| 12 | 
            +
                } unless defined? DEFAULT_CONFIGURATION; DEFAULT_CONFIGURATION.freeze
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                def initialize(configuration)
         | 
| 15 | 
            +
                  @configuration = merge_with_default_configuration(configuration)
         | 
| 16 | 
            +
                  validate_local_mode_configuration
         | 
| 17 | 
            +
                  @private_key = OpenSSL::PKey::EC.new(@configuration['private_key'])
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                def inject_store_provider(store_provider)
         | 
| 21 | 
            +
                  @store_provider = store_provider
         | 
| 22 | 
            +
                end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                def generate(authenticated_identifier:, flow_identifier: nil)
         | 
| 25 | 
            +
                  token_meta = generate_meta(authenticated_identifier)
         | 
| 26 | 
            +
                  token = encode(token_meta)
         | 
| 27 | 
            +
                  add_token_to_store(token_meta,flow_identifier)
         | 
| 28 | 
            +
                  [token, token_meta]
         | 
| 29 | 
            +
                end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                private
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                def generate_meta(authenticated_identifier)
         | 
| 34 | 
            +
                  current_time = Time.now
         | 
| 35 | 
            +
                  { 'authenticated_identifier' => authenticated_identifier,
         | 
| 36 | 
            +
                    'token_issue_time'         => current_time.utc.iso8601(3),
         | 
| 37 | 
            +
                    'token_expiry_time'        => (current_time + @configuration['expiry']).utc.iso8601(3),
         | 
| 38 | 
            +
                    'token_identifier'         => SecureRandom.hex(32)
         | 
| 39 | 
            +
                  }
         | 
| 40 | 
            +
                end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                def encode(meta)
         | 
| 43 | 
            +
                  JWT.encode(meta, @private_key, 'ES512')
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                def validate_local_mode_configuration
         | 
| 47 | 
            +
                  raise "'private_key' must be configured in local mode" unless @configuration['private_key']
         | 
| 48 | 
            +
                  raise "'expiry' must be configured in local mode" unless @configuration['expiry']
         | 
| 49 | 
            +
                  raise "'expiry' must be an integer" unless Integer(@configuration['expiry'])
         | 
| 50 | 
            +
                end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                def merge_with_default_configuration(configuration)
         | 
| 53 | 
            +
                  configuration = {} unless configuration
         | 
| 54 | 
            +
                  Hash.deep_merge(DEFAULT_CONFIGURATION,configuration)
         | 
| 55 | 
            +
                end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                def add_token_to_store(meta,flow_identifier)
         | 
| 58 | 
            +
                  @store_provider.add(
         | 
| 59 | 
            +
                    token_identifier:         meta['token_identifier'],
         | 
| 60 | 
            +
                    authenticated_identifier: meta['authenticated_identifier'],
         | 
| 61 | 
            +
                    token_issue_time:         meta['token_issue_time'],
         | 
| 62 | 
            +
                    token_expiry_time:        meta['token_expiry_time'],
         | 
| 63 | 
            +
                    flow_identifier:          flow_identifier)
         | 
| 64 | 
            +
                end
         | 
| 65 | 
            +
              end
         | 
| 66 | 
            +
            end
         | 
| @@ -0,0 +1,89 @@ | |
| 1 | 
            +
            require 'jwt'
         | 
| 2 | 
            +
            require 'authenticated_client'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            module SoarAuthenticationToken
         | 
| 5 | 
            +
              class JwtTokenValidator
         | 
| 6 | 
            +
                def initialize(configuration)
         | 
| 7 | 
            +
                  @configuration = configuration
         | 
| 8 | 
            +
                  set_configuration_defaults
         | 
| 9 | 
            +
                  validate_configuration
         | 
| 10 | 
            +
                  @public_key = OpenSSL::PKey::EC.new(@configuration['public_key'])
         | 
| 11 | 
            +
                  @public_key.private_key = nil
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                def inject_store_provider(store_provider)
         | 
| 15 | 
            +
                  @store_provider = store_provider
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                def validate(authentication_token:,flow_identifier: nil)
         | 
| 19 | 
            +
                  meta = decode_token_meta(authentication_token)
         | 
| 20 | 
            +
                  return rejection_result(reason: "Expired token <#{meta['token_expiry_time']}> for <#{meta['authenticated_identifier']}>") if token_expired?(meta)
         | 
| 21 | 
            +
                  return rejection_result(reason: "Unknown token for <#{meta['authenticated_identifier']}>") unless token_exist_in_store?(meta,flow_identifier)
         | 
| 22 | 
            +
                  success_result(token_meta: meta)
         | 
| 23 | 
            +
                rescue JWT::VerificationError, JWT::DecodeError
         | 
| 24 | 
            +
                  rejection_result(reason: 'Token decode/verification failure')
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                private
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                def set_configuration_defaults
         | 
| 30 | 
            +
                  @configuration['expiry'] ||= 86400
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                def decode_token_meta(authentication_token)
         | 
| 34 | 
            +
                  decoded_token_payload = decode(authentication_token)
         | 
| 35 | 
            +
                  compile_meta(token_identifier:         decoded_token_payload[0]['token_identifier'],
         | 
| 36 | 
            +
                               authenticated_identifier: decoded_token_payload[0]['authenticated_identifier'],
         | 
| 37 | 
            +
                               token_issue_time:         decoded_token_payload[0]['token_issue_time'],
         | 
| 38 | 
            +
                               token_expiry_time:        decoded_token_payload[0]['token_expiry_time'])
         | 
| 39 | 
            +
                end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                def compile_meta(token_identifier:,
         | 
| 42 | 
            +
                                 authenticated_identifier:,
         | 
| 43 | 
            +
                                 token_issue_time:,
         | 
| 44 | 
            +
                                 token_expiry_time:)
         | 
| 45 | 
            +
                  {
         | 
| 46 | 
            +
                    'token_identifier' =>         token_identifier,
         | 
| 47 | 
            +
                    'authenticated_identifier' => authenticated_identifier,
         | 
| 48 | 
            +
                    'token_issue_time' =>         token_issue_time,
         | 
| 49 | 
            +
                    'token_expiry_time' =>        token_expiry_time,
         | 
| 50 | 
            +
                    'token_age' =>                token_age(token_issue_time)
         | 
| 51 | 
            +
                  }
         | 
| 52 | 
            +
                end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                def token_age(token_issue_time)
         | 
| 55 | 
            +
                  Time.now - Time.parse(token_issue_time.to_s)
         | 
| 56 | 
            +
                end
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                def validate_configuration
         | 
| 59 | 
            +
                  raise "'public_key' must be configured in local mode" unless @configuration['public_key']
         | 
| 60 | 
            +
                  raise "'expiry' must be configured in local mode" unless @configuration['expiry']
         | 
| 61 | 
            +
                  raise "'expiry' must be an integer" unless Integer(@configuration['expiry'])
         | 
| 62 | 
            +
                end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                def decode(authentication_token)
         | 
| 65 | 
            +
                  JWT.decode(authentication_token, @public_key, true, { :algorithm => 'ES512' })
         | 
| 66 | 
            +
                end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                def token_expired?(meta)
         | 
| 69 | 
            +
                  Time.parse(meta['token_expiry_time'].to_s) < Time.now
         | 
| 70 | 
            +
                end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                def token_exist_in_store?(meta,flow_identifier)
         | 
| 73 | 
            +
                  @store_provider.token_exist?(
         | 
| 74 | 
            +
                    token_identifier:         meta['token_identifier'],
         | 
| 75 | 
            +
                    authenticated_identifier: meta['authenticated_identifier'],
         | 
| 76 | 
            +
                    token_issue_time:         meta['token_issue_time'],
         | 
| 77 | 
            +
                    token_expiry_time:        meta['token_expiry_time'],
         | 
| 78 | 
            +
                    flow_identifier:          flow_identifier)
         | 
| 79 | 
            +
                end
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                def rejection_result(reason:)
         | 
| 82 | 
            +
                  [false, nil, reason]
         | 
| 83 | 
            +
                end
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                def success_result(token_meta:)
         | 
| 86 | 
            +
                  [true, token_meta, "Valid token for <#{token_meta['authenticated_identifier']}>" ]
         | 
| 87 | 
            +
                end
         | 
| 88 | 
            +
              end
         | 
| 89 | 
            +
            end
         | 
| @@ -0,0 +1,44 @@ | |
| 1 | 
            +
            require 'json'
         | 
| 2 | 
            +
            require 'authenticated_client'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            module SoarAuthenticationToken
         | 
| 5 | 
            +
              class RemoteTokenGenerator
         | 
| 6 | 
            +
                def initialize(configuration)
         | 
| 7 | 
            +
                  @configuration = configuration
         | 
| 8 | 
            +
                  validate_configuration
         | 
| 9 | 
            +
                end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                def inject_store_provider(store_provider)
         | 
| 12 | 
            +
                  #ignore the store provider since this generator does not use a store
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                def generate(authenticated_identifier:, flow_identifier: nil)
         | 
| 16 | 
            +
                  client = authenticated_client(authenticated_identifier,flow_identifier)
         | 
| 17 | 
            +
                  validate_and_extract_token_from_response(client.request)
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                private
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                def authenticated_client(authenticated_identifier,flow_identifier)
         | 
| 23 | 
            +
                  client = AuthenticatedClient::Client.new
         | 
| 24 | 
            +
                  client.url = @configuration['generator-url']
         | 
| 25 | 
            +
                  client.token = @configuration['generator-client-auth-token']
         | 
| 26 | 
            +
                  client.verb = :post
         | 
| 27 | 
            +
                  client.parameters = {'flow_identifier' => flow_identifier}
         | 
| 28 | 
            +
                  client.body = { 'authenticated_identifier' => authenticated_identifier }
         | 
| 29 | 
            +
                  client
         | 
| 30 | 
            +
                end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                def validate_and_extract_token_from_response(response)
         | 
| 33 | 
            +
                  raise "Failure generating token with token generation service. Code #{response.code}" if '200' != response.code
         | 
| 34 | 
            +
                  body = JSON.parse(response.body)
         | 
| 35 | 
            +
                  raise 'Failure generating token by token service' if 'success' != body['status']
         | 
| 36 | 
            +
                  raise 'Token service did not provide token' if body['data'].nil? or body['data']['token'].nil?
         | 
| 37 | 
            +
                  body['data']['token']
         | 
| 38 | 
            +
                end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                def validate_configuration
         | 
| 41 | 
            +
                  raise "'generator-url' must be configured" if @configuration['generator-url'].nil?
         | 
| 42 | 
            +
                end
         | 
| 43 | 
            +
              end
         | 
| 44 | 
            +
            end
         | 
| @@ -0,0 +1,57 @@ | |
| 1 | 
            +
            require 'authenticated_client'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module SoarAuthenticationToken
         | 
| 4 | 
            +
              class RemoteTokenValidator
         | 
| 5 | 
            +
                def initialize(configuration)
         | 
| 6 | 
            +
                  @configuration = configuration
         | 
| 7 | 
            +
                  validate_configuration
         | 
| 8 | 
            +
                end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                def inject_store_provider(store_provider)
         | 
| 11 | 
            +
                  #ignore the store provider since this validator does not use a store
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                def validate(authentication_token:,flow_identifier: nil)
         | 
| 15 | 
            +
                  response = send_request(authentication_token,flow_identifier)
         | 
| 16 | 
            +
                  validate_and_extract_information_from_response(response)
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                private
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                def send_request(authentication_token,flow_identifier)
         | 
| 22 | 
            +
                  uri = URI.parse(@configuration['validator-url'])
         | 
| 23 | 
            +
                  uri.query = URI.encode_www_form( {'flow_identifier' => flow_identifier} )
         | 
| 24 | 
            +
                  http = Net::HTTP.new(uri.host, uri.port)
         | 
| 25 | 
            +
                  http.use_ssl = true if uri.is_a?(URI::HTTPS)
         | 
| 26 | 
            +
                  request = Net::HTTP::Post.new(uri.request_uri)
         | 
| 27 | 
            +
                  request.body = { 'authentication_token' => authentication_token }.to_json
         | 
| 28 | 
            +
                  http.request(request)
         | 
| 29 | 
            +
                end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                def validate_and_extract_information_from_response(response)
         | 
| 32 | 
            +
                  raise "Failure validating token with token validation service. Code #{response.code} received" if '200' != response.code
         | 
| 33 | 
            +
                  body = JSON.parse(response.body)
         | 
| 34 | 
            +
                  if ('success' == body['status']) and body['data']
         | 
| 35 | 
            +
                    token_validity = body['data']['token_validity']
         | 
| 36 | 
            +
                    token_meta = body['data']['token_meta']
         | 
| 37 | 
            +
                    message = body['data']['message']
         | 
| 38 | 
            +
                    raise 'Token validation service did not provide token_validity' if token_validity.nil?
         | 
| 39 | 
            +
                    raise 'Token validation service did not provide token_meta' if token_validity and token_meta.nil?
         | 
| 40 | 
            +
                    raise 'Token validation service did not provide message' if message.nil?
         | 
| 41 | 
            +
                    return [token_validity, token_meta, message]
         | 
| 42 | 
            +
                  end
         | 
| 43 | 
            +
                  if 'fail' == body['status']
         | 
| 44 | 
            +
                    return rejection_result(reason: 'remote validation failed')
         | 
| 45 | 
            +
                  end
         | 
| 46 | 
            +
                  raise "Failure validating token with token validation service. Status '#{body['status']}' received"
         | 
| 47 | 
            +
                end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                def validate_configuration
         | 
| 50 | 
            +
                  raise "'validator-url' must be configured in remote mode" unless @configuration['validator-url']
         | 
| 51 | 
            +
                end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                def rejection_result(reason:)
         | 
| 54 | 
            +
                  [false, nil, reason]
         | 
| 55 | 
            +
                end
         | 
| 56 | 
            +
              end
         | 
| 57 | 
            +
            end
         | 
| @@ -0,0 +1,64 @@ | |
| 1 | 
            +
            module SoarAuthenticationToken
         | 
| 2 | 
            +
              class StaticTokenValidator
         | 
| 3 | 
            +
                def initialize(configuration)
         | 
| 4 | 
            +
                  @configuration = configuration
         | 
| 5 | 
            +
                  validate_configuration
         | 
| 6 | 
            +
                end
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                def inject_store_provider(store_provider)
         | 
| 9 | 
            +
                  #ignore the store provider since this validator does not use a store
         | 
| 10 | 
            +
                end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                def validate(authentication_token:,flow_identifier: nil)
         | 
| 13 | 
            +
                  found_static_token = find_configured_static_token(authentication_token)
         | 
| 14 | 
            +
                  return rejection_result(reason: 'Unknown static token') if found_static_token.nil?
         | 
| 15 | 
            +
                  meta = compile_meta(token_identifier:         'static_token',
         | 
| 16 | 
            +
                                      authenticated_identifier: found_static_token['authenticated_identifier'],
         | 
| 17 | 
            +
                                      token_issue_time:         found_static_token['token_issue_time'],
         | 
| 18 | 
            +
                                      token_expiry_time:        found_static_token['token_expiry_time'])
         | 
| 19 | 
            +
                  return rejection_result(reason: "Expired token <#{meta['token_expiry_time']}> for <#{meta['authenticated_identifier']}>") if token_expired?(meta)
         | 
| 20 | 
            +
                  return success_result(token_meta: meta)
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                private
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                def find_configured_static_token(authentication_token)
         | 
| 26 | 
            +
                  @configuration['static_tokens'].each { |static_token|
         | 
| 27 | 
            +
                    return static_token if authentication_token == static_token['token']
         | 
| 28 | 
            +
                  }
         | 
| 29 | 
            +
                  nil
         | 
| 30 | 
            +
                end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                def compile_meta(token_identifier:,
         | 
| 33 | 
            +
                                 authenticated_identifier:,
         | 
| 34 | 
            +
                                 token_issue_time:,
         | 
| 35 | 
            +
                                 token_expiry_time:)
         | 
| 36 | 
            +
                  {
         | 
| 37 | 
            +
                    'token_identifier' =>         token_identifier,
         | 
| 38 | 
            +
                    'authenticated_identifier' => authenticated_identifier,
         | 
| 39 | 
            +
                    'token_issue_time' =>         token_issue_time,
         | 
| 40 | 
            +
                    'token_expiry_time' =>        token_expiry_time,
         | 
| 41 | 
            +
                    'token_age' =>                token_age(token_issue_time)
         | 
| 42 | 
            +
                  }
         | 
| 43 | 
            +
                end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                def token_age(token_issue_time)
         | 
| 46 | 
            +
                  Time.now - Time.parse(token_issue_time.to_s)
         | 
| 47 | 
            +
                end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                def validate_configuration
         | 
| 50 | 
            +
                  raise "array of 'static_tokens' must be configured" unless @configuration['static_tokens']
         | 
| 51 | 
            +
                end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                def token_expired?(meta)
         | 
| 54 | 
            +
                  Time.parse(meta['token_expiry_time'].to_s) < Time.now
         | 
| 55 | 
            +
                end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                def rejection_result(reason:)
         | 
| 58 | 
            +
                  [false, nil, reason]
         | 
| 59 | 
            +
                end
         | 
| 60 | 
            +
                def success_result(token_meta:)
         | 
| 61 | 
            +
                  [true, token_meta, "Valid token for <#{token_meta['authenticated_identifier']}>" ]
         | 
| 62 | 
            +
                end
         | 
| 63 | 
            +
              end
         | 
| 64 | 
            +
            end
         | 
| @@ -1,107 +1,28 @@ | |
| 1 | 
            -
            require 'soar_xt'
         | 
| 2 | 
            -
            require 'jwt'
         | 
| 3 | 
            -
            require 'securerandom'
         | 
| 4 | 
            -
            require 'time'
         | 
| 5 | 
            -
            require 'net/http'
         | 
| 6 | 
            -
            require 'uri'
         | 
| 7 | 
            -
            require 'json'
         | 
| 8 | 
            -
            require 'authenticated_client'
         | 
| 9 | 
            -
             | 
| 10 1 | 
             
            module SoarAuthenticationToken
         | 
| 11 2 | 
             
              class TokenGenerator
         | 
| 12 | 
            -
                DEFAULT_CONFIGURATION = {
         | 
| 13 | 
            -
                  'expiry' => 604800 #a days worth of seconds
         | 
| 14 | 
            -
                } unless defined? DEFAULT_CONFIGURATION; DEFAULT_CONFIGURATION.freeze
         | 
| 15 | 
            -
             | 
| 16 3 | 
             
                def initialize(configuration)
         | 
| 17 | 
            -
                  @configuration =  | 
| 4 | 
            +
                  @configuration = configuration
         | 
| 18 5 | 
             
                  validate_configuration
         | 
| 19 | 
            -
                   | 
| 6 | 
            +
                  instantiate_provider
         | 
| 20 7 | 
             
                end
         | 
| 21 8 |  | 
| 22 9 | 
             
                def inject_store_provider(store_provider)
         | 
| 23 | 
            -
                  @store_provider | 
| 10 | 
            +
                  @provider.inject_store_provider(store_provider)
         | 
| 24 11 | 
             
                end
         | 
| 25 12 |  | 
| 26 13 | 
             
                def generate(authenticated_identifier:, flow_identifier: nil)
         | 
| 27 | 
            -
                   | 
| 28 | 
            -
             | 
| 14 | 
            +
                  @provider.generate(authenticated_identifier: authenticated_identifier,
         | 
| 15 | 
            +
                                     flow_identifier:          flow_identifier)
         | 
| 29 16 | 
             
                end
         | 
| 30 17 |  | 
| 31 18 | 
             
                private
         | 
| 32 19 |  | 
| 33 | 
            -
                def  | 
| 34 | 
            -
                   | 
| 35 | 
            -
                  token = encode(token_meta)
         | 
| 36 | 
            -
                  add_token_to_store(token_meta,flow_identifier)
         | 
| 37 | 
            -
                  [token, token_meta]
         | 
| 38 | 
            -
                end
         | 
| 39 | 
            -
             | 
| 40 | 
            -
                def generate_remotely(authenticated_identifier,flow_identifier)
         | 
| 41 | 
            -
                  client = authenticated_client(authenticated_identifier,flow_identifier)
         | 
| 42 | 
            -
                  validate_and_extract_token_from_response(client.request)
         | 
| 43 | 
            -
                end
         | 
| 44 | 
            -
             | 
| 45 | 
            -
                def authenticated_client(authenticated_identifier,flow_identifier)
         | 
| 46 | 
            -
                  client = AuthenticatedClient::Client.new
         | 
| 47 | 
            -
                  client.url = @configuration['generator-url']
         | 
| 48 | 
            -
                  client.token = @configuration['generator-client-auth-token']
         | 
| 49 | 
            -
                  client.verb = :post
         | 
| 50 | 
            -
                  client.parameters = {'flow_identifier' => flow_identifier}
         | 
| 51 | 
            -
                  client.body = { 'authenticated_identifier' => authenticated_identifier }
         | 
| 52 | 
            -
                  client
         | 
| 53 | 
            -
                end
         | 
| 54 | 
            -
             | 
| 55 | 
            -
                def validate_and_extract_token_from_response(response)
         | 
| 56 | 
            -
                  raise "Failure generating token with token generation service. Code #{response.code}" if '200' != response.code
         | 
| 57 | 
            -
                  body = JSON.parse(response.body)
         | 
| 58 | 
            -
                  raise 'Failure generating token by token service' if 'success' != body['status']
         | 
| 59 | 
            -
                  raise 'Token service did not provide token' if body['data'].nil? or body['data']['token'].nil?
         | 
| 60 | 
            -
                  body['data']['token']
         | 
| 61 | 
            -
                end
         | 
| 62 | 
            -
             | 
| 63 | 
            -
                def generate_meta(authenticated_identifier)
         | 
| 64 | 
            -
                  current_time = Time.now
         | 
| 65 | 
            -
                  { 'authenticated_identifier' => authenticated_identifier,
         | 
| 66 | 
            -
                    'token_issue_time'         => current_time.utc.iso8601(3),
         | 
| 67 | 
            -
                    'token_expiry_time'        => (current_time + @configuration['expiry']).utc.iso8601(3),
         | 
| 68 | 
            -
                    'token_identifier'         => SecureRandom.hex(32)
         | 
| 69 | 
            -
                  }
         | 
| 70 | 
            -
                end
         | 
| 71 | 
            -
             | 
| 72 | 
            -
                def encode(meta)
         | 
| 73 | 
            -
                  JWT.encode(meta, @private_key, 'ES512')
         | 
| 20 | 
            +
                def instantiate_provider
         | 
| 21 | 
            +
                  @provider = Object::const_get("SoarAuthenticationToken::#{@configuration['provider']}").new(@configuration)
         | 
| 74 22 | 
             
                end
         | 
| 75 23 |  | 
| 76 24 | 
             
                def validate_configuration
         | 
| 77 | 
            -
                  raise "' | 
| 78 | 
            -
                  raise "'mode' must be configured as either 'local' or 'remote'" unless ['local','remote'].include?(@configuration['mode'])
         | 
| 79 | 
            -
                  validate_local_mode_configuration if 'local' == @configuration['mode']
         | 
| 80 | 
            -
                  validate_remote_mode_configuration if 'remote' == @configuration['mode']
         | 
| 81 | 
            -
                end
         | 
| 82 | 
            -
             | 
| 83 | 
            -
                def validate_remote_mode_configuration
         | 
| 84 | 
            -
                  raise "'generator-url' must be configured in remote mode" if @configuration['generator-url'].nil?
         | 
| 85 | 
            -
                end
         | 
| 86 | 
            -
             | 
| 87 | 
            -
                def validate_local_mode_configuration
         | 
| 88 | 
            -
                  raise "'private_key' must be configured in local mode" unless @configuration['private_key']
         | 
| 89 | 
            -
                  raise "'expiry' must be configured in local mode" unless @configuration['expiry']
         | 
| 90 | 
            -
                  raise "'expiry' must be an integer" unless Integer(@configuration['expiry'])
         | 
| 91 | 
            -
                end
         | 
| 92 | 
            -
             | 
| 93 | 
            -
                def merge_with_default_configuration(configuration)
         | 
| 94 | 
            -
                  configuration = {} unless configuration
         | 
| 95 | 
            -
                  Hash.deep_merge(DEFAULT_CONFIGURATION,configuration)
         | 
| 96 | 
            -
                end
         | 
| 97 | 
            -
             | 
| 98 | 
            -
                def add_token_to_store(meta,flow_identifier)
         | 
| 99 | 
            -
                  @store_provider.add(
         | 
| 100 | 
            -
                    token_identifier:         meta['token_identifier'],
         | 
| 101 | 
            -
                    authenticated_identifier: meta['authenticated_identifier'],
         | 
| 102 | 
            -
                    token_issue_time:         meta['token_issue_time'],
         | 
| 103 | 
            -
                    token_expiry_time:        meta['token_expiry_time'],
         | 
| 104 | 
            -
                    flow_identifier:          flow_identifier)
         | 
| 25 | 
            +
                  raise "'provider' must be configured" unless @configuration['provider']
         | 
| 105 26 | 
             
                end
         | 
| 106 27 | 
             
              end
         | 
| 107 28 | 
             
            end
         | 
| @@ -1,169 +1,28 @@ | |
| 1 | 
            -
            require 'soar_xt'
         | 
| 2 | 
            -
            require 'jwt'
         | 
| 3 | 
            -
            require 'authenticated_client'
         | 
| 4 | 
            -
             | 
| 5 1 | 
             
            module SoarAuthenticationToken
         | 
| 6 2 | 
             
              class TokenValidator
         | 
| 7 | 
            -
                DEFAULT_CONFIGURATION = {
         | 
| 8 | 
            -
                  'expiry' => 604800 #a days worth of seconds
         | 
| 9 | 
            -
                } unless defined? DEFAULT_CONFIGURATION; DEFAULT_CONFIGURATION.freeze
         | 
| 10 | 
            -
             | 
| 11 3 | 
             
                def initialize(configuration)
         | 
| 12 | 
            -
                  @configuration =  | 
| 4 | 
            +
                  @configuration = configuration
         | 
| 13 5 | 
             
                  validate_configuration
         | 
| 14 | 
            -
                   | 
| 15 | 
            -
                  @public_key.private_key = nil
         | 
| 6 | 
            +
                  instantiate_provider
         | 
| 16 7 | 
             
                end
         | 
| 17 8 |  | 
| 18 9 | 
             
                def inject_store_provider(store_provider)
         | 
| 19 | 
            -
                  @store_provider | 
| 10 | 
            +
                  @provider.inject_store_provider(store_provider)
         | 
| 20 11 | 
             
                end
         | 
| 21 12 |  | 
| 22 13 | 
             
                def validate(authentication_token:,flow_identifier: nil)
         | 
| 23 | 
            -
                   | 
| 24 | 
            -
                   | 
| 25 | 
            -
                  return validate_remotely(authentication_token,flow_identifier) if 'remote' == @configuration['mode']
         | 
| 26 | 
            -
                  raise 'invalid validation mode configured'
         | 
| 14 | 
            +
                  @provider.validate(authentication_token: authentication_token,
         | 
| 15 | 
            +
                                     flow_identifier:      flow_identifier)
         | 
| 27 16 | 
             
                end
         | 
| 28 17 |  | 
| 29 18 | 
             
                private
         | 
| 30 19 |  | 
| 31 | 
            -
                def  | 
| 32 | 
            -
                   | 
| 33 | 
            -
                  return rejection_result(reason: 'Unknown static token') if found_static_token.nil?
         | 
| 34 | 
            -
                  meta = compile_meta(token_identifier:         'static_token',
         | 
| 35 | 
            -
                                      authenticated_identifier: found_static_token['authenticated_identifier'],
         | 
| 36 | 
            -
                                      token_issue_time:         found_static_token['token_issue_time'],
         | 
| 37 | 
            -
                                      token_expiry_time:        found_static_token['token_expiry_time'])
         | 
| 38 | 
            -
                  return rejection_result(reason: "Expired token <#{meta['token_expiry_time']}> for <#{meta['authenticated_identifier']}>") if token_expired?(meta)
         | 
| 39 | 
            -
                  return success_result(token_meta: meta)
         | 
| 40 | 
            -
                end
         | 
| 41 | 
            -
             | 
| 42 | 
            -
                def find_configured_static_token(authentication_token)
         | 
| 43 | 
            -
                  @configuration['static_tokens'].each { |static_token|
         | 
| 44 | 
            -
                    if authentication_token == static_token['token']
         | 
| 45 | 
            -
                      return static_token
         | 
| 46 | 
            -
                    end
         | 
| 47 | 
            -
                  }
         | 
| 48 | 
            -
                  nil
         | 
| 49 | 
            -
                end
         | 
| 50 | 
            -
             | 
| 51 | 
            -
                def validate_locally(authentication_token,flow_identifier)
         | 
| 52 | 
            -
                  meta = decode_token_meta(authentication_token)
         | 
| 53 | 
            -
                  return rejection_result(reason: "Expired token <#{meta['token_expiry_time']}> for <#{meta['authenticated_identifier']}>") if token_expired?(meta)
         | 
| 54 | 
            -
                  return rejection_result(reason: "Unknown token for <#{meta['authenticated_identifier']}>") unless token_exist_in_store?(meta,flow_identifier)
         | 
| 55 | 
            -
                  success_result(token_meta: meta)
         | 
| 56 | 
            -
                rescue JWT::VerificationError, JWT::DecodeError
         | 
| 57 | 
            -
                  rejection_result(reason: 'Token decode/verification failure')
         | 
| 58 | 
            -
                end
         | 
| 59 | 
            -
             | 
| 60 | 
            -
                def decode_token_meta(authentication_token)
         | 
| 61 | 
            -
                  decoded_token_payload = decode(authentication_token)
         | 
| 62 | 
            -
                  compile_meta(token_identifier:         decoded_token_payload[0]['token_identifier'],
         | 
| 63 | 
            -
                               authenticated_identifier: decoded_token_payload[0]['authenticated_identifier'],
         | 
| 64 | 
            -
                               token_issue_time:         decoded_token_payload[0]['token_issue_time'],
         | 
| 65 | 
            -
                               token_expiry_time:        decoded_token_payload[0]['token_expiry_time'])
         | 
| 66 | 
            -
                end
         | 
| 67 | 
            -
             | 
| 68 | 
            -
                def compile_meta(token_identifier:,
         | 
| 69 | 
            -
                                 authenticated_identifier:,
         | 
| 70 | 
            -
                                 token_issue_time:,
         | 
| 71 | 
            -
                                 token_expiry_time:)
         | 
| 72 | 
            -
                  {
         | 
| 73 | 
            -
                    'token_identifier' =>         token_identifier,
         | 
| 74 | 
            -
                    'authenticated_identifier' => authenticated_identifier,
         | 
| 75 | 
            -
                    'token_issue_time' =>         token_issue_time,
         | 
| 76 | 
            -
                    'token_expiry_time' =>        token_expiry_time,
         | 
| 77 | 
            -
                    'token_age' =>                token_age(token_issue_time)
         | 
| 78 | 
            -
                  }
         | 
| 79 | 
            -
                end
         | 
| 80 | 
            -
             | 
| 81 | 
            -
                def token_age(token_issue_time)
         | 
| 82 | 
            -
                  Time.now - Time.parse(token_issue_time.to_s)
         | 
| 83 | 
            -
                end
         | 
| 84 | 
            -
             | 
| 85 | 
            -
                def validate_remotely(authentication_token,flow_identifier)
         | 
| 86 | 
            -
                  response = send_request(authentication_token,flow_identifier)
         | 
| 87 | 
            -
                  validate_and_extract_information_from_response(response)
         | 
| 88 | 
            -
                end
         | 
| 89 | 
            -
             | 
| 90 | 
            -
                def send_request(authentication_token,flow_identifier)
         | 
| 91 | 
            -
                  uri = URI.parse(@configuration['validator-url'])
         | 
| 92 | 
            -
                  uri.query = URI.encode_www_form( {'flow_identifier' => flow_identifier} )
         | 
| 93 | 
            -
                  http = Net::HTTP.new(uri.host, uri.port)
         | 
| 94 | 
            -
                  http.use_ssl = true if uri.is_a?(URI::HTTPS)
         | 
| 95 | 
            -
                  request = Net::HTTP::Post.new(uri.request_uri)
         | 
| 96 | 
            -
                  request.body = { 'authentication_token' => authentication_token }.to_json
         | 
| 97 | 
            -
                  http.request(request)
         | 
| 98 | 
            -
                end
         | 
| 99 | 
            -
             | 
| 100 | 
            -
                def validate_and_extract_information_from_response(response)
         | 
| 101 | 
            -
                  raise "Failure validating token with token validation service. Code #{response.code} received" if '200' != response.code
         | 
| 102 | 
            -
                  body = JSON.parse(response.body)
         | 
| 103 | 
            -
                  if ('success' == body['status']) and body['data']
         | 
| 104 | 
            -
                    token_validity = body['data']['token_validity']
         | 
| 105 | 
            -
                    token_meta = body['data']['token_meta']
         | 
| 106 | 
            -
                    message = body['data']['message']
         | 
| 107 | 
            -
                    raise 'Token validation service did not provide token_validity' if token_validity.nil?
         | 
| 108 | 
            -
                    raise 'Token validation service did not provide token_meta' if token_validity and token_meta.nil?
         | 
| 109 | 
            -
                    raise 'Token validation service did not provide message' if message.nil?
         | 
| 110 | 
            -
                    return [token_validity, token_meta, message]
         | 
| 111 | 
            -
                  end
         | 
| 112 | 
            -
                  if 'fail' == body['status']
         | 
| 113 | 
            -
                    return rejection_result(reason: 'remote validation failed')
         | 
| 114 | 
            -
                  end
         | 
| 115 | 
            -
                  raise "Failure validating token with token validation service. Status '#{body['status']}' received"
         | 
| 20 | 
            +
                def instantiate_provider
         | 
| 21 | 
            +
                  @provider = Object::const_get("SoarAuthenticationToken::#{@configuration['provider']}").new(@configuration)
         | 
| 116 22 | 
             
                end
         | 
| 117 23 |  | 
| 118 24 | 
             
                def validate_configuration
         | 
| 119 | 
            -
                  raise "' | 
| 120 | 
            -
                  raise "'mode' must be configured as either 'local', 'remote' or 'static'" unless ['local','remote', 'static'].include?(@configuration['mode'])
         | 
| 121 | 
            -
                  validate_remote_configuration if 'remote' == @configuration['mode']
         | 
| 122 | 
            -
                  validate_local_configuration  if 'local' == @configuration['mode']
         | 
| 123 | 
            -
                  validate_static_configuration if 'static' == @configuration['mode']
         | 
| 124 | 
            -
                end
         | 
| 125 | 
            -
             | 
| 126 | 
            -
                def validate_remote_configuration
         | 
| 127 | 
            -
                  raise "'validator-url' must be configured in remote mode" unless @configuration['validator-url']
         | 
| 128 | 
            -
                end
         | 
| 129 | 
            -
             | 
| 130 | 
            -
                def validate_local_configuration
         | 
| 131 | 
            -
                  raise "'public_key' must be configured in local mode" unless @configuration['public_key']
         | 
| 132 | 
            -
                  raise "'expiry' must be configured in local mode" unless @configuration['expiry']
         | 
| 133 | 
            -
                  raise "'expiry' must be an integer" unless Integer(@configuration['expiry'])
         | 
| 134 | 
            -
                end
         | 
| 135 | 
            -
             | 
| 136 | 
            -
                def validate_static_configuration
         | 
| 137 | 
            -
                  raise "'static_tokens' must be configured in local mode" unless @configuration['static_tokens']
         | 
| 138 | 
            -
                end
         | 
| 139 | 
            -
             | 
| 140 | 
            -
                def merge_with_default_configuration(configuration)
         | 
| 141 | 
            -
                  Hash.deep_merge(DEFAULT_CONFIGURATION,configuration)
         | 
| 142 | 
            -
                end
         | 
| 143 | 
            -
             | 
| 144 | 
            -
                def decode(authentication_token)
         | 
| 145 | 
            -
                  JWT.decode(authentication_token, @public_key, true, { :algorithm => 'ES512' })
         | 
| 146 | 
            -
                end
         | 
| 147 | 
            -
             | 
| 148 | 
            -
                def token_expired?(meta)
         | 
| 149 | 
            -
                  Time.parse(meta['token_expiry_time'].to_s) < Time.now
         | 
| 150 | 
            -
                end
         | 
| 151 | 
            -
             | 
| 152 | 
            -
                def token_exist_in_store?(meta,flow_identifier)
         | 
| 153 | 
            -
                  @store_provider.token_exist?(
         | 
| 154 | 
            -
                    token_identifier:         meta['token_identifier'],
         | 
| 155 | 
            -
                    authenticated_identifier: meta['authenticated_identifier'],
         | 
| 156 | 
            -
                    token_issue_time:         meta['token_issue_time'],
         | 
| 157 | 
            -
                    token_expiry_time:        meta['token_expiry_time'],
         | 
| 158 | 
            -
                    flow_identifier:          flow_identifier)
         | 
| 159 | 
            -
                end
         | 
| 160 | 
            -
             | 
| 161 | 
            -
                def rejection_result(reason:)
         | 
| 162 | 
            -
                  [false, nil, reason]
         | 
| 163 | 
            -
                end
         | 
| 164 | 
            -
             | 
| 165 | 
            -
                def success_result(token_meta:)
         | 
| 166 | 
            -
                  [true, token_meta, "Valid token for <#{token_meta['authenticated_identifier']}>" ]
         | 
| 25 | 
            +
                  raise "'provider' must be configured" unless @configuration['provider']
         | 
| 167 26 | 
             
                end
         | 
| 168 27 | 
             
              end
         | 
| 169 28 | 
             
            end
         | 
| @@ -9,7 +9,7 @@ describe SoarAuthenticationToken::RackMiddleware do | |
| 9 9 | 
             
                keypair_generator = SoarAuthenticationToken::KeypairGenerator.new
         | 
| 10 10 | 
             
                private_key, public_key = keypair_generator.generate
         | 
| 11 11 | 
             
                configuration = {
         | 
| 12 | 
            -
                  ' | 
| 12 | 
            +
                  'provider' => 'RemoteTokenGenerator',
         | 
| 13 13 | 
             
                  'generator-url' => 'http://authentication-token-generator-service:9393/generate',
         | 
| 14 14 | 
             
                  'generator-client-auth-token' => 'test_ecosystem_token_for_auth_token_aaapi_authenticator_service'
         | 
| 15 15 | 
             
                }
         | 
| @@ -22,7 +22,7 @@ describe SoarAuthenticationToken::RackMiddleware do | |
| 22 22 | 
             
                keypair_generator = SoarAuthenticationToken::KeypairGenerator.new
         | 
| 23 23 | 
             
                private_key, public_key = keypair_generator.generate
         | 
| 24 24 | 
             
                configuration = {
         | 
| 25 | 
            -
                  ' | 
| 25 | 
            +
                  'provider' => 'JwtTokenGenerator',
         | 
| 26 26 | 
             
                  'private_key' => private_key,
         | 
| 27 27 | 
             
                  'public_key' => public_key
         | 
| 28 28 | 
             
                }
         | 
| @@ -52,8 +52,7 @@ describe SoarAuthenticationToken::RackMiddleware do | |
| 52 52 | 
             
                  [200, {"Content-Type"=>"text/html"}, test_app_response_data ]
         | 
| 53 53 | 
             
                end
         | 
| 54 54 | 
             
                @iut_configuration = {
         | 
| 55 | 
            -
                  ' | 
| 56 | 
            -
                  'generator-url' => 'http://authentication-token-generator-service:9393/generate',
         | 
| 55 | 
            +
                  'provider' => 'RemoteTokenValidator',
         | 
| 57 56 | 
             
                  'validator-url' => 'http://authentication-token-validator-service:9393/validate'
         | 
| 58 57 | 
             
                }
         | 
| 59 58 | 
             
                @iut = SoarAuthenticationToken::RackMiddleware.new(@test_app, @iut_configuration)
         | 
| @@ -9,19 +9,22 @@ describe SoarAuthenticationToken::TokenGenerator do | |
| 9 9 |  | 
| 10 10 | 
             
              before :each do
         | 
| 11 11 | 
             
                @generator_configuration_local = {
         | 
| 12 | 
            -
                  ' | 
| 12 | 
            +
                  'provider' => 'JwtTokenGenerator',
         | 
| 13 13 | 
             
                  'private_key' => @private_key
         | 
| 14 14 | 
             
                }
         | 
| 15 15 | 
             
                @validator_configuration_local = {
         | 
| 16 | 
            -
                  ' | 
| 16 | 
            +
                  'provider' => 'JwtTokenValidator',
         | 
| 17 17 | 
             
                  'public_key' => @public_key
         | 
| 18 18 | 
             
                }
         | 
| 19 | 
            -
                @ | 
| 20 | 
            -
                  ' | 
| 19 | 
            +
                @configuration_remote_generator = {
         | 
| 20 | 
            +
                  'provider' => 'RemoteTokenGenerator',
         | 
| 21 21 | 
             
                  'generator-url' => 'http://authentication-token-generator-service:9393/generate',
         | 
| 22 | 
            -
                  'validator-url' => 'http://authentication-token-validator-service:9393/validate',
         | 
| 23 22 | 
             
                  'generator-client-auth-token' => 'test_ecosystem_token_for_auth_token_aaapi_authenticator_service'
         | 
| 24 23 | 
             
                }
         | 
| 24 | 
            +
                @configuration_remote_validator = {
         | 
| 25 | 
            +
                  'provider' => 'RemoteTokenValidator',
         | 
| 26 | 
            +
                  'validator-url' => 'http://authentication-token-validator-service:9393/validate',
         | 
| 27 | 
            +
                }
         | 
| 25 28 |  | 
| 26 29 | 
             
                @test_store = AuthTokenStoreProvider::StubClient.new
         | 
| 27 30 | 
             
              end
         | 
| @@ -52,12 +55,12 @@ describe SoarAuthenticationToken::TokenGenerator do | |
| 52 55 |  | 
| 53 56 | 
             
              context "when generating a new token remotely" do
         | 
| 54 57 | 
             
                it 'should request the token from the configured remote service' do
         | 
| 55 | 
            -
                  @iut = SoarAuthenticationToken::TokenGenerator.new(@ | 
| 58 | 
            +
                  @iut = SoarAuthenticationToken::TokenGenerator.new(@configuration_remote_generator)
         | 
| 56 59 | 
             
                  @iut.inject_store_provider(@test_store)
         | 
| 57 60 |  | 
| 58 61 | 
             
                  token, token_generator_meta = @iut.generate(authenticated_identifier: @test_authenticated_identifier, flow_identifier: 'test-flow-id')
         | 
| 59 62 |  | 
| 60 | 
            -
                  @validator = SoarAuthenticationToken::TokenValidator.new(@ | 
| 63 | 
            +
                  @validator = SoarAuthenticationToken::TokenValidator.new(@configuration_remote_validator)
         | 
| 61 64 | 
             
                  @iut.inject_store_provider(@test_store)
         | 
| 62 65 | 
             
                  token_validity, token_validator_meta, messages = @validator.validate(authentication_token: token, flow_identifier: 'test-flow-id')
         | 
| 63 66 |  | 
| @@ -10,11 +10,11 @@ describe SoarAuthenticationToken::TokenValidator do | |
| 10 10 | 
             
                @invalid_private_key, @invalid_public_key = keypair_generator.generate
         | 
| 11 11 | 
             
                @test_identifier = 'a@b.co.za'
         | 
| 12 12 | 
             
                @local_valid_generator_configuration = {
         | 
| 13 | 
            -
                  ' | 
| 13 | 
            +
                  'provider' => 'JwtTokenGenerator',
         | 
| 14 14 | 
             
                  'private_key' => @valid_private_key
         | 
| 15 15 | 
             
                }
         | 
| 16 16 | 
             
                @local_invalid_generator_configuration = {
         | 
| 17 | 
            -
                  ' | 
| 17 | 
            +
                  'provider' => 'JwtTokenGenerator',
         | 
| 18 18 | 
             
                  'private_key' => @invalid_private_key
         | 
| 19 19 | 
             
                }
         | 
| 20 20 |  | 
| @@ -25,7 +25,7 @@ describe SoarAuthenticationToken::TokenValidator do | |
| 25 25 |  | 
| 26 26 | 
             
                current_time = Time.now
         | 
| 27 27 | 
             
                @static_validator_configuration = {
         | 
| 28 | 
            -
                  ' | 
| 28 | 
            +
                  'provider' => 'StaticTokenValidator',
         | 
| 29 29 | 
             
                  'static_tokens' => [
         | 
| 30 30 | 
             
                    {
         | 
| 31 31 | 
             
                      'token'                    => 'some_secret_token_string_1111',
         | 
| @@ -48,16 +48,16 @@ describe SoarAuthenticationToken::TokenValidator do | |
| 48 48 | 
             
                  ]
         | 
| 49 49 | 
             
                }
         | 
| 50 50 | 
             
                @local_validator_configuration = {
         | 
| 51 | 
            -
                  ' | 
| 51 | 
            +
                  'provider' => 'JwtTokenValidator',
         | 
| 52 52 | 
             
                  'public_key' => @valid_public_key
         | 
| 53 53 | 
             
                }
         | 
| 54 54 | 
             
                @remote_generator_configuration = {
         | 
| 55 | 
            -
                  ' | 
| 55 | 
            +
                  'provider' => 'RemoteTokenGenerator',
         | 
| 56 56 | 
             
                  'generator-url' => 'http://authentication-token-generator-service:9393/generate',
         | 
| 57 57 | 
             
                  'generator-client-auth-token' => 'test_ecosystem_token_for_auth_token_aaapi_authenticator_service'
         | 
| 58 58 | 
             
                }
         | 
| 59 59 | 
             
                @remote_validator_configuration = {
         | 
| 60 | 
            -
                  ' | 
| 60 | 
            +
                  'provider' => 'RemoteTokenValidator',
         | 
| 61 61 | 
             
                  'validator-url' => 'http://authentication-token-validator-service:9393/validate',
         | 
| 62 62 | 
             
                  'generator-client-auth-token' => 'test_ecosystem_token_for_auth_token_aaapi_authenticator_service'
         | 
| 63 63 | 
             
                }
         | 
| @@ -97,7 +97,7 @@ describe SoarAuthenticationToken::TokenValidator do | |
| 97 97 | 
             
                    it 'indicate token is valid' do
         | 
| 98 98 | 
             
                      expect(token_validity).to eq true
         | 
| 99 99 | 
             
                    end
         | 
| 100 | 
            -
             | 
| 100 | 
            +
             | 
| 101 101 | 
             
                    it 'provide the token meta' do
         | 
| 102 102 | 
             
                      expect(token_meta['authenticated_identifier']).to eq @test_identifier
         | 
| 103 103 | 
             
                    end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: soar_authentication_token
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version:  | 
| 4 | 
            +
              version: 4.0.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Barney de Villiers
         | 
| 8 8 | 
             
            autorequire: 
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2017- | 
| 11 | 
            +
            date: 2017-02-06 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: soar_xt
         | 
| @@ -223,6 +223,11 @@ files: | |
| 223 223 | 
             
            - docker-compose.yml
         | 
| 224 224 | 
             
            - lib/soar_authentication_token.rb
         | 
| 225 225 | 
             
            - lib/soar_authentication_token/keypair_generator.rb
         | 
| 226 | 
            +
            - lib/soar_authentication_token/providers/jwt_token_generator.rb
         | 
| 227 | 
            +
            - lib/soar_authentication_token/providers/jwt_token_validator.rb
         | 
| 228 | 
            +
            - lib/soar_authentication_token/providers/remote_token_generator.rb
         | 
| 229 | 
            +
            - lib/soar_authentication_token/providers/remote_token_validator.rb
         | 
| 230 | 
            +
            - lib/soar_authentication_token/providers/static_token_validator.rb
         | 
| 226 231 | 
             
            - lib/soar_authentication_token/rack_middleware.rb
         | 
| 227 232 | 
             
            - lib/soar_authentication_token/token_generator.rb
         | 
| 228 233 | 
             
            - lib/soar_authentication_token/token_validator.rb
         |