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
|