warden-github-rails 0.0.1

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.
Files changed (43) hide show
  1. data/.gitignore +20 -0
  2. data/.rspec +2 -0
  3. data/.travis.yml +10 -0
  4. data/Gemfile +14 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +188 -0
  7. data/Rakefile +6 -0
  8. data/VERSION +1 -0
  9. data/lib/warden/github/rails.rb +42 -0
  10. data/lib/warden/github/rails/config.rb +52 -0
  11. data/lib/warden/github/rails/controller_helpers.rb +35 -0
  12. data/lib/warden/github/rails/railtie.rb +27 -0
  13. data/lib/warden/github/rails/routes.rb +83 -0
  14. data/lib/warden/github/rails/test_helpers.rb +28 -0
  15. data/lib/warden/github/rails/test_helpers/mock_user.rb +28 -0
  16. data/lib/warden/github/rails/version.rb +9 -0
  17. data/lib/warden/github/rails/view_helpers.rb +18 -0
  18. data/spec/integration/controller_helpers_spec.rb +95 -0
  19. data/spec/integration/membership_spec.rb +183 -0
  20. data/spec/integration/route_spec.rb +82 -0
  21. data/spec/integration/scope_spec.rb +33 -0
  22. data/spec/rails_app/app/controllers/scoped_controller.rb +28 -0
  23. data/spec/rails_app/app/controllers/unscoped_controller.rb +28 -0
  24. data/spec/rails_app/config.ru +2 -0
  25. data/spec/rails_app/config/application.rb +20 -0
  26. data/spec/rails_app/config/boot.rb +3 -0
  27. data/spec/rails_app/config/environment.rb +3 -0
  28. data/spec/rails_app/config/environments/development.rb +8 -0
  29. data/spec/rails_app/config/environments/production.rb +8 -0
  30. data/spec/rails_app/config/environments/test.rb +11 -0
  31. data/spec/rails_app/config/initializers/secret_token.rb +1 -0
  32. data/spec/rails_app/config/initializers/session_store.rb +1 -0
  33. data/spec/rails_app/config/initializers/warden_github_rails.rb +12 -0
  34. data/spec/rails_app/config/initializers/wrap_parameters.rb +4 -0
  35. data/spec/rails_app/config/routes.rb +49 -0
  36. data/spec/rails_app/script/rails +6 -0
  37. data/spec/spec_helper.rb +37 -0
  38. data/spec/unit/config_spec.rb +67 -0
  39. data/spec/unit/mock_user_spec.rb +20 -0
  40. data/spec/unit/rails_spec.rb +11 -0
  41. data/spec/unit/test_helpers_spec.rb +39 -0
  42. data/warden-github-rails.gemspec +25 -0
  43. metadata +229 -0
data/.gitignore ADDED
@@ -0,0 +1,20 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ tags
19
+ bin/
20
+ log
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format documentation
data/.travis.yml ADDED
@@ -0,0 +1,10 @@
1
+ language: ruby
2
+ script: "bundle exec rspec"
3
+ rvm:
4
+ - 1.8.7
5
+ - 1.9.2
6
+ - 1.9.3
7
+ - ruby-head
8
+ matrix:
9
+ allow_failures:
10
+ - rvm: ruby-head
data/Gemfile ADDED
@@ -0,0 +1,14 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ if ENV['EDGE']
6
+ gem 'warden-github', :github => 'atmos/warden-github'
7
+ end
8
+
9
+ group :development do
10
+ unless ENV['CI']
11
+ gem 'debugger', :platforms => :ruby_19, :require => false
12
+ gem 'ruby-debug', :platforms => :ruby_18, :require => false
13
+ end
14
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Philipe Fatio
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,188 @@
1
+ # warden-github-rails
2
+
3
+ [![Build Status](https://travis-ci.org/fphilipe/warden-github-rails.png)](https://travis-ci.org/fphilipe/warden-github-rails)
4
+ [![Gem Version](https://badge.fury.io/rb/warden-github-rails.png)](http://badge.fury.io/rb/warden-github-rails)
5
+ [![Dependency Status](https://gemnasium.com/fphilipe/warden-github-rails.png)](https://gemnasium.com/fphilipe/warden-github-rails)
6
+ [![Code Climate](https://codeclimate.com/github/fphilipe/warden-github-rails.png)](https://codeclimate.com/github/fphilipe/warden-github-rails)
7
+
8
+ A gem for rails that provides easy GitHub OAuth integration.
9
+ It is built on top of [warden-github](https://github.com/atmos/warden-github), which gives you an easy to use [warden](https://github.com/hassox/warden) strategy to authenticate GitHub users.
10
+
11
+ ## Motivation
12
+
13
+ **Wouldn't it be nice to**
14
+
15
+ - use your organization and it's teams for user access control?
16
+ - add a new employee to your GitHub organization or team in order to grant them access to your app's admin area?
17
+
18
+ The motivation for this gem was to provide a very easy authorization (not authentication) mechanism to existing rails apps for admins, especially in combination with organization and team memberships.
19
+ The provided routing helpers do exactly that.
20
+ They allow you to restrict access to members of your organization or a certain team.
21
+
22
+ This is how your rails `routes.rb` could look like:
23
+
24
+ ```ruby
25
+ constraints(:subdomain => 'admin') do
26
+ github_authenticate(:org => 'my_company_inc') do
27
+ resources :users
28
+ resources :projects
29
+
30
+ github_authenticated(:team => 'sysadmins') do
31
+ resource :infrastructure
32
+ end
33
+ end
34
+ end
35
+ ```
36
+
37
+ Of course, this gem can also be used for user registration and authentication.
38
+ Several helper methods are available in the controller to accomplish this:
39
+
40
+ ```ruby
41
+ class UsersController < ApplicationController
42
+ # ...
43
+
44
+ def new
45
+ github_authenticate! # Performs OAuth flow when not logged in.
46
+ @user = User.new(:name => github_user.name, :email => github_user.email)
47
+ end
48
+
49
+ def create
50
+ attrs = params.require(:user).permit(:name, :email).merge(:github_id => github_user.id)
51
+ @user = User.create(attrs)
52
+
53
+ if @user
54
+ redirect_to :show
55
+ else
56
+ render :new
57
+ end
58
+ end
59
+
60
+ # ...
61
+ end
62
+ ```
63
+
64
+ ## Usage
65
+
66
+ ### Configuration
67
+
68
+ First off, you might want to configure this gem by creating an initializer such as `config/initializers/warden_github_rails.rb`.
69
+ There you can define:
70
+
71
+ - various scopes and their configs (scopes are types of users with different configs)
72
+ - the default scope (which is `:user` by default)
73
+ - team aliases (GitHub teams are identified by a numerical ID; defining an alias for a team makes it easier to use)
74
+
75
+ Here's how such a config might look like:
76
+
77
+ ```ruby
78
+ Warden::GitHub::Rails.setup do |config|
79
+ config.add_scope :user, :client_id => 'foo',
80
+ :client_secret => 'bar',
81
+ :scope => 'user'
82
+
83
+ config.add_scope :admin, :client_id => 'abc',
84
+ :client_secret => 'xyz',
85
+ :redirect_uri => '/admin/login/callback',
86
+ :scope => 'repo'
87
+
88
+ config.default_scope = :admin
89
+
90
+ config.add_team :marketing, 456
91
+ end
92
+ ```
93
+
94
+ For a list of allowed config parameters to use in `#add_scope`, read the [warden-github documentation](https://github.com/atmos/warden-github#parameters).
95
+
96
+ ### Inside `routes.rb`
97
+
98
+ The available routing helpers are defined and documented in [lib/warden/github/rails/routes.rb](lib/warden/github/rails/routes.rb).
99
+ They all accept an optional scope that, when omitted, falls back to the default_scope configured in the initializer.
100
+
101
+ Examples:
102
+
103
+ ```ruby
104
+ # Performs login if not logged in already.
105
+ github_authenticate do
106
+ resource :profile
107
+ end
108
+
109
+ # Does not perform login when not logged in.
110
+ github_authenticated do
111
+ delete '/logout' => 'sessions#delete'
112
+ end
113
+
114
+ # Only matches when not logged in. Does not perform login.
115
+ github_unauthenticated do
116
+ resource :registration
117
+ end
118
+
119
+ # Only matches when member of the organization. Initiates login if not logged in.
120
+ github_authenticate(:org => 'my_company') do
121
+ resource :admin
122
+ end
123
+
124
+ # Only matches when member of the team. Does not initiate login if not logged in.
125
+ github_authenticated(:team => 'markting') do
126
+ get '/dashboard' => 'dashboard#show'
127
+ end
128
+
129
+ # Using dynamic membership values:
130
+ github_authenticate(:org => lambda { |req| r.params[:id] }) do
131
+ get '/orgs/:id' => 'orgs#show'
132
+ end
133
+ ```
134
+
135
+ ### Inside a Controller
136
+
137
+ The available controller helpers are defined and documented in [lib/warden/github/rails/controller_helpers.rb](lib/warden/github/rails/controller_helpers.rb).
138
+ They all accept an optional scope that, when omitted, falls back to the default_scope configured in the initializer.
139
+
140
+ ```ruby
141
+ class SomeController < ActionController::Base
142
+ def show
143
+ @is_admin = github_authenticated?(:admin)
144
+ end
145
+
146
+ def delete
147
+ github_logout
148
+ redirect_to '/'
149
+ end
150
+
151
+ def settings
152
+ github_authenticate!
153
+ @settings = UserSettings.find_by_github_user_id(github_user.id)
154
+ end
155
+
156
+ def finish_wizard
157
+ github_session[:wizard_completed] = true
158
+ end
159
+
160
+ def followers
161
+ @followers = github_user.api.followers
162
+ end
163
+ end
164
+ ```
165
+
166
+ ### Communicating with the GitHub API
167
+
168
+ Once a user is logged in, you'll have access to it in the controller using `github_user`. It is an instance of `Warden::GitHub::User` which is defined in the [warden-github](https://github.com/atmos/warden-github/blob/master/lib/warden/github/user.rb) gem. The instance has several methods to access user information such as `#name`, `#id`, `#email`, etc. It also features a method `#api` which returns a preconfigured [Octokit](https://github.com/pengwynn/octokit) client for that user.
169
+
170
+ ## Known Limitations
171
+
172
+ Currently, this gem does not play nicely with [Devise](https://github.com/plataformatec/devise). Devise is built on top of warden and performs some monkeypatching of warden which breaks this gem. Support will be added as soon as possible.
173
+
174
+ ## Additional Information
175
+
176
+ ### Dependencies
177
+
178
+ - [warden-github](https://github.com/atmos/warden-github)
179
+ - [warden](https://github.com/hassox/warden)
180
+ - [octokit](https://github.com/pengwynn/octokit)
181
+
182
+ ### Maintainers
183
+
184
+ - Philipe Fatio ([@fphilipe](https://github.com/fphilipe))
185
+
186
+ ### License
187
+
188
+ MIT License. Copyright 2013 Philipe Fatio
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
@@ -0,0 +1,42 @@
1
+ require 'warden/github'
2
+
3
+ require 'warden/github/rails/version'
4
+ require 'warden/github/rails/routes'
5
+ require 'warden/github/rails/railtie'
6
+ require 'warden/github/rails/config'
7
+ require 'warden/github/rails/controller_helpers'
8
+ require 'warden/github/rails/view_helpers'
9
+
10
+ require 'forwardable'
11
+
12
+ module Warden
13
+ module GitHub
14
+ module Rails
15
+ extend SingleForwardable
16
+
17
+ def_delegators :config,
18
+ :default_scope,
19
+ :warden_config,
20
+ :warden_config=,
21
+ :scopes,
22
+ :team_id
23
+
24
+ @config = Config.new
25
+
26
+ def self.config
27
+ @config
28
+ end
29
+
30
+ # Use this method to setup this gem.
31
+ #
32
+ # @example
33
+ #
34
+ # Warden::GitHub::Rails.setup do |config|
35
+ # # ...
36
+ # end
37
+ def self.setup
38
+ yield config
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,52 @@
1
+ module Warden
2
+ module GitHub
3
+ module Rails
4
+ class Config
5
+ BadConfig = Class.new(StandardError)
6
+
7
+ # A cache of the warden config for the active manager.
8
+ attr_accessor :warden_config
9
+
10
+ # Default scope to use when not explicitly specified.
11
+ attr_accessor :default_scope
12
+
13
+ # The list of scopes and their configs. This is used to add custom
14
+ # configs to a specific scope. When using a scope that is not listed
15
+ # here, it will use the default configs from warden-github.
16
+ attr_reader :scopes
17
+
18
+ # A hash containing team alias names and their numeric id.
19
+ attr_reader :teams
20
+
21
+ def initialize
22
+ @default_scope = :user
23
+ @scopes = {}
24
+ @teams = {}
25
+ end
26
+
27
+ # Adds a scope with custom configurations to the list of scopes.
28
+ def add_scope(name, config={})
29
+ scopes[name] = config
30
+ end
31
+
32
+ # Maps a team id to a name in order to easier reference it.
33
+ def add_team(name, id)
34
+ teams[name.to_sym] = Integer(id)
35
+ end
36
+
37
+ # Gets the team id for a team id or alias.
38
+ def team_id(team)
39
+ # In ruby 1.8 doing a Integer(:symbol) returns an integer. Thus, test
40
+ # for symbol first.
41
+ if team.is_a? Symbol
42
+ teams.fetch(team)
43
+ else
44
+ Integer(team) rescue teams.fetch(team.to_sym)
45
+ end
46
+ rescue IndexError
47
+ fail BadConfig, "No team id defined for team #{team}."
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,35 @@
1
+ module Warden
2
+ module GitHub
3
+ module Rails
4
+ module ControllerHelpers
5
+ # Initiates the OAuth flow if not already authenticated for the
6
+ # specified scope.
7
+ def github_authenticate!(scope=Rails.default_scope)
8
+ request.env['warden'].authenticate!(:scope => scope)
9
+ end
10
+
11
+ # Logs out a user if currently logged in for the specified scope.
12
+ def github_logout(scope=Rails.default_scope)
13
+ request.env['warden'].logout(scope)
14
+ end
15
+
16
+ # Checks whether a user is logged in for the specified scope.
17
+ def github_authenticated?(scope=Rails.default_scope)
18
+ request.env['warden'].authenticated?(scope)
19
+ end
20
+
21
+ # Returns the currently signed in user for the specified scope. See the
22
+ # documentation for Warden::GitHub::User for available methods.
23
+ def github_user(scope=Rails.default_scope)
24
+ request.env['warden'].user(scope)
25
+ end
26
+
27
+ # Accessor for the currently signed in user's session. This will be
28
+ # cleared once logged out.
29
+ def github_session(scope=Rails.default_scope)
30
+ request.env['warden'].session(scope) if github_authenticated?(scope)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,27 @@
1
+ module Warden
2
+ module GitHub
3
+ module Rails
4
+ class Railtie < ::Rails::Railtie
5
+ config.app_middleware.use Warden::Manager do |config|
6
+ config.failure_app = lambda { |env| [403, {}, [env['warden'].message]] }
7
+ Rails.warden_config = config
8
+ Rails.scopes.each do |scope, scope_config|
9
+ config.scope_defaults scope, :strategies => [:github],
10
+ :config => scope_config
11
+ end
12
+ end
13
+
14
+ initializer 'warden-github-rails.controller_helpers' do
15
+ ActiveSupport.on_load(:action_controller) do
16
+ include ControllerHelpers
17
+ end
18
+
19
+ ActiveSupport.on_load(:action_view) do
20
+ include ViewHelpers
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+
@@ -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)