diff --git a/app/views/users/omniauth_callbacks/complete.html.erb b/app/views/users/omniauth_callbacks/complete.html.erb index 98a3d9dec9d..08257bb3803 100644 --- a/app/views/users/omniauth_callbacks/complete.html.erb +++ b/app/views/users/omniauth_callbacks/complete.html.erb @@ -2,7 +2,7 @@
diff --git a/config/initializers/omniauth.rb b/config/initializers/omniauth.rb index d6d2b2cad52..0d1eedc3d56 100644 --- a/config/initializers/omniauth.rb +++ b/config/initializers/omniauth.rb @@ -33,6 +33,8 @@ Rails.application.config.middleware.use OmniAuth::Builder do :name => p.name, :require => "omniauth-oauth2" }.merge(p.options) + else + provider p.type, *p.options end end diff --git a/lib/auth_provider.rb b/lib/auth_provider.rb deleted file mode 100644 index dc8c9524006..00000000000 --- a/lib/auth_provider.rb +++ /dev/null @@ -1,4 +0,0 @@ -class AuthProvider - attr_accessor :type, :glyph, :background_color, :name, :title, - :message, :frame_width, :frame_height, :options -end diff --git a/lib/discourse.rb b/lib/discourse.rb index 145a15bd1c5..77b4f5fcf99 100644 --- a/lib/discourse.rb +++ b/lib/discourse.rb @@ -1,4 +1,5 @@ require 'cache' +require_dependency 'plugin/instance' module Discourse @@ -24,7 +25,7 @@ module Discourse class CSRF < Exception; end def self.activate_plugins! - @plugins = Plugin.find_all("#{Rails.root}/plugins") + @plugins = Plugin::Instance.find_all("#{Rails.root}/plugins") @plugins.each do |plugin| plugin.activate! end diff --git a/lib/plugin.rb b/lib/plugin.rb index 75c6e4b743f..e69de29bb2d 100644 --- a/lib/plugin.rb +++ b/lib/plugin.rb @@ -1,192 +0,0 @@ -require_dependency 'auth_provider' -require 'digest/sha1' -require 'fileutils' - -class Plugin - - METADATA = [:name, :about, :version, :authors] - - attr_accessor :path - attr_accessor *METADATA - attr_reader :auth_providers - attr_reader :assets - - def self.find_all(parent_path) - plugins = [] - Dir["#{parent_path}/**/plugin.rb"].each do |path| - plugin = parse(File.read(path)) - plugin.path = path - plugins << plugin - end - - plugins - end - - def self.parse(text) - plugin = self.new - - text.each_line do |line| - break unless plugin.parse_line(line) - end - - plugin - end - - def initialize - @assets = [] - end - - def parse_line(line) - line = line.strip - - unless line.empty? - return false unless line[0] == "#" - attribute, *description = line[1..-1].split(":") - - description = description.join(":") - attribute = attribute.strip.to_sym - - if METADATA.include?(attribute) - self.send("#{attribute}=", description.strip) - end - end - - true - end - - # will make sure all the assets this plugin needs are registered - def generate_automatic_assets! - paths = [] - automatic_assets.each do |path, contents| - unless File.exists? path - ensure_directory path - File.open(path,"w") do |f| - f.write(contents) - end - end - paths << path - end - - delete_extra_automatic_assets(paths) - - paths - end - - def delete_extra_automatic_assets(good_paths) - filenames = good_paths.map{|f| File.basename(f)} - # nuke old files - Dir.foreach(auto_generated_path) do |p| - next if [".", ".."].include?(p) - next if filenames.include?(p) - File.delete(auto_generated_path + "/#{p}") - end - end - - def ensure_directory(path) - dirname = File.dirname(path) - unless File.directory?(dirname) - FileUtils.mkdir_p(dirname) - end - end - - def auto_generated_path - File.dirname(path) << "/auto_generated" - end - - def register_css(style) - @styles ||= [] - @styles << style - end - - def register_javascript(js) - @javascripts ||= [] - @javascripts << js - end - - - def register_asset(file,opts=nil) - full_path = File.dirname(path) << "/assets/" << file - assets << full_path - if opts == :server_side - @server_side_javascripts ||= [] - @server_side_javascripts << full_path - end - end - - def automatic_assets - css = "" - js = "(function(){" - - css = @styles.join("\n") if @styles - js = @javascripts.join("\n") if @javascripts - - unless auth_providers.blank? - auth_providers.each do |auth| - overrides = "" - overrides = ", titleOverride: '#{auth.title}'" if auth.title - overrides << ", messageOverride: '#{auth.message}'" if auth.message - overrides << ", frameWidth: '#{auth.frame_width}'" if auth.frame_width - overrides << ", frameHeight: '#{auth.frame_height}'" if auth.frame_height - - js << "Discourse.LoginMethod.register(Discourse.LoginMethod.create({name: '#{auth.name}'#{overrides}}));\n" - - if auth.glyph - css << ".btn-social.#{auth.name}:before{ content: '#{auth.glyph}'; }\n" - end - - if auth.background_color - css << ".btn-social.#{auth.name}{ background: #{auth.background_color}; }\n" - end - end - end - - js << "})();" - - # TODO don't serve blank assets - [[css,"css"],[js,"js"]].map do |asset, extension| - hash = Digest::SHA1.hexdigest asset - ["#{auto_generated_path}/plugin_#{hash}.#{extension}", asset] - end - - end - - # note, we need to be able to parse seperately to activation. - # this allows us to present information about a plugin in the UI - # prior to activations - def activate! - self.instance_eval File.read(path) - if auto_assets = generate_automatic_assets! - assets.concat auto_assets - end - unless assets.blank? - assets.each do |asset| - if asset =~ /\.js$/ - DiscoursePluginRegistry.javascripts << asset - elsif asset =~ /\.css$|\.scss$/ - DiscoursePluginRegistry.stylesheets << asset - end - end - - # TODO possibly amend this to a rails engine - Rails.configuration.assets.paths << auto_generated_path - Rails.configuration.assets.paths << File.dirname(path) + "/assets" - end - - if @server_side_javascripts - @server_side_javascripts.each do |js| - DiscoursePluginRegistry.server_side_javascripts << js - end - end - end - - def auth_provider(type, opts) - @auth_providers ||= [] - provider = AuthProvider.new - provider.type = type - [:name, :glyph, :background_color, :title, :message, :frame_width, :frame_height].each do |sym| - provider.send "#{sym}=", opts.delete(sym) - end - provider.options = opts - @auth_providers << provider - end -end diff --git a/lib/plugin/auth_provider.rb b/lib/plugin/auth_provider.rb new file mode 100644 index 00000000000..91d1081ca90 --- /dev/null +++ b/lib/plugin/auth_provider.rb @@ -0,0 +1,4 @@ +class Plugin::AuthProvider + attr_accessor :type, :glyph, :background_color, :name, :title, + :message, :frame_width, :frame_height, :options, :callback +end diff --git a/lib/plugin/instance.rb b/lib/plugin/instance.rb new file mode 100644 index 00000000000..b621c56be95 --- /dev/null +++ b/lib/plugin/instance.rb @@ -0,0 +1,193 @@ +require 'digest/sha1' +require 'fileutils' +require_dependency 'plugin/metadata' +require_dependency 'plugin/auth_provider' + +class Plugin::Instance + + attr_reader :auth_providers, :assets, :path + + def self.find_all(parent_path) + [].tap { |plugins| + Dir["#{parent_path}/**/plugin.rb"].each do |path| + source = File.read(path) + metadata = Plugin::Metadata.parse(source) + plugins << self.new(metadata, path) + end + } + end + + def initialize(metadata, path) + @metadata = metadata + @path = path + @assets = [] + end + + # will make sure all the assets this plugin needs are registered + def generate_automatic_assets! + paths = [] + automatic_assets.each do |path, contents| + unless File.exists? path + ensure_directory path + File.open(path,"w") do |f| + f.write(contents) + end + end + paths << path + end + + delete_extra_automatic_assets(paths) + + paths + end + + def delete_extra_automatic_assets(good_paths) + filenames = good_paths.map{|f| File.basename(f)} + # nuke old files + Dir.foreach(auto_generated_path) do |p| + next if [".", ".."].include?(p) + next if filenames.include?(p) + File.delete(auto_generated_path + "/#{p}") + end + end + + def ensure_directory(path) + dirname = File.dirname(path) + unless File.directory?(dirname) + FileUtils.mkdir_p(dirname) + end + end + + def auto_generated_path + File.dirname(path) << "/auto_generated" + end + + def register_css(style) + @styles ||= [] + @styles << style + end + + def register_javascript(js) + @javascripts ||= [] + @javascripts << js + end + + + def register_asset(file,opts=nil) + full_path = File.dirname(path) << "/assets/" << file + assets << full_path + if opts == :server_side + @server_side_javascripts ||= [] + @server_side_javascripts << full_path + end + end + + def automatic_assets + css = "" + js = "(function(){" + + css = @styles.join("\n") if @styles + js = @javascripts.join("\n") if @javascripts + + unless auth_providers.blank? + auth_providers.each do |auth| + overrides = "" + overrides = ", titleOverride: '#{auth.title}'" if auth.title + overrides << ", messageOverride: '#{auth.message}'" if auth.message + overrides << ", frameWidth: '#{auth.frame_width}'" if auth.frame_width + overrides << ", frameHeight: '#{auth.frame_height}'" if auth.frame_height + + js << "Discourse.LoginMethod.register(Discourse.LoginMethod.create({name: '#{auth.name}'#{overrides}}));\n" + + if auth.glyph + css << ".btn-social.#{auth.name}:before{ content: '#{auth.glyph}'; }\n" + end + + if auth.background_color + css << ".btn-social.#{auth.name}{ background: #{auth.background_color}; }\n" + end + end + end + + js << "})();" + + # TODO don't serve blank assets + [[css,"css"],[js,"js"]].map do |asset, extension| + hash = Digest::SHA1.hexdigest asset + ["#{auto_generated_path}/plugin_#{hash}.#{extension}", asset] + end + + end + + # note, we need to be able to parse seperately to activation. + # this allows us to present information about a plugin in the UI + # prior to activations + def activate! + self.instance_eval File.read(path) + if auto_assets = generate_automatic_assets! + assets.concat auto_assets + end + unless assets.blank? + assets.each do |asset| + if asset =~ /\.js$/ + DiscoursePluginRegistry.javascripts << asset + elsif asset =~ /\.css$|\.scss$/ + DiscoursePluginRegistry.stylesheets << asset + end + end + + # TODO possibly amend this to a rails engine + Rails.configuration.assets.paths << auto_generated_path + Rails.configuration.assets.paths << File.dirname(path) + "/assets" + end + + if @server_side_javascripts + @server_side_javascripts.each do |js| + DiscoursePluginRegistry.server_side_javascripts << js + end + end + end + + def auth_provider(type, opts) + @auth_providers ||= [] + provider = Plugin::AuthProvider.new + provider.type = type + [:name, :glyph, :background_color, :title, :message, :frame_width, :frame_height, :callback].each do |sym| + provider.send "#{sym}=", opts.delete(sym) + end + provider.name ||= type.to_s + provider.options = opts[:middleware_options] || opts + # prepare for splatting + provider.options = [provider.options] if provider.options.is_a? Hash + @auth_providers << provider + end + + + # shotgun approach to gem loading, in future we need to hack bundler + # to at least determine dependencies do not clash before loading + # + # Additionally we want to support multiple ruby versions correctly and so on + # + # This is a very rough initial implementation + def gem(name, version, opts = {}) + gems_path = File.dirname(path) + "/gems/#{RUBY_VERSION}" + spec_path = gems_path + "/specifications" + spec_file = spec_path + "/#{name}-#{version}.gemspec" + unless File.exists? spec_file + command = "gem install #{name} -v #{version} -i #{gems_path} --no-rdoc --no-ri" + puts command + puts `command` + end + if File.exists? spec_file + spec = Gem::Specification.load spec_file + spec.activate + unless opts[:require] == false + require name + end + else + puts "You are specifying the gem #{name} in #{path}, however it does not exist!" + exit -1 + end + end + +end diff --git a/lib/plugin/metadata.rb b/lib/plugin/metadata.rb new file mode 100644 index 00000000000..0d5aa308ba9 --- /dev/null +++ b/lib/plugin/metadata.rb @@ -0,0 +1,33 @@ +# loaded really early +module Plugin; end + +class Plugin::Metadata + FIELDS = [:name, :about, :version, :authors] + attr_accessor *FIELDS + + def self.parse(text) + metadata = self.new + text.each_line do |line| + break unless metadata.parse_line(line) + end + metadata + end + + def parse_line(line) + line = line.strip + + unless line.empty? + return false unless line[0] == "#" + attribute, *description = line[1..-1].split(":") + + description = description.join(":") + attribute = attribute.strip.to_sym + + if FIELDS.include?(attribute) + self.send("#{attribute}=", description.strip) + end + end + + true + end +end diff --git a/spec/components/plugin/instance_spec.rb b/spec/components/plugin/instance_spec.rb new file mode 100644 index 00000000000..e7566ce8541 --- /dev/null +++ b/spec/components/plugin/instance_spec.rb @@ -0,0 +1,32 @@ +require 'spec_helper' +require_dependency 'plugin/instance' + +describe Plugin::Instance do + + context "activate!" do + it "can activate plugins correctly" do + plugin = Plugin.new + plugin.path = "#{Rails.root}/spec/fixtures/plugins/my_plugin/plugin.rb" + junk_file = "#{plugin.auto_generated_path}/junk" + + plugin.ensure_directory(junk_file) + File.open("#{plugin.auto_generated_path}/junk", "w") {|f| f.write("junk")} + plugin.activate! + + plugin.auth_providers.count.should == 1 + auth_provider = plugin.auth_providers[0] + auth_provider.options.should == {:identifier => 'https://zappa.com'} + auth_provider.type.should == :open_id + + # calls ensure_assets! make sure they are there + plugin.assets.count.should == 2 + plugin.assets.each do |a| + File.exists?(a).should be_true + end + + # ensure it cleans up all crap in autogenerated directory + File.exists?(junk_file).should be_false + end + end + +end diff --git a/spec/components/plugin/metadata_spec.rb b/spec/components/plugin/metadata_spec.rb new file mode 100644 index 00000000000..433df189115 --- /dev/null +++ b/spec/components/plugin/metadata_spec.rb @@ -0,0 +1,41 @@ +require 'spec_helper' +require_dependency 'plugin/metadata' + +describe Plugin::Metadata do + context "parse" do + it "correctly parses plugin info" do + metadata = Plugin::Metadata.parse <