hyperoslo/openid-token-proxy

View on GitHub
lib/openid_token_proxy/token.rb

Summary

Maintainability
A
1 hr
Test Coverage
require 'openid_token_proxy/token/expired'
require 'openid_token_proxy/token/invalid_audience'
require 'openid_token_proxy/token/invalid_issuer'
require 'openid_token_proxy/token/malformed'
require 'openid_token_proxy/token/required'
require 'openid_token_proxy/token/unverifiable_signature'

module OpenIDTokenProxy
  class Token
    attr_accessor :access_token, :id_token, :refresh_token

    def initialize(access_token, id_token = nil, refresh_token = nil)
      @access_token = access_token
      if id_token.is_a? Hash
        id_token = OpenIDConnect::ResponseObject::IdToken.new(id_token)
      end
      @id_token = id_token
      @refresh_token = refresh_token
    end

    def to_s
      @access_token
    end

    # Retrieves data from identity attributes
    def [](key)
      id_token.raw_attributes[key]
    end

    # Validates this token's expiration state, application, audiences and issuer
    def validate!(assertions = {})
      raise Expired if expired?

      # TODO: Nonce validation

      audiences = assertions[:audiences] || Array(assertions[:audience])
      if audiences.any?
        audience = id_token.aud
        unless audiences.include? audience
          raise InvalidAudience.new(audience)
        end
      end

      if assertions[:issuer]
        issuer = id_token.iss
        unless issuer == assertions[:issuer]
          raise InvalidIssuer.new(issuer)
        end
      end

      true
    end

    # Whether this token is valid
    def valid?(assertions = {})
      validate!(assertions)
    rescue OpenIDTokenProxy::Error
      false
    end

    def expiry_time
      Time.at(id_token.exp.to_i).utc
    end

    def expired?
      id_token.exp.to_i <= Time.now.to_i
    end

    # Decodes given access token and validates its signature by public key(s)
    # Use :skip_verification as second argument to skip signature validation
    def self.decode!(access_token, keys = OpenIDTokenProxy.config.public_keys)
      raise Required if access_token.blank?

      Array(keys).each do |key|
        begin
          object = OpenIDConnect::RequestObject.decode(access_token, key)
        rescue JSON::JWT::InvalidFormat => e
          raise Malformed.new(e.message)
        rescue JSON::JWT::VerificationFailed
          # Iterate through remaining public keys (if any)
          # Raises UnverifiableSignature if none applied (see below)

          # A failure in Certificate#verify leaves messages on the error queue,
          # which can lead to errors in SSL communication down the road.
          # See: https://bugs.ruby-lang.org/issues/7215
          OpenSSL.errors.clear
        else
          return Token.new(access_token, object.raw_attributes)
        end
      end

      raise UnverifiableSignature
    end
  end
end