rodauth-oauth 1.3.1 → 1.4.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.
- checksums.yaml +4 -4
- data/README.md +19 -12
- data/doc/release_notes/1_3_2.md +14 -0
- data/doc/release_notes/1_4_0.md +49 -0
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/authorize.html.erb +23 -23
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/frontchannel_logout.html.erb +10 -0
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_applications.html.erb +1 -1
- data/lib/generators/rodauth/oauth/templates/db/migrate/create_rodauth_oauth.rb +21 -0
- data/lib/generators/rodauth/oauth/views_generator.rb +2 -2
- data/lib/rodauth/features/oauth_application_management.rb +1 -1
- data/lib/rodauth/features/oauth_assertion_base.rb +1 -1
- data/lib/rodauth/features/oauth_authorize_base.rb +1 -1
- data/lib/rodauth/features/oauth_base.rb +31 -30
- data/lib/rodauth/features/oauth_device_code_grant.rb +2 -2
- data/lib/rodauth/features/oauth_dynamic_client_registration.rb +9 -0
- data/lib/rodauth/features/oauth_grant_management.rb +1 -1
- data/lib/rodauth/features/oauth_jwt.rb +3 -3
- data/lib/rodauth/features/oauth_jwt_base.rb +23 -12
- data/lib/rodauth/features/oauth_jwt_bearer_grant.rb +1 -1
- data/lib/rodauth/features/oauth_jwt_jwks.rb +1 -1
- data/lib/rodauth/features/oauth_jwt_secured_authorization_request.rb +25 -6
- data/lib/rodauth/features/oauth_pushed_authorization_request.rb +33 -24
- data/lib/rodauth/features/oauth_resource_server.rb +1 -1
- data/lib/rodauth/features/oauth_saml_bearer_grant.rb +79 -47
- data/lib/rodauth/features/oauth_tls_client_auth.rb +1 -1
- data/lib/rodauth/features/oauth_token_introspection.rb +1 -1
- data/lib/rodauth/features/oauth_token_revocation.rb +1 -1
- data/lib/rodauth/features/oidc.rb +27 -8
- data/lib/rodauth/features/oidc_backchannel_logout.rb +120 -0
- data/lib/rodauth/features/oidc_dynamic_client_registration.rb +25 -0
- data/lib/rodauth/features/oidc_frontchannel_logout.rb +134 -0
- data/lib/rodauth/features/oidc_logout_base.rb +76 -0
- data/lib/rodauth/features/oidc_rp_initiated_logout.rb +29 -6
- data/lib/rodauth/features/oidc_session_management.rb +89 -0
- data/lib/rodauth/oauth/http_extensions.rb +1 -1
- data/lib/rodauth/oauth/version.rb +1 -1
- data/locales/en.yml +9 -0
- data/locales/pt.yml +9 -0
- data/templates/check_session.str +67 -0
- data/templates/frontchannel_logout.str +17 -0
- metadata +13 -2
@@ -0,0 +1,134 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rodauth/oauth"
|
4
|
+
|
5
|
+
# :nocov:
|
6
|
+
raise LoadError, "the `:oidc_frontchannel_logout` requires rodauth 2.32.0 or higher" if Rodauth::VERSION < "2.32.0"
|
7
|
+
|
8
|
+
# :nocov:
|
9
|
+
|
10
|
+
module Rodauth
|
11
|
+
Feature.define(:oidc_frontchannel_logout, :OidFrontchannelLogout) do
|
12
|
+
depends :logout, :oidc_logout_base
|
13
|
+
|
14
|
+
view "frontchannel_logout", "Logout", "frontchannel_logout"
|
15
|
+
|
16
|
+
translatable_method :oauth_frontchannel_logout_redirecting_lead, "You are being redirected..."
|
17
|
+
translatable_method :oauth_frontchannel_logout_redirecting_label, "please click %<link>s if your browser does not " \
|
18
|
+
"redirect you in a few seconds."
|
19
|
+
translatable_method :oauth_frontchannel_logout_redirecting_link_label, "here"
|
20
|
+
auth_value_method :frontchannel_logout_session_supported, true
|
21
|
+
auth_value_method :frontchannel_logout_redirect_timeout, 5
|
22
|
+
auth_value_method :oauth_applications_frontchannel_logout_uri_column, :frontchannel_logout_uri
|
23
|
+
auth_value_method :oauth_applications_frontchannel_logout_session_required_column, :frontchannel_logout_session_required
|
24
|
+
|
25
|
+
attr_reader :frontchannel_logout_urls
|
26
|
+
|
27
|
+
attr_reader :frontchannel_logout_redirect
|
28
|
+
|
29
|
+
def logout
|
30
|
+
@visited_sites = session[visited_sites_key]
|
31
|
+
|
32
|
+
super
|
33
|
+
end
|
34
|
+
|
35
|
+
def _logout_response
|
36
|
+
visited_sites = @visited_sites
|
37
|
+
|
38
|
+
return super unless visited_sites
|
39
|
+
|
40
|
+
logout_urls = db[oauth_applications_table]
|
41
|
+
.where(oauth_applications_client_id_column => visited_sites.map(&:first))
|
42
|
+
.as_hash(oauth_applications_client_id_column, oauth_applications_frontchannel_logout_uri_column)
|
43
|
+
|
44
|
+
return super if logout_urls.empty?
|
45
|
+
|
46
|
+
generate_frontchannel_logout_urls(visited_sites, logout_urls)
|
47
|
+
|
48
|
+
@frontchannel_logout_redirect = logout_redirect
|
49
|
+
|
50
|
+
set_notice_flash logout_notice_flash
|
51
|
+
return_response frontchannel_logout_view
|
52
|
+
end
|
53
|
+
|
54
|
+
# overrides rp-initiate logout response
|
55
|
+
def _oidc_logout_response
|
56
|
+
visited_sites = @visited_sites
|
57
|
+
|
58
|
+
return super unless visited_sites
|
59
|
+
|
60
|
+
logout_urls = db[oauth_applications_table]
|
61
|
+
.where(oauth_applications_client_id_column => visited_sites.map(&:first))
|
62
|
+
.as_hash(oauth_applications_client_id_column, oauth_applications_frontchannel_logout_uri_column)
|
63
|
+
|
64
|
+
return super if logout_urls.empty?
|
65
|
+
|
66
|
+
generate_frontchannel_logout_urls(visited_sites, logout_urls)
|
67
|
+
|
68
|
+
@frontchannel_logout_redirect = oidc_logout_redirect
|
69
|
+
|
70
|
+
set_notice_flash logout_notice_flash
|
71
|
+
return_response frontchannel_logout_view
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def generate_frontchannel_logout_urls(visited_sites, logout_urls)
|
77
|
+
@frontchannel_logout_urls = logout_urls.flat_map do |client_id, logout_url|
|
78
|
+
next unless logout_url
|
79
|
+
|
80
|
+
sids = visited_sites.select { |cid, _| cid == client_id }.map(&:last)
|
81
|
+
|
82
|
+
sids.map do |sid|
|
83
|
+
logout_url = URI(logout_url)
|
84
|
+
|
85
|
+
if sid
|
86
|
+
query = logout_url.query
|
87
|
+
query = if query
|
88
|
+
URI.decode_www_form(query)
|
89
|
+
else
|
90
|
+
[]
|
91
|
+
end
|
92
|
+
query << ["iss", oauth_jwt_issuer]
|
93
|
+
query << ["sid", sid]
|
94
|
+
logout_url.query = URI.encode_www_form(query)
|
95
|
+
end
|
96
|
+
|
97
|
+
logout_url
|
98
|
+
end
|
99
|
+
end.compact
|
100
|
+
end
|
101
|
+
|
102
|
+
def id_token_claims(oauth_grant, signing_algorithm)
|
103
|
+
claims = super
|
104
|
+
|
105
|
+
return claims unless oauth_application[oauth_applications_frontchannel_logout_uri_column]
|
106
|
+
|
107
|
+
session_id_in_claims(oauth_grant, claims)
|
108
|
+
|
109
|
+
claims
|
110
|
+
end
|
111
|
+
|
112
|
+
def should_set_oauth_application_in_visited_sites?
|
113
|
+
true
|
114
|
+
end
|
115
|
+
|
116
|
+
def should_set_sid_in_visited_sites?(oauth_application)
|
117
|
+
super || requires_frontchannel_logout_session?(oauth_application)
|
118
|
+
end
|
119
|
+
|
120
|
+
def requires_frontchannel_logout_session?(oauth_application)
|
121
|
+
(
|
122
|
+
oauth_application &&
|
123
|
+
oauth_application[oauth_applications_frontchannel_logout_session_required_column]
|
124
|
+
) || frontchannel_logout_session_supported
|
125
|
+
end
|
126
|
+
|
127
|
+
def oauth_server_metadata_body(*)
|
128
|
+
super.tap do |data|
|
129
|
+
data[:frontchannel_logout_supported] = true
|
130
|
+
data[:frontchannel_logout_session_supported] = frontchannel_logout_session_supported
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rodauth/oauth"
|
4
|
+
|
5
|
+
module Rodauth
|
6
|
+
Feature.define(:oidc_logout_base, :OidcLogoutBase) do
|
7
|
+
depends :oidc
|
8
|
+
|
9
|
+
session_key :visited_sites_key, :visited_sites
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
# set application/sid in visited sites when required
|
14
|
+
def create_oauth_grant(create_params = {})
|
15
|
+
sid_in_visited_sites
|
16
|
+
|
17
|
+
super
|
18
|
+
end
|
19
|
+
|
20
|
+
def active_sessions?(session_id)
|
21
|
+
!active_sessions_ds.where(active_sessions_session_id_column => session_id).empty?
|
22
|
+
end
|
23
|
+
|
24
|
+
def session_id_in_claims(oauth_grant, claims)
|
25
|
+
oauth_application_in_visited_sites do
|
26
|
+
if should_set_sid_in_visited_sites?(oauth_application)
|
27
|
+
# id_token or token response types
|
28
|
+
session_id = if (sess = session[session_id_session_key])
|
29
|
+
compute_hmac(sess)
|
30
|
+
else
|
31
|
+
# code response type
|
32
|
+
ds = db[active_sessions_table]
|
33
|
+
ds = ds.where(active_sessions_account_id_column => oauth_grant[oauth_grants_account_id_column])
|
34
|
+
ds = ds.order(Sequel.desc(active_sessions_last_use_column))
|
35
|
+
ds.get(active_sessions_session_id_column)
|
36
|
+
end
|
37
|
+
|
38
|
+
claims[:sid] = session_id
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def oauth_application_in_visited_sites
|
44
|
+
visited_sites = session[visited_sites_key] || []
|
45
|
+
|
46
|
+
session_id = yield
|
47
|
+
|
48
|
+
visited_site = [oauth_application[oauth_applications_client_id_column], session_id]
|
49
|
+
|
50
|
+
return if visited_sites.include?(visited_site)
|
51
|
+
|
52
|
+
visited_sites << visited_site
|
53
|
+
set_session_value(visited_sites_key, visited_sites)
|
54
|
+
end
|
55
|
+
|
56
|
+
def sid_in_visited_sites
|
57
|
+
return unless should_set_oauth_application_in_visited_sites?
|
58
|
+
|
59
|
+
oauth_application_in_visited_sites do
|
60
|
+
if should_set_sid_in_visited_sites?(oauth_application)
|
61
|
+
ds = active_sessions_ds.order(Sequel.desc(active_sessions_last_use_column))
|
62
|
+
|
63
|
+
ds.get(active_sessions_session_id_column)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def should_set_oauth_application_in_visited_sites?
|
69
|
+
false
|
70
|
+
end
|
71
|
+
|
72
|
+
def should_set_sid_in_visited_sites?(*)
|
73
|
+
false
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -4,11 +4,15 @@ require "rodauth/oauth"
|
|
4
4
|
|
5
5
|
module Rodauth
|
6
6
|
Feature.define(:oidc_rp_initiated_logout, :OidcRpInitiatedLogout) do
|
7
|
-
depends :
|
7
|
+
depends :oidc_logout_base
|
8
|
+
response "oidc_logout"
|
8
9
|
|
9
10
|
auth_value_method :oauth_applications_post_logout_redirect_uris_column, :post_logout_redirect_uris
|
11
|
+
translatable_method :oauth_invalid_id_token_hint_message, "Invalid ID token hint"
|
10
12
|
translatable_method :oauth_invalid_post_logout_redirect_uri_message, "Invalid post logout redirect URI"
|
11
13
|
|
14
|
+
attr_reader :oidc_logout_redirect
|
15
|
+
|
12
16
|
# /oidc-logout
|
13
17
|
auth_server_route(:oidc_logout) do |r|
|
14
18
|
require_authorizable_account
|
@@ -19,7 +23,7 @@ module Rodauth
|
|
19
23
|
catch_error do
|
20
24
|
validate_oidc_logout_params
|
21
25
|
|
22
|
-
oauth_application = nil
|
26
|
+
claims = oauth_application = nil
|
23
27
|
|
24
28
|
if (id_token_hint = param_or_nil("id_token_hint"))
|
25
29
|
#
|
@@ -32,7 +36,12 @@ module Rodauth
|
|
32
36
|
#
|
33
37
|
claims = jwt_decode(id_token_hint, verify_claims: false)
|
34
38
|
|
35
|
-
redirect_logout_with_error(
|
39
|
+
redirect_logout_with_error(oauth_invalid_id_token_hint_message) unless claims
|
40
|
+
|
41
|
+
# If the ID Token's sid claim does not correspond to the RP's current session or a
|
42
|
+
# recent session at the OP, the OP SHOULD treat the logout request as suspect, and
|
43
|
+
# MAY decline to act upon it.
|
44
|
+
redirect_logout_with_error(oauth_invalid_client_message) if claims["sid"] && !active_sessions?(claims["sid"])
|
36
45
|
|
37
46
|
oauth_application = db[oauth_applications_table].where(oauth_applications_client_id_column => claims["aud"]).first
|
38
47
|
oauth_grant = db[oauth_grants_table]
|
@@ -40,9 +49,15 @@ module Rodauth
|
|
40
49
|
.where(oauth_grants_oauth_application_id_column => oauth_application[oauth_applications_id_column])
|
41
50
|
.first
|
42
51
|
|
52
|
+
unique_account_id = if oauth_grant
|
53
|
+
oauth_grant[oauth_grants_account_id_column]
|
54
|
+
else
|
55
|
+
account_id
|
56
|
+
end
|
57
|
+
|
43
58
|
# check whether ID token belongs to currently logged-in user
|
44
|
-
redirect_logout_with_error(oauth_invalid_client_message) unless
|
45
|
-
|
59
|
+
redirect_logout_with_error(oauth_invalid_client_message) unless claims["sub"] == jwt_subject(unique_account_id,
|
60
|
+
oauth_application)
|
46
61
|
|
47
62
|
# When an id_token_hint parameter is present, the OP MUST validate that it was the issuer of the ID Token.
|
48
63
|
redirect_logout_with_error(oauth_invalid_client_message) unless claims && claims["iss"] == oauth_jwt_issuer
|
@@ -59,6 +74,8 @@ module Rodauth
|
|
59
74
|
|
60
75
|
if (post_logout_redirect_uri = param_or_nil("post_logout_redirect_uri"))
|
61
76
|
error_message = catch(:default_logout_redirect) do
|
77
|
+
throw(:default_logout_redirect, oauth_invalid_id_token_hint_message) unless claims
|
78
|
+
|
62
79
|
oauth_application = db[oauth_applications_table].where(oauth_applications_client_id_column => claims["client_id"]).first
|
63
80
|
|
64
81
|
throw(:default_logout_redirect, oauth_invalid_client_message) unless oauth_application
|
@@ -78,7 +95,9 @@ module Rodauth
|
|
78
95
|
post_logout_redirect_uri = post_logout_redirect_uri.to_s
|
79
96
|
end
|
80
97
|
|
81
|
-
|
98
|
+
@oidc_logout_redirect = post_logout_redirect_uri
|
99
|
+
|
100
|
+
require_response(:_oidc_logout_response)
|
82
101
|
end
|
83
102
|
|
84
103
|
end
|
@@ -90,6 +109,10 @@ module Rodauth
|
|
90
109
|
end
|
91
110
|
end
|
92
111
|
|
112
|
+
def _oidc_logout_response
|
113
|
+
redirect(oidc_logout_redirect)
|
114
|
+
end
|
115
|
+
|
93
116
|
private
|
94
117
|
|
95
118
|
# Logout
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rodauth/oauth"
|
4
|
+
|
5
|
+
module Rodauth
|
6
|
+
Feature.define(:oidc_session_management, :OidcSessionManagement) do
|
7
|
+
depends :oidc
|
8
|
+
|
9
|
+
view "check_session", "Check Session", "check_session"
|
10
|
+
|
11
|
+
auth_value_method :oauth_oidc_user_agent_state_cookie_key, "_rodauth_oauth_user_agent_state"
|
12
|
+
auth_value_method :oauth_oidc_user_agent_state_cookie_options, {}.freeze
|
13
|
+
auth_value_method :oauth_oidc_user_agent_state_cookie_expires_in, 365 * 24 * 60 * 60 # 1 year
|
14
|
+
|
15
|
+
auth_value_method :oauth_oidc_user_agent_state_js, nil
|
16
|
+
|
17
|
+
auth_value_methods(
|
18
|
+
:oauth_oidc_session_management_salt
|
19
|
+
)
|
20
|
+
# /authorize
|
21
|
+
auth_server_route(:check_session) do |r|
|
22
|
+
allow_cors(r)
|
23
|
+
|
24
|
+
r.get do
|
25
|
+
set_title(:check_session_page_title)
|
26
|
+
scope.view(_view_opts("check_session").merge(layout: false))
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def clear_session
|
31
|
+
super
|
32
|
+
|
33
|
+
# update user agent state in the process
|
34
|
+
# TODO: dangerous if this gets overidden by the user
|
35
|
+
|
36
|
+
user_agent_state_cookie_opts = Hash[oauth_oidc_user_agent_state_cookie_options]
|
37
|
+
user_agent_state_cookie_opts[:value] = oauth_unique_id_generator
|
38
|
+
user_agent_state_cookie_opts[:expires] = convert_timestamp(Time.now + oauth_oidc_user_agent_state_cookie_expires_in)
|
39
|
+
user_agent_state_cookie_opts[:secure] = true
|
40
|
+
::Rack::Utils.set_cookie_header!(response.headers, oauth_oidc_user_agent_state_cookie_key, user_agent_state_cookie_opts)
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def do_authorize(*)
|
46
|
+
params, mode = super
|
47
|
+
|
48
|
+
params["session_state"] = generate_session_state
|
49
|
+
|
50
|
+
[params, mode]
|
51
|
+
end
|
52
|
+
|
53
|
+
def response_error_params(*)
|
54
|
+
payload = super
|
55
|
+
|
56
|
+
return payload unless request.path == authorize_path
|
57
|
+
|
58
|
+
payload["session_state"] = generate_session_state
|
59
|
+
payload
|
60
|
+
end
|
61
|
+
|
62
|
+
def generate_session_state
|
63
|
+
salt = oauth_oidc_session_management_salt
|
64
|
+
|
65
|
+
uri = URI(redirect_uri)
|
66
|
+
origin = if uri.respond_to?(:origin)
|
67
|
+
uri.origin
|
68
|
+
else
|
69
|
+
# TODO: remove when not supporting uri < 0.11
|
70
|
+
"#{uri.scheme}://#{uri.host}#{":#{uri.port}" if uri.port != uri.default_port}"
|
71
|
+
end
|
72
|
+
session_id = "#{oauth_application[oauth_applications_client_id_column]} " \
|
73
|
+
"#{origin} " \
|
74
|
+
"#{request.cookies[oauth_oidc_user_agent_state_cookie_key]} #{salt}"
|
75
|
+
|
76
|
+
"#{Digest::SHA256.hexdigest(session_id)}.#{salt}"
|
77
|
+
end
|
78
|
+
|
79
|
+
def oauth_server_metadata_body(*)
|
80
|
+
super.tap do |data|
|
81
|
+
data[:check_session_iframe] = check_session_url
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def oauth_oidc_session_management_salt
|
86
|
+
oauth_unique_id_generator
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
data/locales/en.yml
CHANGED
@@ -8,7 +8,9 @@ en:
|
|
8
8
|
device_verification_notice_flash: "The device is verified"
|
9
9
|
user_code_not_found_error_flash: "No device to authorize with the given user code"
|
10
10
|
authorize_page_title: "Authorize"
|
11
|
+
frontchannel_logout_page_title: "Logout"
|
11
12
|
authorize_page_lead: "The application %{name} would like to access your data."
|
13
|
+
oauth_frontchannel_logout_redirecting_lead: "You are being redirected..."
|
12
14
|
authorize_error_page_title: "Authorize Error"
|
13
15
|
oauth_cancel_button: "Cancel"
|
14
16
|
oauth_applications_page_title: "Oauth Applications"
|
@@ -18,6 +20,7 @@ en:
|
|
18
20
|
oauth_grants_page_title: "My Oauth Grants"
|
19
21
|
device_verification_page_title: "Device Verification"
|
20
22
|
device_search_page_title: "Device Search"
|
23
|
+
check_session_page_title: "Check Session"
|
21
24
|
oauth_management_pagination_previous_button: "Previous"
|
22
25
|
oauth_management_pagination_next_button: "Next"
|
23
26
|
oauth_grants_type_label: "Grant Type"
|
@@ -41,6 +44,8 @@ en:
|
|
41
44
|
oauth_grant_user_code_label: "User code"
|
42
45
|
oauth_grant_user_jws_jwk_label: "JSON Web Keys"
|
43
46
|
oauth_grant_user_jwt_public_key_label: "Public key"
|
47
|
+
oauth_frontchannel_logout_redirecting_label: "please click %{link} if your browser does not redirect you in a few seconds."
|
48
|
+
oauth_frontchannel_logout_redirecting_link_label: "here"
|
44
49
|
oauth_application_button: "Register"
|
45
50
|
oauth_authorize_button: "Authorize"
|
46
51
|
oauth_grant_revoke_button: "Revoke"
|
@@ -68,3 +73,7 @@ en:
|
|
68
73
|
oauth_invalid_scope_message: "The Access Token expired"
|
69
74
|
oauth_authorize_parameter_required: "Invalid or missing '%{parameter}'"
|
70
75
|
oauth_invalid_post_logout_redirect_uri_message: "Invalid post logout redirect URI"
|
76
|
+
oauth_saml_assertion_not_base64_message: "SAML assertion must be in base64 format"
|
77
|
+
oauth_saml_assertion_single_issuer_message: "SAML assertion must have a single issuer"
|
78
|
+
oauth_saml_settings_not_found_message: "No SAML settings found for issuer"
|
79
|
+
oauth_invalid_id_token_hint_message: "Invalid ID token hint"
|
data/locales/pt.yml
CHANGED
@@ -8,7 +8,9 @@ pt:
|
|
8
8
|
device_verification_notice_flash: "O dispositivo foi verificado com sucesso"
|
9
9
|
user_code_not_found_error_flash: "Não existe nenhum dispositivo a ser autorizado com o código de usuário inserido"
|
10
10
|
authorize_page_title: "Autorizar"
|
11
|
+
frontchannel_logout_page_title: "Saindo"
|
11
12
|
authorize_page_lead: "O aplicativo %{name} gostaria de aceder aos seus dados."
|
13
|
+
oauth_frontchannel_logout_redirecting_lead: "Redireccionando.."
|
12
14
|
authorize_error_page_title: Erro de autorização
|
13
15
|
oauth_cancel_button: "Cancelar"
|
14
16
|
oauth_applications_page_title: "Aplicativos OAuth"
|
@@ -18,6 +20,7 @@ pt:
|
|
18
20
|
oauth_grants_page_title: "As minhas concessões Oauth"
|
19
21
|
device_verification_page_title: "Verificação de dispositivo"
|
20
22
|
device_search_page_title: "Pesquisa de dispositivo"
|
23
|
+
check_session_page_title: "Verificação de Sessão"
|
21
24
|
oauth_management_pagination_previous_button: "Anterior"
|
22
25
|
oauth_management_pagination_next_button: "Próxima"
|
23
26
|
oauth_grants_type_label: "Tipo de concessão"
|
@@ -41,6 +44,8 @@ pt:
|
|
41
44
|
oauth_grant_user_code_label: "Código do usuário"
|
42
45
|
oauth_grant_user_jws_jwk_label: "Chaves JSON Web"
|
43
46
|
oauth_grant_user_jwt_public_key_label: "Chave pública"
|
47
|
+
oauth_frontchannel_logout_redirecting_label: "por favor clique %{link} se o seu navegador não lhe redireccionar em alguns segundos."
|
48
|
+
oauth_frontchannel_logout_redirecting_link_label: "aqui"
|
44
49
|
oauth_application_button: "Registar"
|
45
50
|
oauth_authorize_button: "Autorizar"
|
46
51
|
oauth_grant_revoke_button: "Revogar"
|
@@ -68,3 +73,7 @@ pt:
|
|
68
73
|
oauth_invalid_scope_message: "O Token de acesso expirou"
|
69
74
|
oauth_authorize_parameter_required: "'%{parameter}' inválido ou em falta"
|
70
75
|
oauth_invalid_post_logout_redirect_uri_message: "URI de redireccionamento pós-logout inválido"
|
76
|
+
oauth_saml_assertion_not_base64_message: "A asserção SAML tem que estar no formato base64"
|
77
|
+
oauth_saml_assertion_single_issuer_message: "A asserção SAML tem que ter um único emissor"
|
78
|
+
oauth_saml_settings_not_found_message: "Nenhuma configuração SAML encontrada para o emissor"
|
79
|
+
oauth_invalid_id_token_hint_message: "ID token hint inválido"
|
@@ -0,0 +1,67 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
6
|
+
<title>#{@page_title}</title>
|
7
|
+
</head>
|
8
|
+
<body>
|
9
|
+
<script type="text/javascript">
|
10
|
+
window.addEventListener("message", receiveMessage, false);
|
11
|
+
|
12
|
+
function receiveMessage(e) { // e.data has client_id and session_state
|
13
|
+
var client_id = e.data.substr(0, e.data.lastIndexOf(' '));
|
14
|
+
var session_state = e.data.substr(e.data.lastIndexOf(' ') + 1);
|
15
|
+
var salt = session_state.split('.')[1];
|
16
|
+
|
17
|
+
if (!client_id || !session_state || !salt) {
|
18
|
+
postMessage('error', e.origin);
|
19
|
+
return;
|
20
|
+
}
|
21
|
+
|
22
|
+
#{rodauth.oauth_oidc_user_agent_state_js}
|
23
|
+
|
24
|
+
// get_op_user_agent_state() is an OP defined function
|
25
|
+
// that returns the User Agent's login status at the OP.
|
26
|
+
// How it is done is entirely up to the OP.
|
27
|
+
var opuas = getOpUserAgentState();
|
28
|
+
|
29
|
+
// Here, the session_state is calculated in this particular way,
|
30
|
+
// but it is entirely up to the OP how to do it under the
|
31
|
+
// requirements defined in this specification.
|
32
|
+
var msgBuffer = new TextEncoder('utf-8').encode(client_id + ' ' + e.origin + ' ' + opuas + ' ' + salt);
|
33
|
+
crypto.subtle.digest('SHA-256', msgBuffer).then(function(hash) {
|
34
|
+
var hashArray = Array.from(new Uint8Array(hash)); // convert buffer to byte array
|
35
|
+
var hashHex = hashArray
|
36
|
+
.map(function(b) { return b.toString(16).padStart(2, "0"); })
|
37
|
+
.join("");
|
38
|
+
var ss = hashHex + "." + salt;
|
39
|
+
|
40
|
+
var stat = '';
|
41
|
+
if (session_state === ss) {
|
42
|
+
stat = 'unchanged';
|
43
|
+
} else {
|
44
|
+
stat = 'changed';
|
45
|
+
}
|
46
|
+
|
47
|
+
e.source.postMessage(stat, e.origin);
|
48
|
+
});
|
49
|
+
};
|
50
|
+
|
51
|
+
function getOpUserAgentState() {
|
52
|
+
var name = "#{rodauth.oauth_oidc_user_agent_state_cookie_key}=";
|
53
|
+
var ca = document.cookie.split(';');
|
54
|
+
var value = null;
|
55
|
+
for (var i = 0; i < ca.length; i++) {
|
56
|
+
var c = ca[i].trim();
|
57
|
+
if ((c.indexOf(name)) == 0) {
|
58
|
+
value = c.substr(name.length);
|
59
|
+
break;
|
60
|
+
}
|
61
|
+
}
|
62
|
+
|
63
|
+
return value;
|
64
|
+
}
|
65
|
+
</script>
|
66
|
+
</body>
|
67
|
+
</html>
|
@@ -0,0 +1,17 @@
|
|
1
|
+
<div class="mb-3">
|
2
|
+
<h1>#{rodauth.oauth_frontchannel_logout_redirecting_lead}</h1>
|
3
|
+
<p>
|
4
|
+
#{
|
5
|
+
rodauth.oauth_frontchannel_logout_redirecting_label(
|
6
|
+
link: "<a href=\"#{rodauth.frontchannel_logout_redirect}\">" \
|
7
|
+
"#{rodauth.oauth_frontchannel_logout_redirecting_link_label}</a>"
|
8
|
+
)
|
9
|
+
}
|
10
|
+
</p>
|
11
|
+
#{
|
12
|
+
rodauth.frontchannel_logout_urls.map do |logout_url|
|
13
|
+
"<iframe src=\"#{logout_url}\"></iframe>"
|
14
|
+
end.join
|
15
|
+
}
|
16
|
+
</div>
|
17
|
+
<meta http-equiv="refresh" content="#{rodauth.frontchannel_logout_redirect_timeout}; URL=#{rodauth.frontchannel_logout_redirect}" />
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rodauth-oauth
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tiago Cardoso
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-11-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rodauth
|
@@ -71,6 +71,8 @@ extra_rdoc_files:
|
|
71
71
|
- doc/release_notes/1_2_0.md
|
72
72
|
- doc/release_notes/1_3_0.md
|
73
73
|
- doc/release_notes/1_3_1.md
|
74
|
+
- doc/release_notes/1_3_2.md
|
75
|
+
- doc/release_notes/1_4_0.md
|
74
76
|
files:
|
75
77
|
- CHANGELOG.md
|
76
78
|
- LICENSE.txt
|
@@ -113,6 +115,8 @@ files:
|
|
113
115
|
- doc/release_notes/1_2_0.md
|
114
116
|
- doc/release_notes/1_3_0.md
|
115
117
|
- doc/release_notes/1_3_1.md
|
118
|
+
- doc/release_notes/1_3_2.md
|
119
|
+
- doc/release_notes/1_4_0.md
|
116
120
|
- lib/generators/rodauth/oauth/install_generator.rb
|
117
121
|
- lib/generators/rodauth/oauth/templates/app/models/oauth_application.rb
|
118
122
|
- lib/generators/rodauth/oauth/templates/app/models/oauth_grant.rb
|
@@ -120,6 +124,7 @@ files:
|
|
120
124
|
- lib/generators/rodauth/oauth/templates/app/views/rodauth/authorize_error.erb
|
121
125
|
- lib/generators/rodauth/oauth/templates/app/views/rodauth/device_search.html.erb
|
122
126
|
- lib/generators/rodauth/oauth/templates/app/views/rodauth/device_verification.html.erb
|
127
|
+
- lib/generators/rodauth/oauth/templates/app/views/rodauth/frontchannel_logout.html.erb
|
123
128
|
- lib/generators/rodauth/oauth/templates/app/views/rodauth/new_oauth_application.html.erb
|
124
129
|
- lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_application.html.erb
|
125
130
|
- lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_application_oauth_grants.html.erb
|
@@ -153,9 +158,13 @@ files:
|
|
153
158
|
- lib/rodauth/features/oauth_token_introspection.rb
|
154
159
|
- lib/rodauth/features/oauth_token_revocation.rb
|
155
160
|
- lib/rodauth/features/oidc.rb
|
161
|
+
- lib/rodauth/features/oidc_backchannel_logout.rb
|
156
162
|
- lib/rodauth/features/oidc_dynamic_client_registration.rb
|
163
|
+
- lib/rodauth/features/oidc_frontchannel_logout.rb
|
164
|
+
- lib/rodauth/features/oidc_logout_base.rb
|
157
165
|
- lib/rodauth/features/oidc_rp_initiated_logout.rb
|
158
166
|
- lib/rodauth/features/oidc_self_issued.rb
|
167
|
+
- lib/rodauth/features/oidc_session_management.rb
|
159
168
|
- lib/rodauth/oauth.rb
|
160
169
|
- lib/rodauth/oauth/database_extensions.rb
|
161
170
|
- lib/rodauth/oauth/http_extensions.rb
|
@@ -167,10 +176,12 @@ files:
|
|
167
176
|
- locales/pt.yml
|
168
177
|
- templates/authorize.str
|
169
178
|
- templates/authorize_error.str
|
179
|
+
- templates/check_session.str
|
170
180
|
- templates/client_secret_field.str
|
171
181
|
- templates/description_field.str
|
172
182
|
- templates/device_search.str
|
173
183
|
- templates/device_verification.str
|
184
|
+
- templates/frontchannel_logout.str
|
174
185
|
- templates/homepage_url_field.str
|
175
186
|
- templates/jwks_field.str
|
176
187
|
- templates/name_field.str
|