warden-github 0.12.1 → 0.13.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -0
- data/Gemfile +2 -4
- data/README.md +5 -6
- data/config.ru +7 -7
- data/example/app.rb +98 -0
- data/lib/warden/github.rb +10 -0
- data/lib/warden/github/hook.rb +6 -0
- data/lib/warden/github/oauth.rb +97 -0
- data/lib/warden/github/strategy.rb +122 -0
- data/lib/warden/github/user.rb +81 -0
- data/lib/warden/github/version.rb +5 -0
- data/spec/fixtures/user.json +42 -0
- data/spec/integration/oauth_spec.rb +131 -0
- data/spec/spec_helper.rb +8 -3
- data/spec/unit/oauth_spec.rb +68 -0
- data/spec/unit/user_spec.rb +112 -0
- data/warden-github.gemspec +8 -11
- metadata +73 -89
- data/lib/warden-github.rb +0 -14
- data/lib/warden-github/proxy.rb +0 -45
- data/lib/warden-github/strategy.rb +0 -83
- data/lib/warden-github/user.rb +0 -159
- data/lib/warden-github/version.rb +0 -5
- data/spec/app.rb +0 -71
- data/spec/oauth_spec.rb +0 -18
- data/spec/proxy_spec.rb +0 -34
- data/spec/user_spec.rb +0 -14
data/lib/warden-github.rb
DELETED
@@ -1,14 +0,0 @@
|
|
1
|
-
require 'warden'
|
2
|
-
require 'oauth2'
|
3
|
-
require 'yajl'
|
4
|
-
|
5
|
-
module Warden
|
6
|
-
module Github
|
7
|
-
class GithubMisconfiguredError < StandardError; end
|
8
|
-
end
|
9
|
-
end
|
10
|
-
|
11
|
-
require 'warden-github/user'
|
12
|
-
require 'warden-github/proxy'
|
13
|
-
require 'warden-github/version'
|
14
|
-
require 'warden-github/strategy'
|
data/lib/warden-github/proxy.rb
DELETED
@@ -1,45 +0,0 @@
|
|
1
|
-
module Warden
|
2
|
-
module Github
|
3
|
-
module Oauth
|
4
|
-
class Proxy
|
5
|
-
attr_accessor :client_id, :secret, :scopes, :oauth_domain, :callback_url
|
6
|
-
def initialize(client_id, secret, scopes, oauth_domain, callback_url)
|
7
|
-
@client_id, @secret, @scopes, @oauth_domain, @callback_url = client_id, secret, scopes, oauth_domain, callback_url
|
8
|
-
end
|
9
|
-
|
10
|
-
def ssl_options
|
11
|
-
ca_file = "/usr/lib/ssl/certs/ca-certificates.crt"
|
12
|
-
if File.exists?(ca_file)
|
13
|
-
{ :ca_file => ca_file }
|
14
|
-
else
|
15
|
-
{ :ca_file => ''}
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
def client
|
20
|
-
@client ||= OAuth2::Client.new(@client_id, @secret,
|
21
|
-
:ssl => ssl_options,
|
22
|
-
:site => oauth_domain,
|
23
|
-
:token_url => '/login/oauth/access_token',
|
24
|
-
:authorize_url => '/login/oauth/authorize')
|
25
|
-
end
|
26
|
-
|
27
|
-
def api_for(code)
|
28
|
-
client.auth_code.get_token(code, :redirect_uri => callback_url)
|
29
|
-
end
|
30
|
-
|
31
|
-
def state
|
32
|
-
@state ||= Digest::SHA1.hexdigest(rand(36**8).to_s(36))
|
33
|
-
end
|
34
|
-
|
35
|
-
def authorize_url
|
36
|
-
client.auth_code.authorize_url(
|
37
|
-
:state => state,
|
38
|
-
:scope => scopes,
|
39
|
-
:redirect_uri => callback_url
|
40
|
-
)
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
@@ -1,83 +0,0 @@
|
|
1
|
-
Warden::Strategies.add(:github) do
|
2
|
-
# Need to make sure that we have a pure representation of the query string.
|
3
|
-
# Rails adds an "action" parameter which causes the openid gem to error
|
4
|
-
def params
|
5
|
-
@params ||= Rack::Utils.parse_query(request.query_string)
|
6
|
-
end
|
7
|
-
|
8
|
-
def authenticate!
|
9
|
-
if(params['code'] && params['state'] &&
|
10
|
-
env['rack.session']['github_oauth_state'] &&
|
11
|
-
env['rack.session']['github_oauth_state'].size > 0 &&
|
12
|
-
params['state'] == env['rack.session']['github_oauth_state'])
|
13
|
-
begin
|
14
|
-
api = api_for(params['code'])
|
15
|
-
|
16
|
-
user_info = Yajl.load(user_info_for(api.token))
|
17
|
-
user_info.delete('bio') # Delete bio, as it can easily make the session cookie too long.
|
18
|
-
|
19
|
-
success!(Warden::Github::Oauth::User.new(user_info, api.token))
|
20
|
-
rescue OAuth2::Error
|
21
|
-
%(<p>Outdated ?code=#{params['code']}:</p><p>#{$!}</p><p><a href="/auth/github">Retry</a></p>)
|
22
|
-
end
|
23
|
-
else
|
24
|
-
env['rack.session']['github_oauth_state'] = state
|
25
|
-
env['rack.session']['return_to'] = env['REQUEST_URI']
|
26
|
-
throw(:warden, [ 302, {'Location' => authorize_url}, [ ]])
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
private
|
31
|
-
|
32
|
-
def state
|
33
|
-
oauth_proxy.state
|
34
|
-
end
|
35
|
-
|
36
|
-
def oauth_client
|
37
|
-
oauth_proxy.client
|
38
|
-
end
|
39
|
-
|
40
|
-
def authorize_url
|
41
|
-
oauth_proxy.authorize_url
|
42
|
-
end
|
43
|
-
|
44
|
-
def api_for(code)
|
45
|
-
oauth_proxy.api_for(code)
|
46
|
-
end
|
47
|
-
|
48
|
-
def oauth_proxy
|
49
|
-
@oauth_proxy ||= Warden::Github::Oauth::Proxy.new(env['warden'].config[:github_client_id],
|
50
|
-
env['warden'].config[:github_secret],
|
51
|
-
env['warden'].config[:github_scopes],
|
52
|
-
env['warden'].config[:github_oauth_domain],
|
53
|
-
callback_url)
|
54
|
-
end
|
55
|
-
|
56
|
-
def user_info_for(token)
|
57
|
-
@user_info ||= RestClient.get(github_api_uri + "/user", :params => {:access_token => token})
|
58
|
-
end
|
59
|
-
|
60
|
-
def callback_url
|
61
|
-
absolute_url(request, env['warden'].config[:github_callback_url], env['HTTP_X_FORWARDED_PROTO'])
|
62
|
-
end
|
63
|
-
|
64
|
-
def absolute_url(request, suffix = nil, proto = "http")
|
65
|
-
port_part = case request.scheme
|
66
|
-
when "http"
|
67
|
-
request.port == 80 ? "" : ":#{request.port}"
|
68
|
-
when "https"
|
69
|
-
request.port == 443 ? "" : ":#{request.port}"
|
70
|
-
end
|
71
|
-
|
72
|
-
proto = "http" if proto.nil?
|
73
|
-
"#{proto}://#{request.host}#{port_part}#{suffix}"
|
74
|
-
end
|
75
|
-
|
76
|
-
def github_api_uri
|
77
|
-
if ENV['OCTOKIT_API_ENDPOINT']
|
78
|
-
ENV['OCTOKIT_API_ENDPOINT']
|
79
|
-
else
|
80
|
-
"https://api.github.com"
|
81
|
-
end
|
82
|
-
end
|
83
|
-
end
|
data/lib/warden-github/user.rb
DELETED
@@ -1,159 +0,0 @@
|
|
1
|
-
require 'yajl'
|
2
|
-
require 'octokit'
|
3
|
-
require 'rest-client'
|
4
|
-
|
5
|
-
module Warden
|
6
|
-
module Github
|
7
|
-
module Oauth
|
8
|
-
class User < Struct.new(:attribs, :token)
|
9
|
-
def login
|
10
|
-
attribs['login']
|
11
|
-
end
|
12
|
-
|
13
|
-
def name
|
14
|
-
attribs['name']
|
15
|
-
end
|
16
|
-
|
17
|
-
def gravatar_id
|
18
|
-
attribs['gravatar_id']
|
19
|
-
end
|
20
|
-
|
21
|
-
def email
|
22
|
-
attribs['email']
|
23
|
-
end
|
24
|
-
|
25
|
-
def company
|
26
|
-
attribs['company']
|
27
|
-
end
|
28
|
-
|
29
|
-
# See if the user is a public member of the named organization
|
30
|
-
#
|
31
|
-
# name - the organization name
|
32
|
-
#
|
33
|
-
# Returns: true if the user is publicized as an org member
|
34
|
-
def publicized_organization_member?(org_name)
|
35
|
-
github_raw_request("orgs/#{org_name}/public_members/#{login}").code == 204
|
36
|
-
rescue RestClient::Forbidden, RestClient::Unauthorized, RestClient::ResourceNotFound => e
|
37
|
-
false
|
38
|
-
end
|
39
|
-
|
40
|
-
# See if the user is a member of the named organization
|
41
|
-
#
|
42
|
-
# name - the organization name
|
43
|
-
#
|
44
|
-
# Returns: true if the user has access, false otherwise
|
45
|
-
def organization_member?(org_name)
|
46
|
-
github_raw_request("orgs/#{org_name}/members/#{login}").code == 204
|
47
|
-
rescue RestClient::Forbidden, RestClient::Unauthorized, RestClient::ResourceNotFound => e
|
48
|
-
false
|
49
|
-
end
|
50
|
-
|
51
|
-
# See if the user is a member of the team id
|
52
|
-
#
|
53
|
-
# team_id - the team's id
|
54
|
-
#
|
55
|
-
# Returns: true if the user has access, false otherwise
|
56
|
-
def team_member?(team_id)
|
57
|
-
github_raw_request("teams/#{team_id}/members/#{login}").code == 204
|
58
|
-
rescue RestClient::Forbidden, RestClient::Unauthorized, RestClient::ResourceNotFound => e
|
59
|
-
false
|
60
|
-
end
|
61
|
-
|
62
|
-
# Send a V3 API PUT request to path and parses the response body
|
63
|
-
#
|
64
|
-
# path - the path on api.github.com to hit
|
65
|
-
# params - extra params for calling the api
|
66
|
-
#
|
67
|
-
def get(path, params)
|
68
|
-
github_request(path, params)
|
69
|
-
end
|
70
|
-
|
71
|
-
# Send a V3 API PUT request to path and parses the response body
|
72
|
-
#
|
73
|
-
# path - the path on api.github.com to hit
|
74
|
-
# params - extra params for calling the api
|
75
|
-
#
|
76
|
-
def post(path, params)
|
77
|
-
headers = {:Authorization => "token #{token}", :content_type => :json, :accept => :json}
|
78
|
-
res = RestClient.post("#{github_api_uri}/#{path}", params.to_json, headers)
|
79
|
-
Yajl.load(res)
|
80
|
-
end
|
81
|
-
|
82
|
-
# Send a V3 API PUT request to path and parses the response body
|
83
|
-
#
|
84
|
-
# path - the path on api.github.com to hit
|
85
|
-
# params - extra params for calling the api
|
86
|
-
#
|
87
|
-
def put(path, params)
|
88
|
-
headers = {:Authorization => "token #{token}", :content_type => :json, :accept => :json}
|
89
|
-
res = RestClient.put("#{github_api_uri}/#{path}", params.to_json, headers)
|
90
|
-
Yajl.load(res)
|
91
|
-
end
|
92
|
-
|
93
|
-
# Send a V3 API DELETE request to path and parses the response body
|
94
|
-
#
|
95
|
-
# path - the path on api.github.com to hit
|
96
|
-
# params - extra params for calling the api
|
97
|
-
#
|
98
|
-
def delete(path, params)
|
99
|
-
headers = {:Authorization => "token #{token}", :content_type => :json, :accept => :json}
|
100
|
-
res = RestClient.delete("#{github_api_uri}/#{path}", params.to_json, headers)
|
101
|
-
Yajl.load(res)
|
102
|
-
end
|
103
|
-
|
104
|
-
# Access the GitHub API from Octokit
|
105
|
-
#
|
106
|
-
# Octokit is a robust client library for the GitHub API
|
107
|
-
# https://github.com/pengwynn/octokit
|
108
|
-
#
|
109
|
-
# Returns a cached client object for easy use
|
110
|
-
def api
|
111
|
-
@api ||= Octokit::Client.new(:login => login, :oauth_token => token)
|
112
|
-
end
|
113
|
-
|
114
|
-
# Send a V3 API GET request to path and parse the response body
|
115
|
-
#
|
116
|
-
# path - the path on api.github.com to hit
|
117
|
-
# params - extra params for calling the api
|
118
|
-
#
|
119
|
-
# Returns a parsed JSON response
|
120
|
-
#
|
121
|
-
# Examples
|
122
|
-
# github_request("/user")
|
123
|
-
# # => { 'login' => 'atmos', ... }
|
124
|
-
#
|
125
|
-
# github_request("/user/repos", {:page => 2})
|
126
|
-
# # => [ { 'name' => 'gollum' ... } ]
|
127
|
-
def github_request(path, params = {})
|
128
|
-
Yajl.load(github_raw_request(path, params))
|
129
|
-
end
|
130
|
-
|
131
|
-
# Send a V3 API GET request to path
|
132
|
-
#
|
133
|
-
# path - the path on api.github.com to hit
|
134
|
-
#
|
135
|
-
# Returns a rest client response object
|
136
|
-
#
|
137
|
-
# Examples
|
138
|
-
# github_raw_request("/user")
|
139
|
-
# # => RestClient::Response
|
140
|
-
#
|
141
|
-
# github_raw_request("/user/repos", {:page => 3})
|
142
|
-
# # => RestClient::Response
|
143
|
-
def github_raw_request(path, params = {})
|
144
|
-
headers = {:Authorization => "token #{token}", :accept => :json}
|
145
|
-
RestClient.get("#{github_api_uri}/#{path}", headers.merge(:params => params))
|
146
|
-
end
|
147
|
-
|
148
|
-
private
|
149
|
-
def github_api_uri
|
150
|
-
if ENV['GITHUB_OAUTH_API_DOMAIN']
|
151
|
-
ENV['GITHUB_OAUTH_API_DOMAIN']
|
152
|
-
else
|
153
|
-
"https://api.github.com"
|
154
|
-
end
|
155
|
-
end
|
156
|
-
end
|
157
|
-
end
|
158
|
-
end
|
159
|
-
end
|
data/spec/app.rb
DELETED
@@ -1,71 +0,0 @@
|
|
1
|
-
require 'sinatra'
|
2
|
-
|
3
|
-
module Example
|
4
|
-
class App < Sinatra::Base
|
5
|
-
enable :sessions
|
6
|
-
enable :raise_errors
|
7
|
-
disable :show_exceptions
|
8
|
-
|
9
|
-
use Warden::Manager do |manager|
|
10
|
-
manager.default_strategies :github
|
11
|
-
manager.failure_app = BadAuthentication
|
12
|
-
|
13
|
-
manager[:github_client_id] = ENV['GITHUB_CLIENT_ID'] || 'ee9aa24b64d82c21535a'
|
14
|
-
manager[:github_secret] = ENV['GITHUB_CLIENT_SECRET'] || 'ed8ff0c54067aefb808dab1ca265865405d08d6f'
|
15
|
-
|
16
|
-
manager[:github_scopes] = ''
|
17
|
-
manager[:github_oauth_domain] = ENV['GITHUB_OAUTH_DOMAIN'] || 'https://github.com'
|
18
|
-
manager[:github_callback_url] = '/auth/github/callback'
|
19
|
-
end
|
20
|
-
|
21
|
-
helpers do
|
22
|
-
def ensure_authenticated
|
23
|
-
unless env['warden'].authenticate!
|
24
|
-
throw(:warden)
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
def user
|
29
|
-
env['warden'].user
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
get '/' do
|
34
|
-
ensure_authenticated
|
35
|
-
<<-EOS
|
36
|
-
<h2>Hello There, #{user.name}!</h2>
|
37
|
-
<h3>Rails Org Member: #{user.organization_member?('rails')}.</h3>
|
38
|
-
<h3>Publicized Rails Org Member: #{user.publicized_organization_member?('rails')}.</h3>
|
39
|
-
<h3>Rails Committer Team Member: #{user.team_member?(632)}.</h3>
|
40
|
-
EOS
|
41
|
-
end
|
42
|
-
|
43
|
-
get '/redirect_to' do
|
44
|
-
ensure_authenticated
|
45
|
-
"Hello There, #{user.name}! return_to is working!"
|
46
|
-
end
|
47
|
-
|
48
|
-
get '/auth/github/callback' do
|
49
|
-
ensure_authenticated
|
50
|
-
redirect '/'
|
51
|
-
end
|
52
|
-
|
53
|
-
get '/logout' do
|
54
|
-
env['warden'].logout
|
55
|
-
"Peace!"
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
class BadAuthentication < Sinatra::Base
|
60
|
-
get '/unauthenticated' do
|
61
|
-
status 403
|
62
|
-
"Unable to authenticate, sorry bud."
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
def self.app
|
67
|
-
@app ||= Rack::Builder.new do
|
68
|
-
run App
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
data/spec/oauth_spec.rb
DELETED
@@ -1,18 +0,0 @@
|
|
1
|
-
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
-
|
3
|
-
describe "Warden::Github" do
|
4
|
-
it "requesting an url that requires authentication redirects to github" do
|
5
|
-
response = get "/"
|
6
|
-
|
7
|
-
uri = Addressable::URI.parse(response.headers["Location"])
|
8
|
-
|
9
|
-
uri.scheme.should eql('https')
|
10
|
-
uri.host.should eql('github.com')
|
11
|
-
|
12
|
-
params = uri.query_values
|
13
|
-
params['response_type'].should eql('code')
|
14
|
-
params['scope'].should eql('')
|
15
|
-
params['client_id'].should match(/\w{20}/)
|
16
|
-
params['redirect_uri'].should eql('http://example.org/auth/github/callback')
|
17
|
-
end
|
18
|
-
end
|
data/spec/proxy_spec.rb
DELETED
@@ -1,34 +0,0 @@
|
|
1
|
-
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
-
|
3
|
-
describe "Warden::Github::Oauth::Proxy" do
|
4
|
-
before(:all) do
|
5
|
-
sha = Digest::SHA1.hexdigest(Time.now.to_s)
|
6
|
-
@proxy = Warden::Github::Oauth::Proxy.new(sha[0..19], sha[0..39],
|
7
|
-
'user,public_repo,repo,gist',
|
8
|
-
'http://example.org',
|
9
|
-
'http://example.org/auth/github/callback')
|
10
|
-
end
|
11
|
-
|
12
|
-
it "returns an authorize url" do
|
13
|
-
uri = Addressable::URI.parse(@proxy.authorize_url)
|
14
|
-
|
15
|
-
uri.scheme.should eql('http')
|
16
|
-
uri.host.should eql('example.org')
|
17
|
-
|
18
|
-
params = uri.query_values
|
19
|
-
params['response_type'].should eql('code')
|
20
|
-
params['scope'].should eql('user,public_repo,repo,gist')
|
21
|
-
params['client_id'].should match(/\w{20}/)
|
22
|
-
params['redirect_uri'].should eql('http://example.org/auth/github/callback')
|
23
|
-
end
|
24
|
-
|
25
|
-
it "has a client object" do
|
26
|
-
@proxy.client.should_not be_nil
|
27
|
-
end
|
28
|
-
|
29
|
-
it "returns access tokens" do
|
30
|
-
pending "this hits the network" do
|
31
|
-
lambda { @proxy.access_token_for(/\w{20}/.gen) }.should_not raise_error
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
data/spec/user_spec.rb
DELETED
@@ -1,14 +0,0 @@
|
|
1
|
-
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
-
|
3
|
-
describe "Warden::Github" do
|
4
|
-
let(:user) do
|
5
|
-
Warden::Github::Oauth::User.new({'login' => 'atmos'}, 'abcde')
|
6
|
-
end
|
7
|
-
|
8
|
-
it "knows the token" do
|
9
|
-
user.token.should eql('abcde')
|
10
|
-
end
|
11
|
-
it "can access the octokit object to make api calls" do
|
12
|
-
user.api.should_not be_nil
|
13
|
-
end
|
14
|
-
end
|