diff --git a/.env.example b/.env.example index 155ec8a..de85a10 100644 --- a/.env.example +++ b/.env.example @@ -19,6 +19,8 @@ LDAP_SUFFIX='dc=kosmos,dc=org' WEBHOOKS_ALLOWED_IPS='10.1.1.163' DISCOURSE_PUBLIC_URL='https://community.kosmos.org' +DISCOURSE_CONNECT_SECRET='discourse_connect_ftw' + GITEA_PUBLIC_URL='https://gitea.kosmos.org' MASTODON_PUBLIC_URL='https://kosmos.social' MEDIAWIKI_PUBLIC_URL='https://wiki.kosmos.org' diff --git a/Gemfile b/Gemfile index 3445c32..dad66b1 100644 --- a/Gemfile +++ b/Gemfile @@ -51,6 +51,9 @@ gem 'faraday' gem 'sidekiq', '< 7' gem 'sidekiq-scheduler' +# Service integrations +gem 'discourse_api' + # Monitoring gem "sentry-ruby" gem "sentry-rails" diff --git a/Gemfile.lock b/Gemfile.lock index 9a37e04..6b33fa8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -108,6 +108,11 @@ GEM devise (>= 3.4.1) net-ldap (>= 0.16.0) diff-lcs (1.5.0) + discourse_api (2.0.0) + faraday (~> 2.7) + faraday-follow_redirects + faraday-multipart + rack (>= 1.6) dotenv (2.8.1) dotenv-rails (2.8.1) dotenv (= 2.8.1) @@ -126,6 +131,10 @@ GEM faraday (2.7.1) faraday-net_http (>= 2.0, < 3.1) ruby2_keywords (>= 0.0.4) + faraday-follow_redirects (0.3.0) + faraday (>= 1, < 3) + faraday-multipart (1.0.4) + multipart-post (~> 2) faraday-net_http (3.0.2) ffi (1.15.5) flipper (0.28.0) @@ -183,6 +192,7 @@ GEM mini_mime (1.1.2) mini_portile2 (2.8.0) minitest (5.16.3) + multipart-post (2.3.0) net-imap (0.3.1) net-protocol net-ldap (0.17.1) @@ -386,6 +396,7 @@ DEPENDENCIES database_cleaner devise (~> 4.9.0) devise_ldap_authenticatable + discourse_api dotenv-rails factory_bot_rails faker diff --git a/app/controllers/discourse/sso_controller.rb b/app/controllers/discourse/sso_controller.rb new file mode 100644 index 0000000..658f434 --- /dev/null +++ b/app/controllers/discourse/sso_controller.rb @@ -0,0 +1,17 @@ +class Discourse::SsoController < ApplicationController + before_action :authenticate_user! + + def connect + secret = Setting.discourse_connect_secret + sso = DiscourseApi::SingleSignOn.parse(request.query_string, secret) + sso.external_id = current_user.id + sso.email = current_user.email + sso.username = current_user.cn + sso.name = current_user.display_name + sso.admin = current_user.is_admin? + sso.sso_secret = secret + + redirect_to sso.to_url("#{Setting.discourse_public_url}/session/sso_login"), + allow_other_host: true + end +end diff --git a/app/models/setting.rb b/app/models/setting.rb index 2cbc615..d25da65 100644 --- a/app/models/setting.rb +++ b/app/models/setting.rb @@ -2,6 +2,9 @@ class Setting < RailsSettings::Base cache_prefix { "v1" } + field :accounts_domain, type: :string, + default: ENV["AKKOUNTS_DOMAIN"].presence + # # Internal services # @@ -41,6 +44,9 @@ class Setting < RailsSettings::Base field :discourse_enabled, type: :boolean, default: (ENV["DISCOURSE_PUBLIC_URL"].present?.to_s || false) + field :discourse_connect_secret, type: :string, readonly: true, + default: ENV["DISCOURSE_CONNECT_SECRET"].presence + # # ejabberd # diff --git a/app/views/admin/settings/services/_discourse.html.erb b/app/views/admin/settings/services/_discourse.html.erb index 498dd5f..6af5525 100644 --- a/app/views/admin/settings/services/_discourse.html.erb +++ b/app/views/admin/settings/services/_discourse.html.erb @@ -7,11 +7,46 @@ title: "Enable Discourse integration", description: "Discourse configuration present and features enabled" ) %> - <% if Setting.discourse_enabled? %> - <%= render FormElements::FieldsetComponent.new(title: "Public URL") do %> - <%= f.text_field :discourse_public_url, - value: Setting.discourse_public_url, - class: "w-full", disabled: true %> - <% end %> +<% if Setting.discourse_enabled? %> + <%= render FormElements::FieldsetComponent.new(title: "Public URL") do %> + <%= f.text_field :discourse_public_url, + value: Setting.discourse_public_url, + class: "w-full", disabled: true %> <% end %> + <%= render FormElements::FieldsetComponent.new(title: "Connect secret") do %> + <%= f.password_field :discourse_connect_secret, + value: Setting.discourse_connect_secret, + class: "w-full", disabled: true %> + <% end %> +<% end %> +<% if Setting.discourse_enabled? %> + <% content_for :documentation do %> +

How to configure Discourse

+
    +
  1. + Set the Discourse Connect URL to the following URL: +
  2. +
  3. + + +
  4. +
  5. + Set the Discourse Connect Secret to the value above. +
  6. +
  7. + Enable Discourse Connect. +
  8. + <% end %> +<% end %> diff --git a/app/views/admin/settings/services/index.html.erb b/app/views/admin/settings/services/index.html.erb index 58360dd..f63653d 100644 --- a/app/views/admin/settings/services/index.html.erb +++ b/app/views/admin/settings/services/index.html.erb @@ -20,4 +20,10 @@

    <% end %> + + <% if content_for?(:documentation) %> +
    + <%= yield :documentation %> +
    + <% end %> <% end %> diff --git a/app/views/devise/sessions/new.html.erb b/app/views/devise/sessions/new.html.erb index f7c0a70..aa964fb 100644 --- a/app/views/devise/sessions/new.html.erb +++ b/app/views/devise/sessions/new.html.erb @@ -1,7 +1,13 @@ +<% + # TODO remove when https://github.com/hotwired/turbo/issues/203 is fixed + enable_turbo = !session[:user_return_to].match?('/discourse/connect') +%> + <%= render HeaderCompactComponent.new(title: "Log in") %> <%= render MainCompactComponent.new do %> - <%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %> + <%= form_for(resource, as: resource_name, url: session_path(resource_name), + data: { turbo: enable_turbo.to_s }) do |f| %> <%= render "devise/shared/error_messages", resource: resource %>
    <%= f.label :cn, 'User', class: 'block mb-2 font-bold' %> diff --git a/config/routes.rb b/config/routes.rb index aa7fac3..89986a3 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -63,6 +63,10 @@ Rails.application.routes.draw do get ".well-known/webfinger" => "webfinger#show" + namespace :discourse do + get "connect", to: 'sso#connect' + end + authenticate :user, ->(user) { user.is_admin? } do mount Sidekiq::Web => '/sidekiq' mount Flipper::UI.app(Flipper) => '/flipper'