lobby_boy 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.
@@ -0,0 +1,4 @@
1
+ require 'omniauth'
2
+ require 'lobby_boy/omni_auth/failure_endpoint'
3
+
4
+ OmniAuth.config.on_failure = LobbyBoy::OmniAuth::FailureEndpoint
data/config/routes.rb ADDED
@@ -0,0 +1,6 @@
1
+ LobbyBoy::Engine.routes.draw do
2
+ get 'session/check'
3
+ get 'session/state'
4
+ get 'session/end'
5
+ get 'session/refresh'
6
+ end
data/lib/lobby_boy.rb ADDED
@@ -0,0 +1,6 @@
1
+ require "lobby_boy/engine"
2
+ require "lobby_boy/configuration"
3
+
4
+ module LobbyBoy
5
+ extend Configuration
6
+ end
@@ -0,0 +1,80 @@
1
+ module LobbyBoy
2
+ module Configuration
3
+ module HashInit
4
+ def initialize(attr)
5
+ attr.each do |name, value|
6
+ instance_variable_set "@#{name}", value
7
+ end
8
+ end
9
+ end
10
+
11
+ class Client
12
+ attr_reader :host, :cookie_domain,
13
+ :logged_in, :end_session_endpoint,
14
+ :refresh_offset, :refresh_interval,
15
+ :on_login_js_partial, :on_logout_js_partial
16
+
17
+ include HashInit
18
+ end
19
+
20
+ class Provider
21
+ attr_reader :name,
22
+ :client_id,
23
+ :issuer,
24
+ :end_session_endpoint,
25
+ :check_session_iframe
26
+
27
+ include HashInit
28
+ end
29
+
30
+ def build_url(host, path)
31
+ if path =~ /^https?:\/\//
32
+ path
33
+ else
34
+ URI.join(host, path).to_s
35
+ end
36
+ end
37
+
38
+ def host_name(host_address)
39
+ URI.parse(host_address).host
40
+ end
41
+
42
+ def configure_client!(options)
43
+ opts = options.dup
44
+
45
+ opts[:end_session_endpoint] = build_url opts[:host], opts[:end_session_endpoint]
46
+ opts[:cookie_domain] ||=
47
+ if opts[:host].is_a? Symbol # e.g. :all to allow all domains
48
+ opts[:host]
49
+ else
50
+ host_name opts[:host]
51
+ end
52
+ opts[:refresh_offset] ||= 60.seconds
53
+ opts[:refresh_interval] ||= 30.seconds
54
+ opts[:logged_in] ||= lambda { false }
55
+
56
+ @client = Client.new opts
57
+ end
58
+
59
+ def configure_provider!(options)
60
+ opts = options.dup
61
+
62
+ opts[:end_session_endpoint] = build_url opts[:issuer], opts[:end_session_endpoint]
63
+ opts[:check_session_iframe] = build_url opts[:issuer], opts[:check_session_iframe]
64
+
65
+ @provider = Provider.new opts
66
+ end
67
+
68
+ def client
69
+ @client
70
+ end
71
+
72
+ def provider
73
+ @provider
74
+ end
75
+
76
+ def configured?
77
+ client && provider
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,31 @@
1
+ module LobbyBoy
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace LobbyBoy
4
+
5
+ config.assets.precompile += %w( jquery.js js.cookie-1.5.1.min.js )
6
+
7
+ config.to_prepare do
8
+ require 'lobby_boy/patches/session_management'
9
+ end
10
+
11
+ ##
12
+ # Use CORS to allow AJAX re-reauthentication.
13
+ initializer 'lobby_boy.add_middleware' do |app|
14
+ require 'rack/cors'
15
+
16
+ app.middleware.insert_before 0, 'Rack::Cors' do
17
+ allow do
18
+ omniauth = ::OmniAuth.config.path_prefix
19
+
20
+ origins '*'
21
+ resource "#{omniauth}/*", headers: :any, methods: [:get], credentials: true
22
+ end
23
+
24
+ allow do
25
+ origins '*'
26
+ resource '/session/*', headers: :any, methods: [:get], credentials: true
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,110 @@
1
+ module LobbyBoy
2
+ module OmniAuth
3
+ # This is a copy of the default OmniAuth FailureEndpoint
4
+ # minus the raise_out! we don't want and plus the actual error message
5
+ # as opposed to always just 'missing_code'.
6
+ #
7
+ # Also when authentication with prompt=none fails it will retry with prompt=login.
8
+ class FailureEndpoint
9
+ attr_reader :env
10
+
11
+ def self.call(env)
12
+ new(env).call
13
+ end
14
+
15
+ def initialize(env)
16
+ @env = env
17
+ end
18
+
19
+ def call
20
+ if script?
21
+ redirect_to '/session/state?state=unauthenticated'
22
+ elsif retry?
23
+ retry_interactive
24
+ else
25
+ redirect_to_failure
26
+ end
27
+ end
28
+
29
+ def params
30
+ request.params
31
+ end
32
+
33
+ def request
34
+ @request ||= ::Rack::Request.new env
35
+ end
36
+
37
+ def error
38
+ env['omniauth.error'] || ::OmniAuth::Error.new(env['omniauth.error.type'])
39
+ end
40
+
41
+ def script?
42
+ origin =~ /#{script_name}\/session\/state/
43
+ end
44
+
45
+ def retry?
46
+ error.message == 'interaction_required'
47
+ end
48
+
49
+ def retry_interactive
50
+ url = "#{omniauth_path}/#{strategy.name}?#{origin_query_param}&prompt=login"
51
+
52
+ redirect_to url
53
+ end
54
+
55
+ def redirect_to_failure
56
+ params = [
57
+ message_query_param(error.message),
58
+ origin_query_param,
59
+ strategy_name_query_param
60
+ ]
61
+
62
+ url = omniauth_path + '/failure?' + params.compact.join('&')
63
+
64
+ redirect_to url
65
+ end
66
+
67
+ def omniauth_path
68
+ script_name + ::OmniAuth.config.path_prefix
69
+ end
70
+
71
+ def script_name
72
+ env['SCRIPT_NAME']
73
+ end
74
+
75
+ def message_query_param(message)
76
+ 'message=' + escape(message)
77
+ end
78
+
79
+ def strategy_name_query_param
80
+ 'strategy=' + escape(strategy.name) if strategy
81
+ end
82
+
83
+ def strategy
84
+ env['omniauth.error.strategy']
85
+ end
86
+
87
+ def origin
88
+ env['omniauth.origin']
89
+ end
90
+
91
+ def origin_query_param
92
+ 'origin=' + escape(origin) if origin
93
+ end
94
+
95
+ module Functions
96
+ module_function
97
+
98
+ def redirect_to(url)
99
+ Rack::Response.new(['302 Moved'], 302, 'Location' => url.to_s).finish
100
+ end
101
+
102
+ def escape(string)
103
+ Rack::Utils.escape(string)
104
+ end
105
+ end
106
+
107
+ include Functions
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,39 @@
1
+ module LobbyBoy
2
+ module OpenIDConnect
3
+ ##
4
+ # Wraps a OpenIDConnect::ResponseObject::IdToken providing some useful
5
+ # methods for it.
6
+ class IdToken < SimpleDelegator
7
+ attr_accessor :jwt_token
8
+
9
+ ##
10
+ # Creates a new IdToken by decoding the given JWT token using the given public key.
11
+ #
12
+ # @param jwt_token [String] The JWT token received from the OpenID Connect provider.
13
+ # @param public_key [String] Public key or secret. Only required for signed tokens.
14
+ def initialize(jwt_token, public_key = nil)
15
+ @jwt_token = jwt_token
16
+ id_token = ::OpenIDConnect::ResponseObject::IdToken.decode jwt_token, public_key
17
+ super id_token
18
+ end
19
+
20
+ def expires_at
21
+ datetime_from_seconds __getobj__.exp
22
+ end
23
+
24
+ def issued_at
25
+ datetime_from_seconds __getobj__.iat
26
+ end
27
+
28
+ ##
29
+ # Number of seconds left until this ID token expires.
30
+ def expires_in
31
+ [0, __getobj__.exp - Time.now.to_i].max
32
+ end
33
+
34
+ def datetime_from_seconds(seconds)
35
+ DateTime.strptime seconds.to_s, '%s'
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,45 @@
1
+ require 'omniauth/strategies/openid_connect'
2
+ require 'lobby_boy/openid_connect/id_token'
3
+ require 'lobby_boy/util/uri'
4
+
5
+ module SessionManagement
6
+ ##
7
+ # Always append 'prompt=none' to every authorization request to make the login
8
+ # automatic if possible.
9
+ def authorize_uri
10
+ LobbyBoy::Util::URI.add_query_params super,
11
+ prompt: request.params['prompt'] || 'none',
12
+ id_token_hint: request.params['id_token_hint']
13
+ end
14
+
15
+ def access_token
16
+ return super unless LobbyBoy.configured?
17
+
18
+ at = super
19
+
20
+ @id_token ||= begin
21
+ session_state = request.params['session_state']
22
+ id_token = ::LobbyBoy::OpenIDConnect::IdToken.new at.id_token
23
+ env['lobby_boy.id_token'] = id_token
24
+
25
+ if session_state
26
+ cookie = {
27
+ state: session_state,
28
+ expires_at: id_token.exp
29
+ }
30
+
31
+ env['lobby_boy.cookie'] = {
32
+ value: cookie.to_json,
33
+ expires: id_token.expires_in.seconds.from_now,
34
+ domain: LobbyBoy.client.cookie_domain
35
+ }
36
+
37
+ id_token
38
+ end
39
+ end
40
+
41
+ at
42
+ end
43
+ end
44
+
45
+ OmniAuth::Strategies::OpenIDConnect.prepend SessionManagement
@@ -0,0 +1,25 @@
1
+ require 'uri'
2
+
3
+ module LobbyBoy
4
+ module Util
5
+ module URI
6
+ module_function
7
+
8
+ def add_query_params(url, additional_params = {})
9
+ return nil if url.nil?
10
+
11
+ uri = ::URI.parse url.to_s
12
+ params = ::URI.decode_www_form(uri.query || '')
13
+
14
+ additional_params.each do |name, value|
15
+ if value
16
+ params << [name, value]
17
+ end
18
+ end
19
+
20
+ uri.query = ::URI.encode_www_form params
21
+ uri.to_s
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,3 @@
1
+ module LobbyBoy
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :lobby_boy do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,118 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lobby_boy
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Finn GmbH
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-07-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 3.2.21
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 3.2.21
27
+ - !ruby/object:Gem::Dependency
28
+ name: rack-cors
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.4.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.4.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: omniauth
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.1'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.1'
55
+ - !ruby/object:Gem::Dependency
56
+ name: omniauth-openid-connect
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0.2'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0.2'
69
+ description:
70
+ email:
71
+ - info@finn.de
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - LICENSE
77
+ - README.md
78
+ - Rakefile
79
+ - app/controllers/lobby_boy/session_controller.rb
80
+ - app/helpers/lobby_boy/session_helper.rb
81
+ - app/views/lobby_boy/_iframes.html.erb
82
+ - app/views/lobby_boy/session/check.html.erb
83
+ - config/initializers/omniauth.rb
84
+ - config/routes.rb
85
+ - lib/lobby_boy.rb
86
+ - lib/lobby_boy/configuration.rb
87
+ - lib/lobby_boy/engine.rb
88
+ - lib/lobby_boy/omni_auth/failure_endpoint.rb
89
+ - lib/lobby_boy/openid_connect/id_token.rb
90
+ - lib/lobby_boy/patches/session_management.rb
91
+ - lib/lobby_boy/util/uri.rb
92
+ - lib/lobby_boy/version.rb
93
+ - lib/tasks/lobby_boy_tasks.rake
94
+ homepage: https://github.com/finnlabs/lobby_boy
95
+ licenses:
96
+ - GPLv3
97
+ metadata: {}
98
+ post_install_message:
99
+ rdoc_options: []
100
+ require_paths:
101
+ - lib
102
+ required_ruby_version: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ version: '0'
107
+ required_rubygems_version: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ requirements: []
113
+ rubyforge_project:
114
+ rubygems_version: 2.4.3
115
+ signing_key:
116
+ specification_version: 4
117
+ summary: Rails engine for OpenIDConnect Session Management
118
+ test_files: []