From 19bafe081f9a88cd2ae27e073bb21fe1af64dd28 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?R=C3=A2u=20Cao?=
Date: Mon, 29 May 2023 23:05:18 +0200
Subject: [PATCH] Integrate Discourse Connect (SSO)
---
.env.example | 2 +
Gemfile | 3 ++
Gemfile.lock | 11 +++++
app/controllers/discourse/sso_controller.rb | 17 +++++++
app/models/setting.rb | 6 +++
.../settings/services/_discourse.html.erb | 47 ++++++++++++++++---
.../admin/settings/services/index.html.erb | 6 +++
app/views/devise/sessions/new.html.erb | 8 +++-
config/routes.rb | 4 ++
9 files changed, 97 insertions(+), 7 deletions(-)
create mode 100644 app/controllers/discourse/sso_controller.rb
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
+
+ -
+ Set the Discourse Connect URL to the following URL:
+
+ -
+
+
+
+ -
+ Set the Discourse Connect Secret to the value above.
+
+ -
+ Enable Discourse Connect.
+
+ <% 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'