From 0edc780ec7b82bf7ec3583dac728c689435fb5e5 Mon Sep 17 00:00:00 2001 From: Patrick Gansterer Date: Fri, 22 Sep 2017 01:14:48 +0200 Subject: [PATCH] Add Discourse plugin --- Gemfile | 1 + Gemfile.lock | 8 +++ app/controllers/application_controller.rb | 11 ++++ app/controllers/sessions_controller.rb | 9 +--- plugins/discourse/README.md | 22 ++++++++ plugins/discourse/Rakefile | 40 ++++++++++++++ .../app/controllers/discourse_controller.rb | 52 +++++++++++++++++++ .../sessions/new/insert_link.html.haml.deface | 5 ++ plugins/discourse/config/locales/de.yml | 6 +++ plugins/discourse/config/locales/en.yml | 6 +++ plugins/discourse/config/routes.rb | 10 ++++ plugins/discourse/foodsoft_discourse.gemspec | 21 ++++++++ plugins/discourse/lib/foodsoft_discourse.rb | 8 +++ .../lib/foodsoft_discourse/engine.rb | 4 ++ .../foodsoft_discourse/redirect_to_login.rb | 23 ++++++++ .../lib/foodsoft_discourse/version.rb | 3 ++ 16 files changed, 221 insertions(+), 8 deletions(-) create mode 100644 plugins/discourse/README.md create mode 100644 plugins/discourse/Rakefile create mode 100644 plugins/discourse/app/controllers/discourse_controller.rb create mode 100644 plugins/discourse/app/overrides/sessions/new/insert_link.html.haml.deface create mode 100644 plugins/discourse/config/locales/de.yml create mode 100644 plugins/discourse/config/locales/en.yml create mode 100644 plugins/discourse/config/routes.rb create mode 100644 plugins/discourse/foodsoft_discourse.gemspec create mode 100644 plugins/discourse/lib/foodsoft_discourse.rb create mode 100644 plugins/discourse/lib/foodsoft_discourse/engine.rb create mode 100644 plugins/discourse/lib/foodsoft_discourse/redirect_to_login.rb create mode 100644 plugins/discourse/lib/foodsoft_discourse/version.rb diff --git a/Gemfile b/Gemfile index b067017d..9df1978f 100644 --- a/Gemfile +++ b/Gemfile @@ -53,6 +53,7 @@ gem 'ruby-filemagic' gem 'acts_as_versioned', git: 'https://github.com/technoweenie/acts_as_versioned.git' gem 'foodsoft_wiki', path: 'plugins/wiki' gem 'foodsoft_messages', path: 'plugins/messages' +gem 'foodsoft_discourse', path: 'plugins/discourse' # plugins not enabled by default #gem 'foodsoft_current_orders', path: 'plugins/current_orders' diff --git a/Gemfile.lock b/Gemfile.lock index 273e2bd0..a91e43cd 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -11,6 +11,13 @@ GIT acts_as_versioned (0.6.0) activerecord (>= 3.0.9) +PATH + remote: plugins/discourse + specs: + foodsoft_discourse (0.0.1) + deface (~> 1.0) + rails + PATH remote: plugins/messages specs: @@ -496,6 +503,7 @@ DEPENDENCIES exception_notification factory_girl_rails faker + foodsoft_discourse! foodsoft_messages! foodsoft_wiki! gaffe diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 054e3a56..187dce02 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -38,6 +38,17 @@ class ApplicationController < ActionController::Base session[:locale] = user.locale end + def login_and_redirect_to_return_to(user, *args) + login user + if session[:return_to].present? + redirect_to_url = session[:return_to] + session[:return_to] = nil + else + redirect_to_url = root_url + end + redirect_to redirect_to_url, *args + end + def logout session[:user_id] = nil session[:return_to] = nil diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index a612d957..10d7a2c6 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -10,14 +10,7 @@ class SessionsController < ApplicationController user = User.authenticate(params[:nick], params[:password]) if user user.update_attribute(:last_login, Time.now) - login user - if session[:return_to].present? - redirect_to_url = session[:return_to] - session[:return_to] = nil - else - redirect_to_url = root_url - end - redirect_to redirect_to_url, :notice => I18n.t('sessions.logged_in') + login_and_redirect_to_return_to user, :notice => I18n.t('sessions.logged_in') else flash.now.alert = I18n.t(FoodsoftConfig[:use_nick] ? 'sessions.login_invalid_nick' : 'sessions.login_invalid_email') render "new" diff --git a/plugins/discourse/README.md b/plugins/discourse/README.md new file mode 100644 index 00000000..4112bfc4 --- /dev/null +++ b/plugins/discourse/README.md @@ -0,0 +1,22 @@ +FoodsoftDiscourse +================= + +This plugin adds the possibility to log in via Discourse. A new button is added +to the login screen. + +This plugin is enabled by default in foodsoft, so you don't need to do anything +to install it. If you still want to, for example when it has been disabled, +add the following to foodsoft's Gemfile: + +```Gemfile +gem 'foodsoft_discourse', path: 'plugins/foodsoft_discourse' +``` + +This plugin introduces the foodcoop config option `discourse_url`, which takes +the URL fo the Discourse installation (e.g. `https://forum.example.com`) and the +config option `discourse_sso_secret`, which must be set to the same values as +configured in the `sso secret` setting of the Discourse installation. The plugin +will be disabled if not both config options are set. + +This plugin is part of the foodsoft package and uses the GPL-3 license (see +foodsoft's LICENSE for the full license text). diff --git a/plugins/discourse/Rakefile b/plugins/discourse/Rakefile new file mode 100644 index 00000000..7b1c2793 --- /dev/null +++ b/plugins/discourse/Rakefile @@ -0,0 +1,40 @@ +#!/usr/bin/env rake +begin + require 'bundler/setup' +rescue LoadError + puts 'You must `gem install bundler` and `bundle install` to run rake tasks' +end +begin + require 'rdoc/task' +rescue LoadError + require 'rdoc/rdoc' + require 'rake/rdoctask' + RDoc::Task = Rake::RDocTask +end + +RDoc::Task.new(:rdoc) do |rdoc| + rdoc.rdoc_dir = 'rdoc' + rdoc.title = 'FoodsoftDiscourse' + rdoc.options << '--line-numbers' + rdoc.rdoc_files.include('README.rdoc') + rdoc.rdoc_files.include('lib/**/*.rb') +end + +APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__) +load 'rails/tasks/engine.rake' + + + +Bundler::GemHelper.install_tasks + +require 'rake/testtask' + +Rake::TestTask.new(:test) do |t| + t.libs << 'lib' + t.libs << 'test' + t.pattern = 'test/**/*_test.rb' + t.verbose = false +end + + +task :default => :test diff --git a/plugins/discourse/app/controllers/discourse_controller.rb b/plugins/discourse/app/controllers/discourse_controller.rb new file mode 100644 index 00000000..037fed4c --- /dev/null +++ b/plugins/discourse/app/controllers/discourse_controller.rb @@ -0,0 +1,52 @@ +class DiscourseController < ApplicationController + + before_filter -> { require_plugin_enabled FoodsoftDiscourse } + skip_before_filter :authenticate + + def initiate + discourse_url = FoodsoftConfig[:discourse_url] + + nonce = SecureRandom.hex() + return_sso_url = url_for(action: :callback, only_path: false) + payload = "nonce=#{nonce}&return_sso_url=#{return_sso_url}" + base64_payload = Base64.encode64 payload + sso = URI.escape base64_payload + sig = get_hmac_hex_string base64_payload + + session[:discourse_sso_nonce] = nonce + redirect_to "#{discourse_url}/session/sso_provider?sso=#{sso}&sig=#{sig}" + end + + def callback + raise I18n.t('discourse.callback.invalid_signature') if get_hmac_hex_string(params[:sso]) != params[:sig] + + info = Rack::Utils.parse_query(Base64.decode64(params[:sso])) + info.symbolize_keys! + + raise I18n.t('discourse.callback.invalid_nonce') if info[:nonce] != session[:discourse_sso_nonce] + session[:discourse_sso_nonce] = nil + + id = info[:external_id].to_i + user = User.find_or_initialize_by(id: id) do |user| + user.id = id + user.password = SecureRandom.random_bytes(25) + end + user.nick = info[:username] + user.email = info[:email] + user.first_name = info[:name] + user.last_name = '' + user.last_login = Time.now + user.save! + + login_and_redirect_to_return_to user, :notice => I18n.t('discourse.callback.logged_in') + rescue => error + redirect_to login_url, :alert => error.to_s + end + + private + + def get_hmac_hex_string payload + discourse_sso_secret = FoodsoftConfig[:discourse_sso_secret] + OpenSSL::HMAC.hexdigest 'sha256', discourse_sso_secret, payload + end +end diff --git a/plugins/discourse/app/overrides/sessions/new/insert_link.html.haml.deface b/plugins/discourse/app/overrides/sessions/new/insert_link.html.haml.deface new file mode 100644 index 00000000..385db468 --- /dev/null +++ b/plugins/discourse/app/overrides/sessions/new/insert_link.html.haml.deface @@ -0,0 +1,5 @@ +/ insert_after 'noscript' +- if FoodsoftDiscourse.enabled? + .center + = link_to t('.discourse_login'), discourse_initiate_path, class: 'btn btn-primary' + %hr diff --git a/plugins/discourse/config/locales/de.yml b/plugins/discourse/config/locales/de.yml new file mode 100644 index 00000000..97e5455c --- /dev/null +++ b/plugins/discourse/config/locales/de.yml @@ -0,0 +1,6 @@ +de: + discourse: + callback: + invalid_nonce: Ungültige nonce + invalid_signature: Ungültige Signature + logged_in: Angemeldet! diff --git a/plugins/discourse/config/locales/en.yml b/plugins/discourse/config/locales/en.yml new file mode 100644 index 00000000..4288994e --- /dev/null +++ b/plugins/discourse/config/locales/en.yml @@ -0,0 +1,6 @@ +en: + discourse: + callback: + invalid_nonce: Invalid nonce + invalid_signature: Invalid signature + logged_in: Logged in! diff --git a/plugins/discourse/config/routes.rb b/plugins/discourse/config/routes.rb new file mode 100644 index 00000000..dde2ff15 --- /dev/null +++ b/plugins/discourse/config/routes.rb @@ -0,0 +1,10 @@ +Rails.application.routes.draw do + + scope '/:foodcoop' do + + get '/discourse/callback' => 'discourse#callback' + get '/discourse/initiate' => 'discourse#initiate' + + end + +end diff --git a/plugins/discourse/foodsoft_discourse.gemspec b/plugins/discourse/foodsoft_discourse.gemspec new file mode 100644 index 00000000..6d2fb5d4 --- /dev/null +++ b/plugins/discourse/foodsoft_discourse.gemspec @@ -0,0 +1,21 @@ +$:.push File.expand_path("../lib", __FILE__) + +# Maintain your gem's version: +require "foodsoft_discourse/version" + +# Describe your gem and declare its dependencies: +Gem::Specification.new do |s| + s.name = "foodsoft_discourse" + s.version = FoodsoftDiscourse::VERSION + s.authors = ["paroga"] + s.email = ["paroga@paroga.com"] + s.homepage = "https://github.com/foodcoops/foodsoft" + s.summary = "Discourse plugin for foodsoft." + s.description = "Allow SSO login via Discourse" + + s.files = Dir["{app,config,db,lib}/**/*"] + ["Rakefile", "README.md"] + s.test_files = Dir["test/**/*"] + + s.add_dependency "rails" + s.add_dependency "deface", "~> 1.0" +end diff --git a/plugins/discourse/lib/foodsoft_discourse.rb b/plugins/discourse/lib/foodsoft_discourse.rb new file mode 100644 index 00000000..c5790cfe --- /dev/null +++ b/plugins/discourse/lib/foodsoft_discourse.rb @@ -0,0 +1,8 @@ +require 'foodsoft_discourse/engine' +require 'foodsoft_discourse/redirect_to_login' + +module FoodsoftDiscourse + def self.enabled? + FoodsoftConfig[:discourse_url] && FoodsoftConfig[:discourse_sso_secret] + end +end diff --git a/plugins/discourse/lib/foodsoft_discourse/engine.rb b/plugins/discourse/lib/foodsoft_discourse/engine.rb new file mode 100644 index 00000000..c8966abb --- /dev/null +++ b/plugins/discourse/lib/foodsoft_discourse/engine.rb @@ -0,0 +1,4 @@ +module FoodsoftDiscourse + class Engine < ::Rails::Engine + end +end diff --git a/plugins/discourse/lib/foodsoft_discourse/redirect_to_login.rb b/plugins/discourse/lib/foodsoft_discourse/redirect_to_login.rb new file mode 100644 index 00000000..e54db131 --- /dev/null +++ b/plugins/discourse/lib/foodsoft_discourse/redirect_to_login.rb @@ -0,0 +1,23 @@ +module FoodsoftDiscourse + + module RedirectToLogin + def self.included(base) # :nodoc: + base.class_eval do + + alias orig_redirect_to_login redirect_to_login + + def redirect_to_login(options={}) + return orig_redirect_to_login(options) unless FoodsoftDiscourse.enabled? + redirect_to discourse_initiate_path + end + + end + end + end + +end + +# modify existing helper +ActiveSupport.on_load(:after_initialize) do + ApplicationController.send :include, FoodsoftDiscourse::RedirectToLogin +end diff --git a/plugins/discourse/lib/foodsoft_discourse/version.rb b/plugins/discourse/lib/foodsoft_discourse/version.rb new file mode 100644 index 00000000..2b8d4138 --- /dev/null +++ b/plugins/discourse/lib/foodsoft_discourse/version.rb @@ -0,0 +1,3 @@ +module FoodsoftDiscourse + VERSION = "0.0.1" +end