warden-github-rails-thinknear-fork 1.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 +15 -0
- data/.gitignore +20 -0
- data/.rspec +2 -0
- data/.travis.yml +39 -0
- data/CHANGELOG.md +20 -0
- data/Gemfile +27 -0
- data/LICENSE.txt +22 -0
- data/README.md +250 -0
- data/Rakefile +6 -0
- data/VERSION +1 -0
- data/lib/warden/github/rails.rb +39 -0
- data/lib/warden/github/rails/config.rb +49 -0
- data/lib/warden/github/rails/controller_helpers.rb +39 -0
- data/lib/warden/github/rails/railtie.rb +42 -0
- data/lib/warden/github/rails/routes.rb +83 -0
- data/lib/warden/github/rails/test_helpers.rb +28 -0
- data/lib/warden/github/rails/test_helpers/mock_user.rb +30 -0
- data/lib/warden/github/rails/version.rb +9 -0
- data/spec/integration/controller_helpers_spec.rb +95 -0
- data/spec/integration/membership_spec.rb +183 -0
- data/spec/integration/route_spec.rb +82 -0
- data/spec/integration/scope_spec.rb +33 -0
- data/spec/integration/view_helpers_spec.rb +19 -0
- data/spec/rails_app/app/controllers/scoped_controller.rb +28 -0
- data/spec/rails_app/app/controllers/unscoped_controller.rb +28 -0
- data/spec/rails_app/app/controllers/view_tests_controller.rb +2 -0
- data/spec/rails_app/app/views/view_tests/authenticated.html.erb +1 -0
- data/spec/rails_app/app/views/view_tests/user.html.erb +1 -0
- data/spec/rails_app/config.ru +2 -0
- data/spec/rails_app/config/application.rb +20 -0
- data/spec/rails_app/config/boot.rb +3 -0
- data/spec/rails_app/config/environment.rb +3 -0
- data/spec/rails_app/config/environments/development.rb +8 -0
- data/spec/rails_app/config/environments/production.rb +8 -0
- data/spec/rails_app/config/environments/test.rb +11 -0
- data/spec/rails_app/config/initializers/secret_token.rb +1 -0
- data/spec/rails_app/config/initializers/session_store.rb +1 -0
- data/spec/rails_app/config/initializers/warden_github_rails.rb +12 -0
- data/spec/rails_app/config/initializers/wrap_parameters.rb +4 -0
- data/spec/rails_app/config/routes.rb +53 -0
- data/spec/rails_app/script/rails +6 -0
- data/spec/spec_helper.rb +40 -0
- data/spec/unit/config_spec.rb +67 -0
- data/spec/unit/mock_user_spec.rb +21 -0
- data/spec/unit/rails_spec.rb +11 -0
- data/spec/unit/test_helpers_spec.rb +39 -0
- data/warden-github-rails.gemspec +25 -0
- metadata +215 -0
@@ -0,0 +1,49 @@
|
|
1
|
+
module Warden
|
2
|
+
module GitHub
|
3
|
+
module Rails
|
4
|
+
class Config
|
5
|
+
BadConfig = Class.new(StandardError)
|
6
|
+
|
7
|
+
# Default scope to use when not explicitly specified.
|
8
|
+
attr_accessor :default_scope
|
9
|
+
|
10
|
+
# The list of scopes and their configs. This is used to add custom
|
11
|
+
# configs to a specific scope. When using a scope that is not listed
|
12
|
+
# here, it will use the default configs from warden-github.
|
13
|
+
attr_reader :scopes
|
14
|
+
|
15
|
+
# A hash containing team alias names and their numeric id.
|
16
|
+
attr_reader :teams
|
17
|
+
|
18
|
+
def initialize
|
19
|
+
@default_scope = :user
|
20
|
+
@scopes = {}
|
21
|
+
@teams = {}
|
22
|
+
end
|
23
|
+
|
24
|
+
# Adds a scope with custom configurations to the list of scopes.
|
25
|
+
def add_scope(name, config={})
|
26
|
+
scopes[name] = config
|
27
|
+
end
|
28
|
+
|
29
|
+
# Maps a team id to a name in order to easier reference it.
|
30
|
+
def add_team(name, id)
|
31
|
+
teams[name.to_sym] = Integer(id)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Gets the team id for a team id or alias.
|
35
|
+
def team_id(team)
|
36
|
+
# In ruby 1.8 doing a Integer(:symbol) returns an integer. Thus, test
|
37
|
+
# for symbol first.
|
38
|
+
if team.is_a? Symbol
|
39
|
+
teams.fetch(team)
|
40
|
+
else
|
41
|
+
Integer(team) rescue teams.fetch(team.to_sym)
|
42
|
+
end
|
43
|
+
rescue IndexError
|
44
|
+
fail BadConfig, "No team id defined for team #{team}."
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Warden
|
2
|
+
module GitHub
|
3
|
+
module Rails
|
4
|
+
module ControllerHelpers
|
5
|
+
def self.included(klass)
|
6
|
+
klass.helper_method(:github_authenticated?, :github_user)
|
7
|
+
end
|
8
|
+
|
9
|
+
# Initiates the OAuth flow if not already authenticated for the
|
10
|
+
# specified scope.
|
11
|
+
def github_authenticate!(scope=Rails.default_scope)
|
12
|
+
request.env['warden'].authenticate!(:scope => scope)
|
13
|
+
end
|
14
|
+
|
15
|
+
# Logs out a user if currently logged in for the specified scope.
|
16
|
+
def github_logout(scope=Rails.default_scope)
|
17
|
+
request.env['warden'].logout(scope)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Checks whether a user is logged in for the specified scope.
|
21
|
+
def github_authenticated?(scope=Rails.default_scope)
|
22
|
+
request.env['warden'].authenticated?(scope)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Returns the currently signed in user for the specified scope. See the
|
26
|
+
# documentation for Warden::GitHub::User for available methods.
|
27
|
+
def github_user(scope=Rails.default_scope)
|
28
|
+
request.env['warden'].user(scope)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Accessor for the currently signed in user's session. This will be
|
32
|
+
# cleared once logged out.
|
33
|
+
def github_session(scope=Rails.default_scope)
|
34
|
+
request.env['warden'].session(scope) if github_authenticated?(scope)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Warden
|
2
|
+
module GitHub
|
3
|
+
module Rails
|
4
|
+
class Railtie < ::Rails::Railtie
|
5
|
+
initializer 'warden-github-rails.warden' do |app|
|
6
|
+
# When devise is used, it inserts a warden middlware. Multiple warden
|
7
|
+
# middlewares do not work properly. Devise allows for a block to be
|
8
|
+
# specified that is invoked when its warden middleware is configured.
|
9
|
+
# This makes it possible to setup warden-github-rails through devise.
|
10
|
+
if defined?(::Devise)
|
11
|
+
::Devise.warden { |config| setup_scopes(config) }
|
12
|
+
else
|
13
|
+
app.config.middleware.use Warden::Manager do |config|
|
14
|
+
setup_failure_app(config)
|
15
|
+
setup_scopes(config)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
initializer 'warden-github-rails.helpers' do
|
21
|
+
ActiveSupport.on_load(:action_controller) do
|
22
|
+
include ControllerHelpers
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def setup_scopes(config)
|
27
|
+
Rails.scopes.each do |scope, scope_config|
|
28
|
+
config.scope_defaults scope, :strategies => [:github],
|
29
|
+
:config => scope_config
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def setup_failure_app(config)
|
34
|
+
config.failure_app = lambda do |env|
|
35
|
+
[403, {}, [env['warden'].message]]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module Warden
|
2
|
+
module GitHub
|
3
|
+
module Rails
|
4
|
+
module Routes
|
5
|
+
# Enforces an authenticated GitHub user for the routes. If not
|
6
|
+
# authenticated, it initiates the OAuth flow.
|
7
|
+
#
|
8
|
+
# Team and organization memberships can be checked by specifying a hash
|
9
|
+
# such as `:team => 'foobar'` or `:org => 'my_company'`.
|
10
|
+
def github_authenticate(scope=nil, options={}, &routes_block)
|
11
|
+
github_constraint(scope, options, routes_block) do |warden, scope|
|
12
|
+
warden.authenticate!(:scope => scope)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# The routes will only be visible to authenticated GitHub users. When
|
17
|
+
# not authenticated, it does not initiate the OAuth flow.
|
18
|
+
#
|
19
|
+
# Team and organization memberships can be checked by specifying a hash
|
20
|
+
# such as `:team => 'foobar'` or `:org => 'my_company'`.
|
21
|
+
def github_authenticated(scope=nil, options={}, &routes_block)
|
22
|
+
github_constraint(scope, options, routes_block) do |warden, scope|
|
23
|
+
warden.authenticated?(:scope => scope)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# The routes will only be visible to all but authenticated GitHub users.
|
28
|
+
#
|
29
|
+
# This constraint currently does not check for memberships since of its
|
30
|
+
# limited usage.
|
31
|
+
def github_unauthenticated(scope=nil, options={}, &routes_block)
|
32
|
+
github_constraint(scope, options, routes_block) do |warden, scope|
|
33
|
+
not warden.authenticated?(:scope => scope)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def github_constraint(scope, options, routes_block, &block)
|
40
|
+
options, scope = scope, nil if scope.is_a? Hash
|
41
|
+
scope ||= Rails.default_scope
|
42
|
+
|
43
|
+
constraint = lambda do |request|
|
44
|
+
warden = request.env['warden']
|
45
|
+
|
46
|
+
if block.call(warden, scope)
|
47
|
+
if (user = warden.user(scope))
|
48
|
+
evaled_options = github_eval_options(options, request)
|
49
|
+
github_enforce_options(user, evaled_options)
|
50
|
+
else
|
51
|
+
true
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
constraints(constraint, &routes_block)
|
57
|
+
end
|
58
|
+
|
59
|
+
def github_enforce_options(user, options)
|
60
|
+
if (team = options[:team])
|
61
|
+
user.team_member?(Rails.team_id(team))
|
62
|
+
elsif (org = options[:org] || options[:organization])
|
63
|
+
user.organization_member?(org)
|
64
|
+
else
|
65
|
+
true
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def github_eval_options(options, request)
|
70
|
+
Hash[options.map { |k,v|
|
71
|
+
if v.is_a?(Proc)
|
72
|
+
[k, v.call(request)]
|
73
|
+
else
|
74
|
+
[k, v]
|
75
|
+
end
|
76
|
+
}]
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
ActionDispatch::Routing::Mapper.send(:include, Warden::GitHub::Rails::Routes)
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'warden/github'
|
2
|
+
require 'warden/github/rails/test_helpers/mock_user'
|
3
|
+
|
4
|
+
module Warden
|
5
|
+
module GitHub
|
6
|
+
module Rails
|
7
|
+
module TestHelpers
|
8
|
+
include ::Warden::Test::Helpers
|
9
|
+
|
10
|
+
# Login a mock GitHub user and return it.
|
11
|
+
def github_login(scope=Rails.default_scope)
|
12
|
+
MockUser.new.tap do |user|
|
13
|
+
login_as(user, :scope => scope)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Add a method to Rack::Response to easily determine if a request resulted in an
|
22
|
+
# OAuth redirect to GitHub.
|
23
|
+
class Rack::Response
|
24
|
+
def github_oauth_redirect?
|
25
|
+
redirect? and
|
26
|
+
location.start_with?('https://github.com/login/oauth/authorize')
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Warden
|
2
|
+
module GitHub
|
3
|
+
module Rails
|
4
|
+
module TestHelpers
|
5
|
+
class MockUser < User
|
6
|
+
attr_reader :memberships
|
7
|
+
|
8
|
+
def initialize(*args)
|
9
|
+
super
|
10
|
+
@memberships = { :team => [], :org => [] }
|
11
|
+
end
|
12
|
+
|
13
|
+
def stub_membership(args)
|
14
|
+
args.each do |type, values|
|
15
|
+
memberships.fetch(type).concat(Array(values))
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def team_member?(id)
|
20
|
+
memberships[:team].include?(id)
|
21
|
+
end
|
22
|
+
|
23
|
+
def organization_member?(id)
|
24
|
+
memberships[:org].include?(id)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'controller helpers' do
|
4
|
+
{
|
5
|
+
:scoped => :admin,
|
6
|
+
:unscoped => Warden::GitHub::Rails.default_scope
|
7
|
+
}.each do |type, scope|
|
8
|
+
context "when using #{type}" do
|
9
|
+
describe '#github_authenticate!' do
|
10
|
+
subject(:request) { get "/#{type}/authenticate" }
|
11
|
+
|
12
|
+
context 'when not logged in' do
|
13
|
+
it 'initiates the oauth flow' do
|
14
|
+
expect(request).to be_github_oauth_redirect
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
context 'when logged in' do
|
19
|
+
before { github_login(scope) }
|
20
|
+
it 'does nothing' do
|
21
|
+
expect(request).to be_ok
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe '#github_logout' do
|
27
|
+
context 'when not logged in' do
|
28
|
+
it 'does nothing' do
|
29
|
+
expect(get("/#{type}/logout").body).to eq('false')
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'when logged in' do
|
34
|
+
it 'logs out the user' do
|
35
|
+
github_login(scope)
|
36
|
+
expect(get("/#{type}/logout").body).to eq('true')
|
37
|
+
expect(get("/#{type}/logout").body).to eq('false')
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe '#github_authenticated?' do
|
43
|
+
subject(:request) { get "/#{type}/authenticated" }
|
44
|
+
|
45
|
+
context 'when not logged in' do
|
46
|
+
it 'returns false' do
|
47
|
+
expect(request.body).to eq('false')
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
context 'when logged in' do
|
52
|
+
it 'returns true' do
|
53
|
+
github_login(scope)
|
54
|
+
expect(request.body).to eq('true')
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe '#github_user' do
|
60
|
+
subject(:request) { get "/#{type}/user" }
|
61
|
+
|
62
|
+
context 'when not logged in' do
|
63
|
+
it 'returns nil' do
|
64
|
+
expect(request.body).to be_blank
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
context 'when logged in' do
|
69
|
+
it 'returns the logged in user' do
|
70
|
+
github_login(scope)
|
71
|
+
expect(request.body).to \
|
72
|
+
include('Warden::GitHub::Rails::TestHelpers::MockUser')
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe '#github_session' do
|
78
|
+
subject(:request) { get "/#{type}/session" }
|
79
|
+
|
80
|
+
context 'when not logged in' do
|
81
|
+
it 'should be nil' do
|
82
|
+
expect(request.body).to be_blank
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
context 'when logged in' do
|
87
|
+
it "returns the user's session" do
|
88
|
+
github_login(scope)
|
89
|
+
expect(request.body).to eq({ :foo => :bar }.to_s)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,183 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'request to a protected resource' do
|
4
|
+
context 'that requires a team membership' do
|
5
|
+
context 'which is specified by a numeric team id' do
|
6
|
+
subject { get '/team/protected' }
|
7
|
+
|
8
|
+
context 'when not logged in' do
|
9
|
+
it { should be_github_oauth_redirect }
|
10
|
+
end
|
11
|
+
|
12
|
+
context 'when logged in' do
|
13
|
+
context 'and team member' do
|
14
|
+
before do
|
15
|
+
user = github_login
|
16
|
+
user.stub_membership(:team => 123)
|
17
|
+
end
|
18
|
+
|
19
|
+
it { should be_ok }
|
20
|
+
end
|
21
|
+
|
22
|
+
context 'and not team member' do
|
23
|
+
before { github_login }
|
24
|
+
it { should be_not_found}
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context 'which is specified by a team alias' do
|
30
|
+
subject { get '/team_alias/protected' }
|
31
|
+
|
32
|
+
context 'when not logged in' do
|
33
|
+
it { should be_github_oauth_redirect }
|
34
|
+
end
|
35
|
+
|
36
|
+
context 'when logged in' do
|
37
|
+
context 'and team member' do
|
38
|
+
before do
|
39
|
+
user = github_login
|
40
|
+
user.stub_membership(:team => 456)
|
41
|
+
end
|
42
|
+
|
43
|
+
it { should be_ok }
|
44
|
+
end
|
45
|
+
|
46
|
+
context 'and not team member' do
|
47
|
+
before { github_login }
|
48
|
+
it { should be_not_found}
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context 'which is specified by a lambda' do
|
54
|
+
subject { get '/dynamic_team/123' }
|
55
|
+
|
56
|
+
context 'when logged in' do
|
57
|
+
context 'and team member' do
|
58
|
+
before do
|
59
|
+
user = github_login
|
60
|
+
user.stub_membership(:team => 123)
|
61
|
+
end
|
62
|
+
|
63
|
+
it { should be_ok }
|
64
|
+
end
|
65
|
+
|
66
|
+
context 'and not team member' do
|
67
|
+
before { github_login }
|
68
|
+
it { should be_not_found}
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
context 'that requires an organization membership' do
|
75
|
+
{ :org => :foobar_inc, :organization => 'some_org' }.each do |key, value|
|
76
|
+
context "which is specified as #{key}" do
|
77
|
+
subject { get "/#{key}/protected" }
|
78
|
+
|
79
|
+
context 'when not logged in' do
|
80
|
+
it { should be_github_oauth_redirect }
|
81
|
+
end
|
82
|
+
|
83
|
+
context 'when logged in' do
|
84
|
+
context 'and organization member' do
|
85
|
+
before do
|
86
|
+
user = github_login
|
87
|
+
user.stub_membership(:org => value)
|
88
|
+
end
|
89
|
+
|
90
|
+
it { should be_ok }
|
91
|
+
end
|
92
|
+
|
93
|
+
context 'and not organization member' do
|
94
|
+
before { github_login }
|
95
|
+
it { should be_not_found }
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
context 'which is specified by a lambda' do
|
102
|
+
subject { get '/dynamic_org/some_org' }
|
103
|
+
|
104
|
+
context 'when logged in' do
|
105
|
+
context 'and organization member' do
|
106
|
+
before do
|
107
|
+
user = github_login
|
108
|
+
user.stub_membership(:org => 'some_org')
|
109
|
+
end
|
110
|
+
|
111
|
+
it { should be_ok }
|
112
|
+
end
|
113
|
+
|
114
|
+
context 'and not organization member' do
|
115
|
+
before { github_login }
|
116
|
+
it { should be_not_found}
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
describe 'request to a resource that only exists when logged in' do
|
124
|
+
context 'that requires a team membership' do
|
125
|
+
context 'which is specified by a numeric team id' do
|
126
|
+
subject { get '/team/conditional' }
|
127
|
+
|
128
|
+
context 'when team member' do
|
129
|
+
before do
|
130
|
+
user = github_login
|
131
|
+
user.stub_membership(:team => 123)
|
132
|
+
end
|
133
|
+
|
134
|
+
it { should be_ok }
|
135
|
+
end
|
136
|
+
|
137
|
+
context 'when not team member' do
|
138
|
+
before { github_login }
|
139
|
+
it { should be_not_found}
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
context 'which is specified by a team alias' do
|
144
|
+
subject { get '/team_alias/conditional' }
|
145
|
+
|
146
|
+
context 'when team member' do
|
147
|
+
before do
|
148
|
+
user = github_login
|
149
|
+
user.stub_membership(:team => 456)
|
150
|
+
end
|
151
|
+
|
152
|
+
it { should be_ok }
|
153
|
+
end
|
154
|
+
|
155
|
+
context 'when not team member' do
|
156
|
+
before { github_login }
|
157
|
+
it { should be_not_found}
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
context 'that requires an organization membership' do
|
163
|
+
{ :org => :foobar_inc, :organization => 'some_org' }.each do |key, value|
|
164
|
+
context "which is specified as #{key}" do
|
165
|
+
subject { get "/#{key}/conditional" }
|
166
|
+
|
167
|
+
context 'when organization member' do
|
168
|
+
before do
|
169
|
+
user = github_login
|
170
|
+
user.stub_membership(:org => value)
|
171
|
+
end
|
172
|
+
|
173
|
+
it { should be_ok }
|
174
|
+
end
|
175
|
+
|
176
|
+
context 'when not organization member' do
|
177
|
+
before { github_login }
|
178
|
+
it { should be_not_found }
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|