add Repositories resource, fix authorized_user attr name, some wb fixes
[arvados.git] / apps / workbench / app / controllers / application_controller.rb
1 class ApplicationController < ActionController::Base
2   protect_from_forgery
3   around_filter :thread_with_api_token, :except => [:render_exception, :render_not_found]
4   before_filter :find_object_by_uuid, :except => [:index, :render_exception, :render_not_found]
5
6   unless Rails.application.config.consider_all_requests_local
7     rescue_from Exception,
8     :with => :render_exception
9     rescue_from ActiveRecord::RecordNotFound,
10     :with => :render_not_found
11     rescue_from ActionController::RoutingError,
12     :with => :render_not_found
13     rescue_from ActionController::UnknownController,
14     :with => :render_not_found
15     rescue_from ActionController::UnknownAction,
16     :with => :render_not_found
17   end
18
19   def unprocessable(message=nil)
20     @errors ||= []
21     @errors << message if message
22     render_error status: 422
23   end
24
25   def render_error(opts)
26     respond_to do |f|
27       f.html { render opts.merge(controller: 'application', action: 'error') }
28       f.json { render opts.merge(json: {success: false, errors: @errors}) }
29     end
30   end
31
32   def render_exception(e)
33     logger.error e.inspect
34     logger.error e.backtrace.collect { |x| x + "\n" }.join('') if e.backtrace
35     if @object and @object.errors and @object.errors.full_messages
36       @errors = @object.errors.full_messages
37     else
38       @errors = [e.inspect]
39     end
40     self.render_error status: 422
41   end
42
43   def render_not_found(e=ActionController::RoutingError.new("Path not found"))
44     logger.error e.inspect
45     @errors = ["Path not found"]
46     self.render_error status: 404
47   end
48
49
50   def index
51     @objects ||= model_class.all
52     respond_to do |f|
53       f.json { render json: @objects }
54       f.html { render }
55     end
56   end
57
58   def show
59     if !@object
60       return render_not_found("object not found")
61     end
62     respond_to do |f|
63       f.json { render json: @object }
64       f.html { render }
65     end
66   end
67
68   def new
69     @object = model_class.new
70   end
71
72   def update
73     if @object.update_attributes params[@object.class.to_s.underscore.singularize.to_sym]
74       show
75     else
76       self.render_error status: 422
77     end
78   end
79
80   def create
81     @object ||= model_class.new params[model_class.to_s.singularize.to_sym]
82     @object.save!
83     redirect_to @object
84   end
85
86   def destroy
87     if @object.destroy
88       redirect_to(params[:return_to] || :back)
89     else
90       self.render_error status: 422
91     end
92   end
93
94   def current_user
95     if Thread.current[:arvados_api_token]
96       Thread.current[:user] ||= User.current
97     else
98       logger.error "No API token in Thread"
99       return nil
100     end
101   end
102
103   def model_class
104     controller_name.classify.constantize
105   end
106
107   protected
108     
109   def find_object_by_uuid
110     if params[:id] and params[:id].match /\D/
111       params[:uuid] = params.delete :id
112     end
113     @object = model_class.where(uuid: params[:uuid]).first
114   end
115
116   def thread_with_api_token
117     begin
118       try_redirect_to_login = true
119       if params[:api_token]
120         try_redirect_to_login = false
121         Thread.current[:arvados_api_token] = params[:api_token]
122         # Before copying the token into session[], do a simple API
123         # call to verify its authenticity.
124         if verify_api_token
125           session[:arvados_api_token] = params[:api_token]
126           if !request.format.json? and request.method == 'GET'
127             # Repeat this request with api_token in the (new) session
128             # cookie instead of the query string.  This prevents API
129             # tokens from appearing in (and being inadvisedly copied
130             # and pasted from) browser Location bars.
131             redirect_to request.fullpath.sub(%r{([&\?]api_token=)[^&\?]*}, '')
132           else
133             yield
134           end
135         else
136           @errors = ['Invalid API token']
137           self.render_error status: 401
138         end
139       elsif session[:arvados_api_token]
140         # In this case, the token must have already verified at some
141         # point, but it might have been revoked since.  We'll try
142         # using it, and catch the exception if it doesn't work.
143         try_redirect_to_login = false
144         Thread.current[:arvados_api_token] = session[:arvados_api_token]
145         begin
146           yield
147         rescue ArvadosApiClient::NotLoggedInException
148           try_redirect_to_login = true
149         end
150       else
151         logger.debug "session is #{session.inspect}"
152       end
153       if try_redirect_to_login
154         respond_to do |f|
155           f.html {
156             if request.method == 'GET'
157               redirect_to $arvados_api_client.arvados_login_url(return_to: request.url)
158             else
159               flash[:error] = "Either you are not logged in, or your session has timed out. I can't automatically log you in and re-attempt this request."
160               redirect_to :back
161             end
162           }
163           f.json {
164             @errors = ['You do not seem to be logged in. You did not supply an API token with this request, and your session (if any) has timed out.']
165             self.render_error status: 422
166           }
167         end
168       end
169     ensure
170       # Remove token in case this Thread is used for anything else.
171       Thread.current[:arvados_api_token] = nil
172     end
173   end
174
175   def verify_api_token
176     begin
177       Link.where(uuid: 'just-verifying-my-api-token')
178       true
179     rescue ArvadosApiClient::NotLoggedInException
180       false
181     end
182   end
183
184   def ensure_current_user_is_admin
185     unless current_user and current_user.is_admin
186       @errors = ['Permission denied']
187       self.render_error status: 401
188     end
189   end
190 end