omniauth-heroku 0.1.2.pre → 0.2.0.pre

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 ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ M2ZlY2IwZjQ5NmNhN2QyMjRhODI0MGE4MmQ5ODFkNzIxZDZiNThlYg==
5
+ data.tar.gz: !binary |-
6
+ MDEyNDQ1MjUzMThjMzQzYjU4NDFmMjQzNjYxYzBkZDVlOTE0OGU4Mw==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ MDJkMTc0MTBiNTIzZGU4ZDI4Y2YwYWRkOGEwNDJkNWFlYTczNzJiNzJmODRk
10
+ YjJjNmNiZTk5NjU0MTNhNWE4ZDcyNTAyZGQwNWJjMGU5NTIzNjI0ZDIwYjVk
11
+ YmRjODNlZGZlZDY5YjU1OGQ0ZTAzYWMxMTk2NGNlMGM3Njg4MTU=
12
+ data.tar.gz: !binary |-
13
+ NDFiYjc5NzNmMzE0MWZhYWFjYTdlZWE5MDdlM2ViOGU5MDNkNGFiZThjYmQz
14
+ NTQ1NjczYzg2MDQ1MjMxNzU0OGIzNGNkMDUzMTMyZDI0ZWI0NGViOWFlNzJi
15
+ YzI2NWNiNTViMTAwYzU1ZDBmMzRiYTE3YTc2MzNlMWYyNTQ4MzE=
data/.travis.yml ADDED
@@ -0,0 +1,14 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1.2
4
+ - 2.1.0
5
+ - 1.9.3
6
+ cache: bundler
7
+ notifications:
8
+ hipchat:
9
+ rooms:
10
+ - 5bc7785d2feb4f25901124279daede@API
11
+ template:
12
+ - '%{repository}#%{build_number} (%{branch} - %{commit} : %{author}): %{message} (<a href="%{build_url}">Details</a> | <a href="%{compare_url}">Change view</a>)'
13
+ format: html
14
+ script: bundle exec rake
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ source "https://www.rubygems.org"
2
+
3
+ gemspec
4
+
5
+ group :test do
6
+ gem "multi_json"
7
+ gem "rake"
8
+ gem "rack-test"
9
+ gem "rspec"
10
+ gem "sinatra"
11
+ gem "webmock"
12
+ end
data/README.md CHANGED
@@ -1,6 +1,12 @@
1
1
  # OmniAuth Heroku
2
2
 
3
- [OmniAuth](https://github.com/intridea/omniauth) strategy for authenticating to Heroku.
3
+ [OmniAuth](https://github.com/intridea/omniauth) strategy for authenticating Heroku users.
4
+
5
+ [![Build Status](https://travis-ci.org/heroku/omniauth-heroku.svg?branch=master)](https://travis-ci.org/heroku/omniauth-heroku)
6
+
7
+ Mount this with your Rack application (be it Rails or Sinatra) to simplify the [OAuth flow with Heroku](https://devcenter.heroku.com/articles/oauth).
8
+
9
+ This is intended for apps already using OmniAuth, for apps that authenticate against more than one service (eg: Heroku and GitHub), or apps that have specific needs on session management. If your app doesn't fall in any of these you should consider using [Heroku Bouncer](https://github.com/heroku/heroku-bouncer) instead.
4
10
 
5
11
 
6
12
  ## Configuration
@@ -13,6 +19,8 @@ use OmniAuth::Builder do
13
19
  end
14
20
  ```
15
21
 
22
+ Obtain a `HEROKU_OAUTH_ID` and `HEROKU_OAUTH_SECRET` by creating a client with the [Heroku OAuth CLI plugin](https://github.com/heroku/heroku-oauth).
23
+
16
24
  Your Heroku OAuth client should be set to receive callbacks on `/auth/heroku/callback`.
17
25
 
18
26
 
@@ -24,6 +32,63 @@ Once the authorization flow is complete and the user is bounced back to your app
24
32
 
25
33
  We recommend using this access token together with [Heroku.rb](https://github.com/heroku/heroku.rb) to make API calls on behalf of the user.
26
34
 
35
+ Refer to the examples below to see how these work.
36
+
37
+
38
+ ### Basic account information
39
+
40
+ If you want this middleware to fetch additional Heroku account information like the user email address and name, use the `fetch_info` option, like:
41
+
42
+ ```ruby
43
+ use OmniAuth::Builder do
44
+ provider :heroku, ENV['HEROKU_OAUTH_ID'], ENV['HEROKU_OAUTH_SECRET'],
45
+ fetch_info: true
46
+ end
47
+ ```
48
+
49
+ This sets name and email in the [omniauth auth hash](https://github.com/intridea/omniauth/wiki/Auth-Hash-Schema). You can access it from your app via `env["omniauth.auth"]["info"]`.
50
+
51
+ It will also add [additional Heroku account info](https://devcenter.heroku.com/articles/platform-api-reference#account) to `env["omniauth.auth"]["extra"]`.
52
+
53
+ ### OAuth scopes
54
+
55
+ [Heroku supports different OAuth scopes](https://devcenter.heroku.com/articles/oauth#scopes). By default this strategy will request global access to the account, but you're encouraged to request for less permissions when possible.
56
+
57
+ To do so, configure it like:
58
+
59
+ ```ruby
60
+ use OmniAuth::Builder do
61
+ provider :heroku, ENV['HEROKU_OAUTH_ID'], ENV['HEROKU_OAUTH_SECRET'],
62
+ scope: "identity"
63
+ end
64
+ ```
65
+
66
+ This will trim down the permissions associated to the access token given back to you.
67
+
68
+
69
+ ## Example - Sinatra
70
+
71
+ ```ruby
72
+ class Myapp < Sinatra::Application
73
+ configure do
74
+ enable :sessions
75
+ end
76
+
77
+ use OmniAuth::Builder do
78
+ provider :heroku, ENV["HEROKU_OAUTH_ID"], ENV["HEROKU_OAUTH_SECRET"]
79
+ end
80
+
81
+ get "/" do
82
+ redirect "/auth/heroku"
83
+ end
84
+
85
+ get "/auth/heroku/callback" do
86
+ access_token = env['omniauth.auth']['credentials']['token']
87
+ heroku_api = Heroku::API.new(api_key: access_token)
88
+ "You have #{heroku_api.get_apps.body.size} apps"
89
+ end
90
+ end
91
+ ```
27
92
 
28
93
  ## Example - Rails
29
94
 
@@ -54,8 +119,8 @@ class SessionsController < ApplicationController
54
119
 
55
120
  def create
56
121
  access_token = request.env['omniauth.auth']['credentials']['token']
57
- heroku_api = Heroku::API.new(:api_key => access_token)
58
- @apps = api.get_apps.body
122
+ heroku_api = Heroku::API.new(api_key: access_token)
123
+ @apps = heroku_api.get_apps.body
59
124
  end
60
125
  end
61
126
  ```
data/Rakefile CHANGED
@@ -1,2 +1,10 @@
1
1
  #!/usr/bin/env rake
2
2
  require "bundler/gem_tasks"
3
+ require "rspec/core/rake_task"
4
+
5
+ task default: :spec
6
+
7
+ desc "Run the specs"
8
+ RSpec::Core::RakeTask.new do |t|
9
+ t.pattern = "spec/**/*_spec.rb"
10
+ end
@@ -1,13 +1,61 @@
1
+ require 'omniauth-oauth2'
2
+
1
3
  module OmniAuth
2
4
  module Strategies
3
- class Heroku < OmniAuth::Strategies::HerokuOAuth2
4
- BaseAuthUrl = ENV["HEROKU_AUTH_URL"] || "https://id.heroku.com"
5
+ class Heroku < OmniAuth::Strategies::OAuth2
6
+ AuthUrl = ENV["HEROKU_AUTH_URL"] || "https://id.heroku.com"
7
+ ApiUrl = ENV["HEROKU_API_URL"] || "https://api.heroku.com"
5
8
 
6
9
  option :client_options, {
7
- :site => BaseAuthUrl,
8
- :authorize_url => "#{BaseAuthUrl}/oauth/authorize",
9
- :token_url => "#{BaseAuthUrl}/oauth/token"
10
+ site: AuthUrl,
11
+ authorize_url: "#{AuthUrl}/oauth/authorize",
12
+ token_url: "#{AuthUrl}/oauth/token"
10
13
  }
14
+
15
+ # whether we should make another API call to Heroku to fetch
16
+ # additional account info like the real user name and email
17
+ option :fetch_info
18
+
19
+ uid do
20
+ access_token.params["user_id"]
21
+ end
22
+
23
+ info do
24
+ if options.fetch_info
25
+ email_hash = Digest::MD5.hexdigest(account_info['email'].to_s)
26
+ default_image_url = "https://dashboard.heroku.com/ninja-avatar-48x48.png"
27
+ image_url = "https://secure.gravatar.com/avatar/#{email_hash}.png?d=#{default_image_url}"
28
+
29
+ {
30
+ name: account_info["name"],
31
+ email: account_info["email"],
32
+ image: image_url,
33
+ }
34
+ else
35
+ { name: "Heroku user" } # only mandatory field
36
+ end
37
+ end
38
+
39
+ extra do
40
+ if options.fetch_info
41
+ account_info
42
+ else
43
+ {}
44
+ end
45
+ end
46
+
47
+ def account_info
48
+ @account_info ||= MultiJson.decode(heroku_api.get("/account").body)
49
+ end
50
+
51
+ def heroku_api
52
+ @heroku_api ||= Faraday.new(
53
+ url: ApiUrl,
54
+ headers: {
55
+ "Accept" => "application/vnd.heroku+json; version=3",
56
+ "Authorization" => "Bearer #{access_token.token}",
57
+ })
58
+ end
11
59
  end
12
60
  end
13
61
  end
@@ -1,2 +1 @@
1
- require 'omniauth/strategies/heroku_oauth2'
2
1
  require 'omniauth/strategies/heroku'
@@ -9,8 +9,8 @@ Gem::Specification.new do |gem|
9
9
  gem.files = `git ls-files`.split("\n")
10
10
  gem.name = "omniauth-heroku"
11
11
  gem.require_paths = ["lib"]
12
- gem.version = "0.1.2.pre"
12
+ gem.version = "0.2.0.pre"
13
13
 
14
- gem.add_dependency 'oauth2', '~> 0.8.0'
15
- gem.add_dependency 'omniauth', '~> 1.0'
14
+ gem.add_dependency 'omniauth', '~> 1.2'
15
+ gem.add_dependency 'omniauth-oauth2', '~> 1.2'
16
16
  end
@@ -0,0 +1,78 @@
1
+ require "spec_helper"
2
+
3
+ describe OmniAuth::Strategies::Heroku do
4
+ before do
5
+ @token = "6e441b93-4c6d-4613-abed-b9976e7cff6c"
6
+ @user_id = "ddc4beff-f08f-4856-99d2-ba5ac63c3eb9"
7
+
8
+ # stub the API call made by the strategy to start the oauth dance
9
+ stub_request(:post, "https://id.heroku.com/oauth/token").
10
+ to_return(
11
+ headers: { "Content-Type" => "application/json" },
12
+ body: MultiJson.encode(
13
+ access_token: @token,
14
+ expires_in: 3600,
15
+ user_id: @user_id))
16
+ end
17
+
18
+ it "redirects to start the OAuth flow" do
19
+ get "/auth/heroku"
20
+ assert_equal 302, last_response.status
21
+ redirect = URI.parse(last_response.headers["Location"])
22
+ redirect_params = CGI::parse(redirect.query)
23
+ assert_equal "https", redirect.scheme
24
+ assert_equal "id.heroku.com", redirect.host
25
+ assert_equal [ENV["HEROKU_OAUTH_ID"]], redirect_params["client_id"]
26
+ assert_equal ["code"], redirect_params["response_type"]
27
+ assert_equal ["http://example.org/auth/heroku/callback"],
28
+ redirect_params["redirect_uri"]
29
+ end
30
+
31
+ it "receives the callback" do
32
+ # start the callback, get the session state
33
+ get "/auth/heroku"
34
+ assert_equal 302, last_response.status
35
+ state = last_response.headers["Location"].match(/state=([\w\d]+)/)[1]
36
+
37
+ # trigger the callback setting the state as a param and in the session
38
+ get "/auth/heroku/callback", { "state" => state },
39
+ { "rack.session" => { "omniauth.state" => state }}
40
+ assert_equal 200, last_response.status
41
+
42
+ omniauth_env = MultiJson.decode(last_response.body)
43
+ assert_equal "heroku", omniauth_env["provider"]
44
+ assert_equal @user_id, omniauth_env["uid"]
45
+ assert_equal "Heroku user", omniauth_env["info"]["name"]
46
+ end
47
+
48
+ it "fetches additional info when requested" do
49
+ # change the app being tested:
50
+ @app = make_app(fetch_info: true)
51
+
52
+ # stub the API call to heroku
53
+ account_info = {
54
+ "email" => "john@example.org",
55
+ "name" => "John"
56
+ }
57
+ stub_request(:get, "https://api.heroku.com/account").
58
+ with(headers: { "Authorization" => "Bearer #{@token}" }).
59
+ to_return(body: MultiJson.encode(account_info))
60
+
61
+ # do the oauth dance
62
+ get "/auth/heroku"
63
+ assert_equal 302, last_response.status
64
+ state = last_response.headers["Location"].match(/state=([\w\d]+)/)[1]
65
+
66
+ get "/auth/heroku/callback", { "state" => state },
67
+ { "rack.session" => { "omniauth.state" => state }}
68
+ assert_equal 200, last_response.status
69
+
70
+ # now make sure there's additional info in the omniauth env
71
+ omniauth_env = MultiJson.decode(last_response.body)
72
+ assert_equal "heroku", omniauth_env["provider"]
73
+ assert_equal @user_id, omniauth_env["uid"]
74
+ assert_equal "john@example.org", omniauth_env["info"]["email"]
75
+ assert_equal "John", omniauth_env["info"]["name"]
76
+ assert_equal account_info, omniauth_env["extra"]
77
+ end
78
+ end
@@ -0,0 +1,48 @@
1
+ ENV["SESSION_SECRET"] = "abcdefghjij"
2
+ ENV["HEROKU_OAUTH_ID"] = "12345"
3
+ ENV["HEROKU_OAUTH_SECRET"] = "klmnopqrstu"
4
+
5
+ require "rubygems"
6
+ require "bundler"
7
+ Bundler.setup(:default, :test)
8
+ require "omniauth/strategies/heroku"
9
+
10
+ require "cgi"
11
+ require "rspec"
12
+ require "rack/test"
13
+ require "sinatra"
14
+ require "webmock/rspec"
15
+
16
+ Dir["./spec/support/*.rb"].each { |f| require f }
17
+
18
+ WebMock.disable_net_connect!
19
+
20
+ OmniAuth.config.logger = Logger.new(StringIO.new)
21
+
22
+ RSpec.configure do |config|
23
+ config.include Rack::Test::Methods
24
+ config.expect_with :minitest
25
+
26
+ def app
27
+ @app || make_app
28
+ end
29
+
30
+ def make_app(omniauth_heroku_options={})
31
+ Sinatra.new do
32
+ configure do
33
+ enable :sessions
34
+ set :show_exceptions, false
35
+ set :session_secret, ENV["SESSION_SECRET"]
36
+ end
37
+
38
+ use OmniAuth::Builder do
39
+ provider :heroku, ENV["HEROKU_OAUTH_ID"], ENV["HEROKU_OAUTH_SECRET"],
40
+ omniauth_heroku_options
41
+ end
42
+
43
+ get "/auth/heroku/callback" do
44
+ MultiJson.encode(env['omniauth.auth'])
45
+ end
46
+ end
47
+ end
48
+ end
metadata CHANGED
@@ -1,48 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: omniauth-heroku
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2.pre
5
- prerelease: 6
4
+ version: 0.2.0.pre
6
5
  platform: ruby
7
6
  authors:
8
7
  - Pedro Belo
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2013-08-23 00:00:00.000000000 Z
11
+ date: 2014-09-11 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
- name: oauth2
14
+ name: omniauth
16
15
  requirement: !ruby/object:Gem::Requirement
17
- none: false
18
16
  requirements:
19
17
  - - ~>
20
18
  - !ruby/object:Gem::Version
21
- version: 0.8.0
19
+ version: '1.2'
22
20
  type: :runtime
23
21
  prerelease: false
24
22
  version_requirements: !ruby/object:Gem::Requirement
25
- none: false
26
23
  requirements:
27
24
  - - ~>
28
25
  - !ruby/object:Gem::Version
29
- version: 0.8.0
26
+ version: '1.2'
30
27
  - !ruby/object:Gem::Dependency
31
- name: omniauth
28
+ name: omniauth-oauth2
32
29
  requirement: !ruby/object:Gem::Requirement
33
- none: false
34
30
  requirements:
35
31
  - - ~>
36
32
  - !ruby/object:Gem::Version
37
- version: '1.0'
33
+ version: '1.2'
38
34
  type: :runtime
39
35
  prerelease: false
40
36
  version_requirements: !ruby/object:Gem::Requirement
41
- none: false
42
37
  requirements:
43
38
  - - ~>
44
39
  - !ruby/object:Gem::Version
45
- version: '1.0'
40
+ version: '1.2'
46
41
  description: OmniAuth strategy for Heroku.
47
42
  email:
48
43
  - pedro@heroku.com
@@ -52,34 +47,36 @@ extra_rdoc_files: []
52
47
  files:
53
48
  - .gitignore
54
49
  - .rspec
50
+ - .travis.yml
51
+ - Gemfile
55
52
  - README.md
56
53
  - Rakefile
57
54
  - lib/omniauth-heroku.rb
58
55
  - lib/omniauth/strategies/heroku.rb
59
- - lib/omniauth/strategies/heroku_oauth2.rb
60
56
  - omniauth-heroku.gemspec
57
+ - spec/omniauth_heroku_spec.rb
58
+ - spec/spec_helper.rb
61
59
  homepage: https://github.com/heroku/omniauth-heroku
62
60
  licenses: []
61
+ metadata: {}
63
62
  post_install_message:
64
63
  rdoc_options: []
65
64
  require_paths:
66
65
  - lib
67
66
  required_ruby_version: !ruby/object:Gem::Requirement
68
- none: false
69
67
  requirements:
70
68
  - - ! '>='
71
69
  - !ruby/object:Gem::Version
72
70
  version: '0'
73
71
  required_rubygems_version: !ruby/object:Gem::Requirement
74
- none: false
75
72
  requirements:
76
73
  - - ! '>'
77
74
  - !ruby/object:Gem::Version
78
75
  version: 1.3.1
79
76
  requirements: []
80
77
  rubyforge_project:
81
- rubygems_version: 1.8.24
78
+ rubygems_version: 2.0.7
82
79
  signing_key:
83
- specification_version: 3
80
+ specification_version: 4
84
81
  summary: OmniAuth strategy for Heroku.
85
82
  test_files: []
@@ -1,124 +0,0 @@
1
- require 'cgi'
2
- require 'uri'
3
- require 'oauth2'
4
- require 'omniauth'
5
- require 'timeout'
6
- require 'securerandom'
7
-
8
- module OmniAuth
9
- module Strategies
10
- # Vendored in from `omniauth-oauth2`, but with slight modifications to
11
- # allow request IDs to be passed through during the token phase so that the
12
- # OAuth 2 dance can be more easily debugged.
13
- class HerokuOAuth2
14
- include OmniAuth::Strategy
15
-
16
- args [:client_id, :client_secret]
17
-
18
- option :client_id, nil
19
- option :client_secret, nil
20
- option :client_options, {}
21
- option :authorize_params, {}
22
- option :authorize_options, [:scope]
23
- option :token_params, {}
24
- option :token_options, []
25
- option :provider_ignores_state, false
26
- option :request_id, lambda { |env| nil }
27
-
28
- attr_accessor :access_token
29
-
30
- def client
31
- ::OAuth2::Client.new(options.client_id, options.client_secret, deep_symbolize(options.client_options))
32
- end
33
-
34
- def callback_url
35
- full_host + script_name + callback_path
36
- end
37
-
38
- credentials do
39
- hash = {'token' => access_token.token}
40
- hash.merge!('refresh_token' => access_token.refresh_token) if access_token.expires? && access_token.refresh_token
41
- hash.merge!('expires_at' => access_token.expires_at) if access_token.expires?
42
- hash.merge!('expires' => access_token.expires?)
43
- hash
44
- end
45
-
46
- def request_phase
47
- redirect client.auth_code.authorize_url({:redirect_uri => callback_url}.merge(authorize_params))
48
- end
49
-
50
- def authorize_params
51
- options.authorize_params[:state] = SecureRandom.hex(24)
52
- params = options.authorize_params.merge(options.authorize_options.inject({}){|h,k| h[k.to_sym] = options[k] if options[k]; h})
53
- if OmniAuth.config.test_mode
54
- @env ||= {}
55
- @env['rack.session'] ||= {}
56
- end
57
- session['omniauth.state'] = params[:state]
58
- params
59
- end
60
-
61
- def token_params
62
- options.token_params.merge(options.token_options.inject({}){|h,k| h[k.to_sym] = options[k] if options[k]; h})
63
- end
64
-
65
- def callback_phase
66
- if request.params['error'] || request.params['error_reason']
67
- raise CallbackError.new(request.params['error'], request.params['error_description'] || request.params['error_reason'], request.params['error_uri'])
68
- end
69
- # if !options.provider_ignores_state && (request.params['state'].to_s.empty? || request.params['state'] != session.delete('omniauth.state'))
70
- # raise CallbackError.new(nil, :csrf_detected)
71
- # end
72
-
73
- self.access_token = build_access_token
74
- params = {}
75
- if request_id = options.request_id.call(@env)
76
- params.merge!(:headers => { "Request-Id" => request_id })
77
- end
78
- self.access_token = access_token.refresh!(params) if access_token.expired?
79
-
80
- super
81
- rescue ::OAuth2::Error, CallbackError => e
82
- fail!(:invalid_credentials, e)
83
- rescue ::MultiJson::DecodeError => e
84
- fail!(:invalid_response, e)
85
- rescue ::Timeout::Error, ::Errno::ETIMEDOUT => e
86
- fail!(:timeout, e)
87
- rescue ::SocketError => e
88
- fail!(:failed_to_connect, e)
89
- end
90
-
91
- protected
92
-
93
- def deep_symbolize(hash)
94
- hash.inject({}) do |h, (k,v)|
95
- h[k.to_sym] = v.is_a?(Hash) ? deep_symbolize(v) : v
96
- h
97
- end
98
- end
99
-
100
- def build_access_token
101
- verifier = request.params['code']
102
- params = {:redirect_uri => callback_url}.
103
- merge(token_params.to_hash(:symbolize_keys => true))
104
- # inject a request ID if one is available
105
- if request_id = options.request_id.call(@env)
106
- params.merge!(:headers => { "Request-Id" => request_id })
107
- end
108
- client.auth_code.get_token(verifier, params)
109
- end
110
-
111
- # An error that is indicated in the OAuth 2.0 callback.
112
- # This could be a `redirect_uri_mismatch` or other
113
- class CallbackError < StandardError
114
- attr_accessor :error, :error_reason, :error_uri
115
-
116
- def initialize(error, error_reason=nil, error_uri=nil)
117
- self.error = error
118
- self.error_reason = error_reason
119
- self.error_uri = error_uri
120
- end
121
- end
122
- end
123
- end
124
- end