Make placeholder users EE (#9003)

* Add global permission to manage (but not delete) placeholders

https://community.openproject.com/work_packages/35508

* Show EE upsale and disable creation of placeholders without it

* Review feedback
pull/9020/head
Oliver Günther 4 years ago committed by GitHub
parent 244eae534e
commit af35670694
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 46
      app/contracts/concerns/requires_enterprise_guard.rb
  2. 3
      app/contracts/placeholder_users/create_contract.rb
  3. 31
      app/services/authorization/enterprise_service.rb
  4. 34
      app/views/placeholder_users/index.html.erb
  5. 3
      config/initializers/menus.rb
  6. 9
      config/initializers/permissions.rb
  7. 6
      config/locales/en.yml
  8. 23
      spec/contracts/placeholder_users/create_contract_spec.rb
  9. 94
      spec/controllers/placeholder_users_controller_spec.rb
  10. 30
      spec/features/placeholder_users/create_spec.rb

@ -0,0 +1,46 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2012-2021 the OpenProject GmbH
#
# 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 docs/COPYRIGHT.rdoc for more details.
#++
module RequiresEnterpriseGuard
extend ActiveSupport::Concern
included do
class_attribute :enterprise_action
validate :has_enterprise
end
module_function
def has_enterprise
unless EnterpriseToken.allows_to?(enterprise_action)
errors.add :base, :error_enterprise_only
end
end
end

@ -30,6 +30,9 @@
module PlaceholderUsers
class CreateContract < BaseContract
include RequiresEnterpriseGuard
self.enterprise_action = :placeholder_users
attribute :type
validate :type_is_placeholder_user

@ -31,20 +31,23 @@
class Authorization::EnterpriseService
attr_accessor :token
GUARDED_ACTIONS = %i(define_custom_style
multiselect_custom_fields
edit_attribute_groups
work_package_query_relation_columns
attribute_help_texts
two_factor_authentication
ldap_groups
custom_fields_in_projects_list
custom_actions
conditional_highlighting
readonly_work_packages
attachment_filters
board_view
grid_widget_wp_graph).freeze
GUARDED_ACTIONS = %i(
define_custom_style
multiselect_custom_fields
edit_attribute_groups
work_package_query_relation_columns
attribute_help_texts
two_factor_authentication
ldap_groups
custom_fields_in_projects_list
custom_actions
conditional_highlighting
readonly_work_packages
attachment_filters
board_view
grid_widget_wp_graph
placeholder_users
).freeze
def initialize(token)
self.token = token

@ -27,20 +27,34 @@ See docs/COPYRIGHT.rdoc for more details.
++#%>
<% html_title t(:label_administration), t(:label_placeholder_user_plural) -%>
<% has_ee = EnterpriseToken.allows_to?(:placeholder_users) %>
<%= toolbar title: t(:label_placeholder_user_plural), title_class: 'no-padding-bottom' do %>
<li class="toolbar-item">
<%= link_to new_placeholder_user_path,
{ class: 'button -alt-highlight',
aria: { label: t(:label_placeholder_user_new) },
title: t(:label_placeholder_user_new) } do %>
<%= op_icon('button--icon icon-add') %>
<span class="button--text"><%= t('activerecord.models.placeholder_user') %></span>
<% end %>
</li>
<% if has_ee %>
<li class="toolbar-item">
<%= link_to new_placeholder_user_path,
{ class: 'button -alt-highlight',
aria: { label: t(:label_placeholder_user_new) },
title: t(:label_placeholder_user_new) } do %>
<%= op_icon('button--icon icon-add') %>
<span class="button--text"><%= t('activerecord.models.placeholder_user') %></span>
<% end %>
</li>
<% end %>
<%= call_hook(:placeholder_user_admin_action_menu) %>
<% end %>
<%=
unless has_ee
render template: 'common/upsale',
locals: {
feature_title: I18n.t('placeholder_users.upsale.title'),
feature_description: I18n.t('placeholder_users.upsale.description'),
feature_reference: 'placeholder_users'
}
end
%>
<%= cell PlaceholderUsers::PlaceholderUserFilterCell, params %>
&nbsp;
<%= cell PlaceholderUsers::TableCell, @placeholder_users, project: @project, current_user: current_user %>
<%= cell PlaceholderUsers::TableCell, @placeholder_users, project: @project, current_user: current_user %>

@ -1,5 +1,4 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2012-2021 the OpenProject GmbH
@ -77,7 +76,7 @@ Redmine::MenuManager.map :account_menu do |menu|
if: Proc.new { User.current.logged? }
menu.push :administration,
{ controller: '/admin', action: 'index' },
if: Proc.new { User.current.admin? || User.current.allowed_to_globally?(:add_user) }
if: Proc.new { User.current.allowed_to_globally?(:add_placeholder_user) || User.current.allowed_to_globally?(:add_user) }
menu.push :logout,
:signout_path,
if: Proc.new { User.current.logged? }

@ -67,6 +67,15 @@ OpenProject::AccessControl.map do |map|
require: :loggedin,
global: true
map.permission :add_placeholder_user,
{
placeholder_users: %i[index show new create edit update],
"placeholder_users/memberships": %i[create update destroy],
admin: %i[index]
},
require: :loggedin,
global: true
map.permission :view_project,
{ projects: [:show],
activities: [:index] },

@ -275,6 +275,11 @@ en:
memberships:
no_results_title_text: This user is currently not a member of a project.
placeholder_users:
upsale:
title: 'Placeholder users are an Enterprise Edition feature'
description: 'Placeholder users allow you create a work plan for users that must not have access to the system (yet).'
prioritiies:
edit:
priority_color_text: |
@ -572,6 +577,7 @@ en:
confirmation: "doesn't match %{attribute}."
could_not_be_copied: "%{dependency} could not be (fully) copied."
does_not_exist: "does not exist."
error_enterprise_only: "is only available in the OpenProject Enterprise Edition"
error_unauthorized: "may not be accessed."
error_readonly: "was attempted to be written but is not writable."
empty: "can't be empty."

@ -34,8 +34,27 @@ require_relative 'shared_contract_examples'
describe PlaceholderUsers::CreateContract do
include_context 'ModelContract shared context'
it_behaves_like 'placeholder user contract' do
let(:placeholder_user) { PlaceholderUser.new(name: placeholder_user_name) }
context 'without enterprise' do
let(:placeholder_user) { PlaceholderUser.new(name: 'foo') }
let(:contract) { described_class.new(placeholder_user, current_user) }
context 'when user with global permission' do
let(:current_user) { FactoryBot.create(:user, global_permission: %i[add_placeholder_user]) }
it_behaves_like 'contract is invalid', base: :error_enterprise_only
end
context 'when user with admin permission' do
let(:current_user) { FactoryBot.build_stubbed(:admin) }
it_behaves_like 'contract is invalid', base: :error_enterprise_only
end
end
context 'with enterprise', with_ee: %i[placeholder_users] do
it_behaves_like 'placeholder user contract' do
let(:placeholder_user) { PlaceholderUser.new(name: placeholder_user_name) }
let(:contract) { described_class.new(placeholder_user, current_user) }
end
end
end

@ -100,61 +100,73 @@ describe PlaceholderUsersController, type: :controller do
post :create, params: params
end
it 'should be assigned their new values' do
user_from_db = PlaceholderUser.last
expect(user_from_db.name).to eq('UX Developer')
end
context 'without ee' do
it 'returns with an error' do
expect { post :create, params: params }.not_to change { PlaceholderUser.count }
expect(response).to be_successful
it 'should show a success notice' do
expect(flash[:notice]).to eql(I18n.t(:notice_successful_create))
expect(assigns(:errors).details[:base])
.to eq([error: :error_enterprise_only])
end
end
it 'should not send an email' do
expect(ActionMailer::Base.deliveries.empty?).to be_truthy
end
context 'with ee', with_ee: %i[placeholder_users] do
it 'should be assigned their new values' do
user_from_db = PlaceholderUser.last
expect(user_from_db.name).to eq('UX Developer')
end
context 'when user chose to directly create the next placeholder user' do
let(:params) do
{
placeholder_user: {
name: 'UX Developer'
},
continue: true
}
it 'should show a success notice' do
expect(flash[:notice]).to eql(I18n.t(:notice_successful_create))
end
it 'should redirect to the new page' do
expect(response).to redirect_to(new_placeholder_user_url)
it 'should not send an email' do
expect(ActionMailer::Base.deliveries.empty?).to be_truthy
end
end
context 'when user chose to NOT directly create the next placeholder user' do
let(:params) do
{
placeholder_user: {
name: 'UX Developer'
context 'when user chose to directly create the next placeholder user' do
let(:params) do
{
placeholder_user: {
name: 'UX Developer'
},
continue: true
}
}
end
end
it 'should redirect to the edit page' do
user_from_db = PlaceholderUser.last
expect(response).to redirect_to(edit_placeholder_user_url(user_from_db))
it 'should redirect to the new page' do
expect(response).to redirect_to(new_placeholder_user_url)
end
end
end
context 'invalid params' do
let(:params) do
{
placeholder_user: {
name: 'x' * 300 # Name is too long
context 'when user chose to NOT directly create the next placeholder user' do
let(:params) do
{
placeholder_user: {
name: 'UX Developer'
}
}
}
end
it 'should redirect to the edit page' do
user_from_db = PlaceholderUser.last
expect(response).to redirect_to(edit_placeholder_user_url(user_from_db))
end
end
it 'should render the edit form with a validation error message' do
expect(assigns(:'placeholder_user').errors.messages[:name].first).to include('is too long')
expect(response).to render_template 'placeholder_users/new'
context 'invalid params' do
let(:params) do
{
placeholder_user: {
name: 'x' * 300 # Name is too long
}
}
end
it 'should render the edit form with a validation error message' do
expect(assigns(:'placeholder_user').errors.messages[:name].first).to include('is too long')
expect(response).to render_template 'placeholder_users/new'
end
end
end
end
@ -214,11 +226,13 @@ describe PlaceholderUsersController, type: :controller do
context 'as an admin' do
current_user { FactoryBot.create :admin }
it_behaves_like 'authorized flows'
end
context 'as a user with global permission' do
current_user { FactoryBot.create :user, global_permission: %i[add_placeholder_user] }
it_behaves_like 'authorized flows'
end

@ -32,17 +32,33 @@ describe 'create placeholder users', type: :feature, selenium: true do
let(:new_placeholder_user_page) { Pages::NewPlaceholderUser.new }
shared_examples_for 'placeholders creation flow' do
it 'creates the placeholder user' do
visit new_placeholder_user_path
context 'with enterprise', with_ee: %i[placeholder_users] do
it 'creates the placeholder user' do
visit new_placeholder_user_path
new_placeholder_user_page.fill_in! name: 'UX Designer'
new_placeholder_user_page.submit!
new_placeholder_user_page.fill_in! name: 'UX Designer'
new_placeholder_user_page.submit!
expect(page).to have_selector('.flash', text: 'Successful creation.')
expect(page).to have_selector('.flash', text: 'Successful creation.')
new_placeholder_user = PlaceholderUser.order(Arel.sql('id DESC')).first
expect(current_path).to eql(edit_placeholder_user_path(new_placeholder_user.id))
end
end
context 'without enterprise' do
it 'creates the placeholder user' do
visit new_placeholder_user_path
new_placeholder_user = PlaceholderUser.order(Arel.sql('id DESC')).first
new_placeholder_user_page.fill_in! name: 'UX Designer'
new_placeholder_user_page.submit!
expect(current_path).to eql(edit_placeholder_user_path(new_placeholder_user.id))
expect(page).to have_text 'is only available in the OpenProject Enterprise Edition'
end
end
end

Loading…
Cancel
Save