opro 0.0.1 → 0.0.2
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/Gemfile +6 -2
- data/Gemfile.lock +37 -33
- data/README.md +60 -31
- data/Rakefile +8 -0
- data/VERSION +1 -1
- data/app/controllers/oauth/auth_controller.rb +35 -36
- data/app/controllers/oauth/client_app_controller.rb +24 -0
- data/app/controllers/oauth/docs_controller.rb +1 -1
- data/app/controllers/oauth/tests_controller.rb +4 -26
- data/app/controllers/oauth/token_controller.rb +40 -0
- data/app/controllers/opro_controller.rb +4 -0
- data/app/models/oauth/access_grant.rb +47 -14
- data/app/models/oauth/{client_application.rb → client_appl.rb} +1 -1
- data/app/views/oauth/{client_application → client_app}/create.html.erb +1 -1
- data/app/views/oauth/{client_application → client_app}/index.html.erb +1 -1
- data/app/views/oauth/{client_application → client_app}/new.html.erb +1 -1
- data/app/views/oauth/docs/index.html.erb +8 -0
- data/app/views/oauth/docs/markdown/oauth.md.erb +2 -2
- data/app/views/oauth/docs/markdown/permissions.md.erb +30 -0
- data/app/views/oauth/docs/markdown/quick_start.md.erb +1 -1
- data/app/views/oauth/docs/markdown/refresh_tokens.md.erb +18 -0
- data/config/routes.rb +5 -10
- data/lib/generators/active_record/opro_generator.rb +1 -1
- data/lib/generators/active_record/templates/access_grants.rb +1 -1
- data/lib/generators/active_record/templates/client_applications.rb +1 -1
- data/lib/generators/opro/install_generator.rb +5 -0
- data/lib/generators/templates/opro.rb +10 -3
- data/lib/opro.rb +7 -1
- data/lib/opro/controllers/application_controller_helper.rb +7 -2
- data/lib/opro/engine.rb +4 -0
- data/lib/opro/rails/routes.rb +17 -0
- data/opro.gemspec +27 -15
- data/test/controllers/refresh_token_test.rb +0 -0
- data/test/dummy/config/environments/test.rb +4 -0
- data/test/dummy/config/initializers/opro.rb +10 -3
- data/test/dummy/config/routes.rb +2 -0
- data/test/dummy/db/migrate/20120514060322_create_opro_access_grants.rb +1 -1
- data/test/dummy/db/migrate/20120514060323_create_opro_client_applications.rb +1 -1
- data/test/integration/action_dispatch/auth_controller_test.rb +64 -0
- data/test/integration/action_dispatch/oauth_flow_test.rb +34 -0
- data/test/integration/action_dispatch/refresh_token_test.rb +54 -0
- data/test/integration/auth_controller_test.rb +8 -7
- data/test/integration/client_app_controller_test.rb +24 -0
- data/test/integration/docs_controller_test.rb +9 -1
- data/test/integration/oauth_test.rb +1 -4
- data/test/integration/refresh_token_test.rb +32 -0
- data/test/support/integration_case.rb +10 -1
- data/test/test_helper.rb +7 -3
- metadata +56 -36
- data/app/controllers/oauth/client_application_controller.rb +0 -21
- data/app/controllers/opro_application_controller.rb +0 -8
- data/test/integration/client_application_controller_test.rb +0 -24
@@ -0,0 +1,24 @@
|
|
1
|
+
class Oauth::ClientAppController < OproController
|
2
|
+
before_filter :opro_authenticate_user!
|
3
|
+
|
4
|
+
def new
|
5
|
+
@client_app = Oauth::ClientApp.new
|
6
|
+
end
|
7
|
+
|
8
|
+
# Show all client applications belonging to the current user
|
9
|
+
def index
|
10
|
+
@client_apps = Oauth::ClientApp.where(:user_id => current_user.id)
|
11
|
+
end
|
12
|
+
|
13
|
+
|
14
|
+
def create
|
15
|
+
@client_app = Oauth::ClientApp.find_by_user_id_and_name(current_user.id, params[:oauth_client_app][:name])
|
16
|
+
@client_app ||= Oauth::ClientApp.create_with_user_and_name(current_user, params[:oauth_client_app][:name])
|
17
|
+
if @client_app.save
|
18
|
+
# do nothing
|
19
|
+
else
|
20
|
+
render :new
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
class Oauth::TestsController <
|
1
|
+
class Oauth::TestsController < OproController
|
2
2
|
allow_oauth!
|
3
3
|
disallow_oauth! :only => [:destroy]
|
4
4
|
|
@@ -10,7 +10,7 @@ class Oauth::TestsController < ApplicationController
|
|
10
10
|
result = if valid_oauth?
|
11
11
|
{:status => 200, :message => 'OAuth Worked!!', :params => params, :user_id => oauth_user.id }
|
12
12
|
else
|
13
|
-
{:status =>
|
13
|
+
{:status => :unauthorized, :message => "OAuth Did not Work :( #{generate_oauth_error_message!}", :params => params}
|
14
14
|
end
|
15
15
|
|
16
16
|
respond_to do |format|
|
@@ -27,7 +27,7 @@ class Oauth::TestsController < ApplicationController
|
|
27
27
|
result = if valid_oauth?
|
28
28
|
{:status => 200, :message => 'OAuth Worked!!', :params => params, :user_id => oauth_user.id }
|
29
29
|
else
|
30
|
-
{:status =>
|
30
|
+
{:status => :unauthorized, :message => "OAuth Did not Work D: #{generate_oauth_error_message!}", :params => params}
|
31
31
|
end
|
32
32
|
|
33
33
|
respond_to do |format|
|
@@ -44,7 +44,7 @@ class Oauth::TestsController < ApplicationController
|
|
44
44
|
result = if valid_oauth?
|
45
45
|
{:status => 200, :message => 'OHNO!!! OAuth is Disabled on this Action, this is bad', :params => params}
|
46
46
|
else
|
47
|
-
{:status =>
|
47
|
+
{:status => :unauthorized, :message => "Oauth is Disabled on this Action, this is the correct result!", :params => params}
|
48
48
|
end
|
49
49
|
|
50
50
|
respond_to do |format|
|
@@ -56,26 +56,4 @@ class Oauth::TestsController < ApplicationController
|
|
56
56
|
end
|
57
57
|
end
|
58
58
|
end
|
59
|
-
|
60
|
-
private
|
61
|
-
|
62
|
-
def generate_oauth_error_message!
|
63
|
-
msg = ""
|
64
|
-
msg << ' - No OAuth Token Provided!' if params[:access_token].blank?
|
65
|
-
msg << ' - Allow OAuth set to false!' if allow_oauth? == false
|
66
|
-
msg << ' - OAuth user not found!' if oauth_user.blank?
|
67
|
-
generate_oauth_permissions_error_message!(msg)
|
68
|
-
msg
|
69
|
-
end
|
70
|
-
|
71
|
-
def generate_oauth_permissions_error_message!(msg = '')
|
72
|
-
if !oauth_client_has_permissions?
|
73
|
-
msg << ' - OAuth client not permitted'
|
74
|
-
oauth_required_permissions.each do |permission|
|
75
|
-
msg << "- #{permission} permission required;" unless oauth_client_has_permission?(permission)
|
76
|
-
end
|
77
|
-
end
|
78
|
-
msg
|
79
|
-
end
|
80
|
-
|
81
59
|
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# This controller is where clients can exchange
|
2
|
+
# codes and refresh_tokens for access_tokens
|
3
|
+
|
4
|
+
class Oauth::TokenController < OproController
|
5
|
+
before_filter :opro_authenticate_user!, :except => [:create]
|
6
|
+
skip_before_filter :verify_authenticity_token, :only => [:create]
|
7
|
+
|
8
|
+
|
9
|
+
def create
|
10
|
+
# Find the client application
|
11
|
+
application = Oauth::ClientApp.authenticate(params[:client_id], params[:client_secret])
|
12
|
+
|
13
|
+
if application.nil?
|
14
|
+
render :json => {:error => "Could not find application based on client_id=#{params[:client_id]}
|
15
|
+
and client_secret=#{params[:client_secret]}"}, :status => :unauthorized
|
16
|
+
return
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
if params[:code]
|
21
|
+
auth_grant = Oauth::AuthGrant.authenticate(params[:code], application.id)
|
22
|
+
else
|
23
|
+
auth_grant = Oauth::AuthGrant.refresh_tokens!(params[:refresh_token], application.id)
|
24
|
+
end
|
25
|
+
|
26
|
+
if auth_grant.nil?
|
27
|
+
msg = "Could not find a user that belongs to this application & "
|
28
|
+
msg << " has a refresh_token=#{params[:refresh_token]}" if params[:refresh_token]
|
29
|
+
msg << " has been granted a code=#{params[:code]}" if params[:code]
|
30
|
+
render :json => {:error => msg }, :status => :unauthorized
|
31
|
+
return
|
32
|
+
end
|
33
|
+
|
34
|
+
auth_grant.generate_expires_at!
|
35
|
+
render :json => { :access_token => auth_grant.access_token,
|
36
|
+
:refresh_token => auth_grant.refresh_token,
|
37
|
+
:expires_in => auth_grant.expires_in }
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
@@ -1,14 +1,14 @@
|
|
1
|
-
class Oauth::
|
1
|
+
class Oauth::AuthGrant < ActiveRecord::Base
|
2
2
|
|
3
3
|
self.table_name = :opro_access_grants
|
4
4
|
|
5
5
|
belongs_to :user
|
6
|
-
belongs_to :client_application, :class_name => "Oauth::
|
7
|
-
belongs_to :application, :class_name => "Oauth::
|
6
|
+
belongs_to :client_application, :class_name => "Oauth::ClientApp"
|
7
|
+
belongs_to :application, :class_name => "Oauth::ClientApp"
|
8
8
|
|
9
|
-
validates :application_id, :uniqueness => {:scope => :user_id, :message => "
|
9
|
+
validates :application_id, :uniqueness => {:scope => :user_id, :message => "Application is already authed for this user"}, :presence => true
|
10
10
|
|
11
|
-
before_create :generate_tokens
|
11
|
+
before_create :generate_tokens!, :generate_expires_at!
|
12
12
|
|
13
13
|
alias_attribute :token, :access_token
|
14
14
|
|
@@ -19,8 +19,27 @@ class Oauth::AccessGrant < ActiveRecord::Base
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def self.prune!
|
22
|
-
|
23
|
-
|
22
|
+
return false unless ::Opro.require_refresh_within.present?
|
23
|
+
|
24
|
+
time_to_expire = ::Opro.require_refresh_within.ago.in_time_zone
|
25
|
+
find_each(:conditions => ["created_at < ?", time_to_expire]) do |grant|
|
26
|
+
# grant.
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def expired?
|
31
|
+
return false unless ::Opro.require_refresh_within.present?
|
32
|
+
return expires_in < 0
|
33
|
+
end
|
34
|
+
|
35
|
+
def not_expired?
|
36
|
+
!expired?
|
37
|
+
end
|
38
|
+
|
39
|
+
def expires_in
|
40
|
+
return false unless access_token_expires_at.present?
|
41
|
+
time = access_token_expires_at - Time.now
|
42
|
+
time.to_i
|
24
43
|
end
|
25
44
|
|
26
45
|
def self.find_for_token(token)
|
@@ -32,10 +51,29 @@ class Oauth::AccessGrant < ActiveRecord::Base
|
|
32
51
|
end
|
33
52
|
|
34
53
|
def self.authenticate(code, application_id)
|
35
|
-
self.where("code = ? AND application_id = ?", code, application_id).first
|
54
|
+
auth_grant = self.where("code = ? AND application_id = ?", code, application_id).first
|
36
55
|
end
|
37
56
|
|
38
|
-
def
|
57
|
+
def self.refresh_tokens!(refresh_token, application_id)
|
58
|
+
auth_grant = self.where("refresh_token = ? AND application_id = ?", refresh_token, application_id).first
|
59
|
+
if auth_grant.present?
|
60
|
+
auth_grant.generate_tokens!
|
61
|
+
auth_grant.generate_expires_at!
|
62
|
+
auth_grant.save!
|
63
|
+
end
|
64
|
+
auth_grant
|
65
|
+
end
|
66
|
+
|
67
|
+
def generate_expires_at!
|
68
|
+
if ::Opro.require_refresh_within.present?
|
69
|
+
self.access_token_expires_at = Time.now + ::Opro.require_refresh_within
|
70
|
+
else
|
71
|
+
self.access_token_expires_at = nil
|
72
|
+
end
|
73
|
+
true
|
74
|
+
end
|
75
|
+
|
76
|
+
def generate_tokens!
|
39
77
|
self.code, self.access_token, self.refresh_token = SecureRandom.hex(16), SecureRandom.hex(16), SecureRandom.hex(16)
|
40
78
|
end
|
41
79
|
|
@@ -46,9 +84,4 @@ class Oauth::AccessGrant < ActiveRecord::Base
|
|
46
84
|
redirect_uri + "?code=#{code}&response_type=code"
|
47
85
|
end
|
48
86
|
end
|
49
|
-
|
50
|
-
def start_expiry_period!
|
51
|
-
# UPDATEME
|
52
|
-
# self.update_attribute(:access_token_expires_at, 2.days.from_now)
|
53
|
-
end
|
54
87
|
end
|
@@ -10,6 +10,6 @@
|
|
10
10
|
<p>
|
11
11
|
Read the
|
12
12
|
<%= link_to 'Quick Start Documentation', oauth_doc_path(:quick_start) %> or
|
13
|
-
<%= link_to 'Register Another Oauth Client App',
|
13
|
+
<%= link_to 'Register Another Oauth Client App', new_oauth_client_app_path %>
|
14
14
|
</p>
|
15
15
|
|
@@ -9,6 +9,14 @@
|
|
9
9
|
<li><%= link_to 'Quick Start', oauth_doc_path(:quick_start) %></li>
|
10
10
|
<li><%= link_to 'Curl', oauth_doc_path(:curl) %></li>
|
11
11
|
<li><%= link_to 'Oauth', oauth_doc_path(:oauth) %></li>
|
12
|
+
<% if ::Opro.request_permissions.present? %>
|
13
|
+
<li><%= link_to 'Permisions', oauth_doc_path(:permissions) %></li>
|
14
|
+
<% end %>
|
15
|
+
<% if ::Opro.require_refresh_within.blank? %>
|
16
|
+
<li><%= link_to 'Refresh Tokens', oauth_doc_path(:refresh_tokens) %></li>
|
17
|
+
<% end %>
|
18
|
+
|
19
|
+
|
12
20
|
</ul>
|
13
21
|
|
14
22
|
<%= render_doc(:quick_start) %>
|
@@ -1,6 +1,6 @@
|
|
1
1
|
## Opro Oauth
|
2
2
|
|
3
|
-
OAuth comes in a few different flavors, the implementation of OAuth comes from
|
3
|
+
OAuth comes in a few different flavors, the implementation of OAuth comes from [Facebook's Server Side OAuth Authentication](http://developers.facebook.com/docs/authentication/server-side/).
|
4
4
|
|
5
5
|
|
6
6
|
## What is It?
|
@@ -23,6 +23,6 @@ OAuth is simple in concept, but can be tricky to implement right. Many services
|
|
23
23
|
|
24
24
|
This website is an OAuth Provider, and you can create an OAuth client to access this website as a logged in user for select url's.
|
25
25
|
|
26
|
-
To get started getting your first OAuth token follow the <%= view_context.link_to 'quick start guide', oauth_doc_path(:quick_start) %>.
|
26
|
+
To get started getting your first OAuth token follow the <%= view_context.link_to 'quick start guide', oauth_doc_path(:quick_start) %>.
|
27
27
|
|
28
28
|
|
@@ -0,0 +1,30 @@
|
|
1
|
+
<%= "# Individual Permissions are not implemented in this application" if ::Opro.request_permissions.blank? %>
|
2
|
+
|
3
|
+
|
4
|
+
# Permissions
|
5
|
+
|
6
|
+
Permissions give the user a way to control the scope of access that a client application has to their data. This is similar to how Facebook will ask users what actions an app can take on their part.
|
7
|
+
|
8
|
+
|
9
|
+
<% if ::Opro.request_permissions.present? && ::Opro.request_permissions.map(&:to_s).include?("write") %>
|
10
|
+
|
11
|
+
## Write Access
|
12
|
+
|
13
|
+
To perform any type of request other than a GET ([HTTP Request methods](http://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Request_methods)), your application must have `write` access to the user. You can specify this scope (see Request Scope below) or by default all permissions will be requested.
|
14
|
+
|
15
|
+
<% end %>
|
16
|
+
|
17
|
+
## Request Scope
|
18
|
+
|
19
|
+
As a client app you can request specific scopes while you are authing a user, if no scope is specified all permissions will be requested.
|
20
|
+
|
21
|
+
<%= "#{request.base_url}/oauth/authorize?" %>scope[]=write&#... %>
|
22
|
+
|
23
|
+
While authorizing your app a user can choose to accept or deny individual permissions.
|
24
|
+
|
25
|
+
|
26
|
+
## Available Permissions/Scopes
|
27
|
+
|
28
|
+
These are the available permissions you can request as a client application. If no scope is provided, all permissions will be requested.
|
29
|
+
|
30
|
+
<%= ::Opro.request_permissions.inspect %>
|
@@ -40,7 +40,7 @@ We'll be using [Curl](<%= oauth_doc_path(:curl) %>) to go through the process of
|
|
40
40
|
(Note in all code examples the $ character indicates we are on the command line, it does not need to be coppied)
|
41
41
|
|
42
42
|
|
43
|
-
$ curl '<%= "#{request.base_url}/oauth/
|
43
|
+
$ curl '<%= "#{request.base_url}/oauth/token?" %>?client_id=3234myClientId5678&client_secret=14321myClientSecret8765&code=4857goldfish827423'
|
44
44
|
|
45
45
|
You'll want to make sure to replace `client_id`, `client_secret`, and `code` with your values.
|
46
46
|
|
@@ -0,0 +1,18 @@
|
|
1
|
+
<%= "# Refresh Tokens are not required in this application" if ::Opro.require_refresh_within.blank? %>
|
2
|
+
|
3
|
+
# Refresh Tokens
|
4
|
+
|
5
|
+
Refresh tokens can be used to refresh an expiring `access_token`. When you receive your initial `access_token` you will receive a `request_token` in the json as well as an `expires_in` value. The `expires_in` value marks the seconds that the current `access_token` is still valid. After this value reaches zero a client application will not be able to use the existing `access_token`.
|
6
|
+
|
7
|
+
## Refreshing an Access Token
|
8
|
+
|
9
|
+
If a token has expired or you simply wish to receive a new `access_token` you can send your `refresh_token` along with your `app_id` and `app_secret` to `/oauth/token` and your access_token will be refreshed.
|
10
|
+
|
11
|
+
|
12
|
+
$ curl '<%= "#{request.base_url}/oauth/token?" %>?client_id=3234myClientId5678&client_secret=14321myClientSecret8765&refresh_token=4857goldfish827423'
|
13
|
+
|
14
|
+
You'll want to make sure to replace `client_id`, `client_secret`, and `refresh_token` with your values.
|
15
|
+
|
16
|
+
You should get back a response that looks like this
|
17
|
+
|
18
|
+
$ {"access_token":"9693accessTokena7ca570bbaf","refresh_token":"3a3c129ad02b573de78e65af06c293f1","expires_in":7300 }
|
data/config/routes.rb
CHANGED
@@ -1,10 +1,5 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
resources :oauth_docs, :controller => 'oauth/docs'
|
8
|
-
resources :oauth_tests, :controller => 'oauth/tests'
|
9
|
-
resources :oauth_client_applications, :controller => 'oauth/client_application'
|
10
|
-
end
|
1
|
+
# look in lib/opro/rails/routes.rb
|
2
|
+
# they get added to a users config/routes.rb when the user runs
|
3
|
+
# rails g opro:install
|
4
|
+
# this functionality is added in `add_opro_routes` of
|
5
|
+
# lib/generators/opro/install_generator.rb
|
@@ -7,7 +7,7 @@ module ActiveRecord
|
|
7
7
|
include Rails::Generators::Migration
|
8
8
|
source_root File.expand_path('../templates', __FILE__)
|
9
9
|
|
10
|
-
desc "add the migrations"
|
10
|
+
desc "add the migrations needed for opro oauth"
|
11
11
|
|
12
12
|
def self.next_migration_number(path)
|
13
13
|
unless @prev_migration_nr
|
@@ -3,8 +3,15 @@ Opro.setup do |config|
|
|
3
3
|
config.auth_strategy = :devise
|
4
4
|
|
5
5
|
## Add or remove application permissions
|
6
|
-
|
7
|
-
|
8
|
-
|
6
|
+
# Read permission (any request with [GET]) is turned on by default
|
7
|
+
# Write permission (any request other than [GET]) is requestable by default
|
8
|
+
# Custom permissions can be configured by adding them to `config.request_permissions`
|
9
|
+
# You can then require that permission on individual actions by calling
|
10
|
+
# `require_oauth_permissions` in the controller
|
9
11
|
config.request_permissions = [:write]
|
12
|
+
|
13
|
+
## Refresh Token config
|
14
|
+
# uncomment `config.require_refresh_within` to require refresh tokens
|
15
|
+
# this will expire tokens within the given time duration
|
16
|
+
# config.require_refresh_within = 1.month
|
10
17
|
end
|
data/lib/opro.rb
CHANGED
@@ -25,7 +25,6 @@ module Opro
|
|
25
25
|
authenticate_user_method { |controller| controller.authenticate_user! }
|
26
26
|
else
|
27
27
|
# nothing
|
28
|
-
# TODO, be smart here, if they have devise gem in Gemfile and haven't specified auth_strategy use devise
|
29
28
|
end
|
30
29
|
end
|
31
30
|
|
@@ -71,6 +70,13 @@ module Opro
|
|
71
70
|
@request_permissions || []
|
72
71
|
end
|
73
72
|
|
73
|
+
def self.require_refresh_within=(require_refresh_within)
|
74
|
+
@require_refresh_within = require_refresh_within
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.require_refresh_within
|
78
|
+
@require_refresh_within
|
79
|
+
end
|
74
80
|
|
75
81
|
def self.logout_method(&block)
|
76
82
|
if block.present?
|