himari 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|