parent
0e1f4ed4eb
commit
49f12b3e21
@ -0,0 +1,24 @@ |
|||||||
|
-----BEGIN PUBLIC KEY----- |
||||||
|
MIIEFjANBgkqhkiG9w0BAQEFAAOCBAMAMIID/gKCA/UAquIZchoog2ffcr9J2KSl |
||||||
|
mlum6sN3smTVNsp9JGd1q4fr/kUFGch6q1cFEX3x5BGDXx7wPPI4ppKzeQHaxWmx |
||||||
|
wxqs3eevcTFUEF9A2MPX7p5Ia0TbH4d7e7D9YMWvDXoQLggrxMFdUHY3ppUnBPgB |
||||||
|
+EJG1Pv0FlBAdxYX0em7kLwhcp9PBP/zXso/qkkKK/pncyKizOLC3zv3E0ixcQ7o |
||||||
|
Nq0aolTJFMHcqEquKaQN1jicDdzU6ks+YKh7kByZvVChe/InlroVXKrUa34hAZDM |
||||||
|
acEkURJma3meN0IyPFA7fHRe1AhiNYF2MatNKysPrbOffYLOjamlaqmHTeJAec6e |
||||||
|
vMHd+LlIz4xXivR0lY2wDawqp0waSLJaW8lZetOf0iwbqQkzZhz4sWDZopyGiqAU |
||||||
|
v9/zS4OjUBr7JQbVcV3LIkzGWwNysSvTMrlvzCesYVsCwpLjP6gFxdclYJuTwEeL |
||||||
|
o+T+AgoNyuj6ixhwHTJxIVhuBpebX44/YTYyUGMgItekDCH2Dxvtv2DaCL7YIqNG |
||||||
|
ibvCyzCyLak7Tz97CMvCUf1EIRVfolbGpphi2Zzpoeqhfheh+0LQ3gmMBJpuLnJU |
||||||
|
VXEtrOPunTkOrdUqL5rD/+mfd8yufsJ16Uk5j7gNUcIJsCcGWZ3Nhfidi3tvmJPF |
||||||
|
H8HgNZ6W6smj9k7+TdZbRsH1LZp1LL/stLch3ffFHHcJye7d2t75uKiOoz1/1JMY |
||||||
|
fl/wfaEaKvTGBKr/NFKcVDSBXHgx6VMA3oWV2AyLnTaY98XVPY1zzbyNKlNQkSaD |
||||||
|
p/VCl334+YMwR9/5aJYJg58lljw6aBu+cozNkydKjCmqEpZNdR1tFusAY7jd/M2G |
||||||
|
yXPBTdUJD6GrLx0Hot/wImJ30gOMgpeoetNUfM9/FimySLy65DRzCtLm1hthAlWM |
||||||
|
Is5vFwMmFFSb3ozTsnkj9W16jHk9HdodKJfezzcPqu3TW2EMMNbbtXa3OPaHkht2 |
||||||
|
huYJGnGu0hNhAj+x+KCxkpLveS0Ajw322qmtAqnwJLvfwShnz//cptNX5kXtrNGy |
||||||
|
+o6I4jHibIATfaMKMt8gHmCZ381zAwrAOU7c0FQna+IkU8dgZDx7T+Xo1Q/GXuaO |
||||||
|
1b2aT6geT60A3VgF+OBnoHe5Ext7vfNL9v0wcN5NLR5KgjexwEhcBcA2FauCTrVt |
||||||
|
FUNgir+5XALd/wBlvxkvPKTJnQld/aK7xF0ui3c3/ryPX5cKzpfm9APK/hOFzkJ7 |
||||||
|
ieARHqrQGqOYdClIUJIH0b/92dFq49Eqn1cKpztVzsU9xzdI/4w5JUSw/kbguVf7 |
||||||
|
Yd0Rdc9KF/9WMwjzrWSti4meNBUO6/18cAognx0Pf5qsrSzOewIDAQAB |
||||||
|
-----END PUBLIC KEY----- |
After Width: | Height: | Size: 4.5 KiB |
@ -0,0 +1,13 @@ |
|||||||
|
.upsale-notification |
||||||
|
max-width: 560px |
||||||
|
margin-bottom: 20px |
||||||
|
padding-bottom: 20px |
||||||
|
|
||||||
|
.widget-box--teaser-image |
||||||
|
width: 30% |
||||||
|
float: right |
||||||
|
margin-top: -20px |
||||||
|
|
||||||
|
.token-form textarea |
||||||
|
font-family: "Lucida Console", Monaco, monospace |
||||||
|
max-width: 560px |
@ -0,0 +1,67 @@ |
|||||||
|
#-- copyright |
||||||
|
# OpenProject is a project management system. |
||||||
|
# Copyright (C) 2012-2017 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 token 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 token |
||||||
|
# as published by the Free Software Foundation; either version 2 |
||||||
|
# of the token, 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 token for more details. |
||||||
|
# |
||||||
|
# You should have received a copy of the GNU General Public token |
||||||
|
# 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. |
||||||
|
#++ |
||||||
|
class EnterprisesController < ApplicationController |
||||||
|
layout 'admin' |
||||||
|
menu_item :enterprise |
||||||
|
|
||||||
|
before_action :require_admin |
||||||
|
|
||||||
|
def show |
||||||
|
@current_token = EnterpriseToken.current |
||||||
|
@token = @current_token || EnterpriseToken.new |
||||||
|
end |
||||||
|
|
||||||
|
def create |
||||||
|
@token = EnterpriseToken.current || EnterpriseToken.new |
||||||
|
@token.encoded_token = params[:enterprise_token][:encoded_token] |
||||||
|
|
||||||
|
if @token.save |
||||||
|
flash[:notice] = t(:notice_successful_update) |
||||||
|
redirect_to action: :show |
||||||
|
else |
||||||
|
render action: :show |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
def destroy |
||||||
|
token = EnterpriseToken.current |
||||||
|
if token |
||||||
|
token.destroy |
||||||
|
flash[:notice] = t(:notice_successful_delete) |
||||||
|
redirect_to action: :show |
||||||
|
else |
||||||
|
render_404 |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
private |
||||||
|
|
||||||
|
def default_breadcrumb |
||||||
|
t(:label_enterprise) |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,107 @@ |
|||||||
|
#-- copyright |
||||||
|
# OpenProject is a project management system. |
||||||
|
# Copyright (C) 2012-2017 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-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 doc/COPYRIGHT.rdoc for more details. |
||||||
|
#++ |
||||||
|
class EnterpriseToken < ActiveRecord::Base |
||||||
|
class << self |
||||||
|
def current |
||||||
|
RequestStore.fetch(cache_key) do |
||||||
|
set_current_token |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
def cache_key |
||||||
|
RequestStore.fetch(:current_ee_token_updated_at) { EnterpriseToken.maximum(:updated_at) } |
||||||
|
most_recent_update = (RequestStore[:current_ee_token_updated_at] || Time.now.utc).to_i |
||||||
|
"/openproject/ee/#{most_recent_update}" |
||||||
|
end |
||||||
|
|
||||||
|
def clear_cache(key = cache_key) |
||||||
|
Rails.cache.delete(key) |
||||||
|
RequestStore.delete key |
||||||
|
RequestStore.delete :current_ee_token_updated_at |
||||||
|
end |
||||||
|
|
||||||
|
def allows_to?(action) |
||||||
|
Authorization::EnterpriseService.new(current).call(action).result |
||||||
|
end |
||||||
|
|
||||||
|
def show_banners |
||||||
|
!current || current.expired? |
||||||
|
end |
||||||
|
|
||||||
|
def set_current_token |
||||||
|
token = EnterpriseToken.order('created_at DESC').first |
||||||
|
|
||||||
|
if token && token.token_object |
||||||
|
token |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
validates_presence_of :encoded_token |
||||||
|
validate :valid_token_object |
||||||
|
|
||||||
|
before_save :unset_current_token |
||||||
|
before_destroy :unset_current_token |
||||||
|
|
||||||
|
delegate :will_expire?, |
||||||
|
:expired?, |
||||||
|
:subscriber, |
||||||
|
:mail, |
||||||
|
:issued_at, |
||||||
|
:starts_at, |
||||||
|
:expires_at, |
||||||
|
:restrictions, |
||||||
|
to: :token_object |
||||||
|
|
||||||
|
def token_object |
||||||
|
@token_object = load_token unless defined?(@token_object) |
||||||
|
@token_object |
||||||
|
end |
||||||
|
|
||||||
|
def allows_to?(action) |
||||||
|
Authorization::EnterpriseService.new(self).call(action).result |
||||||
|
end |
||||||
|
|
||||||
|
def unset_current_token |
||||||
|
# Clear current cache |
||||||
|
self.class.clear_cache |
||||||
|
end |
||||||
|
|
||||||
|
private |
||||||
|
|
||||||
|
def load_token |
||||||
|
OpenProject::Token.import(encoded_token) |
||||||
|
rescue OpenProject::Token::ImportError => error |
||||||
|
Rails.logger.error "Failed to load EE token: #{error}" |
||||||
|
nil |
||||||
|
end |
||||||
|
|
||||||
|
def valid_token_object |
||||||
|
errors.add(:encoded_token, :unreadable) unless token_object |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,64 @@ |
|||||||
|
#-- encoding: UTF-8 |
||||||
|
#-- copyright |
||||||
|
# OpenProject is a project management system. |
||||||
|
# Copyright (C) 2012-2017 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-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 doc/COPYRIGHT.rdoc for more details. |
||||||
|
#++ |
||||||
|
|
||||||
|
class Authorization::EnterpriseService |
||||||
|
attr_accessor :token |
||||||
|
|
||||||
|
def initialize(token) |
||||||
|
self.token = token |
||||||
|
end |
||||||
|
|
||||||
|
# Return a true ServiceResult if the token contains this particular action. |
||||||
|
def call(action) |
||||||
|
|
||||||
|
allowed = |
||||||
|
if token.nil? || token.expired? |
||||||
|
false |
||||||
|
else |
||||||
|
process(action) |
||||||
|
end |
||||||
|
|
||||||
|
result(allowed) |
||||||
|
end |
||||||
|
|
||||||
|
private |
||||||
|
|
||||||
|
def process(action) |
||||||
|
case action |
||||||
|
when :define_custom_style |
||||||
|
true # Every non-expired token |
||||||
|
else |
||||||
|
false |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
def result(bool) |
||||||
|
ServiceResult.new(success: bool, result: bool) |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,38 @@ |
|||||||
|
<div class="enterprise--active-token"> |
||||||
|
<div class="attributes-group"> |
||||||
|
<div class="attributes-key-value"> |
||||||
|
<div class="attributes-key-value--key"><%= EnterpriseToken.human_attribute_name(:subscriber) %></div> |
||||||
|
<div class="attributes-key-value--value-container"> |
||||||
|
<div class="attributes-key-value--value -text"> |
||||||
|
<span><%= @current_token.subscriber %></span> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div class="attributes-key-value--key"><%= EnterpriseToken.human_attribute_name(:mail) %></div> |
||||||
|
<div class="attributes-key-value--value-container"> |
||||||
|
<div class="attributes-key-value--value -text"> |
||||||
|
<span><%= @current_token.mail %></span> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div class="attributes-key-value--key"><%= EnterpriseToken.human_attribute_name(:starts_at) %></div> |
||||||
|
<div class="attributes-key-value--value-container"> |
||||||
|
<div class="attributes-key-value--value -text"> |
||||||
|
<span><%= format_date @current_token.starts_at %></span> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<% if @current_token.will_expire? %> |
||||||
|
<div class="attributes-key-value--key"><%= EnterpriseToken.human_attribute_name(:expires_at) %></div> |
||||||
|
<div class="attributes-key-value--value-container"> |
||||||
|
<div class="attributes-key-value--value -text"> |
||||||
|
<span><%= format_date @current_token.expires_at %></span> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<% end %> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<%= form_tag({ action: :destroy }, |
||||||
|
method: :delete, |
||||||
|
class: 'confirm-form-submit') do %> |
||||||
|
<%= styled_button_tag l(:button_delete), type: 'submit', class: '-with-icon icon-delete' %> |
||||||
|
<% end %> |
@ -0,0 +1,21 @@ |
|||||||
|
<% if @current_token.present? %> |
||||||
|
<collapsible-section section-title="<%=h t('admin.enterprise.replace_token') %>"> |
||||||
|
<% else %> |
||||||
|
<collapsible-section initially-expanded="true" |
||||||
|
section-title="<%=h t('admin.enterprise.add_token') %>"> |
||||||
|
<% end %> |
||||||
|
<%= labelled_tabular_form_for @token, |
||||||
|
url: { action: :create }, |
||||||
|
html: { class: 'token-form' }, |
||||||
|
method: :post do |f|%> |
||||||
|
|
||||||
|
<div class="form--space"> |
||||||
|
<%= f.text_area :encoded_token, :cols => 60, :rows => 15, placeholder: t('admin.enterprise.paste'), autocomplete: "off", autocorrect: "off", autocapitalize: "off", spellcheck: false %> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="form--space"> |
||||||
|
<%= styled_button_tag t(:button_create), id: 'token-submit-button', class: '-highlight -with-icon icon-checkmark' %> |
||||||
|
<%= link_to t(:button_cancel), { action: :show }, class: 'button' %> |
||||||
|
</div> |
||||||
|
<% end %> |
||||||
|
</collapsible-section> |
@ -0,0 +1,48 @@ |
|||||||
|
<% html_title t(:label_administration), t(:label_enterprise_edition) %> |
||||||
|
|
||||||
|
<%= toolbar title: t(:label_enterprise_edition) do %> |
||||||
|
<li class="toolbar-item"> |
||||||
|
<%= link_to( "#{OpenProject::Static::Links.links[:upsale][:href]}?utm_source=ce-token-admin", |
||||||
|
{ class: 'button -alt-highlight', |
||||||
|
title: t('admin.enterprise.order')}) do %> |
||||||
|
<i class="button--icon icon-add"></i> |
||||||
|
<span class="button--text"><%= t('admin.enterprise.order') %></span> |
||||||
|
<% end %> |
||||||
|
</li> |
||||||
|
<% end %> |
||||||
|
|
||||||
|
<%= error_messages_for 'token' %> |
||||||
|
|
||||||
|
<% if @current_token.present? %> |
||||||
|
<%= render partial: "current" %> |
||||||
|
<% else %> |
||||||
|
<div class="notification-box upsale-notification"> |
||||||
|
<div class="notification-box--content"> |
||||||
|
<h3><%= t('admin.enterprise.upgrade_to_ee') %></h3> |
||||||
|
<%= image_tag "enterprise_edition.png", class: "widget-box--teaser-image" %> |
||||||
|
|
||||||
|
<p><%= t('homescreen.blocks.upsale.description') %></p> |
||||||
|
|
||||||
|
<ul class=""> |
||||||
|
<li> |
||||||
|
<%= t('homescreen.blocks.upsale.additional_features') %> |
||||||
|
</li> |
||||||
|
<li> |
||||||
|
<%= t('homescreen.blocks.upsale.professional_support') %> |
||||||
|
</li> |
||||||
|
</ul> |
||||||
|
<p> |
||||||
|
<b><%= t('homescreen.blocks.upsale.become_hero') %></b> <%= t('homescreen.blocks.upsale.you_contribute') %> |
||||||
|
</p> |
||||||
|
<%= link_to( "#{OpenProject::Static::Links.links[:upsale][:href]}?utm_source=ce-enterprise-admin", |
||||||
|
{ class: 'button -alt-highlight', |
||||||
|
aria: {label: t('admin.enterprise.order')}, |
||||||
|
title: t('admin.enterprise.order')}) do %> |
||||||
|
<i class="button--icon icon-add"></i> |
||||||
|
<span class="button--text"><%= t('admin.enterprise.order') %></span> |
||||||
|
<% end %> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<% end %> |
||||||
|
|
||||||
|
<%= render partial: "form" %> |
@ -0,0 +1,30 @@ |
|||||||
|
<h3 class="widget-box--header"> |
||||||
|
<span class="icon-context icon-cart"></span> |
||||||
|
<span class="widget-box--header-title"><%= t('homescreen.blocks.upsale.title') %></span> |
||||||
|
</h3> |
||||||
|
|
||||||
|
<%= image_tag "enterprise_edition.png", class: "widget-box--teaser-image" %> |
||||||
|
|
||||||
|
<p><%= t('homescreen.blocks.upsale.description') %></p> |
||||||
|
|
||||||
|
<ul class="widget-box--feature-list"> |
||||||
|
<li> |
||||||
|
<%= t('homescreen.blocks.upsale.additional_features') %> |
||||||
|
</li> |
||||||
|
<li> |
||||||
|
<%= t('homescreen.blocks.upsale.professional_support') %> |
||||||
|
</li> |
||||||
|
</ul> |
||||||
|
<p> |
||||||
|
<b><%= t('homescreen.blocks.upsale.become_hero') %></b> <%= t('homescreen.blocks.upsale.you_contribute') %> |
||||||
|
</p> |
||||||
|
|
||||||
|
<div class="widget-box--blocks--buttons"> |
||||||
|
<%= link_to "#{OpenProject::Static::Links.links[:upsale][:href]}?utm_source=ce-homescreen", |
||||||
|
{ class: 'button -alt-highlight', |
||||||
|
aria: {label: t('homescreen.blocks.upsale.more_info')}, |
||||||
|
title: t('homescreen.blocks.upsale.more_info')} do %> |
||||||
|
<i class="button--icon icon-info2"></i> |
||||||
|
<span class="button--text"><%= t('homescreen.blocks.upsale.more_info') %></span> |
||||||
|
<% end %> |
||||||
|
</div> |
@ -0,0 +1,7 @@ |
|||||||
|
begin |
||||||
|
data = File.read(Rails.root.join(".openproject-token.pub")) |
||||||
|
key = OpenSSL::PKey::RSA.new(data) |
||||||
|
OpenProject::Token.key = key |
||||||
|
rescue |
||||||
|
warn "WARNING: Missing .openproject-token.pub key" |
||||||
|
end |
@ -0,0 +1,9 @@ |
|||||||
|
class CreateEnterpriseToken < ActiveRecord::Migration[5.0] |
||||||
|
def change |
||||||
|
create_table :enterprise_tokens do |t| |
||||||
|
t.text :encoded_token |
||||||
|
|
||||||
|
t.timestamps |
||||||
|
end |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,99 @@ |
|||||||
|
// -- copyright
|
||||||
|
// OpenProject is a project management system.
|
||||||
|
// Copyright (C) 2012-2015 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.
|
||||||
|
// ++
|
||||||
|
|
||||||
|
import IAugmentedJQuery = angular.IAugmentedJQuery; |
||||||
|
import { IDialogOpenResult, IDialogService } from 'ng-dialog'; |
||||||
|
import {IDialogScope} from 'ng-dialog'; |
||||||
|
|
||||||
|
export class ConfirmFormSubmitController { |
||||||
|
|
||||||
|
// Allow original form submission after dialog was closed
|
||||||
|
private confirmed = false; |
||||||
|
private dialog: IDialogOpenResult; |
||||||
|
|
||||||
|
constructor(protected $element:IAugmentedJQuery, |
||||||
|
protected $scope:angular.IScope, |
||||||
|
protected $http:angular.IHttpService, |
||||||
|
protected $q:angular.IQService, |
||||||
|
protected ngDialog:IDialogService, |
||||||
|
protected I18n:op.I18n) { |
||||||
|
|
||||||
|
this.$scope['text'] = { |
||||||
|
title: I18n.t('js.modals.form_submit.title'), |
||||||
|
text: I18n.t('js.modals.form_submit.text'), |
||||||
|
button_continue: I18n.t('js.button_continue'), |
||||||
|
button_cancel: I18n.t('js.button_cancel') |
||||||
|
}; |
||||||
|
|
||||||
|
this.$scope['confirmAndClose'] = () => { |
||||||
|
this.confirmed = true; |
||||||
|
this.dialog.close(); |
||||||
|
}; |
||||||
|
|
||||||
|
$element.on('submit', (evt) => { |
||||||
|
if (!this.confirmed) { |
||||||
|
evt.preventDefault(); |
||||||
|
this.openConfirmationDialog(); |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
return true; |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
public openConfirmationDialog() { |
||||||
|
this.dialog = this.ngDialog.open({ |
||||||
|
closeByEscape: true, |
||||||
|
showClose: true, |
||||||
|
closeByDocument: true, |
||||||
|
scope: <IDialogScope> this.$scope, |
||||||
|
template: '/components/modals/confirm-form-submit/confirm-form-submit.modal.html', |
||||||
|
className: 'ngdialog-theme-openproject', |
||||||
|
preCloseCallback: () => { |
||||||
|
if (this.confirmed) { |
||||||
|
this.$element.submit(); |
||||||
|
} |
||||||
|
return true; |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function confirmFormSubmit() { |
||||||
|
return { |
||||||
|
restrict: 'AC', |
||||||
|
scope: {}, |
||||||
|
bindToController: true, |
||||||
|
controller: ConfirmFormSubmitController, |
||||||
|
controllerAs: '$ctrl', |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
angular |
||||||
|
.module('openproject.uiComponents') |
||||||
|
.directive('confirmFormSubmit', confirmFormSubmit); |
@ -0,0 +1,18 @@ |
|||||||
|
<div class="confirm-form-submit--modal"> |
||||||
|
<div class="onboarding--start-modal"> |
||||||
|
<div class="onboarding--top-menu"> |
||||||
|
<span class="icon-context icon-attention"></span> |
||||||
|
<h2 ng-bind="::text.title"></h2> |
||||||
|
</div> |
||||||
|
<div class="ngdialog-body onboarding--main"> |
||||||
|
<p ng-bind="::text.text"></p> |
||||||
|
</div> |
||||||
|
<div class="onboarding--footer"> |
||||||
|
<button class="confirm-form-submit--continue button -highlight" |
||||||
|
ng-click="confirmAndClose()" |
||||||
|
ng-bind="::text.button_continue" |
||||||
|
ng-attr-title="::text.button_continue"> |
||||||
|
</button> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
@ -0,0 +1,154 @@ |
|||||||
|
#-- copyright |
||||||
|
# OpenProject is a project management system. |
||||||
|
# Copyright (C) 2012-2017 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 EnterpriseToken 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 EnterpriseToken |
||||||
|
# as published by the Free Software Foundation; either version 2 |
||||||
|
# of the EnterpriseToken, 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 EnterpriseToken for more details. |
||||||
|
# |
||||||
|
# You should have received a copy of the GNU General Public EnterpriseToken |
||||||
|
# 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 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 |
||||||
|
end |
||||||
|
|
||||||
|
before do |
||||||
|
login_as user |
||||||
|
allow(a_token).to receive(:token_object).and_return(token_object) |
||||||
|
end |
||||||
|
|
||||||
|
context 'with admin' do |
||||||
|
let(:user) { FactoryGirl.build(:admin) } |
||||||
|
|
||||||
|
describe '#show' do |
||||||
|
render_views |
||||||
|
|
||||||
|
context 'when token exists' do |
||||||
|
before do |
||||||
|
allow(EnterpriseToken).to receive(:current).and_return(a_token) |
||||||
|
get :show |
||||||
|
end |
||||||
|
|
||||||
|
it 'renders the overview' do |
||||||
|
expect(response).to be_success |
||||||
|
expect(response).to render_template 'show' |
||||||
|
expect(response).to render_template partial: 'enterprises/_current' |
||||||
|
expect(response).to render_template partial: 'enterprises/_form' |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
context 'when no token exists' do |
||||||
|
before do |
||||||
|
allow(EnterpriseToken).to receive(:current).and_return(nil) |
||||||
|
get :show |
||||||
|
end |
||||||
|
|
||||||
|
it 'still renders #show with form' do |
||||||
|
expect(response).not_to render_template partial: 'enterprises/_current' |
||||||
|
expect(response.body).to have_selector '.upsale-notification' |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
describe '#create' do |
||||||
|
let(:params) do |
||||||
|
{ |
||||||
|
enterprise_token: { encoded_token: 'foo' } |
||||||
|
} |
||||||
|
end |
||||||
|
|
||||||
|
before do |
||||||
|
allow(EnterpriseToken).to receive(:new).and_return(a_token) |
||||||
|
expect(a_token).to receive(:encoded_token=).with('foo') |
||||||
|
expect(a_token).to receive(:save).and_return(valid) |
||||||
|
|
||||||
|
post :create, params: params |
||||||
|
end |
||||||
|
|
||||||
|
context 'valid token input' do |
||||||
|
let(:valid) { true } |
||||||
|
|
||||||
|
it 'redirects to index' do |
||||||
|
expect(controller).to set_flash[:notice].to I18n.t(:notice_successful_update) |
||||||
|
expect(response).to redirect_to action: :show |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
context 'invalid token input' do |
||||||
|
let(:valid) { false } |
||||||
|
|
||||||
|
it 'renders with error' do |
||||||
|
expect(response).not_to be_redirect |
||||||
|
expect(response).to render_template 'enterprises/show' |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
describe '#destroy' do |
||||||
|
context 'when a token exists' do |
||||||
|
before do |
||||||
|
expect(EnterpriseToken).to receive(:current).and_return(a_token) |
||||||
|
expect(a_token).to receive(:destroy) |
||||||
|
|
||||||
|
delete :destroy |
||||||
|
end |
||||||
|
|
||||||
|
it 'redirects to show' do |
||||||
|
expect(controller).to set_flash[:notice].to I18n.t(:notice_successful_delete) |
||||||
|
expect(response).to redirect_to action: :show |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
context 'when no token exists' do |
||||||
|
before do |
||||||
|
expect(EnterpriseToken).to receive(:current).and_return(nil) |
||||||
|
delete :destroy |
||||||
|
end |
||||||
|
|
||||||
|
it 'renders 404' do |
||||||
|
expect(response.status).to eq(404) |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
context 'regular user' do |
||||||
|
let(:user) { FactoryGirl.build(:user) } |
||||||
|
|
||||||
|
before do |
||||||
|
get :show |
||||||
|
end |
||||||
|
|
||||||
|
it 'is forbidden' do |
||||||
|
expect(response.status).to eq 403 |
||||||
|
end |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,118 @@ |
|||||||
|
#-- copyright |
||||||
|
# OpenProject is a project management system. |
||||||
|
# Copyright (C) 2012-2017 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 EnterpriseToken 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 EnterpriseToken |
||||||
|
# as published by the Free Software Foundation; either version 2 |
||||||
|
# of the EnterpriseToken, 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 EnterpriseToken for more details. |
||||||
|
# |
||||||
|
# You should have received a copy of the GNU General Public EnterpriseToken |
||||||
|
# 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 'Enterprise token', type: :feature do |
||||||
|
include Redmine::I18n |
||||||
|
|
||||||
|
let(:admin) { FactoryGirl.create(:admin) } |
||||||
|
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 |
||||||
|
end |
||||||
|
|
||||||
|
let(:textarea) { find '#enterprise_token_encoded_token' } |
||||||
|
let(:submit_button) { find '#token-submit-button' } |
||||||
|
|
||||||
|
describe 'EnterpriseToken management' do |
||||||
|
before do |
||||||
|
login_as(admin) |
||||||
|
visit enterprise_path |
||||||
|
end |
||||||
|
|
||||||
|
it 'shows a teaser and token form without a token' do |
||||||
|
expect(page).to have_selector('.upsale-notification a', text: 'Order Enterprise Edition') |
||||||
|
expect(textarea.value).to be_empty |
||||||
|
|
||||||
|
textarea.set 'foobar' |
||||||
|
submit_button.click |
||||||
|
|
||||||
|
# Error output |
||||||
|
expect(page).to have_selector('.errorExplanation', |
||||||
|
text: "Enterprise support token can't be read. Are you sure it is a support token?") |
||||||
|
expect(page).to have_selector('span.errorSpan #enterprise_token_encoded_token') |
||||||
|
|
||||||
|
# Keeps value |
||||||
|
expect(textarea.value).to eq('foobar') |
||||||
|
end |
||||||
|
|
||||||
|
context 'assuming valid input' do |
||||||
|
before do |
||||||
|
allow(OpenProject::Token).to receive(:import).and_return(token_object) |
||||||
|
end |
||||||
|
|
||||||
|
it 'allows token import flow', js: true do |
||||||
|
textarea.set 'foobar' |
||||||
|
submit_button.click |
||||||
|
|
||||||
|
expect(page).to have_selector('.flash.notice', text: I18n.t(:notice_successful_update)) |
||||||
|
expect(page).to have_selector('.enterprise--active-token') |
||||||
|
|
||||||
|
expect(page.all('.attributes-key-value--key').map(&:text)) |
||||||
|
.to eq ['Subscriber', 'Email', 'Valid since'] |
||||||
|
expect(page.all('.attributes-key-value--value').map(&:text)) |
||||||
|
.to eq ['Foobar', 'foo@example.org', format_date(Date.today)] |
||||||
|
|
||||||
|
expect(page).to have_selector('.button.icon-delete', text: I18n.t(:button_delete)) |
||||||
|
|
||||||
|
# Expect section to be collapsed |
||||||
|
expect(page).to have_no_selector('#token_encoded_token', visible: true) |
||||||
|
|
||||||
|
expect(EnterpriseToken.current.encoded_token).to eq('foobar') |
||||||
|
|
||||||
|
# Replace token |
||||||
|
find('.collapsible-section--toggle-link').click |
||||||
|
textarea.set 'blabla' |
||||||
|
submit_button.click |
||||||
|
expect(page).to have_selector('.flash.notice', text: I18n.t(:notice_successful_update)) |
||||||
|
|
||||||
|
# Assume next request |
||||||
|
RequestStore.clear! |
||||||
|
expect(EnterpriseToken.current.encoded_token).to eq('blabla') |
||||||
|
|
||||||
|
# Remove token |
||||||
|
find('.button.icon-delete', text: I18n.t(:button_delete)).click |
||||||
|
|
||||||
|
# Expect modal |
||||||
|
find('.confirm-form-submit--continue').click |
||||||
|
expect(textarea.value).to be_empty |
||||||
|
expect(page).to have_selector('.flash.notice', text: I18n.t(:notice_successful_delete)) |
||||||
|
|
||||||
|
# Assume next request |
||||||
|
RequestStore.clear! |
||||||
|
expect(EnterpriseToken.current).to be_nil |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,92 @@ |
|||||||
|
Grequire 'spec_helper' |
||||||
|
|
||||||
|
RSpec.describe EnterpriseToken, type: :model do |
||||||
|
let(:object) { OpenProject::Token.new } |
||||||
|
subject { EnterpriseToken.new(encoded_token: 'foo') } |
||||||
|
|
||||||
|
before do |
||||||
|
EnterpriseToken.clear_cache |
||||||
|
end |
||||||
|
|
||||||
|
describe 'existing token' do |
||||||
|
before do |
||||||
|
allow_any_instance_of(EnterpriseToken).to receive(:token_object).and_return(object) |
||||||
|
subject.save!(validate: false) |
||||||
|
end |
||||||
|
|
||||||
|
context 'when inner token is active' do |
||||||
|
it 'has an active token' do |
||||||
|
expect(object).to receive(:expired?).and_return(false) |
||||||
|
expect(EnterpriseToken.count).to eq(1) |
||||||
|
expect(EnterpriseToken.current).to eq(subject) |
||||||
|
expect(EnterpriseToken.current.encoded_token).to eq('foo') |
||||||
|
expect(EnterpriseToken.show_banners).to eq(false) |
||||||
|
|
||||||
|
# Deleting it updates the current token |
||||||
|
EnterpriseToken.current.destroy! |
||||||
|
|
||||||
|
expect(EnterpriseToken.count).to eq(0) |
||||||
|
expect(EnterpriseToken.current).to be_nil |
||||||
|
end |
||||||
|
|
||||||
|
it 'delegates to the token object' do |
||||||
|
allow(object).to receive_messages( |
||||||
|
subscriber: 'foo', |
||||||
|
mail: 'bar', |
||||||
|
starts_at: Date.today, |
||||||
|
issued_at: Date.today, |
||||||
|
expires_at: 'never', |
||||||
|
restrictions: { foo: :bar } |
||||||
|
) |
||||||
|
|
||||||
|
expect(subject.subscriber).to eq('foo') |
||||||
|
expect(subject.mail).to eq('bar') |
||||||
|
expect(subject.starts_at).to eq(Date.today) |
||||||
|
expect(subject.issued_at).to eq(Date.today) |
||||||
|
expect(subject.expires_at).to eq('never') |
||||||
|
expect(subject.restrictions).to eq(foo: :bar) |
||||||
|
end |
||||||
|
|
||||||
|
describe '#allows_to?' do |
||||||
|
let(:service_double) { ::Authorization::EnterpriseService.new(subject) } |
||||||
|
|
||||||
|
before do |
||||||
|
expect(::Authorization::EnterpriseService).to receive(:new).twice.with(subject).and_return(service_double) |
||||||
|
end |
||||||
|
|
||||||
|
it 'forwards to EnterpriseTokenService for checks' do |
||||||
|
expect(service_double).to receive(:call).with(:forbidden_action).and_return double('ServiceResult', result: false) |
||||||
|
expect(service_double).to receive(:call).with(:allowed_action).and_return double('ServiceResult', result: true) |
||||||
|
|
||||||
|
expect(EnterpriseToken.allows_to?(:forbidden_action)).to eq false |
||||||
|
expect(EnterpriseToken.allows_to?(:allowed_action)).to eq true |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
context 'when inner token is expired' do |
||||||
|
before do |
||||||
|
expect(object).to receive(:expired?).and_return(true) |
||||||
|
end |
||||||
|
|
||||||
|
it 'has an expired token' do |
||||||
|
expect(EnterpriseToken.current).to eq(subject) |
||||||
|
expect(EnterpriseToken.show_banners).to eq(true) |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
describe 'no token' do |
||||||
|
it do |
||||||
|
expect(EnterpriseToken.current).to be_nil |
||||||
|
expect(EnterpriseToken.show_banners).to eq(true) |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
describe 'invalid token' do |
||||||
|
it 'appears as if no token is shown' do |
||||||
|
expect(EnterpriseToken.current).to be_nil |
||||||
|
expect(EnterpriseToken.show_banners).to eq(true) |
||||||
|
end |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,87 @@ |
|||||||
|
#-- copyright |
||||||
|
# OpenProject is a project management system. |
||||||
|
# Copyright (C) 2012-2017 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-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 doc/COPYRIGHT.rdoc for more details. |
||||||
|
#++ |
||||||
|
|
||||||
|
require 'spec_helper' |
||||||
|
|
||||||
|
describe Authorization::EnterpriseService do |
||||||
|
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 |
||||||
|
end |
||||||
|
let(:token) { mock_model(EnterpriseToken, token_object: token_object) } |
||||||
|
let(:instance) { described_class.new(token) } |
||||||
|
let(:result) { instance.call(action) } |
||||||
|
let(:action) { :an_action } |
||||||
|
|
||||||
|
describe '#initialize' do |
||||||
|
it 'has the token' do |
||||||
|
expect(instance.token).to eql token |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
describe 'expiry' do |
||||||
|
before do |
||||||
|
allow(token).to receive(:expired?).and_return(expired) |
||||||
|
end |
||||||
|
|
||||||
|
context 'when expired' do |
||||||
|
let(:expired) { true } |
||||||
|
|
||||||
|
it 'returns a false result' do |
||||||
|
expect(result).to be_kind_of ServiceResult |
||||||
|
expect(result.result).to be_falsey |
||||||
|
expect(result.success?).to be_falsey |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
context 'when active' do |
||||||
|
let(:expired) { false } |
||||||
|
|
||||||
|
context 'invalid action' do |
||||||
|
it 'returns false' do |
||||||
|
expect(result.result).to be_falsey |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
context 'valid action requires active token' do |
||||||
|
let(:action) { :define_custom_style } |
||||||
|
|
||||||
|
it 'returns a true result' do |
||||||
|
expect(result).to be_kind_of ServiceResult |
||||||
|
expect(result.result).to be_truthy |
||||||
|
expect(result.success?).to be_truthy |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
|
end |
Loading…
Reference in new issue