# frozen_string_literal: true
# name: discourse-development-auth
# about: A fake authentication provider for development puposes only
# version: 1.0
# authors: David Taylor
# url: https://github.com/discourse/discourse-development-auth
raise "discourse-development-auth is highly insecure and should not be installed in production" if Rails.env.production?
PLUGIN_NAME = "discourse-development-auth"
module ::OmniAuth
module Strategies
class Development
include ::OmniAuth::Strategy
FIELDS = %w{
uid
name
email
email_verified
nickname
first_name
last_name
location
description
image
}
COOKIE = "development-auth-defaults"
def request_phase
return unless is_allowed?
if (env['REQUEST_METHOD'] == 'POST') && (request.params['uid'])
data = request.params.slice(*FIELDS)
r = Rack::Response.new
r.set_cookie(COOKIE, { value: data.to_json, path: "/", expires: 1.month.from_now })
uri = URI.parse(callback_path)
uri.query = URI.encode_www_form(data)
r.redirect(uri)
return r.finish
end
build_form.to_response
end
def build_form
token = begin
verifier = CSRFTokenVerifier.new
verifier.call(env)
verifier.form_authenticity_token
end
request = Rack::Request.new(env)
raw_defaults = request.cookies[COOKIE] || "{}"
defaults = JSON.parse(raw_defaults) rescue {}
defaults["uid"] = SecureRandom.hex(8) unless defaults["uid"].present?
defaults["email_verified"] = "true" unless defaults["email_verified"].present?
OmniAuth::Form.build(title: "Fake Authentication Provider") do
html "\n"
FIELDS.each do |f|
label_field(f, f)
if f == "email_verified"
html ""
else
html ""
end
end
end
end
def callback_phase
return unless is_allowed?
super
end
def auth_hash
info = request.params.slice(*FIELDS)
uid = info.delete("uid")
email_verified = (info.delete("email_verified") == "true")
OmniAuth::Utils.deep_merge(super, {
'uid' => uid,
'info' => info,
'extra' => { "raw_info" => { "email_verified" => email_verified } }
})
end
def is_allowed?
return true if DiscourseDev.config.allow_anonymous_to_impersonate
fail!("Enable `allow_anonymous_to_impersonate` setting in `config/dev.yml` file.")
false
end
end
end
end
class DevelopmentAuthenticator < Auth::ManagedAuthenticator
def name
'developmentauth'
end
def can_revoke?
true
end
def can_connect_existing_user?
true
end
def enabled?
DiscourseDev.auth_plugin_enabled?
end
def register_middleware(omniauth)
omniauth.provider :development, name: :developmentauth
end
def primary_email_verified?(auth)
auth['extra']['raw_info']['email_verified']
end
end
auth_provider authenticator: DevelopmentAuthenticator.new
### DiscourseConnect
after_initialize do
module ::DevelopmentAuth
class Engine < ::Rails::Engine
engine_name PLUGIN_NAME
isolate_namespace ::DevelopmentAuth
end
end
class ::DevelopmentAuth::FakeDiscourseConnectController < ::ApplicationController
requires_plugin "discourse-development-auth"
skip_before_action :check_xhr, :preload_json, :redirect_to_login_if_required, :verify_authenticity_token
SIMPLE_FIELDS = %w{
external_id
email
username
name
}
ADVANCED_FIELDS = SingleSignOn::ACCESSORS.map(&:to_s) - SIMPLE_FIELDS
FIELDS = SIMPLE_FIELDS + ADVANCED_FIELDS
BOOLS = SingleSignOn::BOOLS.map(&:to_s)
COOKIE = "development-auth-discourseconnect-defaults"
def auth
return unless is_allowed?
params.require(:sso)
@payload = request.query_string
sso = SingleSignOn.parse(@payload, SiteSetting.discourse_connect_secret)
if request.method == "POST" && params[:external_id]
data = {}
FIELDS.each do |f|
sso.send(:"#{f}=", params[f])
data[f] = params[f]
cookies[COOKIE] = { value: data.to_json, path: "/", expires: 1.month.from_now }
end
return redirect_to sso.to_url(sso.return_sso_url)
end
raw_defaults = cookies[COOKIE] || "{}"
@defaults = JSON.parse(raw_defaults) rescue {}
@defaults["return_sso_url"] = sso.return_sso_url
@defaults["nonce"] = sso.nonce
@defaults["external_id"] = SecureRandom.hex(8) unless @defaults["external_id"].present?
render_form
end
private
def render_form
@simple_fields = SIMPLE_FIELDS
@advanced_fields = ADVANCED_FIELDS
@bools = BOOLS
append_view_path(File.expand_path("../app/views", __FILE__))
render template: "fake_discourse_connect/form", layout: false
end
end
DevelopmentAuth::Engine.routes.draw do
get "/fake-discourse-connect" => "fake_discourse_connect#auth"
post "/fake-discourse-connect" => "fake_discourse_connect#auth"
end
Discourse::Application.routes.append do
mount ::DevelopmentAuth::Engine, at: "/development-auth"
end
DiscourseSingleSignOn.singleton_class.prepend(Module.new do
def sso_url
if DiscourseDev.auth_plugin_enabled?
return "#{Discourse.base_path}/development-auth/fake-discourse-connect"
end
super
end
end)
EnableSsoValidator.prepend(Module.new do
def valid_value?(val)
return true if DiscourseDev.auth_plugin_enabled?
super
end
end)
end