summaryrefslogtreecommitdiff
path: root/chef-server-api/app/controllers/application.rb
blob: 9cd8a08ea66833a9eb2d00a49f48b2e194318898 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
#
# Author:: Adam Jacob (<adam@opscode.com>)
# Author:: Christopher Brown (<cb@opscode.com>)
# Copyright:: Copyright (c) 2008 Opscode, Inc.
# 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"
require "chef" / "cookbook_loader"
require "mixlib/authentication/signatureverification"

class Application < Merb::Controller

  include Chef::Mixin::Checksum

  def authenticate_every
    authenticator = Mixlib::Authentication::SignatureVerification.new

    auth = begin
             headers = request.env.inject({ }) { |memo, kv| memo[$2.downcase.gsub(/\-/,"_").to_sym] = kv[1] if kv[0] =~ /^(HTTP_)(.*)/; memo }
             Chef::Log.debug("Headers in authenticate_every: #{headers.inspect}")
             username = headers[:x_ops_userid].chomp
             Chef::Log.info("Authenticating client #{username}")
             user = Chef::ApiClient.cdb_load(username)
             Chef::Log.debug("Found API Client: #{user.inspect}")
             user_key = OpenSSL::PKey::RSA.new(user.public_key)
             Chef::Log.debug "Authenticating:\n #{user.inspect}\n"
             # Store this for later..
             @auth_user = user
             authenticator.authenticate_user_request(request, user_key)
           rescue StandardError => se
             Chef::Log.debug "Authentication failed: #{se}, #{se.backtrace.join("\n")}"
             nil
           end

    raise Unauthorized, "Failed to authenticate!" unless auth

    auth
  end

  def is_admin
    if @auth_user.admin
      true
    else
      raise Unauthorized, "You are not allowed to take this action."
    end
  end

  def is_admin_or_validator
    if @auth_user.admin || @auth_user.name == Chef::Config[:validation_client_name]
      true
    else
      raise Unauthorized, "You are not allowed to take this action."
    end
  end

  def is_correct_node
    if @auth_user.admin || @auth_user.name == params[:id]
      true
    else
      raise Unauthorized, "You are not the correct node (auth_user name: #{@auth_user.name}, params[:id]: #{params[:id]}), or are not an API administrator (admin: #{@auth_user.admin})."
    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 slice_url(:openid_consumer), :message => { :error => "You don't have access to that, please login."}
    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, segment)
    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.lib_files
    when :providers
      files_list = cookbook.provider_files
    when :resources
      files_list = cookbook.resource_files
    when :files
      files_list = cookbook.remote_files
    when :templates
      files_list = cookbook.template_files
    else
      raise ArgumentError, "segment must be one of :attributes, :recipes, :definitions, :remote_files, :template_files, :resources, :providers or :libraries"
    end
    files_list
  end

  def specific_cookbooks(node_name, cl)
    valid_cookbooks = Hash.new
    begin
      node = Chef::Node.cdb_load(node_name)
      recipes, default_attrs, override_attrs = node.run_list.expand('couchdb')
    rescue Net::HTTPServerException
      recipes = []
    end
    recipes.each do |recipe|
      valid_cookbooks = expand_cookbook_deps(valid_cookbooks, cl, recipe)
    end
    valid_cookbooks
  end

  def expand_cookbook_deps(valid_cookbooks, cl, recipe)
    cookbook = recipe
    if recipe =~ /^(.+)::/
      cookbook = $1
    end
    Chef::Log.debug("Node requires #{cookbook}")
    valid_cookbooks[cookbook] = true
    cl.metadata[cookbook.to_sym].dependencies.each do |dep, versions|
      expand_cookbook_deps(valid_cookbooks, cl, dep) unless valid_cookbooks[dep]
    end
    valid_cookbooks
  end

  def load_cookbook_files(cookbook)
    response = {
      :recipes => Array.new,
      :definitions => Array.new,
      :libraries => Array.new,
      :attributes => Array.new,
      :files => Array.new,
      :templates => Array.new,
      :resources => Array.new,
      :providers => Array.new
    }
    [ :resources, :providers, :recipes, :definitions, :libraries, :attributes, :files, :templates ].each do |segment|
      segment_files(segment, cookbook).each do |sf|
        next if File.directory?(sf)
        file_name = nil
        file_url = nil
        file_specificity = nil

        if segment == :templates || segment == :files
          mo = sf.match("cookbooks/#{cookbook.name}/#{segment}/(.+?)/(.+)")
          unless mo
            Chef::Log.debug("Skipping file #{sf}, as it doesn't have a proper segment.")
            next
          end
          specificity = mo[1]
          file_name = mo[2]
          url_options = { :cookbook_id => cookbook.name.to_s, :segment => segment, :id => file_name }

          case specificity
          when "default"
          when /^host-(.+)$/
            url_options[:fqdn] = $1
          when /^(.+)-(.+)$/
            url_options[:platform] = $1
            url_options[:version] = $2
          when /^(.+)$/
            url_options[:platform] = $1
          end

          file_specificity = specificity
          file_url = absolute_url(:cookbook_segment, url_options)
        else
          mo = sf.match("cookbooks/#{cookbook.name}/#{segment}/(.+)")
          file_name = mo[1]
          url_options = { :cookbook_id => cookbook.name.to_s, :segment => segment, :id => file_name }
          file_url = absolute_url(:cookbook_segment, url_options)
        end
        rs = {
          :name => file_name,
          :uri => file_url,
          :checksum => checksum(sf)
        }
        rs[:specificity] = file_specificity if file_specificity
        response[segment] << rs
      end
    end
    response
  end

  def load_all_files(node_name=nil)
    cl = Chef::CookbookLoader.new
    valid_cookbooks = node_name ? specific_cookbooks(node_name, cl) : {}
    cookbook_list = Hash.new
    cl.each do |cookbook|
      if node_name
        next unless valid_cookbooks[cookbook.name.to_s]
      end
      cookbook_list[cookbook.name.to_s] = load_cookbook_files(cookbook)
    end
    cookbook_list
  end

  def get_available_recipes
    cl = Chef::CookbookLoader.new
    available_recipes = cl.sort{ |a,b| a.name.to_s <=> b.name.to_s }.inject([]) do |result, element|
      element.recipes.sort.each do |r|
        if r =~ /^(.+)::default$/
          result << $1
        else
          result << r
        end
      end
      result
    end
    available_recipes
  end

end