rodauth-oauth 1.3.1 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +19 -12
  3. data/doc/release_notes/1_3_2.md +14 -0
  4. data/doc/release_notes/1_4_0.md +49 -0
  5. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/authorize.html.erb +23 -23
  6. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/frontchannel_logout.html.erb +10 -0
  7. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_applications.html.erb +1 -1
  8. data/lib/generators/rodauth/oauth/templates/db/migrate/create_rodauth_oauth.rb +21 -0
  9. data/lib/generators/rodauth/oauth/views_generator.rb +2 -2
  10. data/lib/rodauth/features/oauth_application_management.rb +1 -1
  11. data/lib/rodauth/features/oauth_assertion_base.rb +1 -1
  12. data/lib/rodauth/features/oauth_authorize_base.rb +1 -1
  13. data/lib/rodauth/features/oauth_base.rb +31 -30
  14. data/lib/rodauth/features/oauth_device_code_grant.rb +2 -2
  15. data/lib/rodauth/features/oauth_dynamic_client_registration.rb +9 -0
  16. data/lib/rodauth/features/oauth_grant_management.rb +1 -1
  17. data/lib/rodauth/features/oauth_jwt.rb +3 -3
  18. data/lib/rodauth/features/oauth_jwt_base.rb +23 -12
  19. data/lib/rodauth/features/oauth_jwt_bearer_grant.rb +1 -1
  20. data/lib/rodauth/features/oauth_jwt_jwks.rb +1 -1
  21. data/lib/rodauth/features/oauth_jwt_secured_authorization_request.rb +25 -6
  22. data/lib/rodauth/features/oauth_pushed_authorization_request.rb +33 -24
  23. data/lib/rodauth/features/oauth_resource_server.rb +1 -1
  24. data/lib/rodauth/features/oauth_saml_bearer_grant.rb +79 -47
  25. data/lib/rodauth/features/oauth_tls_client_auth.rb +1 -1
  26. data/lib/rodauth/features/oauth_token_introspection.rb +1 -1
  27. data/lib/rodauth/features/oauth_token_revocation.rb +1 -1
  28. data/lib/rodauth/features/oidc.rb +27 -8
  29. data/lib/rodauth/features/oidc_backchannel_logout.rb +120 -0
  30. data/lib/rodauth/features/oidc_dynamic_client_registration.rb +25 -0
  31. data/lib/rodauth/features/oidc_frontchannel_logout.rb +134 -0
  32. data/lib/rodauth/features/oidc_logout_base.rb +76 -0
  33. data/lib/rodauth/features/oidc_rp_initiated_logout.rb +29 -6
  34. data/lib/rodauth/features/oidc_session_management.rb +89 -0
  35. data/lib/rodauth/oauth/http_extensions.rb +1 -1
  36. data/lib/rodauth/oauth/version.rb +1 -1
  37. data/locales/en.yml +9 -0
  38. data/locales/pt.yml +9 -0
  39. data/templates/check_session.str +67 -0
  40. data/templates/frontchannel_logout.str +17 -0
  41. 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 :oidc
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(oauth_invalid_client_message) unless claims
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 oauth_grant && claims["sub"] == jwt_subject(oauth_grant,
45
- oauth_application)
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
- redirect(post_logout_redirect_uri)
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
@@ -32,7 +32,7 @@ module Rodauth
32
32
  yield request if block_given?
33
33
 
34
34
  response = http.request(request)
35
- authorization_required unless response.code.to_i == 200
35
+ authorization_required unless (200..299).include?(response.code.to_i)
36
36
  response
37
37
  end
38
38
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Rodauth
4
4
  module OAuth
5
- VERSION = "1.3.1"
5
+ VERSION = "1.4.0"
6
6
  end
7
7
  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.3.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-06-27 00:00:00.000000000 Z
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