hobby-sso-guard 0.0.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,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 9420789b746af7c4123aec30ee4ea25fa07c33ebf08aa362c37c5fad260d8cfb
4
+ data.tar.gz: c30830fe6b47ea022317944de82e2a6a636e649d794ac34757e43b610f8c26c4
5
+ SHA512:
6
+ metadata.gz: f7bb14045ff3c00b36b80ee835b1d78c720ff8aa883ac2697f84014798201bb8952d8651a5c31ee11725425fc5f1d61ac14b9b39d537556b57d7edda9f1d0120
7
+ data.tar.gz: 4979e6a98d70387c08cbd30b85ae18a3ec169af24a0e423da7056a901e4a91388cca15f795afdca41379f8a6327ed615353461a2e20c3865aa5277a3f192828a
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'rspec'
4
+ gem 'rspec-power_assert'
5
+ gem 'pry'
6
+ gem 'awesome_print'
7
+ gem 'puma'
8
+ gem 'redis'
9
+ gem 'watir'
10
+
11
+ gemspec
@@ -0,0 +1,63 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ hobby-sso-guard (0.0.0)
5
+ hobby
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ awesome_print (1.8.0)
11
+ childprocess (0.9.0)
12
+ ffi (~> 1.0, >= 1.0.11)
13
+ coderay (1.1.2)
14
+ diff-lcs (1.3)
15
+ ffi (1.9.23)
16
+ hobby (0.1.2)
17
+ rack
18
+ method_source (0.9.0)
19
+ power_assert (1.1.1)
20
+ pry (0.11.3)
21
+ coderay (~> 1.1.0)
22
+ method_source (~> 0.9.0)
23
+ puma (3.11.3)
24
+ rack (2.0.4)
25
+ redis (4.0.1)
26
+ rspec (3.7.0)
27
+ rspec-core (~> 3.7.0)
28
+ rspec-expectations (~> 3.7.0)
29
+ rspec-mocks (~> 3.7.0)
30
+ rspec-core (3.7.1)
31
+ rspec-support (~> 3.7.0)
32
+ rspec-expectations (3.7.0)
33
+ diff-lcs (>= 1.2.0, < 2.0)
34
+ rspec-support (~> 3.7.0)
35
+ rspec-mocks (3.7.0)
36
+ diff-lcs (>= 1.2.0, < 2.0)
37
+ rspec-support (~> 3.7.0)
38
+ rspec-power_assert (1.1.0)
39
+ power_assert (~> 1.1.0)
40
+ rspec (>= 2.14)
41
+ rspec-support (3.7.1)
42
+ rubyzip (1.2.1)
43
+ selenium-webdriver (3.11.0)
44
+ childprocess (~> 0.5)
45
+ rubyzip (~> 1.2)
46
+ watir (6.10.3)
47
+ selenium-webdriver (~> 3.4, >= 3.4.1)
48
+
49
+ PLATFORMS
50
+ ruby
51
+
52
+ DEPENDENCIES
53
+ awesome_print
54
+ hobby-sso-guard!
55
+ pry
56
+ puma
57
+ redis
58
+ rspec
59
+ rspec-power_assert
60
+ watir
61
+
62
+ BUNDLED WITH
63
+ 1.16.1
@@ -0,0 +1,9 @@
1
+ Gem::Specification.new do |g|
2
+ g.name = 'hobby-sso-guard'
3
+ g.files = `git ls-files`.split($/)
4
+ g.version = '0.0.0'
5
+ g.summary = 'A Rack middleware for SSO(single sign-on).'
6
+ g.authors = ['Anatoly Chernow']
7
+
8
+ g.add_dependency 'hobby'
9
+ end
@@ -0,0 +1,53 @@
1
+ require 'securerandom'
2
+
3
+ module Hobby
4
+ module SSO
5
+ class Guard
6
+ # sessions: { session_id => user_id }
7
+ # tickets: { ticket => session_id } should expire quickly(20 seconds or so)
8
+ # tokens: { token => latest_visited_path }
9
+ def initialize app, sessions:, tickets:, tokens:, auth_server:
10
+ @app, @sessions, @tokens = app, sessions, tokens
11
+ @auth_server = auth_server
12
+ @enter_app = Enter.new tickets, tokens
13
+ end
14
+
15
+ def call env
16
+ @env = env
17
+
18
+ if active_session?
19
+ @app.call env
20
+ else
21
+ if env['PATH_INFO'] == '/enter'
22
+ @enter_app.call env
23
+ else
24
+ redirect_to_auth_server_with_token create_guest_token
25
+ end
26
+ end
27
+ end
28
+
29
+ private
30
+ attr_reader :env
31
+
32
+ def active_session?
33
+ session = env['rack.session']
34
+ @sessions.exists session[:id]
35
+ end
36
+
37
+ def create_guest_token
38
+ token = SecureRandom.urlsafe_base64 64
39
+ env['rack.session'][:guest_token] = token
40
+ @tokens.hset token, 'path', env['PATH_INFO']
41
+ @tokens.hset token, 'query', env['QUERY_STRING']
42
+ token
43
+ end
44
+
45
+ def redirect_to_auth_server_with_token token
46
+ location = "#{@auth_server}?service=#{env['HTTP_HOST']}&token=#{token}"
47
+ [302, { 'Location' => location }, []]
48
+ end
49
+ end
50
+ end
51
+ end
52
+
53
+ require_relative 'guard/enter'
@@ -0,0 +1,41 @@
1
+ require 'hobby'
2
+
3
+ class Hobby::SSO::Guard
4
+ class Enter
5
+ include Hobby
6
+
7
+ def initialize tickets, tokens
8
+ @tickets, @tokens = tickets, tokens
9
+ end
10
+
11
+ get '/enter' do
12
+ if ticket = request.params['ticket']
13
+ if session_id = @tickets.get(ticket)
14
+ session[:id] = session_id
15
+ redirect_to_latest_visited_path_or_root
16
+ else
17
+ response.status = 403
18
+ 'Bad ticket.'
19
+ end
20
+ else
21
+ response.status = 403
22
+ 'No ticket.'
23
+ end
24
+ end
25
+
26
+ def session
27
+ env['rack.session']
28
+ end
29
+
30
+ def redirect_to_latest_visited_path_or_root
31
+ token = session[:guest_token]
32
+ if @tokens.exists token
33
+ path = @tokens.hget token, 'path'
34
+ query = @tokens.hget token, 'query'
35
+ response.redirect "#{path}?#{query}"
36
+ else
37
+ response.redirect '/'
38
+ end
39
+ end
40
+ end
41
+ end
data/links ADDED
@@ -0,0 +1,3 @@
1
+ https://www.owasp.org/index.php/Testing_for_Session_Management
2
+ https://www.owasp.org/index.php/Session_Management_Cheat_Sheet
3
+ https://www.owasp.org/index.php/Authentication_Cheat_Sheet
@@ -0,0 +1,24 @@
1
+ require 'hobby'
2
+ require 'awesome_print'
3
+
4
+ class AuthServer
5
+ include Hobby
6
+
7
+ get do
8
+ if $auth_server_just_returns_HTTP_HOST
9
+ ap env
10
+ env['HTTP_HOST']
11
+ else
12
+ session, ticket = random, random
13
+ $sessions.set session, 'some_user_id'
14
+ $tickets.set ticket, session
15
+
16
+ response.status = 302
17
+ response.redirect "#{TEST_APP_URL}/enter?ticket=#{ticket}"
18
+ end
19
+ end
20
+
21
+ def random
22
+ SecureRandom.urlsafe_base64 64
23
+ end
24
+ end
@@ -0,0 +1,51 @@
1
+ require 'puma'
2
+ require 'redis'
3
+ require 'watir'
4
+
5
+ require 'rspec/power_assert'
6
+ RSpec::PowerAssert.example_assertion_alias :assert
7
+ RSpec::PowerAssert.example_group_assertion_alias :assert
8
+
9
+ require 'fileutils'
10
+ DIR = "/tmp/sso.#{$$}.test"
11
+ FileUtils.mkdir_p DIR
12
+
13
+
14
+ TEST_APP_URL = 'http://127.0.0.1:8080'
15
+ TEST_APP_PORT = 8080
16
+ AUTH_SERVER_URL = 'http://127.0.0.1:8081'
17
+ AUTH_SERVER_PORT = 8081
18
+
19
+ require_relative 'auth_server'
20
+ AUTH_SERVER = AuthServer.new
21
+
22
+ def start_puma port, app, host = '127.0.0.1'
23
+ server = Puma::Server.new app
24
+ server.add_tcp_listener host, port
25
+ server.run
26
+ end
27
+
28
+ RSpec.configure do |config|
29
+ config.before :suite do |example|
30
+ $sessions_pid = spawn "redis-server --unixsocket #{DIR}/sessions.sock --port 0 --dir #{DIR}"
31
+ $tickets_pid = spawn "redis-server --unixsocket #{DIR}/tickets.sock --port 0 --dir #{DIR}"
32
+ $tokens_pid = spawn "redis-server --unixsocket #{DIR}/tokens.sock --port 0 --dir #{DIR}"
33
+
34
+ $sessions = Redis.new path: "#{DIR}/sessions.sock"
35
+ $tickets = Redis.new path: "#{DIR}/tickets.sock"
36
+ $tokens = Redis.new path: "#{DIR}/tokens.sock"
37
+
38
+ $test_app_pid = fork do
39
+ require_relative 'test_app'
40
+ start_puma TEST_APP_PORT, TestApp.new
41
+ sleep
42
+ end
43
+
44
+ start_puma AUTH_SERVER_PORT, AuthServer.new
45
+ end
46
+
47
+ config.after :suite do |example|
48
+ pids = [$sessions_pid, $tickets_pid, $tokens_pid, $test_app_pid]
49
+ pids.each { |pid| `kill #{pid}` }
50
+ end
51
+ end
@@ -0,0 +1,44 @@
1
+ require_relative 'helper'
2
+
3
+ describe do
4
+ let(:browser) { Watir::Browser.new }
5
+
6
+ context 'when there is no active session' do
7
+ it 'redirects to the auth server' do
8
+ $auth_server_just_returns_HTTP_HOST = true
9
+ browser.goto TEST_APP_URL
10
+ assert { browser.text == '127.0.0.1:8081' }
11
+ end
12
+
13
+ after do
14
+ $auth_server_just_returns_HTTP_HOST = false
15
+ end
16
+ end
17
+
18
+ context 'when a user tries to /enter' do
19
+ it 'refuses if there is no ticket' do
20
+ browser.goto "#{TEST_APP_URL}/enter"
21
+ assert { browser.text == 'No ticket.' }
22
+ end
23
+
24
+ it 'refuses if there is a bad ticket' do
25
+ bad_ticket = SecureRandom.urlsafe_base64 64
26
+ browser.goto "#{TEST_APP_URL}/enter?ticket=#{bad_ticket}"
27
+ assert { browser.text == 'Bad ticket.' }
28
+ end
29
+
30
+ context 'when the ticket is valid' do
31
+ it 'sets a session cookie and redirect to an appropriate path' do
32
+ browser.goto "#{TEST_APP_URL}/some_path"
33
+ assert { browser.text == 'some path in test app' }
34
+ end
35
+ end
36
+ end
37
+
38
+ after do
39
+ browser.quit
40
+ $sessions.flushall
41
+ $tickets.flushall
42
+ $tokens.flushall
43
+ end
44
+ end
@@ -0,0 +1,18 @@
1
+ require 'hobby'
2
+ require 'hobby/sso/guard'
3
+
4
+ class TestApp
5
+ include Hobby
6
+
7
+ use Rack::Session::Cookie, secret: SecureRandom.hex(64)
8
+ use Hobby::SSO::Guard, auth_server: AUTH_SERVER_URL,
9
+ sessions: $sessions, tickets: $tickets, tokens: $tokens
10
+
11
+ get do
12
+ 'test app root'
13
+ end
14
+
15
+ get '/some_path' do
16
+ 'some path in test app'
17
+ end
18
+ end
metadata ADDED
@@ -0,0 +1,66 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hobby-sso-guard
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Anatoly Chernow
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-03-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: hobby
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description:
28
+ email:
29
+ executables: []
30
+ extensions: []
31
+ extra_rdoc_files: []
32
+ files:
33
+ - Gemfile
34
+ - Gemfile.lock
35
+ - hobby-sso-guard.gemspec
36
+ - lib/hobby/sso/guard.rb
37
+ - lib/hobby/sso/guard/enter.rb
38
+ - links
39
+ - spec/auth_server.rb
40
+ - spec/helper.rb
41
+ - spec/integration_spec.rb
42
+ - spec/test_app.rb
43
+ homepage:
44
+ licenses: []
45
+ metadata: {}
46
+ post_install_message:
47
+ rdoc_options: []
48
+ require_paths:
49
+ - lib
50
+ required_ruby_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ required_rubygems_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: '0'
60
+ requirements: []
61
+ rubyforge_project:
62
+ rubygems_version: 2.7.6
63
+ signing_key:
64
+ specification_version: 4
65
+ summary: A Rack middleware for SSO(single sign-on).
66
+ test_files: []