enforce token domain

This commit is contained in:
Markus Kahl
2020-05-20 10:15:42 +01:00
parent 676b3a65ad
commit 3f13bc5fef
12 changed files with 309 additions and 21 deletions
+1 -1
View File
@@ -178,7 +178,7 @@ gem 'aws-sdk-core', '~> 3.91.0'
# File upload via fog + screenshots on travis
gem 'aws-sdk-s3', '~> 1.61.0'
gem 'openproject-token', '~> 1.0.2'
gem 'openproject-token', '~> 2.0'
gem 'plaintext', '~> 0.3.2'
+3 -2
View File
@@ -654,7 +654,8 @@ GEM
validate_email
validate_url
webfinger (>= 1.0.1)
openproject-token (1.0.2)
openproject-token (2.0)
activemodel
parallel (1.19.1)
parallel_tests (2.32.0)
parallel
@@ -1061,7 +1062,7 @@ DEPENDENCIES
openproject-pdf_export!
openproject-recaptcha!
openproject-reporting!
openproject-token (~> 1.0.2)
openproject-token (~> 2.0)
openproject-translations!
openproject-two_factor_authentication!
openproject-webhooks!
+11
View File
@@ -38,6 +38,7 @@ class EnterprisesController < ApplicationController
before_action :youtube_content_security_policy
before_action :require_admin
before_action :check_user_limit, only: [:show]
before_action :check_domain, only: [:show]
def show
@current_token = EnterpriseToken.current
@@ -121,4 +122,14 @@ class EnterprisesController < ApplicationController
)
end
end
def check_domain
if OpenProject::Enterprise.token.try(:invalid_domain?)
flash.now[:error] = I18n.t(
"error_enterprise_token_invalid_domain",
expected: Setting.host_name,
actual: OpenProject::Enterprise.token.domain
)
end
end
end
+19 -1
View File
@@ -56,14 +56,16 @@ class EnterpriseToken < ApplicationRecord
validates_presence_of :encoded_token
validate :valid_token_object
validate :valid_domain
before_save :unset_current_token
before_destroy :unset_current_token
delegate :will_expire?,
:expired?,
:subscriber,
:mail,
:company,
:domain,
:issued_at,
:starts_at,
:expires_at,
@@ -84,6 +86,18 @@ class EnterpriseToken < ApplicationRecord
RequestStore.delete :current_ee_token
end
def expired?
token_object.expired? || invalid_domain?
end
##
# The domain is only validated for tokens from version 2.0 onwards.
def invalid_domain?
return false unless token_object&.validate_domain?
token_object.domain != Setting.host_name
end
private
def load_token!
@@ -96,4 +110,8 @@ class EnterpriseToken < ApplicationRecord
def valid_token_object
errors.add(:encoded_token, :unreadable) unless load_token!
end
def valid_domain
errors.add :domain, :invalid if invalid_domain?
end
end
+2 -2
View File
@@ -1,8 +1,8 @@
<enterprise-active-saved-trial
data-subscriber="<%= @current_token.subscriber %>"
data-email="<%= @current_token.mail %>"
data-company="<%= @current_token.has_attribute?(:company) ? @current_token.company : nil %>"
data-domain="<%= @current_token.has_attribute?(:domain) ? @current_token.domain : nil %>"
data-company="<%= @current_token.try(:company) %>"
data-domain="<%= @current_token.try(:domain) %>"
data-user-count="<%= @current_token.restrictions.nil? ? t('js.admin.enterprise.upsale.unlimited') : @current_token.restrictions[:active_user_count] %>"
data-starts-at="<%= format_date @current_token.starts_at %>"
data-expires-at="<%= (!@current_token.will_expire?) ? t('js.admin.enterprise.upsale.unlimited') : (format_date @current_token.expires_at) %>">
+1
View File
@@ -1102,6 +1102,7 @@ en:
error_cookie_missing: 'The OpenProject cookie is missing. Please ensure that cookies are enabled, as this application will not properly function without.'
error_custom_option_not_found: "Option does not exist."
error_enterprise_activation_user_limit: "Your account could not be activated (user limit reached). Please contact your administrator to gain access."
error_enterprise_token_invalid_domain: "The Enterprise Edition is not active. Your Enterprise token's domain (%{actual}) does not match the system's host name (%{expected})."
error_failed_to_delete_entry: 'Failed to delete this entry.'
error_in_dependent: "Error attempting to alter dependent object: %{dependent_class} #%{related_id} - %{related_subject}: %{error}"
error_invalid_selected_value: "Invalid selected value."
+58 -12
View File
@@ -30,14 +30,18 @@ require 'spec_helper'
describe EnterprisesController, type: :controller do
let(:a_token) { EnterpriseToken.new }
let(:token_object) do
token = OpenProject::Token.new
token.subscriber = 'Foobar'
token.mail = 'foo@example.org'
token.starts_at = Date.today
token.expires_at = nil
token
let(:token_attributes) do
{
subscriber: "Foobar",
mail: "foo@example.org",
starts_at: Date.today,
expires_at: nil
}
end
let(:token_object) do
OpenProject::Token.new token_attributes
end
before do
@@ -57,11 +61,53 @@ describe EnterprisesController, type: :controller do
get :show
end
it 'renders the overview' do
expect(response).to be_successful
expect(response).to render_template 'show'
expect(response).to render_template partial: 'enterprises/_current'
expect(response).to render_template partial: 'enterprises/_form'
shared_examples 'it renders the EE overview' do
it 'renders the overview' do
expect(response).to be_successful
expect(response).to render_template 'show'
expect(response).to render_template partial: 'enterprises/_current'
expect(response).to render_template partial: 'enterprises/_form'
end
end
it_behaves_like 'it renders the EE overview'
context 'with version >= 2.0' do
let(:token_attributes) { super().merge version: "2.0" }
context 'with correct domain', with_settings: { host_name: 'community.openproject.com' } do
let(:token_attributes) { super().merge domain: 'community.openproject.com' }
it_behaves_like 'it renders the EE overview'
it "doesn't show any warnings or errors" do
expect(controller).not_to set_flash.now
end
end
context 'with wrong domain', with_settings: { host_name: 'community.openproject.com' } do
let(:token_attributes) { super().merge domain: 'localhost' }
it_behaves_like 'it renders the EE overview'
it "shows an invalid domain error" do
expect(controller).to set_flash.now[:error].to(/.*localhost.*does not match.*community.openproject.com/)
end
end
end
context 'with version < 2.0' do
let(:token_attributes) { super().merge version: "1.0.3" }
context 'with wrong domain', with_settings: { host_name: 'community.openproject.com' } do
let(:token_attributes) { super().merge domain: 'localhost' }
it_behaves_like 'it renders the EE overview'
it "doesn't show any warnings or errors" do
expect(controller).not_to set_flash.now
end
end
end
end
@@ -38,6 +38,7 @@ describe 'Enterprise token', type: :feature, js: true do
token.mail = 'foo@example.org'
token.starts_at = Date.today
token.expires_at = nil
token.domain = Setting.host_name
token
end
@@ -79,9 +80,9 @@ describe 'Enterprise token', type: :feature, js: true do
expect(page).to have_selector('.enterprise--active-token')
expect(page.all('.attributes-key-value--key').map(&:text))
.to eq ['Subscriber', 'Email', 'Maximum active users', 'Starts at', 'Expires at']
.to eq ['Subscriber', 'Email', 'Domain', 'Maximum active users', 'Starts at', 'Expires at']
expect(page.all('.attributes-key-value--value').map(&:text))
.to eq ['Foobar', 'foo@example.org', 'Unlimited', format_date(Date.today), 'Unlimited']
.to eq ['Foobar', 'foo@example.org', Setting.host_name, 'Unlimited', format_date(Date.today), 'Unlimited']
expect(page).to have_selector('.button.icon-delete', text: I18n.t(:button_delete))
@@ -0,0 +1,126 @@
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2012-2020 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-2017 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.
#++
require 'spec_helper'
describe 'Enterprise Edition token domain', type: :feature, js: true do
let(:current_user) { FactoryBot.create :admin }
let(:ee_token) { File.read(Rails.root.join("spec/fixtures/ee_tokens/v2_1_user_localhost_3001.token")) }
before do
allow(User).to receive(:current).and_return current_user
end
shared_examples 'uploading a token' do
before do
visit '/admin/enterprise'
fill_in "enterprise_token[encoded_token]", with: ee_token
click_on "Save"
end
end
describe 'initial upload' do
context 'with correct domain', with_settings: { host_name: 'localhost:3001' } do
it_behaves_like 'uploading a token' do
it 'saves the token' do
expect(body).to have_text 'Successful update.'
end
it 'shows the token info' do
expect(body).to have_text 'operations@openproject.com'
end
end
end
context 'with incorrect domain', with_settings: { host_name: 'localhost:3000' } do
it_behaves_like 'uploading a token' do
it "shows an invalid domain error" do
expect(body).to have_text "Domain is invalid."
end
end
end
end
context 'with an active token', with_settings: { host_name: 'localhost:3001' } do
before do
allow(EnterpriseToken).to receive(:current).and_return(EnterpriseToken.new(encoded_token: ee_token))
visit '/admin/enterprise'
end
shared_examples 'replacing a token' do
let(:new_token) { raise 'define me!' }
before do
click_on "Replace your current support token"
fill_in "enterprise_token[encoded_token]", with: new_token
click_on "Save"
end
end
it 'shows the current token info' do
expect(body).to have_text 'operations@openproject.com'
end
describe 'replacing the token' do
context 'with correct domain' do
it_behaves_like 'replacing a token' do
let(:new_token) { ee_token }
it 'updates the token' do
expect(body).to have_text 'Successful update.'
end
end
end
context 'with incorrect domain' do
it_behaves_like 'replacing a token' do
let(:new_token) { File.read(Rails.root.join("spec/fixtures/ee_tokens/v2_1_user_localhost_3000.token")) }
it 'shows an invalid domain error' do
expect(body).to have_text 'Domain is invalid.'
end
it "shows the new token's info" do
expect(body).to have_text 'localhost:3000'
end
it "but doesn't save the new token" do
visit '/admin/enterprise'
expect(body).to have_text 'localhost:3001'
end
end
end
end
end
end
+42
View File
@@ -0,0 +1,42 @@
-----BEGIN OPENPROJECT-EE TOKEN-----
eyJkYXRhIjoiMVUxUDRJbFR4RmZzaThTZVFFbnU2SlZRZ1BWVVZta3JnWERw
VlVCVnBpZHJjUklZTEN6b1pTWWNsc2dyXG5CaStKTUhKZS9lWTIrQUtuSDVI
Q1BFc3loMXFhUnlXRWlqbTBCUDJTbVBmeXJ2UXRuay9ZbDF0TVp2SndcbjIx
ZHRIeXVvQW05bHdlZStOWEtBS2xWWmNrZjNYb3B5VXQ0Z2xDclB4UGRnU1ht
b3JySFB2UkFoOFBkQlxuTEhLRlVCNU1mcEMyVmlPeTZUS2ZJYjd1ajBhdVEv
QlBIU2lpRjhZb3BmcEVldzBZazR2SUY2Rnk5WDdLXG5ieExkZ3U1VTZQSVAv
T3paZnM5cVpFSFAxcUwwclBwWlM0d21iV1dtZ21yME1WTW91QTBzU1lvdlQ0
VlpcbnBCUXNBMXpWZFl1K2lFSHpQd2JXXG4iLCJrZXkiOiJiR2hwbjFEcjBN
ZHZnQjNSTHNWVkRjaHNaSWlYQWo5Mk9XcTRPUnZDVmtuQXNVSWFpdlNMMXky
QUtzY0dcbkJMR3FjS2RuWHNQS2JXeWx2NTcrdzI5TGIwWHoxMkVzclFYQTQ3
Sk1BaFI5bnpoNkM4blMxVEs3Y1R3eVxubzUyMGZCL2VxY2pUWTRQNzdGQWRx
OTRGOTdLNkl2QnhLWFkxdVVGaVRPUzA2c3dyUTNtRW5rZ0t6ZkFvXG5hamhi
TGcrWnB2R2FLT1ZOZWFQKzdlbUJjSFRZbFhNMnY2S0V2QXYzMzJ1UHVuOVlS
aDZaeTVya2dHTmlcbitEcDZIM2M0Mzd5TjdYZGZhVHdaSWZzYldudWpNMmNO
L25hYk53cDVMYytwL0dFaEVmYnlWRENJTnZvaVxubmh1OGowV0RrL3RVa3BM
MENBWmk5Uk83dXZGcHdJSkluZ0E3dlZ3NVdVOFdScWNKcnlUZkVPVlYyVk15
XG5GV2RnOUlzbmpuNW02MjdJbDQ5eDlTUXIxVzBsR285RFZYYXZpNzI1em95
RG11eXlRQy9rbC91dHZBYzNcbjY0QnlKVWpET3BNM1dDRG5jWEVqZnUwQzNx
UGNka1dvTi9wcmg4Q1RqWlFiNkhsdDhKRWxHUk8zaThxSFxuZGU2Z0hpaGlZ
NlZzNkZzcTdhZGxPb0JUZGZJOURNUnV1eSs5cVZIUld4VDBqaFNjeTZkbnA1
MW1hMUdGXG5yNnJWSWVHMWF4Zm1aV3I0L0tpRVEvM1VRUVFBbXByQm9hYita
OThpaG5vdFhseW80dDRONWZuR0JCVWFcbktQem4rNlJoL1l4YnFNMG1Sa1Jr
aVJVMkpUcHNyWStIQzdlOEt3M09YL1pESjJOWlFTcWZjS0l6dlR5eFxuSG1G
eTVCdDdrSzltMnduT2FMOXo1dldQVG5tRWZEWGQvekFhVC95TU5VUEwyWFQ2
a29GNXlKSENNQnJEXG5aNDBjT0JzYnNhMTRqTExqS05kc2Y0RnlwR2N3K3lE
dG94U2o0STMvaTJPeUpGaEV0QXVYbGVvYUhTN2xcbmhpR0VGdEhVUXVyNGVO
WE9VM3I4SHNiQi9qWUxCSlVLWDREQVhVNGQvakNDa1FUaU1weTZzZWVjYkRm
bVxuZ0lhQVFjcGhPMk5xMWd3c2h6RlM4L1p1MXpkMDF4NnhaOWxwMnRSYjFZ
RmFQaUU3eVpqVFdlUzY5MUVkXG5mSmdNOXNCZENjUkUvOW0wYWc4SzFjQzg0
T3lFT3p3S2NHcWJnSlUzNW41Y2tURWpjK2tQU0I5NTk1MTZcbit3Mlpwek0z
b1VmZDFBc29BUXdZOHhTS3hXM0ZEdGNkQzUwMThnQWJnRDRTcFdKRXlwTld5
QnBya2RCSFxuY2J6ZHdHOWxvTjNoMEpsNWFiL2FuQWt4SHR1MHVWUTVhcjZw
cGhiRXRaQmFpTUtscDhXYVUzSkJ3TWZmXG5UdjFLWVdjVE95U3hHTnZBY3ZO
ZjVkTS9WdGxDVEpjaUpheGVqZTJDZmhWWk5sSVAycE9hTCs4N0lzSmdcblFN
cXBIY0dwNzNvZ0JFK0x0UlZYQXRzZy9xTVVkeVdtN1l3MStQQWxqTENLTDNG
SUduZUVqdlIxdjhJU1xuVklyOXhIL3ZoUFB0emFwRGt0TWJ1TnRwMDlsRFFQ
QjNKT0dwZGFFSlQ0THhpdHhjenZhQ051S1A2aDhzXG44b0ZFNDE5ME9DSExN
d2VtYVR5SDRCQ2RaV3lvR1ZKMlZmU0d3Y3ZzY2t1akI3blBNS21vT3cvUE5U
SXRcbjFVRnNOTkZFSDRpbW4zaUkrS2hPTmRnL3Y1WUY0Zz09XG4iLCJpdiI6
IklrUmtubGNoU0VwNXh2NFErSzdrZUE9PVxuIn0=
-----END OPENPROJECT-EE TOKEN-----
+42
View File
@@ -0,0 +1,42 @@
-----BEGIN OPENPROJECT-EE TOKEN-----
eyJkYXRhIjoiS3BEYlEybnZzeTIzVUpGUkpOSWhidkl4OWNXKy9GeDR3NXJL
QTU2UitWS3ZxYjZrWjdua2Z6VnRzNzJXXG50aklFQmdNODdEb2dMS2xxbVRT
TG5lenE4WjJlL3RjQVBlT1NVRlZNMWovaGNWZFFJYzY5LzBiZXM2OXpcbnpS
bHpNOFdDQnE2R3lNYTdzV2JZMVJQM2FjaCtmZ08wOWdsam9ESXdpUzZ1eWlL
ZFdYRnRzNHMwRUUxUFxuRHlOSUl5dnR4UXB6dlMzQ1o3MXpzZkZNMFl3d0Jp
UTlZQ1I2V3NaOXNzRzJnR011NjI4Q05TdnJwVmcxXG5Tb1dWZE1KbkRPanFz
NlM3aDdiVWR0Y3lESFViVlNJcmRPRHNxTG4yeEQzZzE3ajdUb1lnQ21hUlc0
MTVcbkg3VEVlOGN2UEhvZGhTMFh6WkJsXG4iLCJrZXkiOiJNVjZldXdqS1J6
TmMzS0YxY3hzMTRkTUhUKzQxYXFOcnc3YjE2UDZBS04rcWdBbU5FUTk5S2pm
VFJWaEVcbmFxVmEzQzhoemhZRW85UEZFbW1TT3BZN3A4aFRTK01DT2lyMUo4
R05GT2w5NzRUN3dUVVNzMDdWQnNlNVxuTzdGQkRMNDUzREJOandSQy9XTVpV
RWtyR2xiYjNQYndWcWpkOU5pRWFPWk1xcU84WVJDRnUzdVBMWXBrXG5WM3hl
MXJsZ3FuZUcwWHRUdC80MWVxOENuWFpyS0tKRVlRNTZNaE9QMVM0VkNXTjVl
VGxOeEVIdkJFb0hcbms5Z2NkSEZuTkxNeVdEK01DTXNQazM0SWtFaGJQN3RP
SFRrMGZpaGpTejlIL0tpRmIycWNTdmJ0M2lJQlxuUVhSUjc5L0lVeHZzcmcx
K0o2a25qSlNGaXlERS9sdXV0dVdhRzBEM29EM2dYWmVKbzZIZEhiU3RMbVhj
XG41MkJtemRZaTF3RXI1Um1HU2lZV0V3dTkvY2VRTWVxTldVQTB4OElTamRj
eXU2NTgxeTZidGdKSXg3aEtcbjcxTGNWOFJKN1hlQ1RmQ2ZVcUdVMkVkbkVr
MlpjaFozb1dXOGZya2ZGMWkreDQ1Zm5KVFdNMXM0Smlva1xuUkwwajJrWXVR
U2RFYlhwSWRnREpHNmVHTjZxd0VpbFVVN1E2Z1Vzcm1RbG9mUk90dUgydFA2
UFNhaUhIXG5nb3Q4LzZydDFQT29zR2RidlAwNjNBU2h2SmRTQXpoQXRzckQv
VVRBVDFITkVlZjBNWFcrdS95b3FKRkpcbmdLdm96ZnQxVDFjWURaWno1ZlBy
ajVYQlI5VE5JVmdNYUY3R3AraUtWWDYxbkE2am03OFlDSExaN1hXbVxudXlq
eDBxb2hJenY3ZzBIaU5pMTV4MytOdEJkTkEzZXB3SEJrRjZQemFRL29UOWx3
cWphbnhxQzRHaVQ5XG5yYm81QTd2SytMNE5XeHBidzJGK0k3WDJNc2FNUU52
eXRDTjFDWXFqcGJibnRzb3dQcUY4NXdHMWtRTkhcbnJSVGdZa3lvZ3RENmxR
T1pLZFlJNjlIVGlKaW5GSW1QR2FTeXp2OHliMzljQWVGcFlET2h2Z2czS1Z1
MVxuL0pQMzVVekNRRXEyV1ZzUjVaOWtmdzFFd3FaNzE1a2hEVm04U0Mzc2Rw
d241N2I4UTBUcUxzSnEvQlpRXG40bytWZFlONnAvL3VXWHlVQXF6MDJVeWp3
VGw5ZDJwdWJPUGE2bUQySnY2WEIzck1qUSt2alh5NytsbG5cbmVBaUZzWmJu
Q0t3R2VWNDY3Y0ZjdXIvOTFmejg3Q0FRV1pOc0F0dDBxaXVlR1VTZ2JSSmgx
bUhERDJCMVxuM0RreHlmRms5dWtWUjNRemdJNk9LMStMM0FJbU42ckxUMzlV
NVVPeFZ3N2YrYktmQlVScmxzckZscWZwXG5OdGhyUEpxVTJCT3U4MUE5QnR0
SnlFTm5wdG4xcWY3bG9QSzBaV21WSW5xTmJQbXpndEdRbzRRaDN4MTlcbis4
Z2FHandZaUlBYk02bS84T1BhK3ZndFNZckFCNjR1aHlIbkJETTl5ekIzVGxT
WUlSTlcxVkF3RUNaNVxuRE91VWQxcFNnWC9TbzZjbUpoSHA1S2toQnJVQVFw
K24zZWZFU2JXdi9RcTljL2dBc2o3N3VZOGFlcWk1XG51cFNxa1FLMGpMd1hv
K1luRjRnY08reklBR1V5RHRYb2pBWVErSDJRMTVhdFE5ZjlhclpSZUlEWG1j
THBcblMvU204RjVUQm5Td1lQbGovSGQvUDA0RkhEclBHQT09XG4iLCJpdiI6
IkpvUXFxU1BmREI4dDNKWW1qYWVJRmc9PVxuIn0=
-----END OPENPROJECT-EE TOKEN-----
+1 -1
View File
@@ -1,7 +1,7 @@
require 'spec_helper'
RSpec.describe EnterpriseToken, type: :model do
let(:object) { OpenProject::Token.new }
let(:object) { OpenProject::Token.new domain: Setting.host_name }
subject { EnterpriseToken.new(encoded_token: 'foo') }
before do