Add Discourse plugin
This commit is contained in:
parent
f8662c9184
commit
0edc780ec7
16 changed files with 221 additions and 8 deletions
1
Gemfile
1
Gemfile
|
@ -53,6 +53,7 @@ gem 'ruby-filemagic'
|
||||||
gem 'acts_as_versioned', git: 'https://github.com/technoweenie/acts_as_versioned.git'
|
gem 'acts_as_versioned', git: 'https://github.com/technoweenie/acts_as_versioned.git'
|
||||||
gem 'foodsoft_wiki', path: 'plugins/wiki'
|
gem 'foodsoft_wiki', path: 'plugins/wiki'
|
||||||
gem 'foodsoft_messages', path: 'plugins/messages'
|
gem 'foodsoft_messages', path: 'plugins/messages'
|
||||||
|
gem 'foodsoft_discourse', path: 'plugins/discourse'
|
||||||
|
|
||||||
# plugins not enabled by default
|
# plugins not enabled by default
|
||||||
#gem 'foodsoft_current_orders', path: 'plugins/current_orders'
|
#gem 'foodsoft_current_orders', path: 'plugins/current_orders'
|
||||||
|
|
|
@ -11,6 +11,13 @@ GIT
|
||||||
acts_as_versioned (0.6.0)
|
acts_as_versioned (0.6.0)
|
||||||
activerecord (>= 3.0.9)
|
activerecord (>= 3.0.9)
|
||||||
|
|
||||||
|
PATH
|
||||||
|
remote: plugins/discourse
|
||||||
|
specs:
|
||||||
|
foodsoft_discourse (0.0.1)
|
||||||
|
deface (~> 1.0)
|
||||||
|
rails
|
||||||
|
|
||||||
PATH
|
PATH
|
||||||
remote: plugins/messages
|
remote: plugins/messages
|
||||||
specs:
|
specs:
|
||||||
|
@ -496,6 +503,7 @@ DEPENDENCIES
|
||||||
exception_notification
|
exception_notification
|
||||||
factory_girl_rails
|
factory_girl_rails
|
||||||
faker
|
faker
|
||||||
|
foodsoft_discourse!
|
||||||
foodsoft_messages!
|
foodsoft_messages!
|
||||||
foodsoft_wiki!
|
foodsoft_wiki!
|
||||||
gaffe
|
gaffe
|
||||||
|
|
|
@ -38,6 +38,17 @@ class ApplicationController < ActionController::Base
|
||||||
session[:locale] = user.locale
|
session[:locale] = user.locale
|
||||||
end
|
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
|
def logout
|
||||||
session[:user_id] = nil
|
session[:user_id] = nil
|
||||||
session[:return_to] = nil
|
session[:return_to] = nil
|
||||||
|
|
|
@ -10,14 +10,7 @@ class SessionsController < ApplicationController
|
||||||
user = User.authenticate(params[:nick], params[:password])
|
user = User.authenticate(params[:nick], params[:password])
|
||||||
if user
|
if user
|
||||||
user.update_attribute(:last_login, Time.now)
|
user.update_attribute(:last_login, Time.now)
|
||||||
login user
|
login_and_redirect_to_return_to user, :notice => I18n.t('sessions.logged_in')
|
||||||
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')
|
|
||||||
else
|
else
|
||||||
flash.now.alert = I18n.t(FoodsoftConfig[:use_nick] ? 'sessions.login_invalid_nick' : 'sessions.login_invalid_email')
|
flash.now.alert = I18n.t(FoodsoftConfig[:use_nick] ? 'sessions.login_invalid_nick' : 'sessions.login_invalid_email')
|
||||||
render "new"
|
render "new"
|
||||||
|
|
22
plugins/discourse/README.md
Normal file
22
plugins/discourse/README.md
Normal file
|
@ -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).
|
40
plugins/discourse/Rakefile
Normal file
40
plugins/discourse/Rakefile
Normal file
|
@ -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
|
52
plugins/discourse/app/controllers/discourse_controller.rb
Normal file
52
plugins/discourse/app/controllers/discourse_controller.rb
Normal file
|
@ -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
|
|
@ -0,0 +1,5 @@
|
||||||
|
/ insert_after 'noscript'
|
||||||
|
- if FoodsoftDiscourse.enabled?
|
||||||
|
.center
|
||||||
|
= link_to t('.discourse_login'), discourse_initiate_path, class: 'btn btn-primary'
|
||||||
|
%hr
|
6
plugins/discourse/config/locales/de.yml
Normal file
6
plugins/discourse/config/locales/de.yml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
de:
|
||||||
|
discourse:
|
||||||
|
callback:
|
||||||
|
invalid_nonce: Ungültige nonce
|
||||||
|
invalid_signature: Ungültige Signature
|
||||||
|
logged_in: Angemeldet!
|
6
plugins/discourse/config/locales/en.yml
Normal file
6
plugins/discourse/config/locales/en.yml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
en:
|
||||||
|
discourse:
|
||||||
|
callback:
|
||||||
|
invalid_nonce: Invalid nonce
|
||||||
|
invalid_signature: Invalid signature
|
||||||
|
logged_in: Logged in!
|
10
plugins/discourse/config/routes.rb
Normal file
10
plugins/discourse/config/routes.rb
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
Rails.application.routes.draw do
|
||||||
|
|
||||||
|
scope '/:foodcoop' do
|
||||||
|
|
||||||
|
get '/discourse/callback' => 'discourse#callback'
|
||||||
|
get '/discourse/initiate' => 'discourse#initiate'
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
21
plugins/discourse/foodsoft_discourse.gemspec
Normal file
21
plugins/discourse/foodsoft_discourse.gemspec
Normal file
|
@ -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
|
8
plugins/discourse/lib/foodsoft_discourse.rb
Normal file
8
plugins/discourse/lib/foodsoft_discourse.rb
Normal file
|
@ -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
|
4
plugins/discourse/lib/foodsoft_discourse/engine.rb
Normal file
4
plugins/discourse/lib/foodsoft_discourse/engine.rb
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
module FoodsoftDiscourse
|
||||||
|
class Engine < ::Rails::Engine
|
||||||
|
end
|
||||||
|
end
|
|
@ -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
|
3
plugins/discourse/lib/foodsoft_discourse/version.rb
Normal file
3
plugins/discourse/lib/foodsoft_discourse/version.rb
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
module FoodsoftDiscourse
|
||||||
|
VERSION = "0.0.1"
|
||||||
|
end
|
Loading…
Reference in a new issue