Merge branch 'dev' of github.com:opf/openproject into dev

pull/1716/head
Hagen Schink 10 years ago
commit ef78e8bf50
  1. 11
      app/assets/javascripts/angular/helpers/components/work-packages-helper.js
  2. 5
      app/controllers/auth_sources_controller.rb
  3. 17
      app/controllers/settings_controller.rb
  4. 7
      app/controllers/users_controller.rb
  5. 6
      app/models/custom_field.rb
  6. 2
      app/models/custom_value.rb
  7. 8
      app/views/api/experimental/work_packages/index.api.rabl
  8. 18
      app/views/settings/_authentication.html.erb
  9. 23
      app/views/users/_form.html.erb
  10. 2
      app/views/users/index.html.erb
  11. 9
      config/locales/de.yml
  12. 9
      config/locales/en.yml
  13. 6
      karma/tests/controllers/details-tab-overview-controller-test.js
  14. 9
      karma/tests/controllers/work-package-details-controller-test.js
  15. 9
      karma/tests/controllers/work-packages-controller-test.js
  16. 9
      karma/tests/controllers/work-packages-list-controller-test.js
  17. 2
      karma/tests/directives/components/date-time-directive-test.js
  18. 12
      karma/tests/directives/components/toggled-multiselect-directive-test.js
  19. 12
      karma/tests/directives/work_packages/options-dropdown-directive-test.js
  20. 8
      karma/tests/directives/work_packages/work-package-column-directive-test.js
  21. 10
      karma/tests/directives/work_packages/work-package-group-sums-directive-test.js
  22. 12
      karma/tests/directives/work_packages/work-package-relations-directive-test.js
  23. 10
      karma/tests/directives/work_packages/work-package-total-sums-directive-test.js
  24. 19
      karma/tests/helpers/components/work-packages-helper-test.js
  25. 10
      karma/tests/helpers/work-package-context-menu-helper-test.js
  26. 9
      karma/tests/helpers/work-package-table-helper-test.js
  27. 12
      karma/tests/layout/query-menu-item-factory-test.js
  28. 13
      karma/tests/services/query-service-test.js
  29. 8
      karma/tests/services/work-package-service-test.js
  30. 9
      karma/tests/work_packages/column-context-menu-test.js
  31. 9
      karma/tests/work_packages/work-package-context-menu-test.js
  32. 6
      lib/redmine.rb
  33. 44
      spec/controllers/auth_sources_controller_spec.rb
  34. 115
      spec/controllers/settings_controller_spec.rb
  35. 96
      spec/controllers/users_controller_spec.rb
  36. 86
      spec/features/users/edit_users_spec.rb
  37. 18
      spec/models/custom_field_spec.rb
  38. 26
      spec/views/api/experimental/work_packages/index_api_json_spec.rb
  39. 71
      spec/views/layouts/admin.html.erb_spec.rb
  40. 61
      spec/views/settings/_authentication.html.erb_spec.rb
  41. 74
      spec/views/users/edit.html.erb_spec.rb

@ -28,7 +28,7 @@
angular.module('openproject.workPackages.helpers')
.factory('WorkPackagesHelper', ['dateFilter', 'currencyFilter', 'CustomFieldHelper', function(dateFilter, currencyFilter, CustomFieldHelper) {
.factory('WorkPackagesHelper', ['TimezoneService', 'currencyFilter', 'CustomFieldHelper', function(TimezoneService, currencyFilter, CustomFieldHelper) {
var WorkPackagesHelper = {
getRowObjectContent: function(object, option) {
var content;
@ -97,15 +97,20 @@ angular.module('openproject.workPackages.helpers')
formatValue: function(value, dataType) {
switch(dataType) {
case 'datetime':
return value ? dateFilter(WorkPackagesHelper.parseDateTime(value), 'medium') : '';
var dateTime;
if (value) {
dateTime = TimezoneService.formattedDate(value) + " " + TimezoneService.formattedTime(value);
}
return dateTime || '';
case 'date':
return value ? dateFilter(WorkPackagesHelper.parseDateTime(value), 'mediumDate') : '';
return value ? TimezoneService.formattedDate(value) : '';
case 'currency':
return currencyFilter(value, 'EUR ');
default:
return value;
}
},
formatWorkPackageProperty: function(value, propertyName) {
var mappings = {
dueDate: 'date',

@ -32,6 +32,7 @@ class AuthSourcesController < ApplicationController
layout 'admin'
before_filter :require_admin
before_filter :block_if_password_login_disabled
def index
@auth_sources = AuthSource.page(params[:page])
@ -99,4 +100,8 @@ class AuthSourcesController < ApplicationController
def default_breadcrumb
l(:label_auth_source_plural)
end
def block_if_password_login_disabled
render_404 if OpenProject::Configuration.disable_password_login?
end
end

@ -40,12 +40,16 @@ class SettingsController < ApplicationController
def edit
@notifiables = Redmine::Notifiable.all
if request.post? && params[:settings] && params[:settings].is_a?(Hash)
settings = (params[:settings] || {}).dup.symbolize_keys
settings = (params[:settings] || {}).dup.symbolize_keys.tap do |set|
set.except! *password_settings if OpenProject::Configuration.disable_password_login?
end
settings.each do |name, value|
# remove blank values in array settings
value.delete_if {|v| v.blank? } if value.is_a?(Array)
Setting[name] = value
end
flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'edit', :tab => params[:tab]
else
@ -74,4 +78,15 @@ class SettingsController < ApplicationController
def default_breadcrumb
l(:label_settings)
end
private
##
# Returns all password-login related setting keys.
def password_settings
[
:password_min_length, :password_active_rules, :password_min_adhered_rules,
:password_days_valid, :password_count_former_banned, :lost_password
]
end
end

@ -44,6 +44,9 @@ class UsersController < ApplicationController
before_filter :authorize_for_user, :only => [:destroy]
before_filter :check_if_deletion_allowed, :only => [:deletion_info,
:destroy]
before_filter :block_if_password_login_disabled, :only => [:new, :create]
accept_key_auth :index, :show, :create, :update, :destroy
include SortHelper
@ -352,4 +355,8 @@ class UsersController < ApplicationController
'admin'
end
end
def block_if_password_login_disabled
render_404 if OpenProject::Configuration.disable_password_login?
end
end

@ -75,9 +75,9 @@ class CustomField < ActiveRecord::Base
validate :validate_name
validates :min_length, numericality: { only_integer: true, greater_than_or_equal: 0 }
validates :max_length, numericality: { only_integer: true, greater_than_or_equal: 0 }
validates :min_length, numericality: { less_than_or_equal_to: :max_length, message: :greater_than_or_equal_to_max_length}, unless: Proc.new { |cf| cf.max_length.blank?}
validates :min_length, numericality: { only_integer: true, greater_than_or_equal_to: 0 }
validates :max_length, numericality: { only_integer: true, greater_than_or_equal_to: 0 }
validates :min_length, numericality: { less_than_or_equal_to: :max_length, message: :smaller_than_or_equal_to_max_length}, unless: Proc.new { |cf| cf.max_length.blank?}

@ -94,7 +94,7 @@ class CustomValue < ActiveRecord::Base
end
def validate_length_of_value
if value.present?
if value.present? && custom_field.min_length.present? && custom_field.max_length.present?
errors.add(:value, :too_short, :count => custom_field.min_length) if custom_field.min_length > 0 and value.length < custom_field.min_length
errors.add(:value, :too_long, :count => custom_field.max_length) if custom_field.max_length > 0 and value.length > custom_field.max_length
end

@ -61,6 +61,14 @@ child @work_packages => :work_packages do
wp.parent_id
end
node :updated_at do |wp|
wp.updated_at.utc.iso8601
end
node :created_at do |wp|
wp.updated_at.utc.iso8601
end
node :_actions do |wp|
@can.actions(wp)
end

@ -42,6 +42,7 @@ See doc/COPYRIGHT.rdoc for more details.
<fieldset>
<legend><%= I18n.t(:passwords, :scope => [:settings]) %></legend>
<% if !OpenProject::Configuration.disable_password_login? %>
<p><%= setting_text_field :password_min_length, :size => 6 %></p>
<p><%= setting_multiselect :password_active_rules,
OpenProject::Passwords::Evaluator.known_rules.map do |r|
@ -51,13 +52,28 @@ See doc/COPYRIGHT.rdoc for more details.
<p><%= setting_text_field :password_days_valid, :size => 6 %></p>
<p><%= setting_text_field :password_count_former_banned, :size => 6 %></p>
<p><%= setting_check_box :lost_password, :label => :label_password_lost %></p>
<% else %>
<p>
<label><%= I18n.t :note %></label>
<%=
url = 'https://github.com/opf/openproject/blob/dev/doc/CONFIGURATION.md#disable-password-login'
explanation = I18n.t :note_password_login_disabled,
:configuration => "<a href=\"#{url}\">#{I18n.t('label_configuration')}</a>"
explanation.html_safe
%>
</p>
<% end %>
</fieldset>
<% unless OpenProject::Configuration.disable_password_login? %>
<fieldset>
<legend><%= I18n.t(:brute_force_prevention, :scope => [:settings]) %></legend>
<p><%= setting_text_field :brute_force_block_after_failed_logins %></p>
<p><%= setting_text_field :brute_force_block_minutes %></p>
</fieldset>
<% end %>
<fieldset>
<legend><%= I18n.t(:session, :scope => [:settings]) %></legend>
@ -82,9 +98,11 @@ See doc/COPYRIGHT.rdoc for more details.
</fieldset>
</div>
<% unless OpenProject::Configuration.disable_password_login? %>
<div style="float:right;">
<%= link_to l(:label_ldap_authentication), {:controller => '/ldap_auth_sources', :action => 'index'}, :class => 'icon icon-server-key' %>
</div>
<% end %>
<%= submit_tag l(:button_save) %>
<% end %>

@ -61,11 +61,19 @@ See doc/COPYRIGHT.rdoc for more details.
</p>
<%= I18n.t('user.authentication_settings_disabled_due_to_external_authentication') %>
<% else %>
<% unless @auth_sources.empty? %>
<% unless @auth_sources.empty? || OpenProject::Configuration.disable_password_login? %>
<p><%= f.select :auth_source_id, ([[l(:label_internal), ""]] + @auth_sources.collect { |a| [a.name, a.id] }) %></p>
<% end %>
<% end %>
<div id="password_fields" style="<%= 'display:none;' unless @user.change_password_allowed? %>">
<% if !OpenProject::Configuration.disable_password_login? %>
<%
pw_style =
if @user.change_password_allowed?
''
else
' style="display: none;"'
end
%>
<div id="password_fields"<%= pw_style.html_safe %>>
<% assign_random_password_enabled = params[:user] &&
params[:user][:assign_random_password] %>
<p>
@ -94,6 +102,15 @@ See doc/COPYRIGHT.rdoc for more details.
:disabled => assign_random_password_enabled %>
</p>
</div>
<% else %>
<div id="no_password_info">
<p>
<label><%= I18n.t 'warning' %></label>
<%= I18n.t 'user.no_login' %>
</p>
</div>
<% end %>
<% end %>
</div>
<div class="box">

@ -27,9 +27,11 @@ See doc/COPYRIGHT.rdoc for more details.
++#%>
<% unless OpenProject::Configuration.disable_password_login? %>
<% content_for :action_menu_specific do %>
<%= link_to l(:label_user_new), {:action => 'new'}, :class => 'icon icon-add' %>
<% end %>
<% end %>
<h2><%=l(:label_user_plural)%></h2>

@ -196,7 +196,7 @@ de:
exclusion: "ist nicht verfügbar"
greater_than: "muss größer als %{count} sein"
greater_than_or_equal_to: "muss größer oder gleich %{count} sein"
greater_than_or_equal_to_max_length: "muss größer oder gleich der maximalen Länge sein"
smaller_than_or_equal_to_max_length: "muss kleiner als oder gleich der maximalen Länge sein"
greater_than_start_date: "muss größer als Anfangsdatum sein"
inclusion: "ist kein gültiger Wert"
invalid: "ist nicht gültig"
@ -644,6 +644,7 @@ de:
label_close_versions: "Vollständige Versionen schließen"
label_closed_work_packages: "geschlossen"
label_collapse: "Zuklappen"
label_configuration: Konfiguration
label_comment_add: "Kommentar hinzufügen"
label_comment_added: "Kommentar hinzugefügt"
label_comment_delete: "Kommentar löschen"
@ -1606,6 +1607,8 @@ de:
status_user_and_brute_force: "%{user} und %{brute_force}"
unlock: "Entsperren"
unlock_and_reset_failed_logins: "Entsperren und fehlgeschlagene Logins zurücksetzen"
no_login: "Dieser Nutzer wird per Passwort authentifiziert. Er kann sich jedoch nicht einloggen, da Login per Passwort deaktiviert ist."
password_change_unsupported: Passwortänderung wird nicht unterstützt
authorization_rejected: "Sie dürfen sich nicht einloggen."
version_status_closed: "abgeschlossen"
@ -1613,6 +1616,10 @@ de:
version_status_open: "offen"
warning_attachments_not_saved: "%{count} Datei(en) konnten nicht gespeichert werden."
note: Hinweis
note_password_login_disabled: "Der Passwort-Login wurde per %{configuration} deaktiviert."
warning: Warnung
menu_item: "Menüpunkt"
menu_item_setting: "Sichtbarkeit"
wiki_menu_item_for: "Menüpunkt für die Wikiseite \"%{title}\""

@ -196,7 +196,7 @@ en:
exclusion: "is reserved"
greater_than: "must be greater than %{count}"
greater_than_or_equal_to: "must be greater than or equal to %{count}"
greater_than_or_equal_to_max_length: "must be greater than or equal to maximum length"
smaller_than_or_equal_to_max_length: "must be smaller than or equal to maximum length"
greater_than_start_date: "must be greater than start date"
inclusion: "is not included in the list"
invalid: "is invalid"
@ -641,6 +641,7 @@ en:
label_close_versions: "Close completed versions"
label_closed_work_packages: "closed"
label_collapse: "Collapse"
label_configuration: configuration
label_comment_add: "Add a comment"
label_comment_added: "Comment added"
label_comment_delete: "Delete comments"
@ -1596,13 +1597,17 @@ en:
status_user_and_brute_force: "%{user} and %{brute_force}"
unlock: "Unlock"
unlock_and_reset_failed_logins: "Unlock and reset failed logins"
no_login: "This user authenticates through login by password. Since it is disabled, they cannot log in."
password_change_unsupported: Change of password is not supported.
authorization_rejected: "You are not allowed to sign in."
version_status_closed: "closed"
version_status_locked: "locked"
version_status_open: "open"
note: Note
note_password_login_disabled: "Password login has been disabled by %{configuration}."
warning: Warning
warning_attachments_not_saved: "%{count} file(s) could not be saved."
menu_item: "Menu item"

@ -241,7 +241,7 @@ describe('DetailsTabOverviewController', function() {
});
it('renders the due date and a placeholder for the start date as date property', function() {
expect(fetchPresentPropertiesWithName('date')[0].value).to.equal(placeholder + ' - Jul 10, 2014');
expect(fetchPresentPropertiesWithName('date')[0].value).to.equal(placeholder + ' - 07/10/2014');
});
});
@ -262,7 +262,7 @@ describe('DetailsTabOverviewController', function() {
});
it('renders the start date and a placeholder for the due date as date property', function() {
expect(fetchPresentPropertiesWithName('date')[0].value).to.equal('Jul 9, 2014 - ' + placeholder);
expect(fetchPresentPropertiesWithName('date')[0].value).to.equal('07/09/2014 - ' + placeholder);
});
});
@ -275,7 +275,7 @@ describe('DetailsTabOverviewController', function() {
});
it('combines them and renders them as date property', function() {
expect(fetchPresentPropertiesWithName('date')[0].value).to.equal('Jul 9, 2014 - Jul 10, 2014');
expect(fetchPresentPropertiesWithName('date')[0].value).to.equal('07/09/2014 - 07/10/2014');
});
});
});

@ -87,7 +87,14 @@ describe('WorkPackageDetailsController', function() {
return workPackage;
}
beforeEach(module('openproject.api', 'openproject.services', 'openproject.workPackages.controllers'));
beforeEach(module('openproject.api', 'openproject.services', 'openproject.workPackages.controllers', 'openproject.services'));
beforeEach(module('templates', function($provide) {
configurationService = new Object();
configurationService.isTimezoneSet = sinon.stub().returns(false);
$provide.constant('ConfigurationService', configurationService);
}));
beforeEach(inject(function($rootScope, $controller, $timeout) {
var workPackageId = 99;

@ -31,7 +31,14 @@
describe('WorkPackagesController', function() {
var scope, win, testParams, buildController;
beforeEach(module('openproject.workPackages.controllers', 'openproject.api'));
beforeEach(module('openproject.workPackages.controllers', 'openproject.api', 'openproject.services'));
beforeEach(module('templates', function($provide) {
configurationService = new Object();
configurationService.isTimezoneSet = sinon.stub().returns(false);
$provide.constant('ConfigurationService', configurationService);
}));
beforeEach(inject(function($rootScope, $controller, $timeout) {
scope = $rootScope.$new();
}));

@ -33,7 +33,14 @@ describe('WorkPackagesListController', function() {
testProjectService, testWorkPackageService, testQueryService, testPaginationService;
var buildController;
beforeEach(module('openproject.api', 'openproject.workPackages.controllers', 'openproject.workPackages.services', 'ng-context-menu', 'btford.modal'));
beforeEach(module('openproject.api', 'openproject.workPackages.controllers', 'openproject.workPackages.services', 'ng-context-menu', 'btford.modal', 'openproject.services'));
beforeEach(module('templates', function($provide) {
configurationService = new Object();
configurationService.isTimezoneSet = sinon.stub().returns(false);
$provide.constant('ConfigurationService', configurationService);
}));
beforeEach(inject(function($rootScope, $controller, $timeout) {
scope = $rootScope.$new();
win = {

@ -27,7 +27,7 @@
//++
describe('date time Directives', function() {
var I18n, compile, element, scope, timezoneService, configurationService;
var I18n, compile, element, scope, configurationService;
var formattedDate = function() {
var formattedDateElement = element[0];

@ -29,8 +29,16 @@
describe('toggledMultiselect Directive', function() {
var compile, element, rootScope, scope;
beforeEach(angular.mock.module('openproject.uiComponents', 'openproject.workPackages.helpers'));
beforeEach(module('templates'));
beforeEach(angular.mock.module('openproject.uiComponents',
'openproject.workPackages.helpers',
'openproject.services'));
beforeEach(module('templates', function($provide) {
configurationService = new Object();
configurationService.isTimezoneSet = sinon.stub().returns(false);
$provide.constant('ConfigurationService', configurationService);
}));
beforeEach(inject(function($rootScope, $compile) {
var html;

@ -30,7 +30,17 @@ describe('optionsDropdown Directive', function() {
var compile, element, rootScope, scope;
beforeEach(angular.mock.module('openproject.workPackages.directives'));
beforeEach(module('openproject.models', 'openproject.workPackages.controllers', 'openproject.api'));
beforeEach(module('openproject.models',
'openproject.workPackages.controllers',
'openproject.api',
'openproject.services'));
beforeEach(module('templates', function($provide) {
configurationService = new Object();
configurationService.isTimezoneSet = sinon.stub().returns(false);
$provide.constant('ConfigurationService', configurationService);
}));
beforeEach(module('templates', function($provide) {
var state = { go: function() { return false; } };

@ -30,8 +30,14 @@ describe('workPackageColumn Directive', function() {
var compile, element, rootScope, scope;
beforeEach(angular.mock.module('openproject.workPackages.directives'));
beforeEach(module('templates', 'openproject.api'));
beforeEach(module('templates', 'openproject.api', 'openproject.services'));
beforeEach(module('templates', function($provide) {
configurationService = new Object();
configurationService.isTimezoneSet = sinon.stub().returns(false);
$provide.constant('ConfigurationService', configurationService);
}));
beforeEach(inject(function($rootScope, $compile) {
var html;
html = '<span work-package-column work-package="workPackage" column="column" display-type="displayType" display-empty="-"></span>';

@ -29,8 +29,14 @@
describe('workPackageGroupSums Directive', function() {
var compile, element, rootScope, scope;
beforeEach(angular.mock.module('openproject.workPackages.directives'));
beforeEach(module('openproject.api', 'templates'));
beforeEach(angular.mock.module('openproject.workPackages.directives', 'openproject.services'));
beforeEach(module('openproject.api', 'templates', function($provide) {
configurationService = new Object();
configurationService.isTimezoneSet = sinon.stub().returns(false);
$provide.constant('ConfigurationService', configurationService);
}));
beforeEach(inject(function($rootScope, $compile) {
var html;

@ -29,8 +29,18 @@
describe('Work Package Relations Directive', function() {
var I18n, PathHelper, compile, element, scope;
beforeEach(angular.mock.module('openproject.workPackages.tabs', 'openproject.api', 'openproject.helpers', 'ngSanitize'));
beforeEach(angular.mock.module('openproject.workPackages.tabs',
'openproject.api',
'openproject.helpers',
'openproject.services',
'ngSanitize'));
beforeEach(module('templates', function($provide) {
configurationService = new Object();
configurationService.isTimezoneSet = sinon.stub().returns(false);
$provide.constant('ConfigurationService', configurationService);
}));
beforeEach(inject(function($rootScope, $compile, _I18n_, _PathHelper_, _WorkPackagesHelper_) {

@ -29,8 +29,14 @@
describe('workPackageTotalSums Directive', function() {
var compile, element, rootScope, scope;
beforeEach(angular.mock.module('openproject.workPackages.directives'));
beforeEach(module('openproject.api', 'templates'));
beforeEach(angular.mock.module('openproject.workPackages.directives', 'openproject.services'));
beforeEach(module('openproject.api', 'templates', function($provide) {
configurationService = new Object();
configurationService.isTimezoneSet = sinon.stub().returns(false);
$provide.constant('ConfigurationService', configurationService);
}));
beforeEach(inject(function($rootScope, $compile) {
var html;

@ -31,7 +31,16 @@
describe('Work packages helper', function() {
var WorkPackagesHelper;
beforeEach(module('openproject.helpers'));
beforeEach(module('openproject.helpers', 'openproject.services'));
beforeEach(module('templates', function($provide) {
configurationService = new Object();
configurationService.isTimezoneSet = sinon.stub();
configurationService.dateFormatPresent = sinon.stub();
configurationService.timeFormatPresent = sinon.stub();
$provide.constant('ConfigurationService', configurationService);
}));
beforeEach(inject(function(_WorkPackagesHelper_) {
WorkPackagesHelper = _WorkPackagesHelper_;
}));
@ -128,9 +137,13 @@ describe('Work packages helper', function() {
expect(formatValue(null, 'date')).to.equal("");
});
var TIME = '2014-01-01T00:00:00';
var EXPECTED_DATE = '01/01/2014';
var EXPECTED_DATETIME = '01/01/2014 12:00 AM';
it('should display parsed dates and datetimes', function(){
expect(formatValue("01/01/2014", 'date')).to.equal("Jan 1, 2014");
expect(formatValue("01/01/2014 08:19 AM", 'datetime')).to.equal("Jan 1, 2014 12:00:00 AM");
expect(formatValue(TIME, 'date')).to.equal(EXPECTED_DATE);
expect(formatValue(TIME, 'datetime')).to.equal(EXPECTED_DATETIME);
})
});

@ -31,7 +31,15 @@
describe('WorkPackageContextMenuHelper', function() {
var WorkPackageContextMenuHelper;
beforeEach(module('openproject.workPackages.helpers', 'openproject.models', 'openproject.api'));
beforeEach(module('openproject.workPackages.helpers', 'openproject.models', 'openproject.api', 'openproject.services'));
beforeEach(module('templates', function($provide) {
configurationService = new Object();
configurationService.isTimezoneSet = sinon.stub().returns(false);
$provide.constant('ConfigurationService', configurationService);
}));
beforeEach(inject(function(_WorkPackageContextMenuHelper_) {
WorkPackageContextMenuHelper = _WorkPackageContextMenuHelper_;

@ -31,7 +31,14 @@
describe('WorkPackagesTableHelper', function() {
var WorkPackagesTableHelper;
beforeEach(module('openproject.workPackages.helpers'));
beforeEach(module('openproject.workPackages.helpers', 'openproject.services'));
beforeEach(module('templates', function($provide) {
configurationService = new Object();
configurationService.isTimezoneSet = sinon.stub().returns(false);
$provide.constant('ConfigurationService', configurationService);
}));
beforeEach(inject(function(_WorkPackagesTableHelper_) {
WorkPackagesTableHelper = _WorkPackagesTableHelper_;
}));

@ -33,7 +33,17 @@ describe('queryMenuItemFactory', function() {
queryMenuItemFactory, stateParams = {};
beforeEach(angular.mock.module('openproject.layout'));
beforeEach(module('templates', 'openproject.services', 'openproject.models', 'openproject.api'));
beforeEach(module('templates',
'openproject.services',
'openproject.models',
'openproject.api',
function($provide) {
configurationService = new Object();
configurationService.isTimezoneSet = sinon.stub().returns(false);
$provide.constant('ConfigurationService', configurationService);
}));
beforeEach(module('templates', function($provide) {
$provide.value('$stateParams', stateParams);

@ -32,7 +32,18 @@ describe('QueryService', function() {
var QueryService, query, queryData;
beforeEach(module('openproject.services', 'openproject.models', 'openproject.api'));
beforeEach(module('openproject.services',
'openproject.models',
'openproject.api',
'openproject.services'));
beforeEach(module('templates', function($provide) {
configurationService = new Object();
configurationService.isTimezoneSet = sinon.stub().returns(false);
$provide.constant('ConfigurationService', configurationService);
}));
beforeEach(inject(function(_QueryService_){
QueryService = _QueryService_;

@ -33,6 +33,14 @@ describe('WorkPackageService', function() {
var WorkPackageService;
beforeEach(module('openproject.api','openproject.services', 'openproject.models'));
beforeEach(module('templates', function($provide) {
configurationService = new Object();
configurationService.isTimezoneSet = sinon.stub().returns(false);
$provide.constant('ConfigurationService', configurationService);
}));
beforeEach(inject(function(_WorkPackageService_, _HALAPIResource_){
WorkPackageService = _WorkPackageService_;
}));

@ -36,8 +36,17 @@ describe('columnContextMenu', function() {
'openproject.workPackages.controllers',
'openproject.models',
'openproject.api',
'openproject.services',
'templates'));
beforeEach(module('templates', function($provide) {
configurationService = new Object();
configurationService.isTimezoneSet = sinon.stub().returns(false);
$provide.constant('ConfigurationService', configurationService);
}));
beforeEach(function() {
var html = '<div></div>';
container = angular.element(html);

@ -36,8 +36,17 @@ describe('workPackageContextMenu', function() {
'openproject.api',
'openproject.workPackages',
'openproject.models',
'openproject.services',
'templates'));
beforeEach(module('templates', function($provide) {
configurationService = new Object();
configurationService.isTimezoneSet = sinon.stub().returns(false);
$provide.constant('ConfigurationService', configurationService);
}));
beforeEach(function() {
var html = '<div></div>';
container = angular.element(html);

@ -270,8 +270,10 @@ Redmine::MenuManager.map :admin_menu do |menu|
:html => {:class => 'custom_fields icon2 icon-status' }
menu.push :enumerations, {:controller => '/enumerations'}, :html => {:class => "icon2 icon-status"}
menu.push :settings, {:controller => '/settings'}, :html => {:class => "icon2 icon-settings2"}
menu.push :ldap_authentication, {:controller => '/ldap_auth_sources', :action => 'index'},
:html => {:class => 'server_authentication icon2 icon-status'}
menu.push :ldap_authentication,
{:controller => '/ldap_auth_sources', :action => 'index'},
:html => {:class => 'server_authentication icon2 icon-status'},
:if => proc { !OpenProject::Configuration.disable_password_login? }
menu.push :plugins, {:controller => '/admin', :action => 'plugins'}, :last => true, :html => {:class => "icon2 icon-status"}
menu.push :info, {:controller => '/admin', :action => 'info'}, :caption => :label_information_plural, :last => true, :html => {:class => "icon2 icon-info"}
menu.push :colors,

@ -32,6 +32,8 @@ describe AuthSourcesController do
let(:current_user) { FactoryGirl.create(:admin) }
before do
OpenProject::Configuration.stub(:disable_password_login?).and_return(false)
allow(User).to receive(:current).and_return current_user
end
@ -119,4 +121,46 @@ describe AuthSourcesController do
end
end
end
context 'with password login disabled' do
before do
OpenProject::Configuration.stub(:disable_password_login?).and_return(true)
end
it 'cannot find index' do
get :index
expect(response.status).to eq 404
end
it 'cannot find new' do
get :new
expect(response.status).to eq 404
end
it 'cannot find create' do
post :create, auth_source: { name: 'Test' }
expect(response.status).to eq 404
end
it 'cannot find edit' do
get :edit, id: 42
expect(response.status).to eq 404
end
it 'cannot find update' do
post :update, id: 42, auth_source: { name: 'TestUpdate' }
expect(response.status).to eq 404
end
it 'cannot find destroy' do
post :destroy, id: 42
expect(response.status).to eq 404
end
end
end

@ -119,5 +119,120 @@ describe SettingsController do
expect(response.body).not_to have_selector "input[@name='settings[default_projects_modules][]'][@value='activity'][@checked='checked']"
end
end
describe 'password settings' do
let(:old_settings) do
{
password_min_length: 10,
password_active_rules: [],
password_min_adhered_rules: 0,
password_days_valid: 365,
password_count_former_banned: 2,
lost_password: '1'
}
end
let(:new_settings) do
{
password_min_length: 42,
password_active_rules: %w(uppercase lowercase),
password_min_adhered_rules: 7,
password_days_valid: 13,
password_count_former_banned: 80,
lost_password: '3'
}
end
let(:original_settings) { Hash.new }
before do
old_settings.keys.each do |key|
original_settings[key] = Setting[key]
end
old_settings.keys.each do |key|
Setting[key] = old_settings[key]
end
end
after do
# restore settings
old_settings.keys.each do |key|
Setting[key] = original_settings[key]
end
end
describe 'POST #edit with password login enabled' do
before do
OpenProject::Configuration.stub(:disable_password_login?).and_return(false)
post 'edit', tab: 'authentication', settings: new_settings
end
it 'is successful' do
expect(response).to be_redirect # to auth tab
end
it 'sets the minimum password length to 42' do
expect(Setting[:password_min_length]).to eq '42'
end
it 'sets the active character classes to lowercase and uppercase' do
expect(Setting[:password_active_rules]).to eq ['uppercase', 'lowercase']
end
it 'sets the required number of classes to 7' do
expect(Setting[:password_min_adhered_rules]).to eq '7'
end
it 'sets passwords to expire after 13 days' do
expect(Setting[:password_days_valid]).to eq '13'
end
it 'bans the last 80 passwords' do
expect(Setting[:password_count_former_banned]).to eq '80'
end
it 'sets the lost password option to the nonsensical 3' do
expect(Setting[:lost_password]).to eq '3'
end
end
describe 'POST #edit with password login disabled' do
before do
OpenProject::Configuration.stub(:disable_password_login?).and_return(true)
post 'edit', tab: 'authentication', settings: new_settings
end
it 'is successful' do
expect(response).to be_redirect # to auth tab
end
it 'does not set the minimum password length to 42' do
expect(Setting[:password_min_length]).to eq '10'
end
it 'does not set the active character classes to lowercase and uppercase' do
expect(Setting[:password_active_rules]).to eq []
end
it 'does not set the required number of classes to 7' do
expect(Setting[:password_min_adhered_rules]).to eq '0'
end
it 'does not set passwords to expire after 13 days' do
expect(Setting[:password_days_valid]).to eq '365'
end
it 'does not ban the last 80 passwords' do
expect(Setting[:password_count_former_banned]).to eq '2'
end
it 'does not set the lost password option to the nonsensical 3' do
expect(Setting[:lost_password]).to eq '1'
end
end
end
end
end

@ -247,6 +247,40 @@ describe UsersController do
end
describe "index" do
describe 'new user button' do
render_views
context 'with password login enabled' do
before do
OpenProject::Configuration.stub(:disable_password_login?).and_return(false)
as_logged_in_user admin do
get :index
end
end
it 'is shown' do
expect(response.body).to have_selector('a', text: I18n.t('label_user_new'))
end
end
context 'with password login disabled' do
before do
OpenProject::Configuration.stub(:disable_password_login?).and_return(true)
as_logged_in_user admin do
get :index
end
end
# you must not be able to create new users if password login is disabled
# as users are managed externally
it 'is hidden' do
expect(response.body).not_to have_selector('a', text: I18n.t('label_user_new'))
end
end
end
describe "with session lifetime" do
# TODO move this section to a proper place because we test a
# before_filter from the application controller
@ -347,6 +381,68 @@ describe UsersController do
end
end
describe '#new' do
context 'with password login enabled' do
before do
OpenProject::Configuration.stub(:disable_password_login?).and_return(false)
as_logged_in_user admin do
get :new
end
end
it 'should return HTTP 200' do
expect(response.status).to eq 200
end
end
context 'with password login disabled' do
before do
OpenProject::Configuration.stub(:disable_password_login?).and_return(true)
as_logged_in_user admin do
get :new
end
end
# you must not be able to create new users if password login is disabled
it 'should return HTTP 404' do
expect(response.status).to eq 404
end
end
end
describe '#create' do
context 'with password login enabled' do
before do
OpenProject::Configuration.stub(:disable_password_login?).and_return(false)
as_logged_in_user admin do
post :create
end
end
it 'should return HTTP 400 due to missing parameters' do
expect(response.status).to eq 400
end
end
context 'with password login disabled' do
before do
OpenProject::Configuration.stub(:disable_password_login?).and_return(true)
as_logged_in_user admin do
post :create
end
end
# you must not be able to create new users if password login is disabled
it 'should return HTTP 404' do
expect(response.status).to eq 404
end
end
end
describe "update" do
context "fields" do
let(:user) { FactoryGirl.create(:user, :firstname => 'Firstname',

@ -0,0 +1,86 @@
#-- 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'
require 'features/projects/projects_page'
describe 'edit users', js: true do
let(:current_user) { FactoryGirl.create :admin }
let(:user) { FactoryGirl.create :user }
let!(:auth_source) { FactoryGirl.create :auth_source }
before do
allow(User).to receive(:current).and_return current_user
end
def auth_select
find :css, 'select#user_auth_source_id'
end
def user_password
find :css, 'input#user_password'
end
context 'with internal authentication' do
before do
visit edit_user_path(user)
end
it 'shows internal authentication being selected including password settings' do
expect(auth_select.value).to eq '' # selected internal
expect(user_password).to be_visible
end
it 'hides password settings when switching to an LDAP auth source' do
auth_select.select auth_source.name
expect(page).not_to have_selector('input#user_password')
end
end
context 'with external authentication' do
before do
user.auth_source = auth_source
user.save!
visit edit_user_path(user)
end
it 'shows external authentication being selected and no password settings' do
expect(auth_select.value).to eq auth_source.id.to_s
expect(page).not_to have_selector('input#user_password')
end
it 'shows password settings when switching back to internal authentication' do
auth_select.select I18n.t('label_internal')
expect(user_password).to be_visible
end
end
end

@ -382,5 +382,23 @@ describe CustomField do
end
it { expect(field).not_to be_valid }
end
describe "WITH a text field
WITH negative minimum length" do
before do
field.field_format = 'text'
field.min_length = -2
end
it { expect(field).not_to be_valid }
end
describe "WITH a text field
WITH negative maximum length" do
before do
field.field_format = 'text'
field.max_length = -2
end
it { expect(field).not_to be_valid }
end
end
end

@ -78,9 +78,15 @@ describe 'api/experimental/work_packages/index.api.rabl' do
describe 'with 3 work packages but no columns' do
let(:work_packages) { [
FactoryGirl.build(:work_package),
FactoryGirl.build(:work_package),
FactoryGirl.build(:work_package)
FactoryGirl.build(:work_package,
created_at: DateTime.now,
updated_at: DateTime.now),
FactoryGirl.build(:work_package,
created_at: DateTime.now,
updated_at: DateTime.now),
FactoryGirl.build(:work_package,
created_at: DateTime.now,
updated_at: DateTime.now)
] }
let(:column_names) { [] }
let(:custom_field_column_names) { [] }
@ -93,8 +99,12 @@ describe 'api/experimental/work_packages/index.api.rabl' do
describe 'with 2 work packages and columns' do
let(:work_packages) { [
FactoryGirl.build(:work_package),
FactoryGirl.build(:work_package)
FactoryGirl.build(:work_package,
created_at: DateTime.now,
updated_at: DateTime.now),
FactoryGirl.build(:work_package,
created_at: DateTime.now,
updated_at: DateTime.now)
] }
let(:column_names) { %w(subject description due_date) }
let(:custom_field_column_names) { [] }
@ -109,7 +119,11 @@ describe 'api/experimental/work_packages/index.api.rabl' do
end
describe 'with project column' do
let(:work_packages) { [FactoryGirl.build(:work_package)] }
let(:work_packages) { [
FactoryGirl.build(:work_package,
created_at: DateTime.now,
updated_at: DateTime.now)
] }
let(:column_names) { %w(subject project) }
let(:custom_field_column_names) { [] }

@ -0,0 +1,71 @@
#-- 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 'layouts/admin' do
include Redmine::MenuManager::MenuHelper
helper Redmine::MenuManager::MenuHelper
let(:admin) { FactoryGirl.create :admin }
before do
view.stub(:current_menu_item).and_return('overview')
view.stub(:default_breadcrumb)
controller.stub(:default_search_scope)
User.stub(:current).and_return admin
view.stub(:current_user).and_return admin
end
# All password-based authentication is to be hidden and disabled if
# `disable_password_login` is true. This includes LDAP.
describe 'LDAP authentication menu entry' do
context 'with password login enabled' do
before do
OpenProject::Configuration.stub(:disable_password_login?).and_return(false)
render
end
it 'is shown' do
expect(rendered).to have_selector('a', text: I18n.t('label_ldap_authentication'))
end
end
context 'with password login disabled' do
before do
OpenProject::Configuration.stub(:disable_password_login?).and_return(true)
render
end
it 'is hidden' do
expect(rendered).not_to have_selector('a', text: I18n.t('label_ldap_authentication'))
end
end
end
end

@ -0,0 +1,61 @@
#-- 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 'settings/_authentication' do
context 'with password login enabled' do
before do
OpenProject::Configuration.stub(:disable_password_login?).and_return(false)
render
end
it 'shows password settings' do
expect(rendered).to have_text I18n.t('label_password_lost')
end
it 'shows automated user blocking options' do
expect(rendered).to have_text I18n.t(:brute_force_prevention, :scope => [:settings])
end
end
context 'with password login disabled' do
before do
OpenProject::Configuration.stub(:disable_password_login?).and_return(true)
render
end
it 'does not show password settings' do
expect(rendered).not_to have_text I18n.t('label_password_lost')
end
it 'does not show automated user blocking options' do
expect(rendered).not_to have_text I18n.t(:brute_force_prevention, :scope => [:settings])
end
end
end

@ -40,12 +40,82 @@ describe 'users/edit' do
assign(:auth_sources, [])
allow(view).to receive(:current_user).and_return(current_user)
render
end
it 'shows the authentication provider' do
render
expect(response.body).to include('Test Provider')
end
it 'does not show a no-login warning when password login is disabled' do
OpenProject::Configuration.stub(:disable_password_login).and_return(true)
render
expect(response.body).not_to include I18n.t('user.no_login')
end
end
context 'with password-based login' do
let(:user) { FactoryGirl.build :user, id: 42 }
before do
assign :user, user
assign :auth_sources, []
allow(view).to receive(:current_user).and_return(current_user)
end
context 'with password login disabled' do
before do
OpenProject::Configuration.stub(:disable_password_login?).and_return(true)
end
it 'warns that the user cannot login' do
render
expect(response.body).to include I18n.t('user.no_login')
end
context 'with auth sources' do
let(:auth_sources) { [FactoryGirl.create(:auth_source)]}
before do
assign :auth_sources, auth_sources
end
it 'does not show the auth source selection' do
render
expect(rendered).not_to have_selector('#user_auth_source_id')
end
end
end
context 'with password login enabled' do
before do
OpenProject::Configuration.stub(:disable_password_login?).and_return(false)
end
it 'shows password options' do
render
expect(rendered).to have_text I18n.t('user.assign_random_password')
end
context 'with auth sources' do
let(:auth_sources) { [FactoryGirl.create(:auth_source)] }
before do
assign :auth_sources, auth_sources
end
it 'shows the auth source selection' do
render
expect(rendered).to have_selector('#user_auth_source_id')
end
end
end
end
end

Loading…
Cancel
Save