diff options
Diffstat (limited to 'chef-server/lib/controllers')
-rw-r--r-- | chef-server/lib/controllers/application.rb | 150 | ||||
-rw-r--r-- | chef-server/lib/controllers/cookbook_attributes.rb | 55 | ||||
-rw-r--r-- | chef-server/lib/controllers/cookbook_definitions.rb | 56 | ||||
-rw-r--r-- | chef-server/lib/controllers/cookbook_files.rb | 117 | ||||
-rw-r--r-- | chef-server/lib/controllers/cookbook_recipes.rb | 56 | ||||
-rw-r--r-- | chef-server/lib/controllers/cookbook_templates.rb | 77 | ||||
-rw-r--r-- | chef-server/lib/controllers/cookbooks.rb | 50 | ||||
-rw-r--r-- | chef-server/lib/controllers/exceptions.rb | 38 | ||||
-rw-r--r-- | chef-server/lib/controllers/nodes.rb | 78 | ||||
-rw-r--r-- | chef-server/lib/controllers/openid_consumer.rb | 117 | ||||
-rw-r--r-- | chef-server/lib/controllers/openid_register.rb | 89 | ||||
-rw-r--r-- | chef-server/lib/controllers/openid_server.rb | 236 | ||||
-rw-r--r-- | chef-server/lib/controllers/search.rb | 71 | ||||
-rw-r--r-- | chef-server/lib/controllers/search_entries.rb | 68 |
14 files changed, 1258 insertions, 0 deletions
diff --git a/chef-server/lib/controllers/application.rb b/chef-server/lib/controllers/application.rb new file mode 100644 index 0000000000..6a501ea29c --- /dev/null +++ b/chef-server/lib/controllers/application.rb @@ -0,0 +1,150 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require "chef" / "mixin" / "checksum" + + +class Application < Merb::Controller + + def fix_up_node_id + if params.has_key?(:id) + params[:id].gsub!(/_/, '.') + end + end + + def escape_node_id + if params.has_key?(:id) + params[:id].gsub(/_/, '.') + end + end + + def login_required + if session[:openid] + return session[:openid] + else + self.store_location + throw(:halt, :access_denied) + end + end + + def authorized_node + if session[:level] == :admin + Chef::Log.debug("Authorized as Administrator") + true + elsif session[:level] == :node + Chef::Log.debug("Authorized as node") + if session[:node_name] == params[:id].gsub(/\./, '_') + true + else + raise( + Unauthorized, + "You are not the correct node for this action: #{session[:node_name]} instead of #{params[:id]}" + ) + end + else + Chef::Log.debug("Unauthorized") + raise Unauthorized, "You are not allowed to take this action." + end + end + + # Store the URI of the current request in the session. + # + # We can return to this location by calling #redirect_back_or_default. + def store_location + session[:return_to] = request.uri + end + + # Redirect to the URI stored by the most recent store_location call or + # to the passed default. + def redirect_back_or_default(default) + loc = session[:return_to] || default + session[:return_to] = nil + redirect loc + end + + def access_denied + case content_type + when :html + store_location + redirect url(:openid_consumer) + else + raise Unauthorized, "You must authenticate first!" + end + end + + # Load a cookbook and return a hash with a list of all the files of a + # given segment (attributes, recipes, definitions, libraries) + # + # === Parameters + # cookbook_id<String>:: The cookbook to load + # segment<Symbol>:: :attributes, :recipes, :definitions, :libraries + # + # === Returns + # <Hash>:: A hash consisting of the short name of the file in :name, and the full path + # to the file in :file. + def load_cookbook_segment(cookbook_id, segment) + cl = Chef::CookbookLoader.new + cookbook = cl[cookbook_id] + raise NotFound unless cookbook + + files_list = segment_files(segment, cookbook) + + files = Hash.new + files_list.each do |f| + full = File.expand_path(f) + name = File.basename(full) + files[name] = { + :name => name, + :file => full, + } + end + files + end + + def segment_files(segment, cookbook) + files_list = nil + case segment + when :attributes + files_list = cookbook.attribute_files + when :recipes + files_list = cookbook.recipe_files + when :definitions + files_list = cookbook.definition_files + when :libraries + files_list = cookbook.library_files + else + raise ArgumentError, "segment must be one of :attributes, :recipes, :definitions or :libraries" + end + files_list + end + + def load_all_files(segment) + cl = Chef::CookbookLoader.new + files = Array.new + cl.each do |cookbook| + segment_files(segment, cookbook).each do |sf| + files << { + :cookbook => cookbook.name, + :name => File.basename(sf) + } + end + end + files + end + +end
\ No newline at end of file diff --git a/chef-server/lib/controllers/cookbook_attributes.rb b/chef-server/lib/controllers/cookbook_attributes.rb new file mode 100644 index 0000000000..803421b6f5 --- /dev/null +++ b/chef-server/lib/controllers/cookbook_attributes.rb @@ -0,0 +1,55 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + + +class CookbookAttributes < Application + + provides :html, :json + + include Chef::Mixin::Checksum + + def load_cookbook_attributes() + @attribute_files = load_cookbook_segment(params[:cookbook_id], :attributes) + end + + def index + if params[:id] + show + else + load_cookbook_attributes() + display @attribute_files + end + end + + def show + only_provides :json + load_cookbook_attributes + raise NotFound, "Cannot find a suitable attribute file!" unless @attribute_files.has_key?(params[:id]) + to_send = @attribute_files[params[:id]][:file] + current_checksum = checksum(to_send) + Chef::Log.debug("old sum: #{params[:checksum]}, new sum: #{current_checksum}") + if current_checksum == params[:checksum] + display "File #{to_send} has not changed", :status => 304 + else + send_file(to_send) + end + end + +end + + diff --git a/chef-server/lib/controllers/cookbook_definitions.rb b/chef-server/lib/controllers/cookbook_definitions.rb new file mode 100644 index 0000000000..dbfeeca4d7 --- /dev/null +++ b/chef-server/lib/controllers/cookbook_definitions.rb @@ -0,0 +1,56 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + + +class CookbookDefinitions < Application + + provides :html, :json + + include Chef::Mixin::Checksum + + def load_cookbook_definitions() + @definition_files = load_cookbook_segment(params[:cookbook_id], :definitions) + end + + def index + if params[:id] + show + else + load_cookbook_definitions() + display @definition_files + end + end + + def show + only_provides :json + load_cookbook_definitions + raise NotFound, "Cannot find a suitable definition file!" unless @definition_files.has_key?(params[:id]) + + to_send = @definition_files[params[:id]][:file] + current_checksum = checksum(to_send) + Chef::Log.debug("Old sum: #{params[:checksum]}, New sum: #{current_checksum}") + if current_checksum == params[:checksum] + display "File #{to_send} has not changed", :status => 304 + else + send_file(to_send) + end + end + +end + + diff --git a/chef-server/lib/controllers/cookbook_files.rb b/chef-server/lib/controllers/cookbook_files.rb new file mode 100644 index 0000000000..2d8078ad7e --- /dev/null +++ b/chef-server/lib/controllers/cookbook_files.rb @@ -0,0 +1,117 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + + +class CookbookFiles < Application + + provides :html, :json + + include Chef::Mixin::Checksum + + layout nil + + def load_cookbook_files() + @cl = Chef::CookbookLoader.new + @cookbook = @cl[params[:cookbook_id]] + raise NotFound unless @cookbook + + @remote_files = Hash.new + @cookbook.remote_files.each do |rf| + full = File.expand_path(rf) + name = File.basename(full) + rf =~ /^.+#{params[:cookbook_id]}[\\|\/]files[\\|\/](.+?)[\\|\/]#{name}/ + singlecopy = $1 + @remote_files[full] = { + :name => name, + :singlecopy => singlecopy, + :file => full, + } + end + @remote_files + end + + def index + if params[:id] + if params[:recursive] == "true" + show_directory + else + show + end + else + load_cookbook_files() + display @remote_files + end + end + + def show + only_provides :json + to_send = find_preferred_file + raise NotFound, "Cannot find a suitable file!" unless to_send + current_checksum = checksum(to_send) + Chef::Log.debug("old sum: #{params[:checksum]}, new sum: #{current_checksum}") + if current_checksum == params[:checksum] + display "File #{to_send} has not changed", :status => 304 + else + send_file(to_send) + end + end + + def show_directory + Chef::Log.info("totally rocking hte show_directory") + dir_to_send = find_preferred_file + unless (dir_to_send && File.directory?(dir_to_send)) + raise NotFound, "Cannot find a suitable directory" + end + + @directory_listing = Array.new + Dir[::File.join(dir_to_send, '**', '*')].sort { |a,b| b <=> a }.each do |file_to_send| + next if File.directory?(file_to_send) + file_to_send =~ /^#{dir_to_send}\/(.+)$/ + @directory_listing << $1 + end + + display @directory_listing + end + + protected + + def find_preferred_file + load_cookbook_files() + preferences = [ + File.join("host-#{params[:fqdn]}", "#{params[:id]}"), + File.join("#{params[:platform]}-#{params[:version]}", "#{params[:id]}"), + File.join("#{params[:platform]}", "#{params[:id]}"), + File.join("default", "#{params[:id]}") + ] + to_send = nil + @remote_files.each_key do |file| + Chef::Log.debug("Looking at #{file}") + preferences.each do |pref| + Chef::Log.debug("Compared to #{pref}") + if file =~ /#{pref}$/ + Chef::Log.debug("Matched #{pref} for #{file}!") + to_send = file + break + end + end + break if to_send + end + to_send + end + +end diff --git a/chef-server/lib/controllers/cookbook_recipes.rb b/chef-server/lib/controllers/cookbook_recipes.rb new file mode 100644 index 0000000000..5081ddced1 --- /dev/null +++ b/chef-server/lib/controllers/cookbook_recipes.rb @@ -0,0 +1,56 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + + +class CookbookRecipes < Application + + provides :html, :json + + include Chef::Mixin::Checksum + + def load_cookbook_recipes() + @recipe_files = load_cookbook_segment(params[:cookbook_id], :recipes) + end + + def index + if params[:id] + show + else + load_cookbook_recipes() + display @recipe_files + end + end + + def show + only_provides :json + load_cookbook_recipes + raise NotFound, "Cannot find a suitable recipe file!" unless @recipe_files.has_key?(params[:id]) + + to_send = @recipe_files[params[:id]][:file] + current_checksum = checksum(to_send) + Chef::Log.debug("old sum: #{params[:checksum]}, new sum: #{current_checksum}") + if current_checksum == params[:checksum] + display "File #{to_send} has not changed", :status => 304 + else + send_file(to_send) + end + end + +end + + diff --git a/chef-server/lib/controllers/cookbook_templates.rb b/chef-server/lib/controllers/cookbook_templates.rb new file mode 100644 index 0000000000..4cb5e4622c --- /dev/null +++ b/chef-server/lib/controllers/cookbook_templates.rb @@ -0,0 +1,77 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +class CookbookTemplates < Application + + provides :html, :json + + def load_cookbook_templates() + @cl = Chef::CookbookLoader.new + @cookbook = @cl[params[:cookbook_id]] + raise NotFound unless @cookbook + + @templates = Hash.new + @cookbook.template_files.each do |tf| + full = File.expand_path(tf) + name = File.basename(full) + tf =~ /^.+#{params[:cookbook_id]}[\\|\/]templates[\\|\/](.+?)[\\|\/]#{name}/ + singlecopy = $1 + @templates[full] = { + :name => name, + :singlecopy => singlecopy, + :file => full, + } + end + @templates + end + + def index + if params[:id] + show + else + load_cookbook_templates() + display @templates + end + end + + def show + load_cookbook_templates() + preferences = [ + File.join("host-#{params[:fqdn]}", "#{params[:id]}"), + File.join("#{params[:platform]}-#{params[:version]}", "#{params[:id]}"), + File.join("#{params[:platform]}", "#{params[:id]}"), + File.join("default", "#{params[:id]}") + ] + to_send = nil + @templates.each_key do |file| + Chef::Log.debug("Looking at #{file}") + preferences.each do |pref| + Chef::Log.debug("Compared to #{pref}") + if file =~ /#{pref}/ + Chef::Log.debug("Matched #{pref} for #{file}!") + to_send = file + break + end + end + break if to_send + end + raise NotFound, "Cannot find a suitable template!" unless to_send + send_file(to_send) + end + +end diff --git a/chef-server/lib/controllers/cookbooks.rb b/chef-server/lib/controllers/cookbooks.rb new file mode 100644 index 0000000000..e2a9d7f319 --- /dev/null +++ b/chef-server/lib/controllers/cookbooks.rb @@ -0,0 +1,50 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +class Cookbooks < Application + + provides :html, :json + + def index + @cl = Chef::CookbookLoader.new + display @cl + end + + def show + @cl = Chef::CookbookLoader.new + @cookbook = @cl[params[:id]] + raise NotFound unless @cookbook + display @cookbook + end + + def recipe_files + @recipe_files = load_all_files(:recipes) + display @recipe_files + end + + def attribute_files + @attribute_files = load_all_files(:attributes) + display @attribute_files + end + + def definition_files + @definition_files = load_all_files(:definitions) + display @definition_files + end + +end diff --git a/chef-server/lib/controllers/exceptions.rb b/chef-server/lib/controllers/exceptions.rb new file mode 100644 index 0000000000..d8f7307637 --- /dev/null +++ b/chef-server/lib/controllers/exceptions.rb @@ -0,0 +1,38 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +class Exceptions < Application + + provides :html, :json + + # handle NotFound exceptions (404) + def not_found + display params + end + + # handle NotAcceptable exceptions (406) + def not_acceptable + display params + end + + # handle BadRequest exceptions (400) + def bad_request + display params + end + +end
\ No newline at end of file diff --git a/chef-server/lib/controllers/nodes.rb b/chef-server/lib/controllers/nodes.rb new file mode 100644 index 0000000000..655b264862 --- /dev/null +++ b/chef-server/lib/controllers/nodes.rb @@ -0,0 +1,78 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +class Nodes < Application + + provides :html, :json + + before :fix_up_node_id + before :login_required, :only => [ :create, :update, :destroy ] + before :authorized_node, :only => [ :update, :destroy ] + + def index + @node_list = Chef::Node.list + display @node_list + end + + def show + begin + @node = Chef::Node.load(params[:id]) + rescue Net::HTTPServerException => e + raise NotFound, "Cannot load node #{params[:id]}" + end + display @node + end + + def create + @node = params.has_key?("inflated_object") ? params["inflated_object"] : nil + if @node + @status = 202 + @node.save + display @node + else + raise BadRequest, "You must provide a Node to create" + end + end + + def update + @node = params.has_key?("inflated_object") ? params["inflated_object"] : nil + if @node + @status = 202 + @node.save + display @node + else + raise NotFound, "You must provide a Node to update" + end + end + + def destroy + begin + @node = Chef::Node.load(params[:id]) + rescue RuntimeError => e + raise BadRequest, "Node #{params[:id]} does not exist to destroy!" + end + @status = 202 + @node.destroy + if content_type == :html + redirect url(:nodes) + else + display @node + end + end + +end diff --git a/chef-server/lib/controllers/openid_consumer.rb b/chef-server/lib/controllers/openid_consumer.rb new file mode 100644 index 0000000000..4e3980ce3d --- /dev/null +++ b/chef-server/lib/controllers/openid_consumer.rb @@ -0,0 +1,117 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'pathname' + +require "openid" +require 'openid/store/filesystem' + +class OpenidConsumer < Application + + provides :html, :json + + def index + render + end + + def start + check_valid_openid_provider(params[:openid_identifier]) + begin + oidreq = consumer.begin(params[:openid_identifier]) + rescue OpenID::OpenIDError => e + raise BadRequest, "Discovery failed for #{params[:openid_identifier]}: #{e}" + end + return_to = absolute_url(:openid_consumer_complete) + realm = absolute_url(:openid_consumer) + + if oidreq.send_redirect?(realm, return_to, params[:immediate]) + return redirect(oidreq.redirect_url(realm, return_to, params[:immediate])) + else + @form_text = oidreq.form_markup(realm, return_to, params[:immediate], {'id' => 'openid_form'}) + render + end + end + + def complete + # FIXME - url_for some action is not necessarily the current URL. + current_url = absolute_url(:openid_consumer_complete) + parameters = params.reject{|k,v| k == "controller" || k == "action"} + oidresp = consumer.complete(parameters, current_url) + case oidresp.status + when OpenID::Consumer::FAILURE + if oidresp.display_identifier + raise BadRequest, "Verification of #{oidresp.display_identifier} failed: #{oidresp.message}" + else + raise BadRequest, "Verification failed: #{oidresp.message}" + end + when OpenID::Consumer::SUCCESS + session[:openid] = oidresp.identity_url + if oidresp.display_identifier =~ /openid\/server\/node\/(.+)$/ + session[:level] = :node + session[:node_name] = $1 + else + session[:level] = :admin + end + redirect_back_or_default(absolute_url(:nodes)) + return "Verification of #{oidresp.display_identifier} succeeded." + when OpenID::Consumer::SETUP_NEEDED + return "Immediate request failed - Setup Needed" + when OpenID::Consumer::CANCEL + return "OpenID transaction cancelled." + else + end + redirect absolute_url(:openid_consumer) + end + + def logout + session[:openid] = nil if session.has_key?(:openid) + session[:level] = nil if session.has_key?(:level) + session[:node_name] = nil if session.has_key?(:node_name) + redirect url(:top) + end + + private + + # Returns true if the openid is at a valid provider, based on whether :openid_providers is + # defined. Raises an exception if it is not an allowed provider. + def check_valid_openid_provider(openid) + if Chef::Config[:openid_providers] + fp = Chef::Config[:openid_providers].detect do |p| + case openid + when /^http:\/\/#{p}/, /^#{p}/ + true + else + false + end + end + unless fp + raise Unauthorized, "Sorry, #{openid} is not an allowed OpenID Provider." + end + end + true + end + + def consumer + if @consumer.nil? + dir = Chef::Config[:openid_cstore_path] + store = OpenID::Store::Filesystem.new(dir) + @consumer = OpenID::Consumer.new(session, store) + end + return @consumer + end +end diff --git a/chef-server/lib/controllers/openid_register.rb b/chef-server/lib/controllers/openid_register.rb new file mode 100644 index 0000000000..f5113ef267 --- /dev/null +++ b/chef-server/lib/controllers/openid_register.rb @@ -0,0 +1,89 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'openid' + +class OpenidRegister < Application + + provides :html, :json + + before :fix_up_node_id + + def index + @headers['X-XRDS-Location'] = absolute_url(:protocol => "http", :controller => "openid_server", :action => "idp_xrds") + @registered_nodes = Chef::OpenIDRegistration.list(true) + Chef::Log.debug(@registered_nodes.inspect) + display @registered_nodes + end + + def show + begin + @registered_node = Chef::OpenIDRegistration.load(params[:id]) + rescue Net::HTTPServerException => e + if e.message =~ /^404/ + raise NotFound, "Cannot load node registration for #{params[:id]}" + else + raise e + end + end + Merb.logger.debug(@registered_node.inspect) + display @registered_node + end + + def create + params.has_key?(:id) or raise BadRequest, "You must provide an id to register" + params.has_key?(:password) or raise BadRequest, "You must provide a password to register" + if Chef::OpenIDRegistration.has_key?(params[:id]) + raise BadRequest, "You cannot re-register #{params[:id]}!" + end + @registered_node = Chef::OpenIDRegistration.new + @registered_node.name = params[:id] + @registered_node.set_password(params[:password]) + @registered_node.save + display @registered_node + end + + def update + raise BadRequest, "You cannot update your registration -- delete #{params[:id]} and re-register" + end + + def destroy + begin + r = Chef::OpenIDRegistration.load(params[:id]) + rescue Exception => e + raise BadRequest, "Cannot find the registration for #{params[:id]}" + end + r.destroy + if content_type == :html + redirect url(:registrations) + else + display({ :message => "Deleted registration for #{params[:id]}"}) + end + end + + def validate + begin + r = Chef::OpenIDRegistration.load(params[:id]) + rescue Exception => e + raise BadRequest, "Cannot find the registration for #{params[:id]}" + end + r.validated = r.validated ? false : true + r.save + redirect url(:registrations) + end +end diff --git a/chef-server/lib/controllers/openid_server.rb b/chef-server/lib/controllers/openid_server.rb new file mode 100644 index 0000000000..4cdaf67adb --- /dev/null +++ b/chef-server/lib/controllers/openid_server.rb @@ -0,0 +1,236 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'pathname' + +# load the openid library, first trying rubygems +#begin +# require "rubygems" +# require_gem "ruby-openid", ">= 1.0" +#rescue LoadError +require "openid" +require "openid/consumer/discovery" +require 'openid/store/filesystem' +require 'json' +#end + +class OpenidServer < Application + + provides :html, :json + + include Merb::OpenidServerHelper + include OpenID::Server + + layout nil + + before :fix_up_node_id + + def index + + oidreq = server.decode_request(params.reject{|k,v| k == "controller" || k == "action"}) + + # no openid.mode was given + unless oidreq + return "This is the Chef OpenID server endpoint." + end + + oidresp = nil + + if oidreq.kind_of?(CheckIDRequest) + identity = oidreq.identity + + if oidresp + nil + elsif self.is_authorized(identity, oidreq.trust_root) + oidresp = oidreq.answer(true, nil, identity) + elsif oidreq.immediate + server_url = url :openid_server + oidresp = oidreq.answer(false, server_url) + else + if content_type != 'application/json' + session[:last_oidreq] = oidreq + response = { :action => url(:openid_server_decision) } + return response.to_json + else + return show_decision_page(oidreq) + end + end + else + oidresp = server.handle_request(oidreq) + end + + self.render_response(oidresp) + end + + def show_decision_page(oidreq, message="Do you trust this site with your identity?") + session[:last_oidreq] = oidreq + @oidreq = oidreq + + if message + session[:notice] = message + end + + render :template => 'openid_server/decide' + end + + def node_page + unless Chef::OpenIDRegistration.has_key?(params[:id]) + raise NotFound, "Cannot find registration for #{params[:id]}" + end + + # Yadis content-negotiation: we want to return the xrds if asked for. + accept = request.env['HTTP_ACCEPT'] + + # This is not technically correct, and should eventually be updated + # to do real Accept header parsing and logic. Though I expect it will work + # 99% of the time. + if accept and accept.include?('application/xrds+xml') + return node_xrds + end + + # content negotiation failed, so just render the user page + xrds_url = absolute_url(:openid_node_xrds, :id => params[:id]) + identity_page = <<EOS +<html><head> +<meta http-equiv="X-XRDS-Location" content="#{xrds_url}" /> +<link rel="openid.server" href="#{absolute_url(:openid_node, :id => params[:id])}" /> +</head><body><p>OpenID identity page for registration #{params[:id]}</p> +</body></html> +EOS + + # Also add the Yadis location header, so that they don't have + # to parse the html unless absolutely necessary. + @headers['X-XRDS-Location'] = xrds_url + render identity_page + end + + def node_xrds + types = [ + OpenID::OPENID_2_0_TYPE, + OpenID::OPENID_1_0_TYPE + ] + + render_xrds(types) + end + + def idp_xrds + types = [ + OpenID::OPENID_IDP_2_0_TYPE, + ] + + render_xrds(types) + end + + def decision + oidreq = session[:last_oidreq] + session[:last_oidreq] = nil + + if params.has_key?(:cancel) + Merb.logger.info("Cancelling OpenID Authentication") + return(redirect(oidreq.cancel_url)) + else + identity = oidreq.identity + identity =~ /node\/(.+)$/ + openid_node = Chef::OpenIDRegistration.load($1) + unless openid_node.validated + raise Unauthorized, "This nodes registration has not been validated" + end + if openid_node.password == encrypt_password(openid_node.salt, params[:password]) + if session[:approvals] + session[:approvals] << oidreq.trust_root + else + session[:approvals] = [oidreq.trust_root] + end + oidresp = oidreq.answer(true, nil, identity) + return self.render_response(oidresp) + else + raise Unauthorized, "Invalid credentials" + end + end + end + + protected + + def encrypt_password(salt, password) + Digest::SHA1.hexdigest("--#{salt}--#{password}--") + end + + def server + if @server.nil? + server_url = absolute_url(:openid_server) + dir = Chef::Config[:openid_store_path] + store = OpenID::Store::Filesystem.new(dir) + @server = Server.new(store, server_url) + end + return @server + end + + def approved(trust_root) + return false if session[:approvals].nil? + return session[:approvals].member?(trust_root) + end + + def is_authorized(identity_url, trust_root) + return (session[:username] and (identity_url == url_for_user) and self.approved(trust_root)) + end + + def render_xrds(types) + type_str = "" + + types.each { |uri| + type_str += "<Type>#{uri}</Type>\n " + } + + yadis = <<EOS +<?xml version="1.0" encoding="UTF-8"?> +<xrds:XRDS + xmlns:xrds="xri://$xrds" + xmlns="xri://$xrd*($v*2.0)"> + <XRD> + <Service priority="0"> + #{type_str} + <URI>#{absolute_url(:openid_server)}</URI> + </Service> + </XRD> +</xrds:XRDS> +EOS + + @headers['content-type'] = 'application/xrds+xml' + render yadis + end + + def render_response(oidresp) + if oidresp.needs_signing + signed_response = server.signatory.sign(oidresp) + end + web_response = server.encode_response(oidresp) + + case web_response.code + when HTTP_OK + @status = 200 + render web_response.body + when HTTP_REDIRECT + redirect web_response.headers['location'] + else + @status = 400 + render web_response.body + end + end + + +end diff --git a/chef-server/lib/controllers/search.rb b/chef-server/lib/controllers/search.rb new file mode 100644 index 0000000000..0abbf352b8 --- /dev/null +++ b/chef-server/lib/controllers/search.rb @@ -0,0 +1,71 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +class Search < Application + + provides :html, :json + + def index + @s = Chef::Search.new + @search_indexes = @s.list_indexes + display @search_indexes + end + + def show + @s = Chef::Search.new + @results = nil + if params[:q] + @results = @s.search(params[:id], params[:q] == "" ? "?*" : params[:q]) + else + @results = @s.search(params[:id], "?*") + end + # Boy, this should move to the search function + if params[:a] + attributes = params[:a].split(",").collect { |a| a.to_sym } + unless attributes.length == 0 + @results = @results.collect do |r| + nr = Hash.new + nr[:index_name] = r[:index_name] + nr[:id] = r[:id] + attributes.each do |attrib| + if r.has_key?(attrib) + nr[attrib] = r[attrib] + end + end + nr + end + end + end + display @results + end + + def destroy + @s = Chef::Search.new + @entries = @s.search(params[:id], "?*") + @entries.each do |entry| + Chef::Queue.send_msg(:queue, :remove, entry) + end + @status = 202 + if content_type == :html + redirect url(:search) + else + display @entries + end + end + +end diff --git a/chef-server/lib/controllers/search_entries.rb b/chef-server/lib/controllers/search_entries.rb new file mode 100644 index 0000000000..c93add3dc5 --- /dev/null +++ b/chef-server/lib/controllers/search_entries.rb @@ -0,0 +1,68 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +class SearchEntries < Application + + provides :html, :json + + def index + @s = Chef::Search.new + @entries = @s.search(params[:search_id], "?*") + display @entries + end + + def show + @s = Chef::Search.new + @entry = @s.search(params[:search_id], "id:'#{params[:search_id]}_#{params[:id]}'").first + display @entry + end + + def create + @to_index = params + @to_index.delete(:controller) + @to_index["index_name"] = params[:search_id] + @to_index["id"] = "#{params[:search_id]}_#{params[:id]}" + @to_index.delete(:search_id) + Chef::Queue.send_msg(:queue, :index, @to_index) + if content_type == :html + redirect url(:search) + else + @status = 202 + display @to_index + end + end + + def update + create + end + + def destroy + @s = Chef::Search.new + @entries = @s.search(params[:id], "?*") + @entries.each do |entry| + Chef::Queue.send_msg(:queue, :remove, entry) + end + @status = 202 + if content_type == :html + redirect url(:search) + else + display @entries + end + end + +end |