aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorhellekin <hellekin@cepheide.org>2020-10-05 14:39:03 +0200
committerhellekin <hellekin@cepheide.org>2020-10-05 14:39:03 +0200
commit1d53b3c26f4167be4e19e508c96a617d79c67363 (patch)
treea8eacd792ef620e013ca46dec3d890286b944f68
parent270620682c1a3c006af4f9d15f9be58537d4d9ee (diff)
downloadincommon-map-1d53b3c26f4167be4e19e508c96a617d79c67363.tar.gz
Add Discourse SSO code
-rw-r--r--app/lib/sso.rb7
-rw-r--r--app/lib/sso/from_discourse.rb92
-rw-r--r--config/credentials.yml.enc2
-rw-r--r--config/initializers/sso_config.rb16
-rw-r--r--config/routes.rb3
5 files changed, 119 insertions, 1 deletions
diff --git a/app/lib/sso.rb b/app/lib/sso.rb
new file mode 100644
index 0000000..ace2830
--- /dev/null
+++ b/app/lib/sso.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+# Perform Single Sign-On using Discourse
+module SSO
+ require 'securerandom'
+ require_relative '../../config/initializers/sso_config'
+end
diff --git a/app/lib/sso/from_discourse.rb b/app/lib/sso/from_discourse.rb
new file mode 100644
index 0000000..66742e2
--- /dev/null
+++ b/app/lib/sso/from_discourse.rb
@@ -0,0 +1,92 @@
+# frozen_string_literal: true
+
+module SSO
+ class FromDiscourse
+ attr_accessor :nonce, :token, :user_info, :status
+
+ class << self
+ # See config/initializers/sso.rb
+ # This is a hash:
+ # SSO::FromDiscourse.config = {
+ # sso_url: 'https://talk.incommon.cc/session/sso_provider',
+ # return_url: "#{API_ROOT_URL}/my/account",
+ # sso_secret: Rails.application.credentials.sso_secret,
+ # }
+ # In config/routes.rb:
+ # ...
+ # get 'my/account/:token' => 'authentications#sso_login'
+ attr_accessor :config
+ end
+
+ def initialize(options = {})
+ @nonce = options[:nonce] || SecureRandom.hex
+ @token = options[:token] || SecureRandom.hex
+ @user_info = nil
+ @status = :unauthorized
+ end
+
+ def parse(params)
+ params[:sig] =~ %r{\A[a-f0-9]{64}\z} || raise(ArgumentError, 'HMAC invalid')
+ hmac_matches?(params[:sso], params[:sig]) || raise(ArgumentError, 'HMAC mismatch')
+ base64?(params[:sso]) || raise(ArgumentError, 'Encoding invalid')
+
+ response = Rack::Utils.parse_query(Base64.decode64(params[:sso]))
+
+ # Of course, the nonces must match!
+ ActiveSupport::SecurityUtils
+ .fixed_length_secure_compare(response['nonce'], @nonce) ||
+ raise(ArgumentError, 'Nonce mismatch')
+
+ response.delete('nonce')
+
+ @user_info = response.symbolize_keys
+ @status = :ok
+
+ self
+ end
+
+ def request_uri
+ format('%<url>s?sso=%<payload>s&sig=%<hmac>s',
+ url: config[:sso_url].to_s,
+ payload: Rack::Utils.escape(b64_payload),
+ hmac: mac_signature)
+ end
+
+ def success?
+ @status == :ok
+ end
+
+ private
+
+ def config
+ self.class.config
+ end
+
+ # Estimate whether given data is encoded in Base64.
+ # This is not fool-proof but good enough for our purpose.
+ def base64?(data)
+ data.is_a?(String) &&
+ (data.length % 4).zero? &&
+ data.match?(%r{\A[a-zA-Z\d+/]+={,2}\z})
+ end
+
+ def hmac_matches?(payload, signature)
+ hmac = mac_signature(payload)
+ ActiveSupport::SecurityUtils.fixed_length_secure_compare(hmac, signature)
+ end
+
+ def payload
+ format('nonce=%<n>s&return_sso_url=%<u>s',
+ n: @nonce,
+ u: "#{config[:return_url]}/#{token}")
+ end
+
+ def b64_payload
+ Base64.encode64(payload)
+ end
+
+ def mac_signature(payload = b64_payload)
+ OpenSSL::HMAC.hexdigest('SHA256', self.class.config[:sso_secret], payload)
+ end
+ end
+end
diff --git a/config/credentials.yml.enc b/config/credentials.yml.enc
index fd8449c..d9d6416 100644
--- a/config/credentials.yml.enc
+++ b/config/credentials.yml.enc
@@ -1 +1 @@
-eWfeBbVzMevHyb6V/ejx3F0e92OMzwE2lES56Etzwpjw6ylGt+GEk+cmBAyL+Oy9cN5xITEHItX4U7Ssy3oLm3OFJGy1TExJqVkETWfkN4RxI5H1xZLfG99smhiAmAJT20Pq4adWavm8m7Hw1M8IAd+J5Mpzq0vynkrSvw9Xpto4WzD9I5yfxId9Qj3SkN17gYoYToljVl13uUfb3uYyDoikbt8lkxIHX+4ZDkOi3qIdOf8rMoiHFHH9LKoSwfib7Frzra4tPqFSgK3nnn9rQxaZYp88E0EzaskHYT70+J/Sx3upGq4S/4MbStAIrxXj11iPRQHYVdUTCTW50DILOKUeqWDD/ayRTqCIyL3z3AioPu0oeIBGv984t6TansDTkJQlmQJiZqqfcQk2UopH/Zu8NssnC2BdoIdS--KbZxqXMAkn6TQ3wH--V2i2mT97pBJ0ci1VuYCVfQ== \ No newline at end of file
+nlpLLu6lgECvrsn7X/wEVg6LDrflx6pMqXovDczQP3+QDBX2XVDk4m+F3c3sWxipglV3iou1yJeuGBcXuVJKJ8QjjDRK3sTLMcYbfT4Ez/OMD4Y4QVF6hnLE1B6nVt/wSjG6l7tcczcCAgZ6HnBbK8+4A2OLfO0xDqkpujL79XaW6B5oPc4j+0B7hZylHqiGW4mx/t/qXvwVvRLQbjGH11jfrBiW6JLqx+6KuYzJmPDvtLhsPYUxLczltEca/mAgGMc0iYxAN7IRk1p0V9sYahgBId0P9/GegUz5TUvaJY2kEcXe0vAHsqN+b2XHu28cCPoa4x0NoWUZVCl+a6MoH1giC9ZPvoQ5DGVcHnyvqO6rF/KLtb+JRACMhMbNIrmbSlr8Mr5SVZrlb3reY1+AVv+im35RuxVfv0hlkfsfMcQ+o1UB/Iq+xmeSBBG/SYOs8S31CK2F2XRtQXIeQG2C53+n9/VCVmM6FGnPO561x6hYF4tocw==--zKXfSMQvDShHEQGq--Bn0S/jeIjlwvbq7aMDSPgQ== \ No newline at end of file
diff --git a/config/initializers/sso_config.rb b/config/initializers/sso_config.rb
new file mode 100644
index 0000000..b3f23f7
--- /dev/null
+++ b/config/initializers/sso_config.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+# See lib/sso/from_discourse.rb
+# module SSO
+# class FromDiscourse
+# class << self
+# attr_accessor :config
+# end
+# end
+# end
+
+SSO::FromDiscourse.config = {
+ sso_url: 'https://talk.incommon.cc/session/sso_provider',
+ return_url: "http://localhost:3000/authenticate",
+ sso_secret: Rails.application.credentials.sso_secret,
+}
diff --git a/config/routes.rb b/config/routes.rb
index c06383a..a2260d6 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -1,3 +1,6 @@
Rails.application.routes.draw do
# For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
+
+ # Discourse SSO
+ get 'my/account/:token', to: 'authentication#login'
end