himari 0.1.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 +7 -0
 - data/.rspec +3 -0
 - data/Gemfile +11 -0
 - data/Gemfile.lock +152 -0
 - data/LICENSE.txt +21 -0
 - data/Rakefile +8 -0
 - data/himari.gemspec +44 -0
 - data/lib/himari/access_token.rb +119 -0
 - data/lib/himari/app.rb +193 -0
 - data/lib/himari/authorization_code.rb +83 -0
 - data/lib/himari/client_registration.rb +47 -0
 - data/lib/himari/config.rb +39 -0
 - data/lib/himari/decisions/authentication.rb +16 -0
 - data/lib/himari/decisions/authorization.rb +48 -0
 - data/lib/himari/decisions/base.rb +63 -0
 - data/lib/himari/decisions/claims.rb +58 -0
 - data/lib/himari/id_token.rb +57 -0
 - data/lib/himari/item_provider.rb +11 -0
 - data/lib/himari/item_providers/static.rb +20 -0
 - data/lib/himari/log_line.rb +9 -0
 - data/lib/himari/middlewares/authentication_rule.rb +24 -0
 - data/lib/himari/middlewares/authorization_rule.rb +24 -0
 - data/lib/himari/middlewares/claims_rule.rb +24 -0
 - data/lib/himari/middlewares/client.rb +24 -0
 - data/lib/himari/middlewares/config.rb +24 -0
 - data/lib/himari/middlewares/signing_key.rb +24 -0
 - data/lib/himari/provider_chain.rb +26 -0
 - data/lib/himari/rule.rb +7 -0
 - data/lib/himari/rule_processor.rb +81 -0
 - data/lib/himari/services/downstream_authorization.rb +73 -0
 - data/lib/himari/services/jwks_endpoint.rb +40 -0
 - data/lib/himari/services/oidc_authorization_endpoint.rb +82 -0
 - data/lib/himari/services/oidc_provider_metadata_endpoint.rb +56 -0
 - data/lib/himari/services/oidc_token_endpoint.rb +86 -0
 - data/lib/himari/services/oidc_userinfo_endpoint.rb +73 -0
 - data/lib/himari/services/upstream_authentication.rb +106 -0
 - data/lib/himari/session_data.rb +7 -0
 - data/lib/himari/signing_key.rb +128 -0
 - data/lib/himari/storages/base.rb +57 -0
 - data/lib/himari/storages/filesystem.rb +36 -0
 - data/lib/himari/storages/memory.rb +31 -0
 - data/lib/himari/version.rb +5 -0
 - data/lib/himari.rb +4 -0
 - data/public/public/index.css +74 -0
 - data/sig/himari.rbs +4 -0
 - data/views/login.erb +37 -0
 - metadata +174 -0
 
| 
         @@ -0,0 +1,39 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'logger'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'time'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require 'json'
         
     | 
| 
      
 4 
     | 
    
         
            +
            require 'himari/log_line'
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            module Himari
         
     | 
| 
      
 7 
     | 
    
         
            +
              class Config
         
     | 
| 
      
 8 
     | 
    
         
            +
                def initialize(issuer:, storage:, providers: [], log_output: $stdout, log_level: Logger::INFO, preserve_rack_logger: false)
         
     | 
| 
      
 9 
     | 
    
         
            +
                  @issuer = issuer
         
     | 
| 
      
 10 
     | 
    
         
            +
                  @providers = providers
         
     | 
| 
      
 11 
     | 
    
         
            +
                  @storage = storage
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                  @log_output = log_output
         
     | 
| 
      
 14 
     | 
    
         
            +
                  @log_level = log_level
         
     | 
| 
      
 15 
     | 
    
         
            +
                  @preserve_rack_logger = preserve_rack_logger
         
     | 
| 
      
 16 
     | 
    
         
            +
                end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                attr_reader :issuer, :providers, :storage, :preserve_rack_logger
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                def logger
         
     | 
| 
      
 21 
     | 
    
         
            +
                  @logger ||= Logger.new(@log_output).tap do |l|
         
     | 
| 
      
 22 
     | 
    
         
            +
                    l.level = @log_level
         
     | 
| 
      
 23 
     | 
    
         
            +
                    l.formatter = proc do |severity, datetime, progname, msg|
         
     | 
| 
      
 24 
     | 
    
         
            +
                      log = {time: datetime.xmlschema, severity: severity.to_s, pid: Process.pid}
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                      case msg
         
     | 
| 
      
 27 
     | 
    
         
            +
                      when Himari::LogLine
         
     | 
| 
      
 28 
     | 
    
         
            +
                        log[:message] = msg.message
         
     | 
| 
      
 29 
     | 
    
         
            +
                        log[:data] = msg.data
         
     | 
| 
      
 30 
     | 
    
         
            +
                      else
         
     | 
| 
      
 31 
     | 
    
         
            +
                        log[:message] = msg.to_s
         
     | 
| 
      
 32 
     | 
    
         
            +
                      end
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                      "#{JSON.generate(log)}\n"
         
     | 
| 
      
 35 
     | 
    
         
            +
                    end
         
     | 
| 
      
 36 
     | 
    
         
            +
                  end
         
     | 
| 
      
 37 
     | 
    
         
            +
                end
         
     | 
| 
      
 38 
     | 
    
         
            +
              end
         
     | 
| 
      
 39 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,16 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'himari/decisions/base'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'himari/session_data'
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            module Himari
         
     | 
| 
      
 5 
     | 
    
         
            +
              module Decisions
         
     | 
| 
      
 6 
     | 
    
         
            +
                class Authentication < Base
         
     | 
| 
      
 7 
     | 
    
         
            +
                  Context = Struct.new(:provider, :claims, :user_data, :request, keyword_init: true)
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                  allow_effects(:allow, :deny, :skip)
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                  def to_evolve_args
         
     | 
| 
      
 12 
     | 
    
         
            +
                    {}
         
     | 
| 
      
 13 
     | 
    
         
            +
                  end
         
     | 
| 
      
 14 
     | 
    
         
            +
                end
         
     | 
| 
      
 15 
     | 
    
         
            +
              end
         
     | 
| 
      
 16 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,48 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'himari/decisions/base'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Himari
         
     | 
| 
      
 4 
     | 
    
         
            +
              module Decisions
         
     | 
| 
      
 5 
     | 
    
         
            +
                class Authorization < Base
         
     | 
| 
      
 6 
     | 
    
         
            +
                  DEFAULT_ALLOWED_CLAIMS = %i(
         
     | 
| 
      
 7 
     | 
    
         
            +
                    sub
         
     | 
| 
      
 8 
     | 
    
         
            +
                    name
         
     | 
| 
      
 9 
     | 
    
         
            +
                    nickname
         
     | 
| 
      
 10 
     | 
    
         
            +
                    preferred_username
         
     | 
| 
      
 11 
     | 
    
         
            +
                    profile
         
     | 
| 
      
 12 
     | 
    
         
            +
                    picture
         
     | 
| 
      
 13 
     | 
    
         
            +
                    website
         
     | 
| 
      
 14 
     | 
    
         
            +
                    email
         
     | 
| 
      
 15 
     | 
    
         
            +
                    email_verified
         
     | 
| 
      
 16 
     | 
    
         
            +
                  )
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                  Context = Struct.new(:claims, :user_data, :request, :client, keyword_init: true)
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                  allow_effects(:allow, :deny, :continue, :skip)
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                  def initialize(claims: {}, allowed_claims: DEFAULT_ALLOWED_CLAIMS, lifetime: 3600 * 12)
         
     | 
| 
      
 23 
     | 
    
         
            +
                    super()
         
     | 
| 
      
 24 
     | 
    
         
            +
                    @claims = claims
         
     | 
| 
      
 25 
     | 
    
         
            +
                    @allowed_claims = allowed_claims
         
     | 
| 
      
 26 
     | 
    
         
            +
                    @lifetime = lifetime
         
     | 
| 
      
 27 
     | 
    
         
            +
                  end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                  attr_reader :claims, :allowed_claims, :lifetime
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                  def to_evolve_args
         
     | 
| 
      
 32 
     | 
    
         
            +
                    {
         
     | 
| 
      
 33 
     | 
    
         
            +
                      claims: @claims.dup,
         
     | 
| 
      
 34 
     | 
    
         
            +
                      allowed_claims: @allowed_claims.dup,
         
     | 
| 
      
 35 
     | 
    
         
            +
                      lifetime: @lifetime&.to_i,
         
     | 
| 
      
 36 
     | 
    
         
            +
                    }
         
     | 
| 
      
 37 
     | 
    
         
            +
                  end
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                  def as_log
         
     | 
| 
      
 40 
     | 
    
         
            +
                    to_h.merge(claims: output, lifetime: @lifetime&.to_i)
         
     | 
| 
      
 41 
     | 
    
         
            +
                  end
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                  def output
         
     | 
| 
      
 44 
     | 
    
         
            +
                    claims.select { |k,_v| allowed_claims.include?(k) }
         
     | 
| 
      
 45 
     | 
    
         
            +
                  end
         
     | 
| 
      
 46 
     | 
    
         
            +
                end
         
     | 
| 
      
 47 
     | 
    
         
            +
              end
         
     | 
| 
      
 48 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,63 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Himari
         
     | 
| 
      
 2 
     | 
    
         
            +
              module Decisions
         
     | 
| 
      
 3 
     | 
    
         
            +
                class Base
         
     | 
| 
      
 4 
     | 
    
         
            +
                  class DecisionAlreadyMade < StandardError; end
         
     | 
| 
      
 5 
     | 
    
         
            +
                  class InvalidEffect < StandardError; end
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                  def self.allow_effects(*effects)
         
     | 
| 
      
 8 
     | 
    
         
            +
                    @valid_effects = effects
         
     | 
| 
      
 9 
     | 
    
         
            +
                  end
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                  def self.valid_effects
         
     | 
| 
      
 12 
     | 
    
         
            +
                    @valid_effects
         
     | 
| 
      
 13 
     | 
    
         
            +
                  end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                  def initialize
         
     | 
| 
      
 16 
     | 
    
         
            +
                    @rule_name = nil
         
     | 
| 
      
 17 
     | 
    
         
            +
                    @effect = nil
         
     | 
| 
      
 18 
     | 
    
         
            +
                    raise "#{self.class.name}.valid_effects is missing [BUG]" unless self.class.valid_effects
         
     | 
| 
      
 19 
     | 
    
         
            +
                  end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                  attr_reader :effect, :effect_comment, :rule_name
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                  def to_evolve_args
         
     | 
| 
      
 24 
     | 
    
         
            +
                    raise NotImplementedError
         
     | 
| 
      
 25 
     | 
    
         
            +
                  end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                  def to_h
         
     | 
| 
      
 28 
     | 
    
         
            +
                    {
         
     | 
| 
      
 29 
     | 
    
         
            +
                      rule_name: rule_name,
         
     | 
| 
      
 30 
     | 
    
         
            +
                      effect: effect,
         
     | 
| 
      
 31 
     | 
    
         
            +
                      effect_comment: effect_comment,
         
     | 
| 
      
 32 
     | 
    
         
            +
                    }
         
     | 
| 
      
 33 
     | 
    
         
            +
                  end
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                  def as_log
         
     | 
| 
      
 36 
     | 
    
         
            +
                    to_h
         
     | 
| 
      
 37 
     | 
    
         
            +
                  end
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                  def evolve(rule_name)
         
     | 
| 
      
 40 
     | 
    
         
            +
                    self.class.new(**to_evolve_args).set_rule_name(rule_name)
         
     | 
| 
      
 41 
     | 
    
         
            +
                  end
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                  def set_rule_name(rule_name)
         
     | 
| 
      
 44 
     | 
    
         
            +
                    raise "cannot override rule_name" if @rule_name
         
     | 
| 
      
 45 
     | 
    
         
            +
                    @rule_name = rule_name
         
     | 
| 
      
 46 
     | 
    
         
            +
                    self
         
     | 
| 
      
 47 
     | 
    
         
            +
                  end
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                  def decide!(effect, comment = "")
         
     | 
| 
      
 50 
     | 
    
         
            +
                    raise DecisionAlreadyMade, "decision can only be made once per rule (#{rule_name})" if @effect
         
     | 
| 
      
 51 
     | 
    
         
            +
                    raise InvalidEffect, "this effect is not valid under this rule. Valid effects: #{self.class.valid_effects.inspect} (#{rule_name})" unless self.class.valid_effects.include?(effect)
         
     | 
| 
      
 52 
     | 
    
         
            +
                    @effect = effect
         
     | 
| 
      
 53 
     | 
    
         
            +
                    @effect_comment = comment
         
     | 
| 
      
 54 
     | 
    
         
            +
                    nil
         
     | 
| 
      
 55 
     | 
    
         
            +
                  end
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
                  def allow!(comment = ""); decide!(:allow, comment); end
         
     | 
| 
      
 58 
     | 
    
         
            +
                  def continue!(comment = ""); decide!(:continue, comment); end
         
     | 
| 
      
 59 
     | 
    
         
            +
                  def deny!(comment = ""); decide!(:deny, comment); end
         
     | 
| 
      
 60 
     | 
    
         
            +
                  def skip!(comment = ""); decide!(:skip, comment); end
         
     | 
| 
      
 61 
     | 
    
         
            +
                end
         
     | 
| 
      
 62 
     | 
    
         
            +
              end
         
     | 
| 
      
 63 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,58 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'himari/decisions/base'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'himari/session_data'
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            module Himari
         
     | 
| 
      
 5 
     | 
    
         
            +
              module Decisions
         
     | 
| 
      
 6 
     | 
    
         
            +
                class Claims < Base
         
     | 
| 
      
 7 
     | 
    
         
            +
                  class UninitializedError < StandardError; end
         
     | 
| 
      
 8 
     | 
    
         
            +
                  class AlreadyInitializedError < StandardError; end
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                  Context = Struct.new(:request, :auth, keyword_init: true) do
         
     | 
| 
      
 11 
     | 
    
         
            +
                    def provider; auth[:provider]; end
         
     | 
| 
      
 12 
     | 
    
         
            +
                  end
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                  allow_effects(:continue, :skip)
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                  def initialize(claims: nil, user_data: nil)
         
     | 
| 
      
 17 
     | 
    
         
            +
                    super()
         
     | 
| 
      
 18 
     | 
    
         
            +
                    @claims = claims
         
     | 
| 
      
 19 
     | 
    
         
            +
                    @user_data = user_data
         
     | 
| 
      
 20 
     | 
    
         
            +
                  end
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                  def to_evolve_args
         
     | 
| 
      
 23 
     | 
    
         
            +
                    {
         
     | 
| 
      
 24 
     | 
    
         
            +
                      claims: @claims.dup,
         
     | 
| 
      
 25 
     | 
    
         
            +
                      user_data: @user_data.dup,
         
     | 
| 
      
 26 
     | 
    
         
            +
                    }
         
     | 
| 
      
 27 
     | 
    
         
            +
                  end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                  def as_log
         
     | 
| 
      
 30 
     | 
    
         
            +
                    to_h.merge(claims: @claims)
         
     | 
| 
      
 31 
     | 
    
         
            +
                  end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                  def output
         
     | 
| 
      
 34 
     | 
    
         
            +
                    Himari::SessionData.new(claims: claims, user_data: user_data)
         
     | 
| 
      
 35 
     | 
    
         
            +
                  end
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                  def initialize_claims!(claims = {})
         
     | 
| 
      
 38 
     | 
    
         
            +
                    if @claims
         
     | 
| 
      
 39 
     | 
    
         
            +
                      raise AlreadyInitializedError, "Claims already initialized; use decision.claims to make modification, or rule might be behaving wrong"
         
     | 
| 
      
 40 
     | 
    
         
            +
                    end
         
     | 
| 
      
 41 
     | 
    
         
            +
                    @claims = claims.dup
         
     | 
| 
      
 42 
     | 
    
         
            +
                    @user_data = {}
         
     | 
| 
      
 43 
     | 
    
         
            +
                  end
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                  def claims
         
     | 
| 
      
 46 
     | 
    
         
            +
                    unless @claims
         
     | 
| 
      
 47 
     | 
    
         
            +
                      raise UninitializedError, "Claims uninitialized; use decision.initialize_claims! to declare claims first (or rule order might be unintentional)" unless @claims
         
     | 
| 
      
 48 
     | 
    
         
            +
                    end
         
     | 
| 
      
 49 
     | 
    
         
            +
                    @claims
         
     | 
| 
      
 50 
     | 
    
         
            +
                  end
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                  def user_data
         
     | 
| 
      
 53 
     | 
    
         
            +
                    claims # to raise UninitializedError
         
     | 
| 
      
 54 
     | 
    
         
            +
                    @user_data
         
     | 
| 
      
 55 
     | 
    
         
            +
                  end
         
     | 
| 
      
 56 
     | 
    
         
            +
                end
         
     | 
| 
      
 57 
     | 
    
         
            +
              end
         
     | 
| 
      
 58 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,57 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'rack/oauth2'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'openid_connect'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require 'base64'
         
     | 
| 
      
 4 
     | 
    
         
            +
            require 'json/jwt'
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            module Himari
         
     | 
| 
      
 7 
     | 
    
         
            +
              class IdToken
         
     | 
| 
      
 8 
     | 
    
         
            +
                # @param authz [Himari::AuthorizationCode]
         
     | 
| 
      
 9 
     | 
    
         
            +
                def self.from_authz(authz, **kwargs)
         
     | 
| 
      
 10 
     | 
    
         
            +
                  new(
         
     | 
| 
      
 11 
     | 
    
         
            +
                    claims: authz.claims,
         
     | 
| 
      
 12 
     | 
    
         
            +
                    client_id: authz.client_id,
         
     | 
| 
      
 13 
     | 
    
         
            +
                    nonce: authz.nonce,
         
     | 
| 
      
 14 
     | 
    
         
            +
                    **kwargs
         
     | 
| 
      
 15 
     | 
    
         
            +
                  )
         
     | 
| 
      
 16 
     | 
    
         
            +
                end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                def initialize(claims:, client_id:, nonce:, signing_key:, issuer:, access_token: nil, time: Time.now)
         
     | 
| 
      
 19 
     | 
    
         
            +
                  @claims = claims
         
     | 
| 
      
 20 
     | 
    
         
            +
                  @client_id = client_id
         
     | 
| 
      
 21 
     | 
    
         
            +
                  @nonce = nonce
         
     | 
| 
      
 22 
     | 
    
         
            +
                  @signing_key = signing_key
         
     | 
| 
      
 23 
     | 
    
         
            +
                  @issuer = issuer
         
     | 
| 
      
 24 
     | 
    
         
            +
                  @access_token = access_token
         
     | 
| 
      
 25 
     | 
    
         
            +
                  @time = time
         
     | 
| 
      
 26 
     | 
    
         
            +
                end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                attr_reader :claims, :nonce, :signing_key
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                def final_claims
         
     | 
| 
      
 31 
     | 
    
         
            +
                  # https://openid.net/specs/openid-connect-core-1_0.html#IDToken
         
     | 
| 
      
 32 
     | 
    
         
            +
                  claims.merge(
         
     | 
| 
      
 33 
     | 
    
         
            +
                    iss: @issuer,
         
     | 
| 
      
 34 
     | 
    
         
            +
                    aud: @client_id,
         
     | 
| 
      
 35 
     | 
    
         
            +
                    iat: @time.to_i,
         
     | 
| 
      
 36 
     | 
    
         
            +
                    nbf: @time.to_i,
         
     | 
| 
      
 37 
     | 
    
         
            +
                    exp: (@time + 3600).to_i, # TODO: lifetime
         
     | 
| 
      
 38 
     | 
    
         
            +
                  ).merge(
         
     | 
| 
      
 39 
     | 
    
         
            +
                    @nonce ? { nonce: @nonce } : {}
         
     | 
| 
      
 40 
     | 
    
         
            +
                  ).merge(
         
     | 
| 
      
 41 
     | 
    
         
            +
                    @access_token ? { at_hash: at_hash } : {}
         
     | 
| 
      
 42 
     | 
    
         
            +
                  )
         
     | 
| 
      
 43 
     | 
    
         
            +
                end
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                def at_hash
         
     | 
| 
      
 46 
     | 
    
         
            +
                  return nil unless @access_token
         
     | 
| 
      
 47 
     | 
    
         
            +
                  dgst = @signing_key.hash_function.digest(@access_token)
         
     | 
| 
      
 48 
     | 
    
         
            +
                  Base64.urlsafe_encode64(dgst[0, dgst.size/2], padding: false)
         
     | 
| 
      
 49 
     | 
    
         
            +
                end
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                def to_jwt
         
     | 
| 
      
 52 
     | 
    
         
            +
                  jwt = JSON::JWT.new(final_claims)
         
     | 
| 
      
 53 
     | 
    
         
            +
                  jwt.kid = @signing_key.id
         
     | 
| 
      
 54 
     | 
    
         
            +
                  jwt.sign(@signing_key.pkey, @signing_key.alg.to_sym).to_s
         
     | 
| 
      
 55 
     | 
    
         
            +
                end
         
     | 
| 
      
 56 
     | 
    
         
            +
              end
         
     | 
| 
      
 57 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,11 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Himari
         
     | 
| 
      
 2 
     | 
    
         
            +
              module ItemProvider
         
     | 
| 
      
 3 
     | 
    
         
            +
                # :nocov:
         
     | 
| 
      
 4 
     | 
    
         
            +
                # Return items searched by hints. This method can perform fuzzy match with hints. OTOH is not expected to return exact match results.
         
     | 
| 
      
 5 
     | 
    
         
            +
                # Use Item#match_hint? to do exact match in later process. See also: ProviderChain
         
     | 
| 
      
 6 
     | 
    
         
            +
                def collect(**hints) 
         
     | 
| 
      
 7 
     | 
    
         
            +
                  raise NotImplementedError
         
     | 
| 
      
 8 
     | 
    
         
            +
                end
         
     | 
| 
      
 9 
     | 
    
         
            +
                # :nocov:
         
     | 
| 
      
 10 
     | 
    
         
            +
              end
         
     | 
| 
      
 11 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,20 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'himari/item_provider'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Himari
         
     | 
| 
      
 4 
     | 
    
         
            +
              module ItemProviders
         
     | 
| 
      
 5 
     | 
    
         
            +
                class Static
         
     | 
| 
      
 6 
     | 
    
         
            +
                  include Himari::ItemProvider
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                  # @param items [Array<Object>] List of static configuration items
         
     | 
| 
      
 9 
     | 
    
         
            +
                  def initialize(items)
         
     | 
| 
      
 10 
     | 
    
         
            +
                    @items = items.dup.freeze
         
     | 
| 
      
 11 
     | 
    
         
            +
                  end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                  attr_reader :items
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                  def collect(**_hint)
         
     | 
| 
      
 16 
     | 
    
         
            +
                    @items
         
     | 
| 
      
 17 
     | 
    
         
            +
                  end
         
     | 
| 
      
 18 
     | 
    
         
            +
                end
         
     | 
| 
      
 19 
     | 
    
         
            +
              end
         
     | 
| 
      
 20 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,24 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'himari/rule'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'himari/item_providers/static'
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            module Himari
         
     | 
| 
      
 5 
     | 
    
         
            +
              module Middlewares
         
     | 
| 
      
 6 
     | 
    
         
            +
                class AuthenticationRule
         
     | 
| 
      
 7 
     | 
    
         
            +
                  RACK_KEY = 'himari.authn_rule'
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                  def initialize(app, kwargs = {}, &block)
         
     | 
| 
      
 10 
     | 
    
         
            +
                    @app = app
         
     | 
| 
      
 11 
     | 
    
         
            +
                    @rule = Himari::Rule.new(block: block, **kwargs)
         
     | 
| 
      
 12 
     | 
    
         
            +
                    @provider = Himari::ItemProviders::Static.new([@rule])
         
     | 
| 
      
 13 
     | 
    
         
            +
                  end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                  attr_reader :app, :client
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                  def call(env)
         
     | 
| 
      
 18 
     | 
    
         
            +
                    env[RACK_KEY] ||= []
         
     | 
| 
      
 19 
     | 
    
         
            +
                    env[RACK_KEY] += [@provider]
         
     | 
| 
      
 20 
     | 
    
         
            +
                    @app.call(env)
         
     | 
| 
      
 21 
     | 
    
         
            +
                  end
         
     | 
| 
      
 22 
     | 
    
         
            +
                end
         
     | 
| 
      
 23 
     | 
    
         
            +
              end
         
     | 
| 
      
 24 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,24 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'himari/rule'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'himari/item_providers/static'
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            module Himari
         
     | 
| 
      
 5 
     | 
    
         
            +
              module Middlewares
         
     | 
| 
      
 6 
     | 
    
         
            +
                class AuthorizationRule
         
     | 
| 
      
 7 
     | 
    
         
            +
                  RACK_KEY = 'himari.authz_rule'
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                  def initialize(app, kwargs = {}, &block)
         
     | 
| 
      
 10 
     | 
    
         
            +
                    @app = app
         
     | 
| 
      
 11 
     | 
    
         
            +
                    @rule = Himari::Rule.new(block: block, **kwargs)
         
     | 
| 
      
 12 
     | 
    
         
            +
                    @provider = Himari::ItemProviders::Static.new([@rule])
         
     | 
| 
      
 13 
     | 
    
         
            +
                  end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                  attr_reader :app, :client
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                  def call(env)
         
     | 
| 
      
 18 
     | 
    
         
            +
                    env[RACK_KEY] ||= []
         
     | 
| 
      
 19 
     | 
    
         
            +
                    env[RACK_KEY] += [@provider]
         
     | 
| 
      
 20 
     | 
    
         
            +
                    @app.call(env)
         
     | 
| 
      
 21 
     | 
    
         
            +
                  end
         
     | 
| 
      
 22 
     | 
    
         
            +
                end
         
     | 
| 
      
 23 
     | 
    
         
            +
              end
         
     | 
| 
      
 24 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,24 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'himari/rule'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'himari/item_providers/static'
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            module Himari
         
     | 
| 
      
 5 
     | 
    
         
            +
              module Middlewares
         
     | 
| 
      
 6 
     | 
    
         
            +
                class ClaimsRule
         
     | 
| 
      
 7 
     | 
    
         
            +
                  RACK_KEY = 'himari.claims_rule'
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                  def initialize(app, kwargs = {}, &block)
         
     | 
| 
      
 10 
     | 
    
         
            +
                    @app = app
         
     | 
| 
      
 11 
     | 
    
         
            +
                    @rule = Himari::Rule.new(block: block, **kwargs)
         
     | 
| 
      
 12 
     | 
    
         
            +
                    @provider = Himari::ItemProviders::Static.new([@rule])
         
     | 
| 
      
 13 
     | 
    
         
            +
                  end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                  attr_reader :app, :client
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                  def call(env)
         
     | 
| 
      
 18 
     | 
    
         
            +
                    env[RACK_KEY] ||= []
         
     | 
| 
      
 19 
     | 
    
         
            +
                    env[RACK_KEY] += [@provider]
         
     | 
| 
      
 20 
     | 
    
         
            +
                    @app.call(env)
         
     | 
| 
      
 21 
     | 
    
         
            +
                  end
         
     | 
| 
      
 22 
     | 
    
         
            +
                end
         
     | 
| 
      
 23 
     | 
    
         
            +
              end
         
     | 
| 
      
 24 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,24 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'himari/client_registration'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'himari/item_providers/static'
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            module Himari
         
     | 
| 
      
 5 
     | 
    
         
            +
              module Middlewares
         
     | 
| 
      
 6 
     | 
    
         
            +
                class Client
         
     | 
| 
      
 7 
     | 
    
         
            +
                  RACK_KEY = 'himari.clients'
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                  def initialize(app, kwargs = {})
         
     | 
| 
      
 10 
     | 
    
         
            +
                    @app = app
         
     | 
| 
      
 11 
     | 
    
         
            +
                    @client = Himari::ClientRegistration.new(**kwargs)
         
     | 
| 
      
 12 
     | 
    
         
            +
                    @provider = Himari::ItemProviders::Static.new([@client])
         
     | 
| 
      
 13 
     | 
    
         
            +
                  end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                  attr_reader :app, :client
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                  def call(env)
         
     | 
| 
      
 18 
     | 
    
         
            +
                    env[RACK_KEY] ||= []
         
     | 
| 
      
 19 
     | 
    
         
            +
                    env[RACK_KEY] += [@provider]
         
     | 
| 
      
 20 
     | 
    
         
            +
                    @app.call(env)
         
     | 
| 
      
 21 
     | 
    
         
            +
                  end
         
     | 
| 
      
 22 
     | 
    
         
            +
                end
         
     | 
| 
      
 23 
     | 
    
         
            +
              end
         
     | 
| 
      
 24 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,24 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'himari/config'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Himari
         
     | 
| 
      
 4 
     | 
    
         
            +
              module Middlewares
         
     | 
| 
      
 5 
     | 
    
         
            +
                class Config
         
     | 
| 
      
 6 
     | 
    
         
            +
                  RACK_KEY = 'himari.config'
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                  def initialize(app, kwargs = {})
         
     | 
| 
      
 9 
     | 
    
         
            +
                    @app = app
         
     | 
| 
      
 10 
     | 
    
         
            +
                    @config = Himari::Config.new(**kwargs)
         
     | 
| 
      
 11 
     | 
    
         
            +
                  end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                  attr_reader :app, :config
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                  def call(env)
         
     | 
| 
      
 16 
     | 
    
         
            +
                    env[RACK_KEY] = config
         
     | 
| 
      
 17 
     | 
    
         
            +
                    unless config.preserve_rack_logger
         
     | 
| 
      
 18 
     | 
    
         
            +
                      env['rack.logger'] = config.logger
         
     | 
| 
      
 19 
     | 
    
         
            +
                    end
         
     | 
| 
      
 20 
     | 
    
         
            +
                    @app.call(env)
         
     | 
| 
      
 21 
     | 
    
         
            +
                  end
         
     | 
| 
      
 22 
     | 
    
         
            +
                end
         
     | 
| 
      
 23 
     | 
    
         
            +
              end
         
     | 
| 
      
 24 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,24 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'himari/signing_key'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'himari/item_providers/static'
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            module Himari
         
     | 
| 
      
 5 
     | 
    
         
            +
              module Middlewares
         
     | 
| 
      
 6 
     | 
    
         
            +
                class SigningKey
         
     | 
| 
      
 7 
     | 
    
         
            +
                  RACK_KEY = 'himari.signing_keys'
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                  def initialize(app, kwargs = {})
         
     | 
| 
      
 10 
     | 
    
         
            +
                    @app = app
         
     | 
| 
      
 11 
     | 
    
         
            +
                    @signing_key = Himari::SigningKey.new(**kwargs)
         
     | 
| 
      
 12 
     | 
    
         
            +
                    @provider = Himari::ItemProviders::Static.new([@signing_key])
         
     | 
| 
      
 13 
     | 
    
         
            +
                  end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                  attr_reader :app, :signing_key
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                  def call(env)
         
     | 
| 
      
 18 
     | 
    
         
            +
                    env[RACK_KEY] ||= []
         
     | 
| 
      
 19 
     | 
    
         
            +
                    env[RACK_KEY] += [@provider]
         
     | 
| 
      
 20 
     | 
    
         
            +
                    @app.call(env)
         
     | 
| 
      
 21 
     | 
    
         
            +
                  end
         
     | 
| 
      
 22 
     | 
    
         
            +
                end
         
     | 
| 
      
 23 
     | 
    
         
            +
              end
         
     | 
| 
      
 24 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,26 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Himari
         
     | 
| 
      
 2 
     | 
    
         
            +
              class ProviderChain
         
     | 
| 
      
 3 
     | 
    
         
            +
                # @param providers [Array<ItemProvider>]
         
     | 
| 
      
 4 
     | 
    
         
            +
                def initialize(providers)
         
     | 
| 
      
 5 
     | 
    
         
            +
                  @providers = providers
         
     | 
| 
      
 6 
     | 
    
         
            +
                end
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                attr_reader :providers
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                def find(**hint, &block)
         
     | 
| 
      
 11 
     | 
    
         
            +
                  block ||= proc { |i,h| i.match_hint?(**h) } # ItemProvider#collect doesn't guarantee exact matches, so do exact match by match_hint?
         
     | 
| 
      
 12 
     | 
    
         
            +
                  @providers.each do |provider|
         
     | 
| 
      
 13 
     | 
    
         
            +
                    provider.collect(**hint).each do |item|
         
     | 
| 
      
 14 
     | 
    
         
            +
                      return item if block.call(item, hint)
         
     | 
| 
      
 15 
     | 
    
         
            +
                    end
         
     | 
| 
      
 16 
     | 
    
         
            +
                  end
         
     | 
| 
      
 17 
     | 
    
         
            +
                  nil
         
     | 
| 
      
 18 
     | 
    
         
            +
                end
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                def collect(**hint)
         
     | 
| 
      
 21 
     | 
    
         
            +
                  @providers.flat_map do |provider|
         
     | 
| 
      
 22 
     | 
    
         
            +
                    provider.collect(**hint)
         
     | 
| 
      
 23 
     | 
    
         
            +
                  end
         
     | 
| 
      
 24 
     | 
    
         
            +
                end
         
     | 
| 
      
 25 
     | 
    
         
            +
              end
         
     | 
| 
      
 26 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/himari/rule.rb
    ADDED
    
    
| 
         @@ -0,0 +1,81 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Himari
         
     | 
| 
      
 2 
     | 
    
         
            +
              class RuleProcessor
         
     | 
| 
      
 3 
     | 
    
         
            +
                class MissingDecisionError < StandardError; end
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
                Result = Struct.new(:rule_name, :allowed, :explicit_deny, :decision, :decision_log, keyword_init: true) do
         
     | 
| 
      
 6 
     | 
    
         
            +
                  def as_log
         
     | 
| 
      
 7 
     | 
    
         
            +
                    {
         
     | 
| 
      
 8 
     | 
    
         
            +
                      rule_name: rule_name,
         
     | 
| 
      
 9 
     | 
    
         
            +
                      allowed: allowed,
         
     | 
| 
      
 10 
     | 
    
         
            +
                      explicit_deny: explicit_deny,
         
     | 
| 
      
 11 
     | 
    
         
            +
                      decision: decision&.as_log,
         
     | 
| 
      
 12 
     | 
    
         
            +
                      decision_log: decision_log.map(&:to_h),
         
     | 
| 
      
 13 
     | 
    
         
            +
                    }
         
     | 
| 
      
 14 
     | 
    
         
            +
                  end
         
     | 
| 
      
 15 
     | 
    
         
            +
                end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                # @param context [Object] Context data
         
     | 
| 
      
 18 
     | 
    
         
            +
                # @param initial_decision [Himari::Decisions::Base] Initial decision
         
     | 
| 
      
 19 
     | 
    
         
            +
                def initialize(context, initial_decision)
         
     | 
| 
      
 20 
     | 
    
         
            +
                  @context = context
         
     | 
| 
      
 21 
     | 
    
         
            +
                  @initial_decision = initial_decision
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                  @result = Result.new(rule_name: nil, allowed: false, explicit_deny: false, decision: nil, decision_log: [])
         
     | 
| 
      
 24 
     | 
    
         
            +
                  @decision = initial_decision
         
     | 
| 
      
 25 
     | 
    
         
            +
                  @final = false
         
     | 
| 
      
 26 
     | 
    
         
            +
                end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                attr_reader :rules, :context, :initial_decision
         
     | 
| 
      
 29 
     | 
    
         
            +
                attr_reader :result
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                def final?; @final; end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                # @param rules [Himari::Rule] rules
         
     | 
| 
      
 34 
     | 
    
         
            +
                def process(rule)
         
     | 
| 
      
 35 
     | 
    
         
            +
                  raise "cannot process rule for finalized result [BUG]" if final?
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                  decision = @decision.evolve(rule.name)
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                  rule.call(context, decision)
         
     | 
| 
      
 40 
     | 
    
         
            +
                  raise MissingDecisionError, "rule '#{rule.name}' returned no decision; rule must use one of decision.allow!, deny!, continue!, skip!" unless decision.effect
         
     | 
| 
      
 41 
     | 
    
         
            +
                  result.decision_log.push(decision)
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                  case decision.effect
         
     | 
| 
      
 44 
     | 
    
         
            +
                  when :allow
         
     | 
| 
      
 45 
     | 
    
         
            +
                    @decision = decision
         
     | 
| 
      
 46 
     | 
    
         
            +
                    result.rule_name ||= rule.name
         
     | 
| 
      
 47 
     | 
    
         
            +
                    result.decision = decision
         
     | 
| 
      
 48 
     | 
    
         
            +
                    result.allowed = true
         
     | 
| 
      
 49 
     | 
    
         
            +
                    result.explicit_deny = false
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                  when :continue
         
     | 
| 
      
 52 
     | 
    
         
            +
                    @decision = decision
         
     | 
| 
      
 53 
     | 
    
         
            +
                    result.decision = decision
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
                  when :skip
         
     | 
| 
      
 56 
     | 
    
         
            +
                    # do nothing
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
                  when :deny
         
     | 
| 
      
 59 
     | 
    
         
            +
                    @final = true
         
     | 
| 
      
 60 
     | 
    
         
            +
                    result.rule_name = rule.name
         
     | 
| 
      
 61 
     | 
    
         
            +
                    result.decision = nil
         
     | 
| 
      
 62 
     | 
    
         
            +
                    result.allowed = false
         
     | 
| 
      
 63 
     | 
    
         
            +
                    result.explicit_deny = true
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
                  else
         
     | 
| 
      
 66 
     | 
    
         
            +
                    raise "Unknown effect #{decision.effect} [BUG]"
         
     | 
| 
      
 67 
     | 
    
         
            +
                  end
         
     | 
| 
      
 68 
     | 
    
         
            +
                end
         
     | 
| 
      
 69 
     | 
    
         
            +
             
     | 
| 
      
 70 
     | 
    
         
            +
                # @param rules [Array<Himari::Rule>] rules
         
     | 
| 
      
 71 
     | 
    
         
            +
                def run(rules)
         
     | 
| 
      
 72 
     | 
    
         
            +
                  rules.each do |rule|
         
     | 
| 
      
 73 
     | 
    
         
            +
                    process(rule)
         
     | 
| 
      
 74 
     | 
    
         
            +
                    break if final?
         
     | 
| 
      
 75 
     | 
    
         
            +
                  end
         
     | 
| 
      
 76 
     | 
    
         
            +
                  @final = true
         
     | 
| 
      
 77 
     | 
    
         
            +
                  result.decision ||= @initial_decision unless result.explicit_deny
         
     | 
| 
      
 78 
     | 
    
         
            +
                  result
         
     | 
| 
      
 79 
     | 
    
         
            +
                end
         
     | 
| 
      
 80 
     | 
    
         
            +
              end
         
     | 
| 
      
 81 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,73 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'himari/decisions/authorization'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'himari/middlewares/authorization_rule'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require 'himari/rule_processor'
         
     | 
| 
      
 4 
     | 
    
         
            +
            require 'himari/session_data'
         
     | 
| 
      
 5 
     | 
    
         
            +
            require 'himari/provider_chain'
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            module Himari
         
     | 
| 
      
 8 
     | 
    
         
            +
              module Services
         
     | 
| 
      
 9 
     | 
    
         
            +
                class DownstreamAuthorization
         
     | 
| 
      
 10 
     | 
    
         
            +
                  class ForbiddenError < StandardError
         
     | 
| 
      
 11 
     | 
    
         
            +
                    # @param result [Himari::RuleProcessor::Result]
         
     | 
| 
      
 12 
     | 
    
         
            +
                    def initialize(result)
         
     | 
| 
      
 13 
     | 
    
         
            +
                      @result = result
         
     | 
| 
      
 14 
     | 
    
         
            +
                      super("Forbidden")
         
     | 
| 
      
 15 
     | 
    
         
            +
                    end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                    attr_reader :result
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                    def as_log
         
     | 
| 
      
 20 
     | 
    
         
            +
                      result.as_log
         
     | 
| 
      
 21 
     | 
    
         
            +
                    end
         
     | 
| 
      
 22 
     | 
    
         
            +
                  end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                  Result = Struct.new(:client, :claims, :authz_result) do
         
     | 
| 
      
 25 
     | 
    
         
            +
                    def as_log
         
     | 
| 
      
 26 
     | 
    
         
            +
                      {
         
     | 
| 
      
 27 
     | 
    
         
            +
                        client: client.as_log,
         
     | 
| 
      
 28 
     | 
    
         
            +
                        claims: claims,
         
     | 
| 
      
 29 
     | 
    
         
            +
                        decision: {
         
     | 
| 
      
 30 
     | 
    
         
            +
                          authorization: authz_result.as_log,
         
     | 
| 
      
 31 
     | 
    
         
            +
                        },
         
     | 
| 
      
 32 
     | 
    
         
            +
                      }
         
     | 
| 
      
 33 
     | 
    
         
            +
                    end
         
     | 
| 
      
 34 
     | 
    
         
            +
                  end
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                  # @param session [Himari::SessionData]
         
     | 
| 
      
 37 
     | 
    
         
            +
                  # @param client [Himari::ClientRegistration]
         
     | 
| 
      
 38 
     | 
    
         
            +
                  # @param request [Rack::Request]
         
     | 
| 
      
 39 
     | 
    
         
            +
                  # @param authz_rules [Array<Himari::Rule>] Authorization Rules
         
     | 
| 
      
 40 
     | 
    
         
            +
                  # @param logger [Logger]
         
     | 
| 
      
 41 
     | 
    
         
            +
                  def initialize(session:, client:, request: nil, authz_rules: [], logger: nil)
         
     | 
| 
      
 42 
     | 
    
         
            +
                    @session = session
         
     | 
| 
      
 43 
     | 
    
         
            +
                    @client = client
         
     | 
| 
      
 44 
     | 
    
         
            +
                    @request = request
         
     | 
| 
      
 45 
     | 
    
         
            +
                    @authz_rules = authz_rules
         
     | 
| 
      
 46 
     | 
    
         
            +
                    @logger = logger
         
     | 
| 
      
 47 
     | 
    
         
            +
                  end
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                  # @param session [Himari::SessionData]
         
     | 
| 
      
 50 
     | 
    
         
            +
                  # @param client [Himari::ClientRegistration]
         
     | 
| 
      
 51 
     | 
    
         
            +
                  # @param request [Rack::Request]
         
     | 
| 
      
 52 
     | 
    
         
            +
                  def self.from_request(session:, client:, request:)
         
     | 
| 
      
 53 
     | 
    
         
            +
                    new(
         
     | 
| 
      
 54 
     | 
    
         
            +
                      session: session,
         
     | 
| 
      
 55 
     | 
    
         
            +
                      client: client,
         
     | 
| 
      
 56 
     | 
    
         
            +
                      request: request,
         
     | 
| 
      
 57 
     | 
    
         
            +
                      authz_rules: Himari::ProviderChain.new(request.env[Himari::Middlewares::AuthorizationRule::RACK_KEY] || []).collect,
         
     | 
| 
      
 58 
     | 
    
         
            +
                      logger: request.env['rack.logger'],
         
     | 
| 
      
 59 
     | 
    
         
            +
                    )
         
     | 
| 
      
 60 
     | 
    
         
            +
                  end
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
                  def perform
         
     | 
| 
      
 63 
     | 
    
         
            +
                    context = Himari::Decisions::Authorization::Context.new(claims: @session.claims, user_data: @session.user_data, request: @request, client: @client).freeze
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
                    authorization = Himari::RuleProcessor.new(context, Himari::Decisions::Authorization.new(claims: @session.claims.dup)).run(@authz_rules)
         
     | 
| 
      
 66 
     | 
    
         
            +
                    raise ForbiddenError.new(Result.new(@client, nil, authorization)) unless authorization.allowed
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
                    claims = authorization.decision.output
         
     | 
| 
      
 69 
     | 
    
         
            +
                    Result.new(@client, claims, authorization)
         
     | 
| 
      
 70 
     | 
    
         
            +
                  end
         
     | 
| 
      
 71 
     | 
    
         
            +
                end
         
     | 
| 
      
 72 
     | 
    
         
            +
              end
         
     | 
| 
      
 73 
     | 
    
         
            +
            end
         
     |