warden-github 0.12.1 → 0.13.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.
- 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
|