Prepare for API v1 (PR #570)
This commit is contained in:
parent
d9ae0d11b0
commit
fd96b6ccc1
21 changed files with 536 additions and 217 deletions
|
|
@ -1,10 +1,10 @@
|
|||
class Admin::BaseController < ApplicationController
|
||||
before_filter :authenticate_admin
|
||||
|
||||
|
||||
def index
|
||||
@user = self.current_user
|
||||
@user = current_user
|
||||
@groups = Group.where(deleted_at: nil).order('created_on DESC').limit(10)
|
||||
@users = User.order('created_on DESC').limit(10)
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
|
|
|||
60
app/controllers/api/v1/base_controller.rb
Normal file
60
app/controllers/api/v1/base_controller.rb
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
class Api::V1::BaseController < ApplicationController
|
||||
protect_from_forgery with: :null_session
|
||||
|
||||
before_action :skip_session
|
||||
rescue_from ActiveRecord::RecordNotFound, with: :not_found_handler
|
||||
rescue_from ActiveRecord::RecordNotSaved, with: :not_acceptable_handler
|
||||
rescue_from ActiveRecord::RecordInvalid, with: :not_acceptable_handler
|
||||
rescue_from Api::Errors::PermissionRequired, with: :permission_required_handler
|
||||
|
||||
private
|
||||
|
||||
def authenticate
|
||||
doorkeeper_authorize!
|
||||
super if current_user
|
||||
end
|
||||
|
||||
# @return [User] Current user, or +nil+ if no valid token.
|
||||
def current_user
|
||||
@current_user ||= User.undeleted.find(doorkeeper_token.resource_owner_id) if doorkeeper_token
|
||||
end
|
||||
|
||||
# @return [Ordergroup] Current user's ordergroup, or +nil+ if no valid token or user has no ordergroup.
|
||||
def current_ordergroup
|
||||
current_user.try(:ordergroup)
|
||||
end
|
||||
|
||||
def require_ordergroup
|
||||
authenticate
|
||||
raise Api::Errors::PermissionRequired unless current_user.ordergroup.present?
|
||||
end
|
||||
|
||||
def skip_session
|
||||
request.session_options[:skip] = true
|
||||
end
|
||||
|
||||
def not_found_handler(e)
|
||||
# remove where-clauses from error message (not suitable for end-users)
|
||||
msg = e.message.try {|m| m.sub(/\s*\[.*?\]\s*$/, '')} || 'Not found'
|
||||
render status: 404, json: {error: 'not_found', error_description: msg}
|
||||
end
|
||||
|
||||
def not_acceptable_handler(e)
|
||||
render status: 422, json: {error: 'not_acceptable', error_description: e.message || 'Data not acceptable' }
|
||||
end
|
||||
|
||||
def doorkeeper_unauthorized_render_options(error:)
|
||||
{json: {error: error.name, error_description: error.description}}
|
||||
end
|
||||
|
||||
def permission_required_handler(e)
|
||||
msg = e.message || 'Forbidden, user has no access'
|
||||
render status: 403, json: {error: 'forbidden', error_description: msg}
|
||||
end
|
||||
|
||||
# @todo something with ApplicationHelper#show_user
|
||||
def show_user(user = current_user, **options)
|
||||
user.display
|
||||
end
|
||||
|
||||
end
|
||||
|
|
@ -1,142 +1,24 @@
|
|||
# encoding: utf-8
|
||||
class ApplicationController < ActionController::Base
|
||||
include Foodsoft::ControllerExtensions::Locale
|
||||
include Concerns::FoodcoopScope
|
||||
include Concerns::Auth
|
||||
include Concerns::Locale
|
||||
helper_method :current_user
|
||||
helper_method :available_locales
|
||||
|
||||
protect_from_forgery
|
||||
before_filter :select_foodcoop, :authenticate, :set_user_last_activity, :store_controller, :items_per_page
|
||||
before_filter :authenticate, :set_user_last_activity, :store_controller, :items_per_page
|
||||
after_filter :remove_controller
|
||||
around_filter :set_time_zone, :set_currency
|
||||
|
||||
|
||||
|
||||
# Returns the controller handling the current request.
|
||||
def self.current
|
||||
Thread.current[:application_controller]
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def current_user
|
||||
# check if there is a valid session and return the logged-in user (its object)
|
||||
if session[:user_id] && params[:foodcoop]
|
||||
# for shared-host installations. check if the cookie-subdomain fits to request.
|
||||
@current_user ||= User.undeleted.find_by_id(session[:user_id]) if session[:scope] == FoodsoftConfig.scope
|
||||
end
|
||||
end
|
||||
helper_method :current_user
|
||||
|
||||
def deny_access
|
||||
session[:return_to] = request.original_url
|
||||
redirect_to root_url, alert: I18n.t('application.controller.error_denied', sign_in: ActionController::Base.helpers.link_to(t('application.controller.error_denied_sign_in'), login_path))
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def login(user)
|
||||
session[:user_id] = user.id
|
||||
session[:scope] = FoodsoftConfig.scope # Save scope in session to not allow switching between foodcoops with one account
|
||||
session[:locale] = user.locale
|
||||
end
|
||||
|
||||
def login_and_redirect_to_return_to(user, *args)
|
||||
login user
|
||||
if session[:return_to].present?
|
||||
redirect_to_url = session[:return_to]
|
||||
session[:return_to] = nil
|
||||
else
|
||||
redirect_to_url = root_url
|
||||
end
|
||||
redirect_to redirect_to_url, *args
|
||||
end
|
||||
|
||||
def logout
|
||||
session[:user_id] = nil
|
||||
session[:return_to] = nil
|
||||
end
|
||||
|
||||
def authenticate(role = 'any')
|
||||
# Attempt to retrieve authenticated user from controller instance or session...
|
||||
if !current_user
|
||||
# No user at all: redirect to login page.
|
||||
logout
|
||||
session[:return_to] = request.original_url
|
||||
redirect_to_login :alert => I18n.t('application.controller.error_authn')
|
||||
else
|
||||
# We have an authenticated user, now check role...
|
||||
# Roles gets the user through his memberships.
|
||||
hasRole = case role
|
||||
when "admin" then current_user.role_admin?
|
||||
when "finance" then current_user.role_finance?
|
||||
when "article_meta" then current_user.role_article_meta?
|
||||
when "pickups" then current_user.role_pickups?
|
||||
when "suppliers" then current_user.role_suppliers?
|
||||
when "orders" then current_user.role_orders?
|
||||
when "finance_or_orders" then (current_user.role_finance? || current_user.role_orders?)
|
||||
when "pickups_or_orders" then (current_user.role_pickups? || current_user.role_orders?)
|
||||
when "any" then true # no role required
|
||||
else false # any unknown role will always fail
|
||||
end
|
||||
if hasRole
|
||||
current_user
|
||||
else
|
||||
deny_access
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def authenticate_admin
|
||||
authenticate('admin')
|
||||
end
|
||||
|
||||
def authenticate_finance
|
||||
authenticate('finance')
|
||||
end
|
||||
|
||||
def authenticate_article_meta
|
||||
authenticate('article_meta')
|
||||
end
|
||||
|
||||
def authenticate_pickups
|
||||
authenticate('pickups')
|
||||
end
|
||||
|
||||
def authenticate_suppliers
|
||||
authenticate('suppliers')
|
||||
end
|
||||
|
||||
def authenticate_orders
|
||||
authenticate('orders')
|
||||
end
|
||||
|
||||
def authenticate_finance_or_orders
|
||||
authenticate('finance_or_orders')
|
||||
end
|
||||
|
||||
def authenticate_pickups_or_orders
|
||||
authenticate('pickups_or_orders')
|
||||
end
|
||||
|
||||
# checks if the current_user is member of given group.
|
||||
# if fails the user will redirected to startpage
|
||||
def authenticate_membership_or_admin(group_id = params[:id])
|
||||
@group = Group.find(group_id)
|
||||
unless @group.member?(@current_user) || @current_user.role_admin?
|
||||
redirect_to root_path, alert: I18n.t('application.controller.error_members_only')
|
||||
end
|
||||
end
|
||||
|
||||
def authenticate_or_token(prefix, role = 'any')
|
||||
if not params[:token].blank?
|
||||
begin
|
||||
TokenVerifier.new(prefix).verify(params[:token])
|
||||
rescue ActiveSupport::MessageVerifier::InvalidSignature
|
||||
redirect_to root_path, alert: I18n.t('application.controller.error_token')
|
||||
end
|
||||
else
|
||||
authenticate(role)
|
||||
end
|
||||
end
|
||||
|
||||
def set_user_last_activity
|
||||
if current_user && (session[:last_activity] == nil || session[:last_activity] < 1.minutes.ago)
|
||||
current_user.update_attribute(:last_activity, Time.now)
|
||||
|
|
@ -167,16 +49,11 @@ class ApplicationController < ActionController::Base
|
|||
redirect_to root_path, alert: I18n.t('application.controller.error_feature_disabled')
|
||||
end
|
||||
|
||||
# Redirect to the login page, used in authenticate, plugins can override this.
|
||||
def redirect_to_login(options={})
|
||||
redirect_to login_url, options
|
||||
end
|
||||
|
||||
# Stores this controller instance as a thread local varibale to be accessible from outside ActionController/ActionView.
|
||||
def store_controller
|
||||
Thread.current[:application_controller] = self
|
||||
end
|
||||
|
||||
|
||||
# Sets the thread local variable that holds a reference to the current controller to nil.
|
||||
def remove_controller
|
||||
Thread.current[:application_controller] = nil
|
||||
|
|
@ -187,23 +64,6 @@ class ApplicationController < ActionController::Base
|
|||
@supplier = Supplier.find(params[:supplier_id]) if params[:supplier_id]
|
||||
end
|
||||
|
||||
# Set config and database connection for each request
|
||||
# It uses the subdomain to select the appropriate section in the config files
|
||||
# Use this method as a before filter (first filter!) in ApplicationController
|
||||
def select_foodcoop
|
||||
return unless FoodsoftConfig[:multi_coop_install]
|
||||
|
||||
foodcoop = params[:foodcoop]
|
||||
if foodcoop.blank?
|
||||
FoodsoftConfig.select_default_foodcoop
|
||||
redirect_to root_url
|
||||
elsif FoodsoftConfig.allowed_foodcoop? foodcoop
|
||||
FoodsoftConfig.select_foodcoop foodcoop
|
||||
else
|
||||
raise ActionController::RoutingError.new 'Foodcoop Not Found'
|
||||
end
|
||||
end
|
||||
|
||||
def items_per_page
|
||||
if params[:per_page] && params[:per_page].to_i > 0 && params[:per_page].to_i <= 500
|
||||
@per_page = params[:per_page].to_i
|
||||
|
|
@ -212,11 +72,6 @@ class ApplicationController < ActionController::Base
|
|||
end
|
||||
end
|
||||
|
||||
# Always stay in foodcoop url scope
|
||||
def default_url_options(options = {})
|
||||
{foodcoop: FoodsoftConfig.scope}
|
||||
end
|
||||
|
||||
# Set timezone according to foodcoop preference.
|
||||
# @see http://stackoverflow.com/questions/4362663/timezone-with-rails-3
|
||||
# @see http://archives.ryandaigle.com/articles/2008/1/25/what-s-new-in-edge-rails-easier-timezones
|
||||
|
|
|
|||
148
app/controllers/concerns/auth.rb
Normal file
148
app/controllers/concerns/auth.rb
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
# Controller concern for authentication methods
|
||||
#
|
||||
# Split off from main +ApplicationController+ to allow e.g.
|
||||
# Doorkeeper to use it too.
|
||||
module Concerns::Auth
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
protected
|
||||
|
||||
def current_user
|
||||
# check if there is a valid session and return the logged-in user (its object)
|
||||
if session[:user_id] && params[:foodcoop]
|
||||
# for shared-host installations. check if the cookie-subdomain fits to request.
|
||||
@current_user ||= User.undeleted.find_by_id(session[:user_id]) if session[:scope] == FoodsoftConfig.scope
|
||||
end
|
||||
end
|
||||
|
||||
def deny_access
|
||||
session[:return_to] = request.original_url
|
||||
redirect_to root_url, alert: I18n.t('application.controller.error_denied', sign_in: ActionController::Base.helpers.link_to(t('application.controller.error_denied_sign_in'), login_path))
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def login(user)
|
||||
session[:user_id] = user.id
|
||||
session[:scope] = FoodsoftConfig.scope # Save scope in session to not allow switching between foodcoops with one account
|
||||
session[:locale] = user.locale
|
||||
end
|
||||
|
||||
def login_and_redirect_to_return_to(user, *args)
|
||||
login user
|
||||
if session[:return_to].present?
|
||||
redirect_to_url = session[:return_to]
|
||||
session[:return_to] = nil
|
||||
else
|
||||
redirect_to_url = root_url
|
||||
end
|
||||
redirect_to redirect_to_url, *args
|
||||
end
|
||||
|
||||
def logout
|
||||
session[:user_id] = nil
|
||||
session[:return_to] = nil
|
||||
expire_access_tokens
|
||||
end
|
||||
|
||||
def authenticate(role = 'any')
|
||||
# Attempt to retrieve authenticated user from controller instance or session...
|
||||
if !current_user
|
||||
# No user at all: redirect to login page.
|
||||
logout
|
||||
session[:return_to] = request.original_url
|
||||
redirect_to_login :alert => I18n.t('application.controller.error_authn')
|
||||
else
|
||||
# We have an authenticated user, now check role...
|
||||
# Roles gets the user through his memberships.
|
||||
hasRole = case role
|
||||
when 'admin' then current_user.role_admin?
|
||||
when 'finance' then current_user.role_finance?
|
||||
when 'article_meta' then current_user.role_article_meta?
|
||||
when 'pickups' then current_user.role_pickups?
|
||||
when 'suppliers' then current_user.role_suppliers?
|
||||
when 'orders' then current_user.role_orders?
|
||||
when 'finance_or_orders' then (current_user.role_finance? || current_user.role_orders?)
|
||||
when 'pickups_or_orders' then (current_user.role_pickups? || current_user.role_orders?)
|
||||
when 'any' then true # no role required
|
||||
else false # any unknown role will always fail
|
||||
end
|
||||
if hasRole
|
||||
current_user
|
||||
else
|
||||
deny_access
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def authenticate_admin
|
||||
authenticate('admin')
|
||||
end
|
||||
|
||||
def authenticate_finance
|
||||
authenticate('finance')
|
||||
end
|
||||
|
||||
def authenticate_article_meta
|
||||
authenticate('article_meta')
|
||||
end
|
||||
|
||||
def authenticate_pickups
|
||||
authenticate('pickups')
|
||||
end
|
||||
|
||||
def authenticate_suppliers
|
||||
authenticate('suppliers')
|
||||
end
|
||||
|
||||
def authenticate_orders
|
||||
authenticate('orders')
|
||||
end
|
||||
|
||||
def authenticate_finance_or_orders
|
||||
authenticate('finance_or_orders')
|
||||
end
|
||||
|
||||
def authenticate_pickups_or_orders
|
||||
authenticate('pickups_or_orders')
|
||||
end
|
||||
|
||||
# checks if the current_user is member of given group.
|
||||
# if fails the user will redirected to startpage
|
||||
def authenticate_membership_or_admin(group_id = params[:id])
|
||||
@group = Group.find(group_id)
|
||||
unless @group.member?(@current_user) || @current_user.role_admin?
|
||||
redirect_to root_path, alert: I18n.t('application.controller.error_members_only')
|
||||
end
|
||||
end
|
||||
|
||||
def authenticate_or_token(prefix, role = 'any')
|
||||
if not params[:token].blank?
|
||||
begin
|
||||
TokenVerifier.new(prefix).verify(params[:token])
|
||||
rescue ActiveSupport::MessageVerifier::InvalidSignature
|
||||
redirect_to root_path, alert: I18n.t('application.controller.error_token')
|
||||
end
|
||||
else
|
||||
authenticate(role)
|
||||
end
|
||||
end
|
||||
|
||||
# Expires any access tokens for the current user (unless they have the 'offline_access' scope)
|
||||
# @see https://github.com/doorkeeper-gem/doorkeeper/issues/71#issuecomment-5471317
|
||||
def expire_access_tokens
|
||||
return unless @current_user
|
||||
Doorkeeper::AccessToken.transaction do
|
||||
token_scope = Doorkeeper::AccessToken.where(revoked_at: nil, resource_owner_id: @current_user.id)
|
||||
token_scope.each do |token|
|
||||
token.destroy! unless token.scopes.include?('offline_access')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Redirect to the login page, used in authenticate, plugins can override this.
|
||||
def redirect_to_login(options={})
|
||||
redirect_to login_url, options
|
||||
end
|
||||
|
||||
end
|
||||
36
app/controllers/concerns/foodcoop_scope.rb
Normal file
36
app/controllers/concerns/foodcoop_scope.rb
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
# Controller concern to handle foodcoop scope
|
||||
#
|
||||
# Includes a +before_filter+ for selecting foodcoop from url.
|
||||
#
|
||||
module Concerns::FoodcoopScope
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
before_filter :select_foodcoop
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Set config and database connection for each request
|
||||
# It uses the subdomain to select the appropriate section in the config files
|
||||
# Use this method as a before filter (first filter!) in ApplicationController
|
||||
def select_foodcoop
|
||||
return unless FoodsoftConfig[:multi_coop_install]
|
||||
|
||||
foodcoop = params[:foodcoop]
|
||||
if foodcoop.blank?
|
||||
FoodsoftConfig.select_default_foodcoop
|
||||
redirect_to root_url
|
||||
elsif FoodsoftConfig.allowed_foodcoop? foodcoop
|
||||
FoodsoftConfig.select_foodcoop foodcoop
|
||||
else
|
||||
raise ActionController::RoutingError.new 'Foodcoop Not Found'
|
||||
end
|
||||
end
|
||||
|
||||
# Always stay in foodcoop url scope
|
||||
def default_url_options(options = {})
|
||||
super().merge({foodcoop: FoodsoftConfig.scope})
|
||||
end
|
||||
|
||||
end
|
||||
51
app/controllers/concerns/locale.rb
Normal file
51
app/controllers/concerns/locale.rb
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
module Concerns::Locale
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
before_filter :set_locale
|
||||
end
|
||||
|
||||
def explicitly_requested_language
|
||||
params[:locale]
|
||||
end
|
||||
|
||||
def user_settings_language
|
||||
current_user&.locale
|
||||
end
|
||||
|
||||
def session_language
|
||||
session[:locale]
|
||||
end
|
||||
|
||||
def browser_language
|
||||
request.env['HTTP_ACCEPT_LANGUAGE'] ? request.env['HTTP_ACCEPT_LANGUAGE'].scan(/^[a-z]{2}/).first : nil
|
||||
end
|
||||
|
||||
def default_language
|
||||
FoodsoftConfig[:default_locale] || ::I18n.default_locale
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def select_language_according_to_priority
|
||||
language = explicitly_requested_language || session_language || user_settings_language
|
||||
language ||= browser_language unless FoodsoftConfig[:ignore_browser_locale]
|
||||
language.presence&.to_sym unless language.blank?
|
||||
end
|
||||
|
||||
def available_locales
|
||||
::I18n.available_locales
|
||||
end
|
||||
|
||||
def set_locale
|
||||
if available_locales.include?(select_language_according_to_priority)
|
||||
::I18n.locale = select_language_according_to_priority
|
||||
else
|
||||
::I18n.locale = default_language
|
||||
end
|
||||
|
||||
locale = session[:locale] = ::I18n.locale
|
||||
logger.info("Set locale to #{locale}")
|
||||
end
|
||||
|
||||
end
|
||||
|
|
@ -6,6 +6,8 @@
|
|||
- url = action_name == 'show' ? nil : admin_config_path(tab: tab)
|
||||
%li{class: ('active' if @current_tab==tab)}= link_to t("config.tabs.#{tab}"), "#{url}#tab-#{tab}", data: ({toggle: 'tab'} unless url)
|
||||
|
||||
-# make this a button to give some indicator that navigation away might lose changes
|
||||
-# make these buttons to give some indicator that navigation away might lose changes
|
||||
%li.pull-right{class: ('active' if @current_tab=='list')}
|
||||
= link_to t('config.tabs.list'), list_admin_config_path, class: ('btn' unless @current_tab=='list')
|
||||
%li.pull-right
|
||||
= link_to t('config.tabs.applications'), oauth_applications_path, class: 'btn'
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue