doorkeeper_sso 0.1.0.pre.alpha → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Rakefile +0 -5
- data/app/controllers/sso/application_controller.rb +6 -0
- data/app/controllers/sso/sessions_controller.rb +76 -12
- data/app/models/sso/client.rb +28 -0
- data/app/models/sso/session.rb +65 -41
- data/app/serializers/sso/owner_serializer.rb +5 -0
- data/app/serializers/sso/session_serializer.rb +7 -0
- data/db/migrate/{20150414102248_create_sso_sessions.rb → 20150521102248_create_sso_sessions.rb} +2 -3
- data/db/migrate/20150521142926_create_sso_clients.rb +18 -0
- data/db/migrate/20150521165143_remove_extra_columns_from_sso_sessions.rb +9 -0
- data/lib/doorkeeper_sso.rb +1 -0
- data/lib/sso.rb +4 -0
- data/lib/sso/doorkeeper/access_grant_mixin.rb +12 -0
- data/lib/sso/doorkeeper/access_token_mixin.rb +12 -0
- data/lib/sso/doorkeeper/application_mixin.rb +12 -0
- data/lib/sso/doorkeeper/authorizations_controller_mixin.rb +16 -4
- data/lib/sso/doorkeeper/tokens_controller_mixin.rb +15 -5
- data/lib/sso/engine.rb +27 -1
- data/lib/sso/engine.rb.orig +46 -0
- data/lib/sso/logging.rb +1 -1
- data/lib/sso/version.rb +1 -1
- data/lib/sso/warden/hooks/after_authentication.rb +17 -2
- data/lib/sso/warden/hooks/before_logout.rb +14 -5
- data/lib/sso/warden/hooks/session_check.rb +45 -0
- data/spec/api/schemas/session.json +35 -0
- data/spec/controllers/sso/sessions_controller_spec.rb +49 -9
- data/spec/fabricators/api_application_fabricator.rb +2 -2
- data/spec/fabricators/sso_client_fabricator.rb +5 -0
- data/spec/fabricators/sso_session_fabricator.rb +1 -2
- data/spec/fabricators/user_fabricator.rb +5 -3
- data/spec/lib/doorkeeper/access_grant_mixin_spec.rb +29 -0
- data/spec/lib/doorkeeper/access_token_mixin_spec.rb +29 -0
- data/spec/lib/doorkeeper/application_mixin_spec.rb +29 -0
- data/spec/lib/sso/warden/hooks/after_authentication_spec.rb +37 -0
- data/spec/lib/sso/warden/hooks/before_logout_spec.rb +30 -0
- data/spec/models/sso/client_spec.rb +15 -0
- data/spec/models/sso/session_spec.rb +108 -71
- data/spec/spec_helper.rb +1 -0
- data/spec/support/api_schema_matcher.rb +7 -0
- data/spec/test_app/Rakefile +5 -0
- data/spec/test_app/db/schema.rb +15 -1
- metadata +75 -18
@@ -13,10 +13,22 @@ module Sso
|
|
13
13
|
def after_grant_create
|
14
14
|
debug { "AuthorizationsController#Create : after_action" }
|
15
15
|
code_response = authorization.instance_variable_get("@response")
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
16
|
+
oauth_grant = code_response.try(:auth).try(:token)
|
17
|
+
|
18
|
+
warden_session = session["warden.user.user.session"]
|
19
|
+
session = Sso::Session.find_by!(id: warden_session["sso_session_id"])
|
20
|
+
|
21
|
+
if session.try(:active?)
|
22
|
+
error { "AuthorizationsControllerMixin - Sso::Session Inactive #{session.inspect}"}
|
23
|
+
warden.logout(:user) and return
|
24
|
+
end
|
25
|
+
|
26
|
+
if oauth_grant
|
27
|
+
debug { "Sso::Session.update_master_with_grant - #{session.id.inspect}, #{oauth_grant.inspect}" }
|
28
|
+
session.clients.find_or_create_by!(access_grant_id: oauth_grant.id)
|
29
|
+
else
|
30
|
+
error { "AuthorizationsControllerMixin - Unable to get grant id"}
|
31
|
+
warden.logout(:user) and return
|
20
32
|
end
|
21
33
|
end
|
22
34
|
end
|
@@ -1,11 +1,16 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
|
1
3
|
module Sso
|
2
4
|
module Doorkeeper
|
3
5
|
module TokensControllerMixin
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
include ::Sso::Logging
|
8
|
+
|
4
9
|
included do
|
5
10
|
after_action :after_token_create, only: :create
|
6
11
|
end
|
7
12
|
|
8
|
-
|
13
|
+
protected
|
9
14
|
|
10
15
|
def after_token_create
|
11
16
|
debug { "TokensController#Create : after_action" }
|
@@ -16,10 +21,15 @@ module Sso
|
|
16
21
|
# We cannot rely on session[:sso_session_id] here because the end-user might have cookies disabled.
|
17
22
|
# The only thing we can rely on to identify the user/Passport is the incoming grant token.
|
18
23
|
debug { %(Detected outgoing "Access Token" #{outgoing_access_token.inspect}) }
|
19
|
-
|
20
|
-
|
24
|
+
|
25
|
+
unless client = ::Sso::Client.find_by_grant_token(grant_token)
|
26
|
+
error { "::Sso::Client not found for grant token #{grant_token}" }
|
27
|
+
end
|
28
|
+
|
29
|
+
if client.update_access_token(outgoing_access_token)
|
30
|
+
debug { "::Sso::Client.update_access_token success for access_token: #{outgoing_access_token}" }
|
21
31
|
else
|
22
|
-
|
32
|
+
error { "::Sso::Session.update_access_token failed. #{client.errors.inspect}" }
|
23
33
|
warden.logout
|
24
34
|
end
|
25
35
|
end
|
@@ -38,4 +48,4 @@ module Sso
|
|
38
48
|
end
|
39
49
|
end
|
40
50
|
end
|
41
|
-
end
|
51
|
+
end
|
data/lib/sso/engine.rb
CHANGED
@@ -2,7 +2,31 @@ module Sso
|
|
2
2
|
class Engine < ::Rails::Engine
|
3
3
|
isolate_namespace Sso
|
4
4
|
|
5
|
+
# New test framework integration
|
6
|
+
config.generators do |g|
|
7
|
+
g.test_framework :rspec,
|
8
|
+
:fixtures => true,
|
9
|
+
:view_specs => false,
|
10
|
+
:helper_specs => false,
|
11
|
+
:routing_specs => false,
|
12
|
+
:controller_specs => true,
|
13
|
+
:request_specs => false
|
14
|
+
g.fixture_replacement :fabrication
|
15
|
+
end
|
16
|
+
|
17
|
+
initializer :append_migrations do |app|
|
18
|
+
unless app.root.to_s.match root.to_s
|
19
|
+
config.paths["db/migrate"].expanded.each do |expanded_path|
|
20
|
+
app.config.paths["db/migrate"] << expanded_path
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
5
25
|
config.after_initialize do
|
26
|
+
::Doorkeeper::Application.send(:include, Sso::Doorkeeper::ApplicationMixin)
|
27
|
+
::Doorkeeper::AccessGrant.send(:include, Sso::Doorkeeper::AccessGrantMixin)
|
28
|
+
::Doorkeeper::AccessToken.send(:include, Sso::Doorkeeper::AccessTokenMixin)
|
29
|
+
|
6
30
|
|
7
31
|
::Doorkeeper::TokensController.send(:include, AbstractController::Callbacks)
|
8
32
|
::Doorkeeper::TokensController.send(:include, Sso::Doorkeeper::TokensControllerMixin)
|
@@ -11,9 +35,11 @@ module Sso
|
|
11
35
|
::Warden::Manager.after_authentication(scope: :user, &::Sso::Warden::Hooks::AfterAuthentication.to_proc)
|
12
36
|
::Warden::Manager.before_logout(scope: :user, &::Sso::Warden::Hooks::BeforeLogout.to_proc)
|
13
37
|
|
38
|
+
# TODO : Infinite loop with before_logout
|
39
|
+
# ::Warden::Manager.after_fetch(scope: :user, &::Sso::Warden::Hooks::SessionCheck.to_proc)
|
40
|
+
|
14
41
|
# TODO : Why does it need a passport strategy
|
15
42
|
# Warden::Strategies.add :passport, ::Sso::Server::Warden::Strategies::Passport
|
16
|
-
|
17
43
|
end
|
18
44
|
end
|
19
45
|
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Sso
|
2
|
+
class Engine < ::Rails::Engine
|
3
|
+
isolate_namespace Sso
|
4
|
+
|
5
|
+
<<<<<<< HEAD
|
6
|
+
# New test framework integration
|
7
|
+
config.generators do |g|
|
8
|
+
g.test_framework :rspec,
|
9
|
+
:fixtures => true,
|
10
|
+
:view_specs => false,
|
11
|
+
:helper_specs => false,
|
12
|
+
:routing_specs => false,
|
13
|
+
:controller_specs => true,
|
14
|
+
:request_specs => false
|
15
|
+
g.fixture_replacement :fabrication
|
16
|
+
=======
|
17
|
+
initializer :append_migrations do |app|
|
18
|
+
unless app.root.to_s.match root.to_s
|
19
|
+
config.paths["db/migrate"].expanded.each do |expanded_path|
|
20
|
+
app.config.paths["db/migrate"] << expanded_path
|
21
|
+
end
|
22
|
+
end
|
23
|
+
>>>>>>> 4400323a20d61fedd59372c74cf3d32e72a52f09
|
24
|
+
end
|
25
|
+
|
26
|
+
config.after_initialize do
|
27
|
+
::Doorkeeper::Application.send(:include, Sso::Doorkeeper::ApplicationMixin)
|
28
|
+
::Doorkeeper::AccessGrant.send(:include, Sso::Doorkeeper::AccessGrantMixin)
|
29
|
+
::Doorkeeper::AccessToken.send(:include, Sso::Doorkeeper::AccessTokenMixin)
|
30
|
+
|
31
|
+
|
32
|
+
::Doorkeeper::TokensController.send(:include, AbstractController::Callbacks)
|
33
|
+
::Doorkeeper::TokensController.send(:include, Sso::Doorkeeper::TokensControllerMixin)
|
34
|
+
::Doorkeeper::AuthorizationsController.send(:include, Sso::Doorkeeper::AuthorizationsControllerMixin)
|
35
|
+
|
36
|
+
::Warden::Manager.after_authentication(scope: :user, &::Sso::Warden::Hooks::AfterAuthentication.to_proc)
|
37
|
+
::Warden::Manager.before_logout(scope: :user, &::Sso::Warden::Hooks::BeforeLogout.to_proc)
|
38
|
+
|
39
|
+
# TODO : Infinite loop with before_logout
|
40
|
+
# ::Warden::Manager.after_fetch(scope: :user, &::Sso::Warden::Hooks::SessionCheck.to_proc)
|
41
|
+
|
42
|
+
# TODO : Why does it need a passport strategy
|
43
|
+
# Warden::Strategies.add :passport, ::Sso::Server::Warden::Strategies::Passport
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/lib/sso/logging.rb
CHANGED
data/lib/sso/version.rb
CHANGED
@@ -5,6 +5,8 @@ module Sso
|
|
5
5
|
include ::Sso::Logging
|
6
6
|
|
7
7
|
attr_reader :user, :warden, :options
|
8
|
+
delegate :request, to: :warden
|
9
|
+
delegate :params, to: :request
|
8
10
|
|
9
11
|
def self.to_proc
|
10
12
|
proc do |user, warden, options|
|
@@ -18,9 +20,10 @@ module Sso
|
|
18
20
|
|
19
21
|
def call
|
20
22
|
debug { "Starting hook because this is considered the first login of the current session..." }
|
21
|
-
|
22
|
-
|
23
|
+
generate_session
|
24
|
+
end
|
23
25
|
|
26
|
+
def generate_session
|
24
27
|
debug { "Generating a Sso:Session for user #{user.id.inspect} for the session cookie at the Sso server..." }
|
25
28
|
attributes = { ip: request.ip, agent: request.user_agent }
|
26
29
|
|
@@ -28,6 +31,18 @@ module Sso
|
|
28
31
|
debug { "Sso:Session with ID #{sso_session.id} generated successfuly. Persisting it in session..." }
|
29
32
|
session["sso_session_id"] = sso_session.id.to_s
|
30
33
|
end
|
34
|
+
|
35
|
+
def scope
|
36
|
+
scope = options[:scope]
|
37
|
+
end
|
38
|
+
|
39
|
+
def session
|
40
|
+
warden.session(scope)
|
41
|
+
end
|
42
|
+
|
43
|
+
def logged_in?
|
44
|
+
warden.authenticated?(:user) && session
|
45
|
+
end
|
31
46
|
end
|
32
47
|
end
|
33
48
|
end
|
@@ -7,7 +7,6 @@ module Sso
|
|
7
7
|
attr_reader :user, :warden, :options
|
8
8
|
delegate :request, to: :warden
|
9
9
|
delegate :params, to: :request
|
10
|
-
delegate :session, to: :request
|
11
10
|
|
12
11
|
def self.to_proc
|
13
12
|
proc do |user, warden, options|
|
@@ -21,13 +20,23 @@ module Sso
|
|
21
20
|
|
22
21
|
def call
|
23
22
|
# Only run if user is logged in
|
24
|
-
if
|
25
|
-
debug {
|
26
|
-
debug { session.inspect }
|
23
|
+
if logged_in?
|
24
|
+
debug { "Logout Sso::Session - #{session["sso_session_id"]}" }
|
27
25
|
Sso::Session.logout(session["sso_session_id"])
|
28
|
-
#Passports.logout passport_id: params['passport_id'], provider_passport_id: session['sso_session_id']
|
29
26
|
end
|
30
27
|
end
|
28
|
+
|
29
|
+
def scope
|
30
|
+
scope = options[:scope]
|
31
|
+
end
|
32
|
+
|
33
|
+
def session
|
34
|
+
warden.session(scope)
|
35
|
+
end
|
36
|
+
|
37
|
+
def logged_in?
|
38
|
+
warden.authenticated?(:user) && session
|
39
|
+
end
|
31
40
|
end
|
32
41
|
end
|
33
42
|
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Sso
|
2
|
+
module Warden
|
3
|
+
module Hooks
|
4
|
+
class SessionCheck
|
5
|
+
include ::Sso::Logging
|
6
|
+
|
7
|
+
attr_reader :user, :warden, :options
|
8
|
+
delegate :request, to: :warden
|
9
|
+
delegate :params, to: :request
|
10
|
+
|
11
|
+
def self.to_proc
|
12
|
+
proc do |user, warden, options|
|
13
|
+
new(user: user, warden: warden, options: options).call
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(user:, warden:, options:)
|
18
|
+
@user, @warden, @options = user, warden, options
|
19
|
+
end
|
20
|
+
|
21
|
+
def call
|
22
|
+
debug { "Starting hook after user is fetched into the session" }
|
23
|
+
|
24
|
+
# Infinite loop with BeforeLogout - before logout runs this too
|
25
|
+
unless Sso::Session.find_by(id: session["sso_session_id"]).try(:active?)
|
26
|
+
warden.logout(:user)
|
27
|
+
throw(:warden, :scope => scope, :reason => "Sso::Session not found")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def scope
|
32
|
+
scope = options[:scope]
|
33
|
+
end
|
34
|
+
|
35
|
+
def session
|
36
|
+
warden.session(scope)
|
37
|
+
end
|
38
|
+
|
39
|
+
def logged_in?
|
40
|
+
warden.authenticated?(:user) && session
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
{
|
2
|
+
"type": "object",
|
3
|
+
"required" : [
|
4
|
+
"id",
|
5
|
+
"active?",
|
6
|
+
"secret",
|
7
|
+
"owner"
|
8
|
+
],
|
9
|
+
"properties": {
|
10
|
+
"id" : { "type" : "string" },
|
11
|
+
"active?" : { "type" : "boolean" },
|
12
|
+
"revoked_at" : { "type": ["string", "null"], "format": "date-time" },
|
13
|
+
"revoke_reason" : { "type": ["string", "null"] },
|
14
|
+
"secret" : { "type" : "string" },
|
15
|
+
"owner" : {
|
16
|
+
"type" : "object",
|
17
|
+
"required" : [
|
18
|
+
"id",
|
19
|
+
"name",
|
20
|
+
"email",
|
21
|
+
"first_name",
|
22
|
+
"last_name",
|
23
|
+
"lang"
|
24
|
+
],
|
25
|
+
"properties" : {
|
26
|
+
"id" : { "type" : "integer" },
|
27
|
+
"name" : { "type" : "string" },
|
28
|
+
"email" : { "type" : "string" },
|
29
|
+
"first_name" : { "type" : "string" },
|
30
|
+
"last_name" : { "type" : "string" },
|
31
|
+
"lang" : { "type" : "string" }
|
32
|
+
}
|
33
|
+
}
|
34
|
+
}
|
35
|
+
}
|
@@ -4,24 +4,64 @@ RSpec.describe Sso::SessionsController, :type => :controller do
|
|
4
4
|
routes { Sso::Engine.routes }
|
5
5
|
render_views
|
6
6
|
|
7
|
-
|
7
|
+
pending "GET jsonp" do
|
8
8
|
let(:user) { Fabricate(:user) }
|
9
9
|
|
10
10
|
context "logged_in" do
|
11
11
|
before() { sign_in user }
|
12
12
|
|
13
13
|
it "returns not authorized" do
|
14
|
-
get :
|
14
|
+
get :jsonp, format: :json
|
15
15
|
expect(response).to have_http_status(:ok)
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
19
|
context "not logged_in" do
|
20
20
|
it "returns not authorized" do
|
21
|
+
get :jsonp, format: :json
|
22
|
+
expect(response).to have_http_status(:unauthorized)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "GET show" do
|
28
|
+
let(:user) { Fabricate(:user) }
|
29
|
+
|
30
|
+
context "not logged_in" do
|
31
|
+
it do
|
21
32
|
get :show, format: :json
|
22
33
|
expect(response).to have_http_status(:unauthorized)
|
23
34
|
end
|
24
35
|
end
|
36
|
+
|
37
|
+
context "logged_in" do
|
38
|
+
let(:user) { Fabricate(:user) }
|
39
|
+
let(:application) { Fabricate('Doorkeeper::Application') }
|
40
|
+
let(:access_token) { Fabricate('Doorkeeper::AccessToken',
|
41
|
+
resource_owner_id: user.id) }
|
42
|
+
let(:access_grant) { Fabricate('Doorkeeper::AccessGrant',
|
43
|
+
application_id: application.id,
|
44
|
+
resource_owner_id: user.id,
|
45
|
+
redirect_uri: 'http://localhost:3002/oauth/callback'
|
46
|
+
) }
|
47
|
+
|
48
|
+
let(:session) { Fabricate('Sso::Session', owner: user) }
|
49
|
+
let!(:client) { Fabricate('Sso::Client', session: session,
|
50
|
+
application_id: application.id,
|
51
|
+
access_token_id: access_token.id,
|
52
|
+
access_grant_id: access_grant.id) }
|
53
|
+
|
54
|
+
before do
|
55
|
+
allow(controller).to receive(:doorkeeper_authorize!).and_return(true)
|
56
|
+
allow(controller).to receive(:doorkeeper_token).and_return(access_token)
|
57
|
+
|
58
|
+
get :show, format: :json
|
59
|
+
end
|
60
|
+
|
61
|
+
it { expect(response).to have_http_status(:ok) }
|
62
|
+
it { expect(assigns(:session)).to eq session }
|
63
|
+
it { expect(response).to match_response_schema("session") }
|
64
|
+
end
|
25
65
|
end
|
26
66
|
|
27
67
|
describe "POST create" do
|
@@ -37,8 +77,7 @@ RSpec.describe Sso::SessionsController, :type => :controller do
|
|
37
77
|
|
38
78
|
context "logged_in" do
|
39
79
|
let(:user) { Fabricate(:user) }
|
40
|
-
let(:
|
41
|
-
let(:master_sso_session) { Sso::Session.generate_master(user, attributes) }
|
80
|
+
let(:master_sso_session) { Sso::Session.generate_master(user, { ip: "10.1.1.1", agent: "Safari" }) }
|
42
81
|
let(:access_token) { Fabricate("Doorkeeper::AccessToken",
|
43
82
|
resource_owner_id: user.id) }
|
44
83
|
let(:access_grant) { Fabricate('Doorkeeper::AccessGrant',
|
@@ -47,18 +86,19 @@ RSpec.describe Sso::SessionsController, :type => :controller do
|
|
47
86
|
) }
|
48
87
|
|
49
88
|
before do
|
50
|
-
master_sso_session.access_token_id = access_token.id
|
51
|
-
master_sso_session.access_grant_id = access_grant.id
|
52
|
-
master_sso_session.save
|
53
|
-
|
54
89
|
allow(controller).to receive(:doorkeeper_authorize!).and_return(true)
|
55
90
|
allow(controller).to receive(:doorkeeper_token).and_return(access_token)
|
56
91
|
|
92
|
+
# Create a client with access grant & access token
|
93
|
+
master_sso_session.clients.find_or_create_by!(access_grant_id: access_grant.id, access_token_id: access_token.id)
|
57
94
|
post :create, params
|
58
95
|
end
|
59
96
|
|
60
97
|
it { expect(response).to have_http_status(:created) }
|
61
|
-
it { expect(assigns(:
|
98
|
+
it { expect(assigns(:session)).to eq master_sso_session }
|
99
|
+
it { expect(response).to match_response_schema("session") }
|
100
|
+
it { expect(master_sso_session.clients).to include ::Sso::Client.find_by(access_token: access_token) }
|
101
|
+
it { expect(master_sso_session.clients.map(&:ip)).to include "202.188.0.133" }
|
62
102
|
end
|
63
103
|
end
|
64
104
|
|