Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ gem 'oai'
gem 'omniauth_openid_connect'
gem 'pg'
gem 'private_address_check'
gem 'public_suffix'
gem 'public_activity'
gem 'pundit'
gem 'rack-cors', require: 'rack/cors'
Expand Down
6 changes: 4 additions & 2 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,8 @@ GEM
loofah (2.25.2)
crass (~> 1.0.2)
nokogiri (>= 1.12.0)
mail (2.8.1)
mail (2.9.1)
logger
mini_mime (>= 0.1.1)
net-imap
net-pop
Expand Down Expand Up @@ -402,7 +403,7 @@ GEM
uri (>= 0.11.1)
net-http-persistent (4.0.8)
connection_pool (>= 2.2.4, < 4)
net-imap (0.6.4.1)
net-imap (0.6.6)
date
net-protocol
net-pop (0.1.2)
Expand Down Expand Up @@ -904,6 +905,7 @@ DEPENDENCIES
private_address_check
pry-byebug
public_activity
public_suffix
puma
pundit
rack-cors
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/concerns/space_redirect.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ module SpaceRedirect
private

def redirect_to_space(path, space)
if space&.is_subdomain?
if space&.valid_login_domain?(request.host)
port_part = ''
port_part = ":#{request.port}" if (request.protocol == "http://" && request.port != 80) ||
(request.protocol == "https://" && request.port != 443)
Expand Down
4 changes: 3 additions & 1 deletion app/controllers/orcid_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,12 @@ def callback

def set_oauth_client
config = Rails.application.config.secrets.orcid
redirect_uris = Array(config[:redirect_uri].presence || orcid_callback_url(host: TeSS::Config.base_uri.host))

@oauth2_client ||= Rack::OAuth2::Client.new(
identifier: config[:client_id],
secret: config[:secret],
redirect_uri: config[:redirect_uri].presence || orcid_callback_url(host: TeSS::Config.base_uri.host),
redirect_uri: TessOmniauthRedirectUris.resolve_for_host(redirect_uris, request.host),
authorization_endpoint: '/oauth/authorize',
token_endpoint: '/oauth/token',
host: config[:host].presence || (Rails.env.production? ? 'orcid.org' : 'sandbox.orcid.org')
Expand Down
15 changes: 7 additions & 8 deletions app/helpers/application_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -705,15 +705,14 @@ def theme_path
"themes/#{params[:theme_preview] || current_space&.theme || TeSS::Config.site['default_theme'] || 'default'}"
end

def omniauth_login_link(provider, config)
def omniauth_login_link(provider, config, html_options = {})
params = Space.current_space&.default? ? {} : { space_id: Space.current_space.id }
link_to(
t('authentication.omniauth.log_in_with',
provider: config.options[:label] ||
t("authentication.omniauth.providers.#{provider}", default: provider.to_s.titleize)),
omniauth_authorize_path('user', provider, **params),
method: :post
)
label = t('authentication.omniauth.log_in_with',
provider: config.options[:label] ||
t("authentication.omniauth.providers.#{provider}", default: provider.to_s.titleize))

link_to(capture { block_given? ? yield : label }, omniauth_authorize_path('user', provider, **params),
{ method: :post }.merge(html_options))
end

def per_page_options_for_select
Expand Down
22 changes: 21 additions & 1 deletion app/helpers/spaces_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,27 @@ def space_feature_options
end
end

def omniauth_providers_for_space(space = current_space)
host = space.try(:host) || TeSS::Config.base_uri.host

Devise.omniauth_configs.select do |_provider, config|
Array(config.options[:redirect_uris]).any? do |uri|
TessOmniauthRedirectUris.valid_login_domain?(URI.parse(uri).host, host)
end
end
end

def space_supports_omniauth?(space = current_space)
space.nil? || space.default? || space.is_subdomain?(TeSS::Config.base_uri.domain)
omniauth_providers_for_space(space).any?
end

def space_supports_orcid_auth?(space = current_space)
host = space.try(:host) || TeSS::Config.base_uri.host
config = Rails.application.config.secrets.orcid
redirect_uris = Array(config[:redirect_uri].presence || "#{TeSS::Config.base_url.chomp('/')}/orcid/callback")

redirect_uris.any? do |uri|
TessOmniauthRedirectUris.valid_login_domain?(URI.parse(uri).host, host)
end
end
end
12 changes: 6 additions & 6 deletions app/models/space.rb
Original file line number Diff line number Diff line change
Expand Up @@ -146,15 +146,15 @@ def enabled_features
(FEATURES - disabled_features)
end

# Checks whether this space's host is the given domain, or a subdomain of
# it.
# Checks whether a cookie set for the given domain can be read by this
# space's host.
#
# domain:: the domain to compare against; defaults to
# <tt>TeSS::Config.base_uri.domain</tt>.
# login_host:: the domain to compare against; defaults to
# <tt>TeSS::Config.base_uri.host</tt>.
#
# Returns:: +true+ or +false+.
def is_subdomain?(domain = TeSS::Config.base_uri.domain)
(host == domain || host.ends_with?(".#{domain}"))
def valid_login_domain?(login_host = TeSS::Config.base_uri.host)
TessOmniauthRedirectUris.valid_login_domain?(host, login_host)
end

# Equality by id: two Space instances are equal if they are both Space
Expand Down
10 changes: 5 additions & 5 deletions app/views/devise/sessions/_omniauth_options.html.erb
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
<% if devise_mapping.omniauthable? -%>
<% Devise.omniauth_configs.each do |provider, config| -%>
<%= link_to(omniauth_authorize_path(resource_name, provider), method: :post,
class: config.options[:logo] ? '' :
TeSS::Config.feature["login_through_oidc_only"] ? 'btn btn-default btn-lg btn-oidc-only' : 'btn btn-default') do %>
<% omniauth_providers_for_space.each do |provider, config| -%>
<%= omniauth_login_link(provider, config,
class: config.options[:logo] ? '' :
TeSS::Config.feature["login_through_oidc_only"] ? 'btn btn-default btn-lg btn-oidc-only' : 'btn btn-default') do %>
<% if config.options[:logo].present? %>
<%= image_tag(config.options[:logo], class: "omniauth-logo omniauth-#{provider}") -%>
<% else %>
<%= t('authentication.omniauth.log_in_with',
provider: config.options[:label] || t("authentication.omniauth.providers.#{provider}", default: provider.to_s.titleize)) -%>
<% end %>
<% end -%>
<% end %>
<% end -%>
<% end -%>
2 changes: 1 addition & 1 deletion app/views/devise/sessions/new.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<% end %>
</div>
<div class="form-middle">
<% if resource_class.omniauth_providers.any? && devise_mapping.omniauthable? %>
<% if space_supports_omniauth? && devise_mapping.omniauthable? %>
<div class="h4"><%= t('authentication.omniauth.title') %></div>
<p><%= t('authentication.omniauth.description') %></p>
<%= render partial: 'devise/sessions/omniauth_options' %>
Expand Down
6 changes: 3 additions & 3 deletions app/views/layouts/_login_menu.html.erb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<% if TeSS::Config.feature["login_through_oidc_only"] && Devise.omniauth_configs.size == 1 %>
<% if TeSS::Config.feature["login_through_oidc_only"] && omniauth_providers_for_space.size == 1 %>
<li>
<% provider, config = Devise.omniauth_configs.first %>
<% provider, config = omniauth_providers_for_space.first %>
<%= omniauth_login_link(provider, config) %>
</li>
<% else %>
Expand All @@ -10,7 +10,7 @@
</a>
<ul class="dropdown-menu dropdown-menu-right">
<% if space_supports_omniauth? %>
<% Devise.omniauth_configs.each do |provider, config| -%>
<% omniauth_providers_for_space.each do |provider, config| -%>
<li class="dropdown-item"><%= omniauth_login_link(provider, config) %></li>
<% end %>
<% end %>
Expand Down
2 changes: 1 addition & 1 deletion app/views/users/show.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
<% if TeSS::Config.orcid_authentication_enabled? &&
current_user == @user &&
!@user.profile.orcid_authenticated? &&
space_supports_omniauth? %>
space_supports_orcid_auth? %>
<%= button_to t(@user.profile.orcid.blank? ? 'orcid.link' : 'orcid.authenticate'), authenticate_orcid_path, class: 'btn btn-default' %>
<% end %>
</p>
Expand Down
39 changes: 39 additions & 0 deletions config/initializers/omniauth.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,42 @@
module TessOmniauthRedirectUris
module_function

def valid_login_domain?(space_host, login_host)
return false if space_host.blank? || login_host.blank?

own_domain = PublicSuffix.domain(space_host)
target_domain = PublicSuffix.domain(login_host)
Comment thread
eilmiv marked this conversation as resolved.

return space_host == login_host if own_domain.nil? || target_domain.nil?

own_domain == target_domain
end

def resolve_for_host(redirect_uris, host)
uris = Array(redirect_uris)
uris.find { |uri| valid_login_domain?(URI.parse(uri).host, host) } || uris.first
end
end

module OmniAuth
module Strategies
class HostRedirectOpenIDConnect < OpenIDConnect
option :redirect_uris, []

def redirect_uri
resolved_uri = TessOmniauthRedirectUris.resolve_for_host(options[:redirect_uris], request&.host)
Comment thread
eilmiv marked this conversation as resolved.

return super unless resolved_uri.present?

# preserve params['redirect_uri'] behavior from OpenIDConnect strategy
return resolved_uri unless params['redirect_uri']

"#{resolved_uri}?redirect_uri=#{CGI.escape(params['redirect_uri'])}"
end
end
end
end

Rails.application.config.middleware.use OmniAuth::Builder do
OmniAuth.config.allowed_request_methods = [:post]

Expand Down
11 changes: 9 additions & 2 deletions config/initializers/omniauth/ls_login.rb
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
# OpenID Connect configuration for LS Login nee Elixir AAI
unless Rails.application.config.secrets.dig(:elixir_aai, :client_id).blank?
redirect_uris = Array(
Rails.application.config.secrets.elixir_aai[:redirect_uri].presence ||
# Url helper user_elixir_aai_omniauth_callback_url can not be used here
"#{TeSS::Config.base_url.chomp('/')}/users/auth/elixir_aai/callback"
)

Devise.omniauth :openid_connect, {
logo: 'ls-login.png',
name: :elixir_aai,
scope: [:openid, :email, :profile],
response_type: :code,
issuer: 'https://login.aai.lifescience-ri.eu/oidc/',
discovery: true,
strategy_class: OmniAuth::Strategies::HostRedirectOpenIDConnect,
redirect_uris: redirect_uris,
client_options: {
identifier: Rails.application.config.secrets.elixir_aai[:client_id],
secret: Rails.application.config.secrets.elixir_aai[:secret],
# Wish I could use the url helper for this! (user_elixir_aai_omniauth_callback_url)
redirect_uri: "#{TeSS::Config.base_url.chomp('/')}/users/auth/elixir_aai/callback",
redirect_uri: redirect_uris.first,
}
}
end
11 changes: 8 additions & 3 deletions config/initializers/omniauth/oidc.rb
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
# OpenID Connect configuration
unless Rails.application.config.secrets.dig(:oidc, :client_id).blank?
redirect_uris = Array(
Rails.application.config.secrets.oidc[:redirect_uri].presence ||
"#{TeSS::Config.base_url.chomp('/')}/users/auth/oidc/callback"
)

Devise.omniauth :openid_connect, {
name: :oidc,
label: Rails.application.config.secrets.oidc[:label],
logo: Rails.application.config.secrets.oidc[:logo],
issuer: Rails.application.config.secrets.oidc[:issuer],
strategy_class: OmniAuth::Strategies::OpenIDConnect,
strategy_class: OmniAuth::Strategies::HostRedirectOpenIDConnect,
scope: [:openid, :email, :profile],
response_type: :code,
discovery: true,
redirect_uris: redirect_uris,
client_options: {
identifier: Rails.application.config.secrets.oidc[:client_id],
secret: Rails.application.config.secrets.oidc[:secret],
redirect_uri: Rails.application.config.secrets.oidc[:redirect_uri].presence ||
"#{TeSS::Config.base_url.chomp('/')}/users/auth/oidc/callback"
redirect_uri: redirect_uris.first
}
}
end
11 changes: 8 additions & 3 deletions config/initializers/omniauth/oidc2.rb
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
# Secondary OpenID Connect configuration
unless Rails.application.config.secrets.dig(:oidc2, :client_id).blank?
redirect_uris = Array(
Rails.application.config.secrets.oidc2[:redirect_uri].presence ||
"#{TeSS::Config.base_url.chomp('/')}/users/auth/oidc2/callback"
)

Devise.omniauth :openid_connect, {
name: :oidc2,
label: Rails.application.config.secrets.oidc2[:label],
logo: Rails.application.config.secrets.oidc2[:logo],
issuer: Rails.application.config.secrets.oidc2[:issuer],
strategy_class: OmniAuth::Strategies::OpenIDConnect,
strategy_class: OmniAuth::Strategies::HostRedirectOpenIDConnect,
scope: [:openid, :email, :profile],
response_type: :code,
discovery: true,
redirect_uris: redirect_uris,
client_options: {
identifier: Rails.application.config.secrets.oidc2[:client_id],
secret: Rails.application.config.secrets.oidc2[:secret],
redirect_uri: Rails.application.config.secrets.oidc2[:redirect_uri].presence ||
"#{TeSS::Config.base_url.chomp('/')}/users/auth/oidc2/callback"
redirect_uri: redirect_uris.first
}
}
end
22 changes: 21 additions & 1 deletion config/initializers/session_store.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,26 @@
require 'ipaddr'
require 'public_suffix'

# Be sure to restart your server when you modify this file.
opts = {
domain: :all
domain: lambda do |request|
host = request&.host.to_s.strip.downcase

next nil if host.blank? || !host.include?('.')

begin
IPAddr.new(host)
next nil
rescue IPAddr::InvalidAddressError
nil
end

begin
PublicSuffix.domain(host)
rescue PublicSuffix::Error
nil
end
end
}

if Rails.env.production?
Expand Down
4 changes: 4 additions & 0 deletions config/secrets.example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,20 @@ external_api_keys: &external_api_keys
elixir_aai: # LS-Login authentication config
client_id: # client id
secret: # secret
redirect_uri: # either one URL or a YAML list of URLs, e.g. https://<host>/users/auth/elixir_aai/callback
oidc: # OpenID Connect authentication config
label: # name of auth provider to display to users e.g. AAF (Australia)
logo: # optional path to logo to display to users under app/assets/images e.g. dresa/aaf_service_223x54.png
client_id: # client id
secret: # secret
redirect_uri: # either one URL or a YAML list of URLs, e.g. https://<host>/users/auth/oidc/callback
issuer: # issuer uri e.g. https://central.test.aaf.edu.au
oidc2: # Secondary OpenID Connect authentication config
label: # name of auth provider to display to users e.g. Tuakiri (NZ)
logo: # optional path to logo to display to users under app/assets/images
client_id: # client id
secret: # secret
redirect_uri: # either one URL or a YAML list of URLs, e.g. https://<host>/users/auth/oidc2/callback
issuer: # issuer uri e.g. https://openidconnect.test.tuakiri.ac.nz
bioportal_api_key:
fairsharing:
Expand All @@ -42,6 +45,7 @@ external_api_keys: &external_api_keys
orcid:
client_id:
secret:
redirect_uri: # either one URL or a YAML list of URLs, e.g. https://<host>/orcid/callback
host:

#Internal config
Expand Down
Loading