on_success callback for Authorization

pull/1636/head
Markus Kahl 10 years ago
parent c430349640
commit 41603b0f43
  1. 20
      app/controllers/account_controller.rb
  2. 21
      app/controllers/concerns/omniauth_login.rb
  3. 69
      lib/open_project/omni_auth/authorization.rb
  4. 16
      spec/controllers/concerns/omniauth_login_spec.rb
  5. 59
      spec/lib/open_project/omni_auth/authorization_spec.rb

@ -299,23 +299,25 @@ class AccountController < ApplicationController
end end
# Register a user depending on Setting.self_registration # Register a user depending on Setting.self_registration
def register_user_according_to_setting(user, &block) def register_user_according_to_setting(user, opts = {}, &block)
case Setting.self_registration case Setting.self_registration
when '1' when '1'
register_by_email_activation(user, &block) register_by_email_activation(user, opts, &block)
when '3' when '3'
register_automatically(user, &block) register_automatically(user, opts, &block)
else else
register_manually_by_administrator(user, &block) register_manually_by_administrator(user, opts, &block)
end end
end end
# Register a user for email activation. # Register a user for email activation.
# #
# Pass a block for behavior when a user fails to save # Pass a block for behavior when a user fails to save
def register_by_email_activation(user, &block) def register_by_email_activation(user, opts = {})
token = Token.new(:user => user, :action => "register") token = Token.new(:user => user, :action => "register")
if user.save and token.save if user.save and token.save
opts[:on_success].call user if opts[:on_success]
UserMailer.user_signed_up(token).deliver UserMailer.user_signed_up(token).deliver
flash[:notice] = l(:notice_account_register_done) flash[:notice] = l(:notice_account_register_done)
redirect_to :action => 'login' redirect_to :action => 'login'
@ -327,12 +329,14 @@ class AccountController < ApplicationController
# Automatically register a user # Automatically register a user
# #
# Pass a block for behavior when a user fails to save # Pass a block for behavior when a user fails to save
def register_automatically(user, &block) def register_automatically(user, opts = {})
# Automatic activation # Automatic activation
user.activate user.activate
user.last_login_on = Time.now user.last_login_on = Time.now
if user.save if user.save
opts[:on_success].call user if opts[:on_success]
self.logged_user = user self.logged_user = user
flash[:notice] = l(:notice_account_registered_and_logged_in) flash[:notice] = l(:notice_account_registered_and_logged_in)
redirect_after_login(user) redirect_after_login(user)
@ -344,8 +348,10 @@ class AccountController < ApplicationController
# Manual activation by the administrator # Manual activation by the administrator
# #
# Pass a block for behavior when a user fails to save # Pass a block for behavior when a user fails to save
def register_manually_by_administrator(user, &block) def register_manually_by_administrator(user, opts = {})
if user.save if user.save
opts[:on_success].call user if opts[:on_success]
# Sends an email to the administrators # Sends an email to the administrators
admins = User.admin.active admins = User.admin.active
admins.each do |admin| admins.each do |admin|

@ -51,7 +51,10 @@ module Concerns::OmniauthLogin
if user.new_record? if user.new_record?
create_user_from_omniauth user, auth_hash create_user_from_omniauth user, auth_hash
else else
user.log_successful_login if user.active? if user.active?
user.log_successful_login
OpenProject::OmniAuth::Authorization.authorized! user
end
login_user_if_active(user) login_user_if_active(user)
end end
end end
@ -74,8 +77,10 @@ module Concerns::OmniauthLogin
fill_user_fields_from_omniauth user, auth_hash fill_user_fields_from_omniauth user, auth_hash
opts = { on_success: ->(u) { OpenProject::OmniAuth::Authorization.authorized! u } }
# Create on the fly # Create on the fly
register_user_according_to_setting(user) do register_user_according_to_setting(user, opts) do
# Allow registration form to show provider-specific title # Allow registration form to show provider-specific title
@omniauth_strategy = auth_hash[:provider] @omniauth_strategy = auth_hash[:provider]
@ -91,9 +96,9 @@ module Concerns::OmniauthLogin
auth = session[:auth_source_registration] auth = session[:auth_source_registration]
return if handle_omniauth_registration_expired(auth) return if handle_omniauth_registration_expired(auth)
fill_user_fields_from_omniauth(@user, auth) fill_user_fields_from_omniauth(user, auth)
@user.update_attributes(permitted_params.user_register_via_omniauth) user.update_attributes(permitted_params.user_register_via_omniauth)
register_user_according_to_setting(@user) register_user_according_to_setting(user)
end end
def fill_user_fields_from_omniauth(user, auth) def fill_user_fields_from_omniauth(user, auth)
@ -102,12 +107,6 @@ module Concerns::OmniauthLogin
user user
end end
def user_with_info(user, auth_hash)
user.dup.tap do |u|
u.assign_attributes omniauth_hash_to_user_attributes(auth_hash)
end
end
def omniauth_hash_to_user_attributes(auth) def omniauth_hash_to_user_attributes(auth)
info = auth[:info] info = auth[:info]
{ {

@ -20,6 +20,16 @@ module OpenProject
rejection || Approval.new rejection || Approval.new
end end
##
# Signals that the given user has been successfully authorized.
#
# Note: Only call if you know what you are doing.
def self.authorized!(user)
authorized_callbacks.each do |callback|
callback.authorized user
end
end
## ##
# Adds a callback to be executed before a user is logged in. # Adds a callback to be executed before a user is logged in.
# The given callback may reject the user to prevent authorization by # The given callback may reject the user to prevent authorization by
@ -40,12 +50,12 @@ module OpenProject
if opts[:provider] if opts[:provider]
authorize_user_for_provider opts[:provider], &block authorize_user_for_provider opts[:provider], &block
else else
add_authorize_user_callback BlockCallback.new(&block) add_authorize_user_callback AuthorizationBlockCallback.new(&block)
end end
end end
def self.authorize_user_for_provider(provider, &block) def self.authorize_user_for_provider(provider, &block)
callback = BlockCallback.new do |dec, auth_hash| callback = AuthorizationBlockCallback.new do |dec, auth_hash|
if auth_hash.provider.to_sym == provider.to_sym if auth_hash.provider.to_sym == provider.to_sym
block.call dec, auth_hash block.call dec, auth_hash
else else
@ -56,6 +66,19 @@ module OpenProject
add_authorize_user_callback callback add_authorize_user_callback callback
end end
##
# Registers a callback on the event of a successful user authorization.
#
# @yield [user] Callback called with the successfully authorized user.
# @yieldparam user [User] User who has been authorized.
def self.on_success(&block)
add_authorized_callback AuthorizedBlockCallback.new(&block)
end
##
# Registers a new callback to decide whether or not a user is to be authorized.
#
# @param [AuthorizationCallback] Callback to be called upon user authorization.
def self.add_authorize_user_callback(callback) def self.add_authorize_user_callback(callback)
callbacks << callback callbacks << callback
end end
@ -64,9 +87,21 @@ module OpenProject
@callbacks ||= [] @callbacks ||= []
end end
##
# Registers a new callback to successful user authorization.
#
# @param [AuthorizedCallback] Callback to be called upon successful authorization.
def self.add_authorized_callback(callback)
authorized_callbacks << callback
end
def self.authorized_callbacks
@authorized_callbacks ||= []
end
## ##
# Performs user authorization. # Performs user authorization.
class Callback class AuthorizationCallback
## ##
# Given an OmniAuth auth hash this decides if a user is authorized or not. # Given an OmniAuth auth hash this decides if a user is authorized or not.
# #
@ -81,7 +116,7 @@ module OpenProject
## ##
# A callback triggering a given block. # A callback triggering a given block.
class BlockCallback < Callback class AuthorizationBlockCallback < AuthorizationCallback
attr_reader :block attr_reader :block
def initialize(&block) def initialize(&block)
@ -96,6 +131,32 @@ module OpenProject
end end
end end
##
# A callback for reacting to a user being authorized.
class AuthorizedCallback
##
# Is called after a user has been authorized successfully.
#
# @param [User] User who has been authorized.
def authorized(user)
fail "subclass responsibility: authorized(#{user})"
end
end
##
# A authorized callback triggering a given block.
class AuthorizedBlockCallback < AuthorizedCallback
attr_reader :block
def initialize(&block)
@block = block
end
def authorized(user)
block.call user
end
end
## ##
# Abstract base class for an authorization decision. # Abstract base class for an authorization decision.
# Any subclass must either override #approve? or #reject? # Any subclass must either override #approve? or #reject?

@ -258,6 +258,8 @@ describe AccountController do
end end
it 'works' do it 'works' do
expect(OpenProject::OmniAuth::Authorization).to receive(:authorized!).with(user)
post :omniauth_login post :omniauth_login
expect(response).to redirect_to my_page_path expect(response).to redirect_to my_page_path
@ -269,6 +271,8 @@ describe AccountController do
end end
it 'is rejected against google' do it 'is rejected against google' do
expect(OpenProject::OmniAuth::Authorization).not_to receive(:authorized!).with(user)
post :omniauth_login post :omniauth_login
expect(response).to redirect_to signin_path expect(response).to redirect_to signin_path
@ -276,6 +280,8 @@ describe AccountController do
end end
it 'is rejected against any other provider too' do it 'is rejected against any other provider too' do
expect(OpenProject::OmniAuth::Authorization).not_to receive(:authorized!).with(user)
omniauth_hash.provider = 'any other' omniauth_hash.provider = 'any other'
post :omniauth_login post :omniauth_login
@ -292,6 +298,8 @@ describe AccountController do
end end
it 'is rejected against google' do it 'is rejected against google' do
expect(OpenProject::OmniAuth::Authorization).not_to receive(:authorized!).with(user)
post :omniauth_login post :omniauth_login
expect(response).to redirect_to signin_path expect(response).to redirect_to signin_path
@ -299,6 +307,12 @@ describe AccountController do
end end
it 'is approved against any other provider' do it 'is approved against any other provider' do
expect(OpenProject::OmniAuth::Authorization).to receive(:authorized!) do |u|
new_user = User.find_by_identity_url 'some other:123545'
expect(u).to eq new_user
end
omniauth_hash.provider = 'some other' omniauth_hash.provider = 'some other'
post :omniauth_login post :omniauth_login
@ -312,6 +326,8 @@ describe AccountController do
# ... and to confirm that, here's what happens when the authorization fails # ... and to confirm that, here's what happens when the authorization fails
it 'is rejected against any other provider with the wrong email' do it 'is rejected against any other provider with the wrong email' do
expect(OpenProject::OmniAuth::Authorization).not_to receive(:authorized!).with(user)
omniauth_hash.provider = 'yet another' omniauth_hash.provider = 'yet another'
config.global_email = 'yarrrr@joro.es' config.global_email = 'yarrrr@joro.es'

@ -0,0 +1,59 @@
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2014 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
require 'spec_helper'
describe OpenProject::OmniAuth::Authorization do
describe '.authorized!' do
let(:user) { FactoryGirl.create :user, mail: 'foo@bar.de' }
let(:state) { Struct.new(:number, :user_email).new 0, nil }
before do
OpenProject::OmniAuth::Authorization.authorized_callbacks.clear
OpenProject::OmniAuth::Authorization.on_success do |_|
state.number = 42
end
OpenProject::OmniAuth::Authorization.on_success do |user|
state.user_email = user.mail
end
end
after do
OpenProject::OmniAuth::Authorization.authorized_callbacks.clear
end
it 'triggers every callback setting number to 42 and user_email to foo@bar.de' do
OpenProject::OmniAuth::Authorization.authorized! user
expect(state.number).to eq 42
expect(state.user_email).to eq 'foo@bar.de'
end
end
end
Loading…
Cancel
Save