├── .coveralls.yml
├── .gitignore
├── .rspec
├── .travis.yml
├── CHANGELOG.md
├── Gemfile
├── LICENSE.txt
├── README.md
├── Rakefile
├── VERSION
├── example
├── app
│ ├── controllers
│ │ ├── application_controller.rb
│ │ ├── home_controller.rb
│ │ ├── organizations_controller.rb
│ │ ├── sessions_controller.rb
│ │ ├── teams_controller.rb
│ │ └── users_controller.rb
│ └── views
│ │ ├── application
│ │ └── not_found.html.erb
│ │ ├── home
│ │ └── show.html.erb
│ │ ├── layouts
│ │ └── application.html.erb
│ │ ├── organizations
│ │ ├── index.html.erb
│ │ └── show.html.erb
│ │ ├── teams
│ │ └── show.html.erb
│ │ └── users
│ │ └── show.html.erb
├── config.ru
├── config
│ ├── application.rb
│ ├── boot.rb
│ ├── environment.rb
│ ├── environments
│ │ ├── development.rb
│ │ ├── production.rb
│ │ └── test.rb
│ ├── initializers
│ │ ├── cookies_serializer.rb
│ │ ├── secret_token.rb
│ │ ├── session_store.rb
│ │ └── warden_github_rails.rb
│ ├── routes.rb
│ └── secrets.yml
└── script
│ └── rails
├── lib
└── warden
│ └── github
│ ├── rails.rb
│ └── rails
│ ├── config.rb
│ ├── controller_helpers.rb
│ ├── railtie.rb
│ ├── routes.rb
│ ├── test_helpers.rb
│ ├── test_helpers
│ └── mock_user.rb
│ └── version.rb
├── spec
├── integration
│ ├── controller_helpers_spec.rb
│ ├── membership_spec.rb
│ ├── route_spec.rb
│ ├── scope_spec.rb
│ └── view_helpers_spec.rb
├── rails_app
│ ├── app
│ │ ├── controllers
│ │ │ ├── scoped_controller.rb
│ │ │ ├── unscoped_controller.rb
│ │ │ └── view_tests_controller.rb
│ │ └── views
│ │ │ └── view_tests
│ │ │ ├── authenticated.html.erb
│ │ │ └── user.html.erb
│ ├── config.ru
│ ├── config
│ │ ├── application.rb
│ │ ├── boot.rb
│ │ ├── environment.rb
│ │ ├── environments
│ │ │ ├── development.rb
│ │ │ ├── production.rb
│ │ │ └── test.rb
│ │ ├── initializers
│ │ │ ├── cookies_serializer.rb
│ │ │ ├── secret_token.rb
│ │ │ ├── session_store.rb
│ │ │ ├── warden_github_rails.rb
│ │ │ └── wrap_parameters.rb
│ │ ├── routes.rb
│ │ └── secrets.yml
│ └── script
│ │ └── rails
├── spec_helper.rb
└── unit
│ ├── config_spec.rb
│ ├── mock_user_spec.rb
│ ├── rails_spec.rb
│ └── test_helpers_spec.rb
└── warden-github-rails.gemspec
/.coveralls.yml:
--------------------------------------------------------------------------------
1 | service_name: travis-ci
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
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 | spec/examples.txt
16 | test/tmp
17 | test/version_tmp
18 | tmp
19 | tags
20 | bin/
21 | log
22 |
--------------------------------------------------------------------------------
/.rspec:
--------------------------------------------------------------------------------
1 | --color
2 | --format documentation
3 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | language: ruby
3 | cache: bundler
4 | script: bundle exec rspec
5 | rvm:
6 | - 2.4.1
7 | env:
8 | matrix:
9 | - RAILS_VERSION=4
10 | - RAILS_VERSION=5
11 | - RAILS_VERSION=master
12 | matrix:
13 | fast_finish: true
14 | allow_failures:
15 | - env: RAILS_VERSION=master
16 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## v1.3.0
4 |
5 | - Use new warden-github version that fixes a membership caching bug
6 |
7 | ## v1.2.3
8 |
9 | - Use custom session serialization for proper compatibility with mock users
10 | (#16)
11 |
12 | ## v1.2.2
13 |
14 | - Improve serialization and devise compatibility
15 |
16 | ## v1.2.1
17 |
18 | - Add support for multiple teams ([@bhuga])
19 |
20 | ## v1.2.0
21 |
22 | - Add warden config to not intercept 401 status
23 | - Support Rails 4.2 default of JSON marshalling
24 | - Drop support for Rails 3.1
25 |
26 | ## v1.1.2
27 |
28 | - Prevent loss of memberships in mocked user when marshaling
29 |
30 | ## v1.1.1
31 |
32 | - Transform string to integer when stubbing team membership
33 | - Place redirect helper in correct rack module
34 |
35 | ## v1.1.0
36 |
37 | - Upgrade to octokit.rb version 2
38 | - Require ruby 1.9 or higher
39 |
40 | ## v1.0.1
41 |
42 | - Fully test on Rails 4
43 | - Improve mock user membership stubbing
44 | - Add testing instructions to README
45 |
46 | ## v1.0.0
47 |
48 | - Add Devise compatibility
49 |
50 | ## v0.0.1
51 |
52 | - Initial release
53 |
54 | [@bhuga]: https://github.com/bhuga
55 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
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 | rails_version = ENV['RAILS_VERSION']
10 |
11 | rails_opts = case rails_version
12 | when 'master'
13 | { github: 'rails/rails' }
14 | when nil
15 | {}
16 | else
17 | "~> #{rails_version}"
18 | end
19 |
20 | gem "rails", rails_opts
21 |
22 | group :development do
23 | unless ENV['CI']
24 | gem 'byebug', require: false
25 | end
26 | end
27 |
28 | platforms :rbx do
29 | gem 'rubysl'
30 | gem 'racc'
31 | end
32 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
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.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # warden-github-rails
2 |
3 | [![Build Status][build-image]][build-link]
4 | [![Gem Version][gem-image]][gem-link]
5 | [![Dependency Status][deps-image]][deps-link]
6 | [![Code Climate][gpa-image]][gpa-link]
7 | [![Coverage Status][cov-image]][cov-link]
8 |
9 | A gem for rails that provides easy GitHub OAuth integration.
10 | 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.
11 |
12 | ## Motivation
13 |
14 | **Wouldn't it be nice to**
15 |
16 | - use your organization and its teams for user access control?
17 | - add a new employee to your GitHub organization or team in order to grant them access to your app's admin area?
18 |
19 | 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.
20 | The provided routing helpers do exactly that.
21 | They allow you to restrict access to members of your organization or a certain team.
22 |
23 | This is how your rails `routes.rb` could look like:
24 |
25 | ```ruby
26 | constraints(subdomain: 'admin') do
27 | github_authenticate(org: 'my_company_inc') do
28 | resources :users
29 | resources :projects
30 |
31 | github_authenticated(team: 'sysadmins') do
32 | resource :infrastructure
33 | end
34 | end
35 | end
36 | ```
37 |
38 | Of course, this gem can also be used for user registration and authentication.
39 | Several helper methods are available in the controller to accomplish this:
40 |
41 | ```ruby
42 | class UsersController < ApplicationController
43 | # ...
44 |
45 | def new
46 | github_authenticate! # Performs OAuth flow when not logged in.
47 | @user = User.new(name: github_user.name, email: github_user.email)
48 | end
49 |
50 | def create
51 | attrs = params.require(:user).permit(:name, :email).merge(github_id: github_user.id)
52 | @user = User.create(attrs)
53 |
54 | if @user.persisted?
55 | redirect_to :show
56 | else
57 | render :new
58 | end
59 | end
60 |
61 | # ...
62 | end
63 | ```
64 |
65 | ## Example App
66 |
67 | This repository includes an example app in [example/](example/).
68 | To play with it, follow these steps:
69 |
70 | 1. [Create an OAuth application in your GitHub settings](https://github.com/settings/applications/new).
71 | Set the callback URL to `http://localhost:3000/`
72 |
73 | 2. Check out this repo and run:
74 |
75 | ```
76 | $ bundle
77 | $ cd example
78 | $ GITHUB_CLIENT_ID=your_id_from_step1 GITHUB_CLIENT_SECRET=your_secret_from_step1 bundle exec rails s
79 | ```
80 |
81 | 3. Point your browser to [http://localhost:3000/](http://localhost:3000/) and enjoy!
82 |
83 | ## Installation
84 |
85 | To use this gem, add it to your `Gemfile`:
86 |
87 | ```ruby
88 | gem 'warden-github-rails'
89 | ```
90 |
91 | If you're using devise, make sure to use version 2.2.4 or newer.
92 | Previous versions are not compatible with warden-github-rails and thus will not work.
93 | See the note at [*Using alongside Devise and other Warden Gems*](#using-alongside-devise-and-other-warden-gems) for an explanation.
94 |
95 | ## Usage
96 |
97 | ### Configuration
98 |
99 | First off, you might want to configure this gem by creating an initializer such as `config/initializers/warden_github_rails.rb`.
100 | There you can define:
101 |
102 | - various scopes and their configs (scopes are types of users with different configs)
103 | - the default scope (which is `:user` by default)
104 | - team aliases (GitHub teams are identified by a numerical ID; defining an alias for a team makes it easier to use)
105 |
106 | Here's how such a config might look like:
107 |
108 | ```ruby
109 | Warden::GitHub::Rails.setup do |config|
110 | config.add_scope :user, client_id: 'foo',
111 | client_secret: 'bar',
112 | scope: 'user:email'
113 |
114 | config.add_scope :admin, client_id: 'abc',
115 | client_secret: 'xyz',
116 | redirect_uri: '/admin/login/callback',
117 | scope: 'read:org'
118 |
119 | config.default_scope = :admin
120 |
121 | config.add_team :marketing, 456
122 | end
123 | ```
124 |
125 | For a list of allowed config parameters to use in `#add_scope`, read the [warden-github documentation](https://github.com/atmos/warden-github#parameters).
126 |
127 | ### Inside `routes.rb`
128 |
129 | The available routing helpers are defined and documented in [lib/warden/github/rails/routes.rb](lib/warden/github/rails/routes.rb).
130 | They all accept an optional scope that, when omitted, falls back to the default_scope configured in the initializer.
131 |
132 | Examples:
133 |
134 | ```ruby
135 | # Performs login if not logged in already.
136 | github_authenticate do
137 | resource :profile
138 | end
139 |
140 | # Does not perform login when not logged in.
141 | github_authenticated do
142 | delete '/logout' => 'sessions#delete'
143 | end
144 |
145 | # Only matches when not logged in. Does not perform login.
146 | github_unauthenticated do
147 | resource :registration
148 | end
149 |
150 | # Only matches when member of the organization. Initiates login if not logged in.
151 | github_authenticate(org: 'my_company') do
152 | resource :admin
153 | end
154 |
155 | # Only matches when member of the team. Does not initiate login if not logged in.
156 | github_authenticated(team: 'markting') do
157 | get '/dashboard' => 'dashboard#show'
158 | end
159 |
160 | # Matches if a member of any of the teams given. Does not initiate login if not logged in.
161 | github_authenticated(team: ['markting', 'graphic-design']) do
162 | get '/dashboard' => 'dashboard#show'
163 | end
164 |
165 | # Using dynamic membership values:
166 | github_authenticate(org: lambda { |req| req.params[:id] }) do
167 | get '/orgs/:id' => 'orgs#show'
168 | end
169 | ```
170 |
171 | ### Inside a Controller
172 |
173 | The available controller helpers are defined and documented in [lib/warden/github/rails/controller_helpers.rb](lib/warden/github/rails/controller_helpers.rb).
174 | They all accept an optional scope that, when omitted, falls back to the default_scope configured in the initializer.
175 |
176 | ```ruby
177 | class SomeController < ActionController::Base
178 | def show
179 | @is_admin = github_authenticated?(:admin)
180 | end
181 |
182 | def delete
183 | github_logout
184 | redirect_to '/'
185 | end
186 |
187 | def settings
188 | github_authenticate!
189 | @settings = UserSettings.find_by(github_user_id: github_user.id)
190 | end
191 |
192 | def finish_wizard
193 | github_session[:wizard_completed] = true
194 | end
195 |
196 | def followers
197 | @followers = github_user.api.followers
198 | end
199 | end
200 | ```
201 |
202 | ### Communicating with the GitHub API
203 |
204 | Once a user is logged in, you'll have access to it in the controller using `github_user`.
205 | 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.
206 | The instance has several methods to access user information such as `#name`, `#id`, `#email`, etc.
207 | It also features a method `#api` which returns a preconfigured [Octokit](https://github.com/octokit/octokit.rb) client for that user.
208 |
209 | ### Test Helpers
210 |
211 | This gems comes with a couple test helpers to make your life easier:
212 |
213 | - A method is added to `Rack::Response` called `#github_oauth_redirect?` which
214 | returns true if the response is a redirect to a url that starts with
215 | `https://github.com/login/oauth/authorize`. You can use it in your request
216 | tests to make sure the OAuth dance is initiated. In rspec you could verify
217 | this as follows:
218 |
219 | ```ruby
220 | subject { get '/some-url-that-triggers-oauth' }
221 | it { is_expected.to be_github_oauth_redirect }
222 | ```
223 |
224 | - A mock user that allows you to stub team and organization memberships:
225 |
226 | ```ruby
227 | user = Warden::GitHub::Rails::TestHelpers::MockUser.new
228 | user.stub_membership(team: [234, 987], org: 'some-inc')
229 | user.team_member?(234) # => true
230 | user.organization_member?('rails') # => false
231 | ```
232 |
233 | - A method that creates a mock user and logs it in. If desired, the scope can
234 | be specified. The method returns the mock user so that you can manipulate it
235 | further:
236 |
237 | ```ruby
238 | user = github_login(:admin)
239 |
240 | get '/org/rails/admin'
241 | expect(response).to be_not_found
242 |
243 | user.stub_membership(org: 'rails')
244 | get '/org/rails/admin'
245 | expect(response).to be_ok
246 | ```
247 |
248 | In order to use the mock user and the `#github_login` method, make sure to `require 'warden/github/rails/test_helpers'` and to `include Warden::GitHub::Rails::TestHelpers` in your tests.
249 |
250 | ## Using alongside Devise and other Warden Gems
251 |
252 | Currently this gem does not play nicely with other gems that setup a warden middleware.
253 | The reason is that warden simply does not have support for multiple middlewares.
254 | The warden middleware configures a warden instance and adds it to the rack environment.
255 | Any other warden middleware downstream checks for any existing warden instance in the environment and, if present, skips itself.
256 | I've opened an [issue](https://github.com/hassox/warden/issues/67) on the warden repository to discuss possible workarounds.
257 |
258 | Nevertheless, this gem is compatible with devise for version 2.2.4 and newer.
259 | devise allows you to specify a block that will be invoked when the warden middleware is configured.
260 | This functionality is used in this gem in order to setup the github strategy for warden instead of inserting our own middleware.
261 |
262 | ## Additional Information
263 |
264 | ### Dependencies
265 |
266 | - [warden-github](https://github.com/atmos/warden-github)
267 | - [warden](https://github.com/hassox/warden)
268 | - [octokit](https://github.com/octokit/octokit.rb)
269 |
270 | ### Author
271 |
272 | Philipe Fatio ([@fphilipe][fphilipe twitter])
273 |
274 | [![Support via Gittip][tip-image]][tip-link]
275 |
276 | ### License
277 |
278 | MIT License. Copyright 2013 Philipe Fatio
279 |
280 | [build-image]: https://travis-ci.org/fphilipe/warden-github-rails.svg
281 | [build-link]: https://travis-ci.org/fphilipe/warden-github-rails
282 | [gem-image]: https://badge.fury.io/rb/warden-github-rails.svg
283 | [gem-link]: https://rubygems.org/gems/warden-github-rails
284 | [deps-image]: https://gemnasium.com/fphilipe/warden-github-rails.svg
285 | [deps-link]: https://gemnasium.com/fphilipe/warden-github-rails
286 | [gpa-image]: https://codeclimate.com/github/fphilipe/warden-github-rails.svg
287 | [gpa-link]: https://codeclimate.com/github/fphilipe/warden-github-rails
288 | [cov-image]: https://coveralls.io/repos/fphilipe/warden-github-rails/badge.svg
289 | [cov-link]: https://coveralls.io/r/fphilipe/warden-github-rails
290 | [tip-image]: https://rawgithub.com/twolfson/gittip-badge/0.1.0/dist/gittip.png
291 | [tip-link]: https://www.gittip.com/fphilipe/
292 |
293 | [fphilipe twitter]: https://twitter.com/fphilipe
294 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require 'bundler/gem_tasks'
2 | require 'rspec/core/rake_task'
3 |
4 | RSpec::Core::RakeTask.new(:spec)
5 |
6 | task default: :spec
7 |
--------------------------------------------------------------------------------
/VERSION:
--------------------------------------------------------------------------------
1 | 1.3.0
2 |
--------------------------------------------------------------------------------
/example/app/controllers/application_controller.rb:
--------------------------------------------------------------------------------
1 | class ApplicationController < ActionController::Base
2 | def not_found
3 | render status: 404
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/example/app/controllers/home_controller.rb:
--------------------------------------------------------------------------------
1 | class HomeController < ApplicationController
2 | end
3 |
--------------------------------------------------------------------------------
/example/app/controllers/organizations_controller.rb:
--------------------------------------------------------------------------------
1 | class OrganizationsController < ApplicationController
2 | def index
3 | @orgs = github_user.api.organizations
4 | end
5 |
6 | def show
7 | @org = github_user.api.organization(params[:id])
8 | @members = github_user.api.organization_members(params[:id])
9 | @repos = github_user.api.organization_repositories(params[:id])
10 | @teams = github_user.api.organization_teams(params[:id])
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/example/app/controllers/sessions_controller.rb:
--------------------------------------------------------------------------------
1 | class SessionsController < ApplicationController
2 | def create
3 | github_authenticate!
4 | redirect_to root_url
5 | end
6 |
7 | def destroy
8 | github_logout
9 | redirect_to root_url
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/example/app/controllers/teams_controller.rb:
--------------------------------------------------------------------------------
1 | class TeamsController < ApplicationController
2 | def show
3 | @team = github_user.api.team(params[:id])
4 | @repos = github_user.api.team_repositories(params[:id])
5 | @members = github_user.api.team_members(params[:id])
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/example/app/controllers/users_controller.rb:
--------------------------------------------------------------------------------
1 | class UsersController < ApplicationController
2 | def show
3 | @user = github_user.api.user(params[:id])
4 | @repos = github_user.api.repos(params[:id])
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/example/app/views/application/not_found.html.erb:
--------------------------------------------------------------------------------
1 |