Ensure user field is not selecatable with only log_own_time permissions

pull/10996/head
Oliver Günther 2 years ago
parent bbab52daa1
commit d37492a1c4
No known key found for this signature in database
GPG Key ID: A3A8BDAD7C0C552C
  1. 20
      docs/api/apiv3/tags/time_entries.yml
  2. 3
      frontend/src/app/shared/components/fields/edit/field-types/user-edit-field.component.html
  3. 2
      frontend/src/app/shared/components/time_entries/form/form.component.html
  4. 3
      modules/costs/app/contracts/time_entries/base_contract.rb
  5. 17
      modules/costs/spec/contracts/time_entries/create_contract_spec.rb
  6. 11
      modules/costs/spec/requests/api/time_entries/create_form_resource_spec.rb
  7. 2
      modules/costs/spec/requests/api/time_entries/update_form_resource_spec.rb
  8. 26
      spec/features/support/components/time_logging_modal.rb
  9. 139
      spec/features/work_packages/display_fields/spent_time_display_spec.rb
  10. 17
      spec/support/edit_fields/edit_field.rb

@ -10,13 +10,13 @@ description: |-
## Linked Properties
| Link | Description | Type | Constraints | Supported operations | Condition |
| :-----------: | -------------------------------------------------------------- | ------------- | --------------------- | -------------------- | ----------------------------------------- |
| self | This time entry | TimeEntry | not null | READ | |
| project | The project the time entry is bundled in. The project might be different from the work package's project once the workPackage is moved. | Project | not null | READ / WRITE | |
| workPackage | The work package the time entry is created on | WorkPackage | | READ / WRITE | |
| user | The user the time entry tracks expenditures for | User | not null | READ | |
| activity | The time entry activity the time entry is categorized as | TimeEntriesActivity | not null | READ / WRITE | |
| Link | Description | Type | Constraints | Supported operations | Condition |
| :-----------: | -------------------------------------------------------------- | ------------- | --------------------- | -------------------- | --------------------------------------------------------- |
| self | This time entry | TimeEntry | not null | READ | |
| project | The project the time entry is bundled in. The project might be different from the work package's project once the workPackage is moved. | Project | not null | READ / WRITE | |
| workPackage | The work package the time entry is created on | WorkPackage | | READ / WRITE | |
| user | The user the time entry tracks expenditures for | User | not null | READ / WRITE | **Permission**: Log time for other users permission |
| activity | The time entry activity the time entry is categorized as | TimeEntriesActivity | not null | READ / WRITE | |
Depending on custom fields defined for time entries, additional links might exist.
@ -28,10 +28,10 @@ description: |-
| :----------: | --------------------------------------------------------- | -------- | ---------------------------------------------------- | -------------------- | ----------------------------------------------------------- |
| id | Time entries' id | Integer | x > 0 | READ | |
| comment | A text provided by the user detailing the time entry | String | max 255 characters | READ / WRITE | |
| spentOn | The date the expenditure is booked for | Date | | READ / WRITE |   |
| spentOn | The date the expenditure is booked for | Date | | READ / WRITE | |
| hours | The time quantifying the expenditure | Time | | READ / WRITE | |
| createdAt | The time the time entry was created | DateTime | | READ |   |
| updatedAt | The time the time entry was last updated | DateTime | | READ |   |
| createdAt | The time the time entry was created | DateTime | | READ | |
| updatedAt | The time the time entry was last updated | DateTime | | READ | |
Depending on custom fields defined for time entries, additional properties might exist.
name: Time Entries

@ -5,9 +5,10 @@
[url]="url"
[inviteUserToProject]="resource.project?.id"
[focusDirectly]="!(handler.inEditMode || isNew)"
[openDirectly]="!isNew"
[openDirectly]="!(handler.inEditMode || isNew)"
appendTo="body"
[value]="value"
[id]="handler.htmlId"
(valueChange)="onModelChange($event)"
(cancel)="handler.handleUserCancel()"
(userInvited)="onModelChange($event)"

@ -4,7 +4,7 @@
[inEditMode]="inEditMode"
(onSaved)="signalModifiedEntry($event)">
<div class="attributes-map">
<ng-container *ngIf="showUserField">
<ng-container *ngIf="showUserField && schema.user.writable">
<div class="attributes-map--key"
[ngClass]="{'-required': isRequired('user')}"
[textContent]="schema.user.name">

@ -58,7 +58,8 @@ module TimeEntries
attribute :tyear
attribute :tmonth
attribute :tweek
attribute :user_id
attribute :user_id,
permission: :log_time
def assignable_activities
if model.project

@ -65,6 +65,23 @@ describe TimeEntries::CreateContract do
end
end
context 'if user has only permission to log own time' do
let(:permissions) { %i[log_own_time] }
it 'is valid' do
expect_valid(true)
end
context 'when trying to log for other user' do
let(:time_entry_user) { build_stubbed(:user) }
let(:changed_by_system) { {} }
it 'is invalid' do
expect_valid(false, base: %i(error_unauthorized))
end
end
end
context 'if time_entry user is not contract user' do
let(:other_user) do
build_stubbed(:user) do |user|

@ -49,7 +49,7 @@ describe ::API::V3::TimeEntries::CreateFormAPI, content_type: :json do
create(:work_package, project:)
end
let(:other_user) { create(:user) }
let(:permissions) { %i[log_own_time view_work_packages] }
let(:permissions) { %i[log_time view_work_packages] }
let(:path) { api_v3_paths.create_time_entry_form }
let(:parameters) { {} }
@ -199,5 +199,14 @@ describe ::API::V3::TimeEntries::CreateFormAPI, content_type: :json do
expect(response.status).to eq(403)
end
end
context 'without the log_time permisson' do
let(:permissions) { %i[log_own_time view_work_packages] }
it 'does not render the user href' do
expect(body)
.not_to have_json_path('_embedded/payload/_links/user/href')
end
end
end
end

@ -50,7 +50,7 @@ describe ::API::V3::TimeEntries::UpdateFormAPI, content_type: :json do
create(:work_package, project:)
end
let(:other_user) { create(:user) }
let(:permissions) { %i[view_time_entries edit_time_entries view_work_packages] }
let(:permissions) { %i[view_time_entries log_time edit_time_entries view_work_packages] }
let(:path) { api_v3_paths.time_entry_form(time_entry.id) }
let(:parameters) { {} }

@ -35,7 +35,8 @@ module Components
:comment_field,
:hours_field,
:spent_on_field,
:work_package_field
:work_package_field,
:user_field
def initialize
@activity_field = EditField.new(page, 'activity')
@ -43,6 +44,7 @@ module Components
@hours_field = EditField.new(page, 'hours')
@spent_on_field = EditField.new(page, 'spentOn')
@work_package_field = EditField.new(page, 'workPackage')
@user_field = EditField.new(page, 'user')
end
def is_visible(visible)
@ -74,8 +76,13 @@ module Components
def update_field(field_name, value)
field = field_object field_name
field.input_element.click
field.set_value value
if field_name == 'user'
field.unset_value
field.autocomplete value
else
field.input_element.click
field.set_value value
end
end
def update_work_package_field(value, recent = false)
@ -120,18 +127,7 @@ module Components
end
def field_object(field_name)
case field_name
when 'activity'
activity_field
when 'hours'
hours_field
when 'spent_on'
spent_on_field
when 'comment'
comment_field
when 'work_package'
work_package_field
end
send("#{field_name}_field")
end
def modal_container

@ -29,31 +29,35 @@
require 'spec_helper'
describe 'Logging time within the work package view', type: :feature, js: true do
let(:project) { create :project }
let(:admin) { create :admin }
let(:user_without_permissions) do
create(:user,
member_in_project: project,
member_with_permissions: %i[view_time_entries view_work_packages edit_work_packages])
end
shared_let(:project) { create :project }
shared_let(:admin) { create :admin }
shared_let(:work_package) { create :work_package, project: }
shared_let(:activity) { create :time_entry_activity, project: }
let(:user) { admin }
let!(:activity) { create :time_entry_activity, project: }
let(:spent_time_field) { ::SpentTimeEditField.new(page, 'spentTime') }
let(:work_package) { create :work_package, project: }
let(:wp_page) { Pages::FullWorkPackage.new(work_package, project) }
let(:time_logging_modal) { ::Components::TimeLoggingModal.new }
def log_time_via_modal
def log_time_via_modal(user_field_visible: true, log_for_user: nil)
time_logging_modal.is_visible true
# the fields are visible
time_logging_modal.has_field_with_value 'spent_on', Date.today.strftime("%Y-%m-%d")
time_logging_modal.shows_field 'work_package', false
time_logging_modal.shows_field 'user', user_field_visible
time_logging_modal.update_field 'activity', activity.name
if log_for_user
time_logging_modal.update_field 'user', log_for_user.name
elsif user_field_visible
expect(page).to have_selector('.ng-value-label', text: user.name)
end
# a click on save creates a time entry
time_logging_modal.perform_action I18n.t('js.label_create')
wp_page.expect_and_dismiss_toaster message: I18n.t('js.notice_successful_create')
@ -61,7 +65,7 @@ describe 'Logging time within the work package view', type: :feature, js: true d
context 'as an admin' do
before do
login_as(admin)
login_as(user)
wp_page.visit!
loading_indicator_saveguard
spent_time_field.time_log_icon_visible true
@ -77,6 +81,30 @@ describe 'Logging time within the work package view', type: :feature, js: true d
spent_time_field.expect_display_value '1 h'
end
context 'with another user in the project' do
let!(:other_user) do
create(:user,
firstname: 'Loggable',
lastname: 'User',
member_in_project: project,
member_with_permissions: %i[view_work_packages edit_work_packages work_package_assigned])
end
it 'can log time for that user' do
# click on button opens modal
spent_time_field.open_time_log_modal
log_time_via_modal log_for_user: other_user
# the value is updated automatically
spent_time_field.expect_display_value '1 h'
time_entry = TimeEntry.last
expect(time_entry.user).to eq other_user
expect(time_entry.logged_by).to eq user
end
end
it 'the context menu entry to log time leads to the modal' do
# click on context menu opens the modal
find('#action-show-more-dropdown-menu .button').click
@ -89,7 +117,7 @@ describe 'Logging time within the work package view', type: :feature, js: true d
end
context 'with a user with non-one unit numbers', with_settings: { available_languages: %w[en ja] } do
let(:admin) { create :admin, language: 'ja' }
let(:user) { create :admin, language: 'ja' }
before do
I18n.locale = 'ja'
@ -105,44 +133,75 @@ describe 'Logging time within the work package view', type: :feature, js: true d
spent_time_field.expect_display_value '1 h'
end
end
end
context 'as a user who cannot log time' do
before do
login_as(user_without_permissions)
wp_page.visit!
loading_indicator_saveguard
end
context 'as a user who cannot log time' do
let(:user) do
create(:user,
member_in_project: project,
member_with_permissions: %i[view_time_entries view_work_packages edit_work_packages])
end
it 'shows no logging button within the display field' do
spent_time_field.time_log_icon_visible false
spent_time_field.expect_display_value '-'
end
before do
login_as(user)
wp_page.visit!
loading_indicator_saveguard
end
it 'shows no logging button within the display field' do
spent_time_field.time_log_icon_visible false
spent_time_field.expect_display_value '-'
end
end
context 'within the table' do
let(:wp_table) { Pages::WorkPackagesTable.new(project) }
let(:second_work_package) { create :work_package, project: }
let(:query) { create :public_query, project:, column_names: ['subject', 'spent_hours'] }
context 'as a user who can only log own time' do
let(:user) do
create(:user,
member_in_project: project,
member_with_permissions: %i[view_time_entries view_work_packages log_own_time])
end
before do
work_package
second_work_package
login_as(admin)
before do
login_as(user)
wp_page.visit!
loading_indicator_saveguard
end
wp_table.visit_query query
loading_indicator_saveguard
end
it 'can log its own time' do
spent_time_field.time_log_icon_visible true
# click on button opens modal
spent_time_field.open_time_log_modal
it 'shows no logging button within the display field' do
wp_table.expect_work_package_listed work_package, second_work_package
log_time_via_modal user_field_visible: false
find('tr:nth-of-type(1) .wp-table--cell-td.spentTime .icon-time').click
# the value is updated automatically
spent_time_field.expect_display_value '1 h'
end
end
log_time_via_modal
context 'when in the table' do
let(:wp_table) { Pages::WorkPackagesTable.new(project) }
let(:second_work_package) { create :work_package, project: }
let(:query) { create :public_query, project:, column_names: ['subject', 'spent_hours'] }
expect(page).to have_selector('tr:nth-of-type(1) .wp-table--cell-td.spentTime', text: '1 h')
expect(page).to have_selector('tr:nth-of-type(2) .wp-table--cell-td.spentTime', text: '-')
end
before do
work_package
second_work_package
login_as(admin)
wp_table.visit_query query
loading_indicator_saveguard
end
it 'shows no logging button within the display field' do
wp_table.expect_work_package_listed work_package, second_work_package
find('tr:nth-of-type(1) .wp-table--cell-td.spentTime .icon-time').click
log_time_via_modal
expect(page).to have_selector('tr:nth-of-type(1) .wp-table--cell-td.spentTime', text: '1 h')
expect(page).to have_selector('tr:nth-of-type(2) .wp-table--cell-td.spentTime', text: '-')
end
end
end

@ -243,21 +243,16 @@ class EditField
end
def derive_field_type
case property_name.to_s
when 'version'
case property_name.to_sym
when :version
'version-autocompleter'
when 'assignee',
'responsible'
when :assignee, :responsible, :user
'op-user-autocompleter'
when 'priority',
'status',
'type',
'category',
'workPackage'
when :priority, :status, :type, :category, :workPackage
'create-autocompleter'
when 'project'
when :project
'op-autocompleter'
when 'activity'
when :activity
'activity-autocompleter'
else
'input'

Loading…
Cancel
Save