diff options
author | hellekin <hellekin@cepheide.org> | 2020-10-05 14:39:03 +0200 |
---|---|---|
committer | hellekin <hellekin@cepheide.org> | 2020-10-05 14:39:03 +0200 |
commit | 1d53b3c26f4167be4e19e508c96a617d79c67363 (patch) | |
tree | a8eacd792ef620e013ca46dec3d890286b944f68 /app/lib/sso | |
parent | 270620682c1a3c006af4f9d15f9be58537d4d9ee (diff) | |
download | incommon-map-1d53b3c26f4167be4e19e508c96a617d79c67363.tar.gz |
Add Discourse SSO code
Diffstat (limited to 'app/lib/sso')
-rw-r--r-- | app/lib/sso/from_discourse.rb | 92 |
1 files changed, 92 insertions, 0 deletions
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 |