Commit 6c25cadf authored by Oswaldo Ferreira's avatar Oswaldo Ferreira Committed by Douwe Maan

License plan check on namespaces

parent e72964c7
......@@ -174,7 +174,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:shared_runners_minutes,
:minimum_mirror_sync_time,
:geo_status_timeout,
:elasticsearch_experimental_indexer
:elasticsearch_experimental_indexer,
:check_namespace_plan
]
end
end
......@@ -85,7 +85,8 @@ class Admin::GroupsController < Admin::ApplicationController
def group_params_ee
[
:repository_size_limit,
:shared_runners_minutes_limit
:shared_runners_minutes_limit,
:plan
]
end
end
......@@ -206,7 +206,7 @@ class Admin::UsersController < Admin::ApplicationController
def user_params_ee
[
:note,
namespace_attributes: [:id, :shared_runners_minutes_limit]
namespace_attributes: [:id, :shared_runners_minutes_limit, :plan]
]
end
end
......@@ -45,7 +45,7 @@ class Projects::PathLocksController < Projects::ApplicationController
private
def check_license
unless license_allows_file_locks?
unless @project.feature_available?(:file_lock)
flash[:alert] = 'You need a different license to enable FileLocks feature'
redirect_to admin_license_path
end
......
......@@ -52,7 +52,7 @@ class Projects::RefsController < Projects::ApplicationController
contents.push(*tree.blobs)
contents.push(*tree.submodules)
show_path_locks = license_allows_file_locks? && @project.path_locks.any?
show_path_locks = @project.feature_available?(:file_lock) && @project.path_locks.any?
@logs = contents[@offset, @limit].to_a.map do |content|
file = @path ? File.join(@path, content.name) : content.name
......
module AuditorUserHelper
def license_allows_auditor_user?
@license_allows_auditor_user ||= (::License.current && ::License.current.add_on?('GitLab_Auditor_User'))
@license_allows_auditor_user ||= (::License.current&.feature_available?(:auditor_user))
end
end
......@@ -3,10 +3,6 @@ module PathLocksHelper
can?(current_user, :admin_path_locks, project) || path_lock.user == current_user
end
def license_allows_file_locks?
@license_allows_file_locks ||= (::License.current && ::License.current.add_on?('GitLab_FileLocks'))
end
def text_label_for_lock(file_lock, path)
if file_lock.path == path
"Locked by #{file_lock.user.name}"
......
......@@ -109,7 +109,7 @@ module TreeHelper
end
def lock_file_link(project = @project, path = @path, html_options: {})
return unless license_allows_file_locks? && current_user
return unless project.feature_available?(:file_lock) && current_user
return if path.blank?
path_lock = project.find_path_lock(path, downstream: true)
......@@ -169,7 +169,7 @@ module TreeHelper
end
def render_lock_icon(path)
return unless license_allows_file_locks?
return unless @project.feature_available?(:file_lock)
return unless @project.root_ref?(@ref)
if file_lock = @project.find_path_lock(path, exact_match: true)
......
......@@ -10,5 +10,9 @@ module EE
validates :shared_runners_minutes,
numericality: { greater_than_or_equal_to: 0 }
end
def should_check_namespace_plan?
check_namespace_plan? && (::Gitlab.com? || Rails.env.development?)
end
end
end
......@@ -6,11 +6,36 @@ module EE
module Namespace
extend ActiveSupport::Concern
BRONZE_PLAN = 'bronze'.freeze
SILVER_PLAN = 'silver'.freeze
GOLD_PLAN = 'gold'.freeze
EARLY_ADOPTER_PLAN = 'early_adopter'.freeze
EE_PLANS = {
BRONZE_PLAN => License::STARTER_PLAN,
SILVER_PLAN => License::PREMIUM_PLAN,
GOLD_PLAN => License::ULTIMATE_PLAN,
EARLY_ADOPTER_PLAN => License::EARLY_ADOPTER_PLAN
}.freeze
prepended do
has_one :namespace_statistics, dependent: :destroy
delegate :shared_runners_minutes, :shared_runners_seconds, :shared_runners_seconds_last_reset,
to: :namespace_statistics, allow_nil: true
validates :plan, inclusion: { in: EE_PLANS.keys }, allow_nil: true
end
# Checks features (i.e. https://about.gitlab.com/products/) availabily
# for a given Namespace plan. This method should consider ancestor groups
# being licensed.
def feature_available?(feature)
@features_available ||= Hash.new do |h, feature|
h[feature] = plans.any? { |plan| License.plan_includes_feature?(EE_PLANS[plan], feature) }
end
@features_available[feature]
end
def actual_shared_runners_minutes_limit
......@@ -27,5 +52,16 @@ module EE
shared_runners_minutes_limit_enabled? &&
shared_runners_minutes.to_i >= actual_shared_runners_minutes_limit
end
private
def plans
@ancestors_plans ||=
if parent_id
ancestors.where.not(plan: nil).reorder(nil).pluck('DISTINCT plan') + [plan]
else
[plan]
end
end
end
end
......@@ -24,6 +24,17 @@ module EE
!public? && shared_runners_enabled? && namespace.shared_runners_minutes_limit_enabled?
end
# Checks licensed feature availability if `feature` matches any
# key on License::FEATURE_CODES. Otherwise, check feature availability
# through ProjectFeature.
def feature_available?(feature, user = nil)
if License::FEATURE_CODES.key?(feature)
licensed_feature_available?(feature)
else
super
end
end
def service_desk_address
return nil unless service_desk_available?
......@@ -35,6 +46,17 @@ module EE
private
def licensed_feature_available?(feature)
globally_available = License.current&.feature_available?(feature)
if current_application_settings.should_check_namespace_plan?
globally_available &&
(public? && namespace.public? || namespace.feature_available?(feature))
else
globally_available
end
end
def service_desk_available?
return @service_desk_available if defined?(@service_desk_available)
......
class License < ActiveRecord::Base
include ActionView::Helpers::NumberHelper
DEPLOY_BOARD_FEATURE = 'GitLab_DeployBoard'.freeze
FILE_LOCK_FEATURE = 'GitLab_FileLocks'.freeze
GEO_FEATURE = 'GitLab_Geo'.freeze
AUDITOR_USER_FEATURE = 'GitLab_Auditor_User'.freeze
SERVICE_DESK_FEATURE = 'GitLab_ServiceDesk'.freeze
FEATURE_CODES = {
geo: GEO_FEATURE,
auditor_user: AUDITOR_USER_FEATURE,
service_desk: SERVICE_DESK_FEATURE,
# Features that make sense to Namespace:
deploy_board: DEPLOY_BOARD_FEATURE,
file_lock: FILE_LOCK_FEATURE
}.freeze
STARTER_PLAN = 'starter'.freeze
PREMIUM_PLAN = 'premium'.freeze
ULTIMATE_PLAN = 'ultimate'.freeze
EARLY_ADOPTER_PLAN = 'early_adopter'.freeze
EES_FEATURES = [
# ..
].freeze
EEP_FEATURES = [
*EES_FEATURES,
{ 'GitLab_DeployBoard' => 1 },
{ 'GitLab_FileLocks' => 1 },
{ 'GitLab_Geo' => 1 },
{ 'GitLab_Auditor_User' => 1 },
{ 'GitLab_ServiceDesk' => 1 }
{ DEPLOY_BOARD_FEATURE => 1 },
{ FILE_LOCK_FEATURE => 1 },
{ GEO_FEATURE => 1 },
{ AUDITOR_USER_FEATURE => 1 },
{ SERVICE_DESK_FEATURE => 1 }
].freeze
EEU_FEATURES = [
*EEP_FEATURES
# ..
].freeze
# List all features available for early adopters,
# i.e. users that started using GitLab.com before
# the introduction of Bronze, Silver, Gold plans.
# Obs.: Do not extend from other feature constants.
# Early adopters should not earn new features as they're
# introduced.
EARLY_ADOPTER_FEATURES = [
# TODO: Add EES features
# https://gitlab.com/gitlab-org/gitlab-ee/issues/2335)
{ DEPLOY_BOARD_FEATURE => 1 },
{ FILE_LOCK_FEATURE => 1 },
{ GEO_FEATURE => 1 },
{ AUDITOR_USER_FEATURE => 1 },
{ SERVICE_DESK_FEATURE => 1 }
].freeze
FEATURES_BY_PLAN = {
'starter' => EES_FEATURES,
'premium' => EEP_FEATURES
STARTER_PLAN => EES_FEATURES,
PREMIUM_PLAN => EEP_FEATURES,
ULTIMATE_PLAN => EEU_FEATURES,
EARLY_ADOPTER_PLAN => EARLY_ADOPTER_FEATURES
}.freeze
validate :valid_license
......@@ -48,6 +91,13 @@ class License < ActiveRecord::Base
RequestStore.delete(:current_license)
end
def plan_includes_feature?(plan, code)
features = features_for_plan(plan)
feature = FEATURE_CODES.fetch(code)
features[feature].to_i > 0
end
def block_changes?
!current || current.block_changes?
end
......@@ -115,8 +165,9 @@ class License < ActiveRecord::Base
explicit_add_ons.merge(plan_features)
end
def add_on?(code)
add_ons[code].to_i > 0
def feature_available?(code)
feature = FEATURE_CODES.fetch(code)
add_ons[feature].to_i > 0
end
def restricted_user_count
......
......@@ -76,7 +76,7 @@ class ProjectPolicy < BasePolicy
can! :read_deployment
can! :read_merge_request
if License.current&.add_on?('GitLab_DeployBoard') || Rails.env.development?
if project.feature_available?(:deploy_board) || Rails.env.development?
can! :read_deploy_board
end
end
......
.form-group
= f.label :plan, class: 'control-label'
.col-sm-10
= f.select :plan, options_for_select(Namespace::EE_PLANS.keys.map { |plan| [plan.titleize, plan] }, f.object.plan), {}, class: 'form-control'
......@@ -95,6 +95,16 @@
= f.check_box :user_default_external
Newly registered users will by default be external
- if Gitlab.com? || Rails.env.development?
.form-group
= f.label :check_namespace_plan, 'Check feature availability on namespace plan', class: 'control-label col-sm-2'
.col-sm-10
.checkbox
= f.label :check_namespace_plan do
= f.check_box :check_namespace_plan
Enabling this will only make licensed EE features available to projects if the project namespace's plan
includes the feature or if the project is public.
%fieldset
%legend Sign-up Restrictions
.form-group
......
......@@ -4,6 +4,9 @@
= render 'groups/repository_size_limit_setting', f: f
- if current_application_settings.should_check_namespace_plan?
= render 'admin/namespace_plan', f: f
.form-group.group-description-holder
= f.label :avatar, "Group avatar", class: 'control-label'
.col-sm-10
......
......@@ -70,6 +70,12 @@
= f.label :note, 'Note', class: 'control-label'
.col-sm-10= f.text_area :note, class: 'form-control'
- if current_application_settings.should_check_namespace_plan?
= f.fields_for :namespace do |namespace_form|
%fieldset
%legend Plan
= render 'admin/namespace_plan', f: namespace_form
.form-actions
- if @user.new_record?
= f.submit 'Create user', class: "btn btn-create"
......
......@@ -20,7 +20,7 @@
= render 'projects/fork_suggestion'
- if license_allows_file_locks?
- if @project.feature_available?(:file_lock)
:javascript
PathLocks.init(
'#{toggle_namespace_project_path_locks_path(@project.namespace, @project)}',
......
......@@ -35,7 +35,7 @@
= link_to charts_namespace_project_graph_path(@project.namespace, @project, current_ref) do
Charts
- if license_allows_file_locks?
- if @project.feature_available?(:file_lock)
= nav_link(controller: [:path_locks]) do
= link_to namespace_project_path_locks_path(@project.namespace, @project) do
Locked Files
......@@ -23,7 +23,7 @@
= render 'projects/blob/upload', title: 'Upload New File', placeholder: 'Upload new file', button_title: 'Upload file', form_path: namespace_project_create_blob_path(@project.namespace, @project, @id), method: :post
= render 'projects/blob/new_dir'
- if license_allows_file_locks?
- if @project.feature_available?(:file_lock)
:javascript
PathLocks.init(
'#{toggle_namespace_project_path_locks_path(@project.namespace, @project)}',
......
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class AddPlanToNamespace < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
# Set this constant to true if this migration requires downtime.
DOWNTIME = false
def change
add_column(:namespaces, :plan, :string)
end
end
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class AddCheckNamespacePlanToApplicationSettings < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
DOWNTIME = false
def up
add_column_with_default :application_settings,
:check_namespace_plan,
:boolean,
default: false,
allow_null: false
end
def down
remove_column(:application_settings, :check_namespace_plan)
end
end
......@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20170511101000) do
ActiveRecord::Schema.define(version: 20170512173638) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
......@@ -137,6 +137,7 @@ ActiveRecord::Schema.define(version: 20170511101000) do
t.integer "cached_markdown_version"
t.boolean "clientside_sentry_enabled", default: false, null: false
t.string "clientside_sentry_dsn"
t.boolean "check_namespace_plan", default: false, null: false
end
create_table "approvals", force: :cascade do |t|
......@@ -880,6 +881,7 @@ ActiveRecord::Schema.define(version: 20170511101000) do
t.boolean "require_two_factor_authentication", default: false, null: false
t.integer "two_factor_grace_period", default: 48, null: false
t.integer "cached_markdown_version"
t.string "plan"
end
add_index "namespaces", ["created_at"], name: "index_namespaces_on_created_at", using: :btree
......
......@@ -474,6 +474,7 @@ module API
end
class Namespace < Grape::Entity
expose :plan, if: lambda { |_, options| options[:current_user] && options[:current_user].admin? }
expose :id, :name, :path, :kind, :full_path
end
......
......@@ -69,6 +69,14 @@ module API
end
end
def find_namespace(id)
if id =~ /^\d+$/
Namespace.find_by(id: id)
else
Namespace.find_by_full_path(id)
end
end
def find_group!(id)
group = find_group(id)
......
......@@ -19,6 +19,26 @@ module API
present paginate(namespaces), with: Entities::Namespace
end
desc 'Update a namespace' do
success Entities::Namespace
end
params do
optional :plan, type: String, desc: "Namespace or Group plan"
end
put ':id' do
authenticated_as_admin!
namespace = find_namespace(params[:id])
return not_found!('Namespace') unless namespace
if namespace.update(declared_params)
present namespace, with: Entities::Namespace, current_user: current_user
else
render_validation_error!(namespace)
end
end
end
end
end
......@@ -2,8 +2,7 @@ module EE
module Gitlab
module ServiceDesk
def self.enabled?
::License.current &&
::License.current.add_on?('GitLab_ServiceDesk') &&
::License.current&.feature_available?(:service_desk) &&
::Gitlab::IncomingEmail.enabled? &&
::Gitlab::IncomingEmail.supports_wildcard?
end
......
require_dependency 'gitlab/git'
module Gitlab
SUBDOMAIN_REGEX = %r{\Ahttps://[a-z0-9]+\.gitlab\.com\z}
def self.com?
# Check `staging?` as well to keep parity with gitlab.com
Gitlab.config.gitlab.url == 'https://gitlab.com' || staging?
# Check `gl_subdomain?` as well to keep parity with gitlab.com
Gitlab.config.gitlab.url == 'https://gitlab.com' || gl_subdomain?
end
def self.staging?
Gitlab.config.gitlab.url == 'https://staging.gitlab.com'
def self.gl_subdomain?
SUBDOMAIN_REGEX === Gitlab.config.gitlab.url
end
end
......@@ -233,7 +233,7 @@ module Gitlab
end
def validate_path_locks?
@validate_path_locks ||= license_allows_file_locks? &&
@validate_path_locks ||= @project.feature_available?(:file_lock) &&
project.path_locks.any? && @newrev && @oldrev &&
project.default_branch == @branch_name # locks protect default branch only
end
......
......@@ -47,7 +47,7 @@ module Gitlab
end
def self.license_allows?
::License.current && ::License.current.add_on?('GitLab_Geo')
::License.current&.feature_available?(:geo)
end
def self.primary?
......
......@@ -55,7 +55,7 @@ module Gitlab
end
def service_desk_counts
return {} unless ::License.current&.add_on?('GitLab_ServiceDesk')
return {} unless ::License.current&.feature_available?(:service_desk)
projects_with_service_desk = Project.where(service_desk_enabled: true)
......
......@@ -9,7 +9,7 @@ describe Projects::EnvironmentsController do
end
before do
allow_any_instance_of(License).to receive(:add_on?).and_return(false)
allow_any_instance_of(License).to receive(:feature_available?).and_return(false)
project.team << [user, :master]
......@@ -46,7 +46,7 @@ describe Projects::EnvironmentsController do
context 'when requesting available environments scope' do
before do
allow_any_instance_of(License).to receive(:add_on?).with('GitLab_DeployBoard').and_return(true)
allow_any_instance_of(License).to receive(:feature_available?).with(:deploy_board).and_return(true)
get :index, environment_params(format: :json, scope: :available)
end
......@@ -87,7 +87,7 @@ describe Projects::EnvironmentsController do
context 'when license does not has the GitLab_DeployBoard add-on' do
before do
allow_any_instance_of(License).to receive(:add_on?).with('GitLab_DeployBoard').and_return(false)
allow_any_instance_of(License).to receive(:feature_available?).with(:deploy_board).and_return(false)
get :index, environment_params(format: :json)
end
......@@ -294,7 +294,7 @@ describe Projects::EnvironmentsController do
let(:project) { create(:kubernetes_project) }
before do
allow_any_instance_of(License).to receive(:add_on?).with('GitLab_DeployBoard').and_return(true)
allow_any_instance_of(License).to receive(:feature_available?).with(:deploy_board).and_return(true)
allow_any_instance_of(Environment).to receive(:deployment_service_ready?).and_return(true)
end
......@@ -322,7 +322,7 @@ describe Projects::EnvironmentsController do
context 'when license does not has the GitLab_DeployBoard add-on' do
before do
allow_any_instance_of(License).to receive(:add_on?).with('GitLab_DeployBoard').and_return(false)
allow_any_instance_of(License).to receive(:feature_available?).with(:deploy_board).and_return(false)
end
it 'does not return any data' do
......
......@@ -5,8 +5,8 @@ describe Projects::ServiceDeskController do
let(:user) { create(:user) }
before do
allow_any_instance_of(License).to receive(:add_on?).and_call_original
allow_any_instance_of(License).to receive(:add_on?).with('GitLab_ServiceDesk') { true }
allow_any_instance_of(License).to receive(:feature_available?).and_call_original
allow_any_instance_of(License).to receive(:feature_available?).with(:service_desk) { true }
allow(Gitlab::IncomingEmail).to receive(:enabled?) { true }
allow(Gitlab::IncomingEmail).to receive(:supports_wildcard?) { true }
project.update(service_desk_enabled: true)
......
......@@ -6,7 +6,7 @@ feature 'Path Locks', feature: true, js: true do
let(:project_tree_path) { namespace_project_tree_path(project.namespace, project, project.repository.root_ref) }
before do
allow_any_instance_of(PathLocksHelper).to receive(:license_allows_file_locks?).and_return(true)
allow(project).to receive(:feature_available?).with(:file_lock) { true }
project.team << [user, :master]
login_with(user)
......
......@@ -9,8 +9,8 @@ describe 'Service Desk Setting', js: true, feature: true do
before do
project.add_master(user)
login_as(user)
allow_any_instance_of(License).to receive(:add_on?).and_call_original
allow_any_instance_of(License).to receive(:add_on?).with('GitLab_ServiceDesk') { true }
allow_any_instance_of(License).to receive(:feature_available?).and_call_original
allow_any_instance_of(License).to receive(:feature_available?).with(:service_desk) { true }
allow(::Gitlab::IncomingEmail).to receive(:enabled?) { true }
allow(::Gitlab::IncomingEmail).to receive(:supports_wildcard?) { true }
......
......@@ -35,7 +35,7 @@ describe TreeHelper do
before do
allow(helper).to receive(:can?).and_return(true)
allow(helper).to receive(:current_user).and_return(user)
allow(helper).to receive(:license_allows_file_locks?).and_return(true)
allow(project).to receive(:feature_available?).with(:file_lock) { true }
project.reload
end
......
......@@ -2,8 +2,8 @@ require 'spec_helper'
describe EE::Gitlab::ServiceDesk, lib: true do
before do
allow_any_instance_of(License).to receive(:add_on?).and_call_original
allow_any_instance_of(License).to receive(:add_on?).with('GitLab_ServiceDesk') { true }
allow_any_instance_of(License).to receive(:feature_available?).and_call_original
allow_any_instance_of(License).to receive(:feature_available?).with(:service_desk) { true }
allow(::Gitlab::IncomingEmail).to receive(:enabled?) { true }
allow(::Gitlab::IncomingEmail).to receive(:supports_wildcard?) { true }
end
......@@ -14,7 +14,7 @@ describe EE::Gitlab::ServiceDesk, lib: true do
context 'when license does not support service desk' do
before do
allow_any_instance_of(License).to receive(:add_on?).with('GitLab_ServiceDesk') { false }
allow_any_instance_of(License).to receive(:feature_available?).with(:service_desk) { false }
end
it { is_expected.to be_falsy }
......
......@@ -336,12 +336,11 @@ describe Gitlab::Checks::ChangeAccess, lib: true do
let!(:path_lock) { create(:path_lock, path: 'README', project: project) }
before do
allow_any_instance_of(PathLocksHelper).to receive(:license_allows_file_locks?).and_return(true)
allow(project.repository).to receive(:new_commits).and_return(
project.repository.commits_between('be93687618e4b132087f430a4d8fc3a609c9b77c', '54fcc214b94e78d7a41a9a8fe6d87a5e59500e51')
)
end
it 'returns an error if the changes update a path locked by another user' do
expect(subject.status).to be(false)
expect(subject.message).to eq("The path 'README' is locked by #{path_lock.user.name}")
......
......@@ -19,8 +19,8 @@ describe Gitlab::Email::Handler::EE::ServiceDeskHandler do
allow(Notify).to receive(:service_desk_thank_you_email)
.with(kind_of(Integer)).and_return(double(deliver_later!: true))
allow_any_instance_of(License).to receive(:add_on?).and_call_original
allow_any_instance_of(License).to receive(:add_on?).with('GitLab_ServiceDesk') { true }
allow_any_instance_of(License).to receive(:feature_available?).and_call_original
allow_any_instance_of(License).to receive(:feature_available?).with(:service_desk) { true }
allow(::Gitlab::IncomingEmail).to receive(:enabled?) { true }
allow(::Gitlab::IncomingEmail).to receive(:supports_wildcard?) { true }
end
......@@ -56,8 +56,8 @@ describe Gitlab::Email::Handler::EE::ServiceDeskHandler do
context 'when license does not support service desk' do
before do
allow_any_instance_of(License).to receive(:add_on?).and_call_original
allow_any_instance_of(License).to receive(:add_on?).with('GitLab_ServiceDesk') { false }
allow_any_instance_of(License).to receive(:feature_available?).and_call_original
allow_any_instance_of(License).to receive(:feature_available?).with(:service_desk) { false }
end
it 'does not create an issue or send email' do
......
......@@ -13,15 +13,15 @@ describe Gitlab::Email::Handler, lib: true do
context 'a Service Desk email' do
it 'uses the Service Desk handler when Service Desk is enabled' do
allow_any_instance_of(License).to receive(:add_on?).and_call_original
allow_any_instance_of(License).to receive(:add_on?).with('GitLab_ServiceDesk').and_return(true)
allow_any_instance_of(License).to receive(:feature_available?).and_call_original
allow_any_instance_of(License).to receive(:feature_available?).with(:service_desk).and_return(true)
expect(handler_for('emails/service_desk.eml', 'some/project')).to be_instance_of(Gitlab::Email::Handler::EE::ServiceDeskHandler)
end
it 'uses no handler when Service Desk is disabled' do
allow_any_instance_of(License).to receive(:add_on?).and_call_original
allow_any_instance_of(License).to receive(:add_on?).with('GitLab_ServiceDesk').and_return(false)
allow_any_instance_of(License).to receive(:feature_available?).and_call_original
allow_any_instance_of(License).to receive(:feature_available?).with(:service_desk).and_return(false)
expect(handler_for('emails/service_desk.eml', 'some/project')).to be_nil
end
......@@ -31,15 +31,15 @@ describe Gitlab::Email::Handler, lib: true do
let!(:user) { create(:user, email: 'jake@adventuretime.ooo', incoming_email_token: 'auth_token') }
it 'uses the create issue handler when Service Desk is enabled' do
allow_any_instance_of(License).to receive(:add_on?).and_call_original
allow_any_instance_of(License).to receive(:add_on?).with('GitLab_ServiceDesk').and_return(true)
allow_any_instance_of(License).to receive(:feature_available?).and_call_original
allow_any_instance_of(License).to receive(:feature_available?).with(:service_desk).and_return(true)
expect(handler_for('emails/valid_new_issue.eml', 'some/project+auth_token')).to be_instance_of(Gitlab::Email::Handler::CreateIssueHandler)
end
it 'uses the create issue handler when Service Desk is disabled' do
allow_any_instance_of(License).to receive(:add_on?).and_call_original
allow_any_instance_of(License).to receive(:add_on?).with('GitLab_ServiceDesk').and_return(false)
allow_any_instance_of(License).to receive(:feature_available?).and_call_original
allow_any_instance_of(License).to receive(:feature_available?).with(:service_desk).and_return(false)
expect(handler_for('emails/valid_new_issue.eml', 'some/project+auth_token')).to be_instance_of(Gitlab::Email::Handler::CreateIssueHandler)
end
......
......@@ -88,12 +88,12 @@ describe Gitlab::Geo, lib: true do
describe 'license_allows?' do
it 'returns true if license has Geo addon' do
allow_any_instance_of(License).to receive(:add_on?).with('GitLab_Geo') { true }
allow_any_instance_of(License).to receive(:feature_available?).with(:geo) { true }
expect(described_class.license_allows?).to be_truthy
end
it 'returns false if license doesnt have Geo addon' do
allow_any_instance_of(License).to receive(:add_on?).with('GitLab_Geo') { false }
allow_any_instance_of(License).to receive(:feature_available?).with(:geo) { false }
expect(described_class.license_allows?).to be_falsey
end
......
......@@ -98,7 +98,7 @@ describe Gitlab::UsageData do
context 'when Service Desk is disabled' do
it 'returns an empty hash' do
allow_any_instance_of(License).to receive(:add_on?).with('GitLab_ServiceDesk').and_return(false)
allow_any_instance_of(License).to receive(:feature_available?).with(:service_desk).and_return(false)
expect(subject).to eq({})
end
......@@ -115,7 +115,7 @@ describe Gitlab::UsageData do
context 'when Service Desk is enabled' do
it 'gathers Service Desk data' do
create_list(:issue, 3, confidential: true, author: User.support_bot, project: [project3, project4].sample)
allow_any_instance_of(License).to receive(:add_on?).with('GitLab_ServiceDesk').and_return(true)
allow_any_instance_of(License).to receive(:feature_available?).with(:service_desk).and_return(true)
expect(subject).to eq(service_desk_enabled_projects: 2,
service_desk_issues: 3)
......
......@@ -14,6 +14,10 @@ describe Gitlab, lib: true do
expect(described_class.com?).to eq true
end
it 'is true when on other gitlab subdomain' do
stub_config_setting(url: 'https://example.gitlab.com')
end
it 'is false when not on GitLab.com' do
stub_config_setting(url: 'http://example.com')
......
require 'spec_helper'
describe ApplicationSetting do
let(:setting) { described_class.create_from_defaults }
describe '#should_check_namespace_plan?' do
before do
stub_application_setting(check_namespace_plan: check_namespace_plan_column)
allow(::Gitlab).to receive(:com?) { gl_com }
end
subject { setting.should_check_namespace_plan? }
context 'when check_namespace_plan true AND on GitLab.com' do
let(:check_namespace_plan_column) { true }
let(:gl_com) { true }
it 'returns true' do
is_expected.to eq(true)
end
end
context 'when check_namespace_plan true AND NOT on GitLab.com' do
let(:check_namespace_plan_column) { true }
let(:gl_com) { false }
it 'returns false' do
is_expected.to eq(false)
end
end
context 'when check_namespace_plan false AND on GitLab.com' do
let(:check_namespace_plan_column) { false }
let(:gl_com) { true }
it 'returns false' do
is_expected.to eq(false)
end
end
end
end
......@@ -8,6 +8,41 @@ describe Namespace, models: true do
it { is_expected.to delegate_method(:shared_runners_minutes).to(:namespace_statistics) }
it { is_expected.to delegate_method(:shared_runners_seconds).to(:namespace_statistics) }
it { is_expected.to delegate_method(:shared_runners_seconds_last_reset).to(:namespace_statistics) }
it { is_expected.to validate_inclusion_of(:plan).in_array(Namespace::EE_PLANS.keys).allow_nil }
describe '#feature_available?' do
let(:group) { create(:group, plan: plan_license) }
subject { group.feature_available?(feature) }
context 'when feature available' do
let(:feature) { :deploy_board }
let(:plan_license) { Namespace::GOLD_PLAN }
context 'when feature available for current group' do
it 'returns false' do
is_expected.to eq(true)
end
end
context 'when license is applied to parent group' do
let(:child_group) { create :group, parent: group }
it 'child group has feature available' do
expect(child_group.feature_available?(feature)).to eq(true)
end
end
end
context 'when feature not available' do
let(:feature) { :deploy_board }
let(:plan_license) { Namespace::BRONZE_PLAN }
it 'returns false' do
is_expected.to eq(false)
end
end
end
describe '#shared_runners_enabled?' do
subject { namespace.shared_runners_enabled? }
......
......@@ -11,6 +11,101 @@ describe Project, models: true do
it { is_expected.to delegate_method(:shared_runners_minutes_used?).to(:namespace) }
end
describe '#feature_available?' do
let(:namespace) { build_stubbed(:namespace) }
let(:project) { build_stubbed(:project, namespace: namespace) }
let(:user) { build_stubbed(:user) }
subject { project.feature_available?(feature, user) }
context 'when feature symbol is included on Namespace features code' do
before do
stub_application_setting('check_namespace_plan?' => check_namespace_plan)
allow(Gitlab).to receive(:com?) { true }
expect_any_instance_of(License).to receive(:feature_available?).with(feature) { allowed_on_global_license }
allow(namespace).to receive(:plan) { plan_license }
end
License::FEATURE_CODES.each do |feature_sym, feature_code|
let(:feature) { feature_sym }
let(:feature_code) { feature_code }
context "checking #{feature} availabily both on Global and Namespace license" do
let(:check_namespace_plan) { true }
context 'allowed by Plan License AND Global License' do
let(:allowed_on_global_license) { true }
let(:plan_license) { Namespace::GOLD_PLAN }
it 'returns true' do
is_expected.to eq(true)
end
end
context 'not allowed by Plan License but project and namespace are public' do
let(:allowed_on_global_license) { true }
let(:plan_license) { Namespace::BRONZE_PLAN }
it 'returns true' do
allow(namespace).to receive(:public?) { true }
allow(project).to receive(:public?) { true }
is_expected.to eq(true)
end
end
context 'not allowed by Plan License' do
let(:allowed_on_global_license) { true }
let(:plan_license) { Namespace::BRONZE_PLAN }
it 'returns false' do
is_expected.to eq(false)
end
end
context 'not allowed by Global License' do
let(:allowed_on_global_license) { false }
let(:plan_license) { Namespace::GOLD_PLAN }
it 'returns false' do
is_expected.to eq(false)
end
end
end
context "when checking #{feature_code} only for Global license" do
let(:check_namespace_plan) { false }
context 'allowed by Global License' do
let(:allowed_on_global_license) { true }
it 'returns true' do
is_expected.to eq(true)
end
end
context 'not allowed by Global License' do
let(:allowed_on_global_license) { false }
it 'returns false' do
is_expected.to eq(false)
end
end
end
end
end
context 'when feature symbol is not included on Namespace features code' do
let(:feature) { :issues }
it 'checks availability of licensed feature' do
expect(project.project_feature).to receive(:feature_available?).with(feature, user)
subject
end
end
end
describe '#any_runners_limit' do
let(:project) { create(:empty_project, shared_runners_enabled: shared_runners_enabled) }
let(:specific_runner) { create(:ci_runner) }
......@@ -120,8 +215,8 @@ describe Project, models: true do
let(:project) { create(:empty_project, service_desk_enabled: true) }
before do
allow_any_instance_of(License).to receive(:add_on?).and_call_original
allow_any_instance_of(License).to receive(:add_on?).with('GitLab_ServiceDesk') { true }
allow_any_instance_of(License).to receive(:feature_available?).and_call_original
allow_any_instance_of(License).to receive(:feature_available?).with(:service_desk) { true }
allow(Gitlab.config.incoming_email).to receive(:enabled).and_return(true)
allow(Gitlab.config.incoming_email).to receive(:address).and_return("test+%{key}@mail.com")
end
......
......@@ -218,7 +218,45 @@ describe License do
end
it 'returns empty Hash if no features for given plan' do
expect(described_class.features_for_plan('starter')).to eq({})
expect(described_class.features_for_plan('bronze')).to eq({})
end
end
describe '.plan_includes_feature?' do
let(:feature) { :deploy_board }
subject { described_class.plan_includes_feature?(plan, feature) }
context 'when addon included' do
let(:plan) { 'premium' }
it 'returns true' do
is_expected.to eq(true)
end
end
context 'when addon not included' do
let(:plan) { 'starter' }
it 'returns false' do
is_expected.to eq(false)
end
end
context 'when plan is not set' do
let(:plan) { nil }
it 'returns false' do
is_expected.to eq(false)
end
end
context 'when feature does not exists' do
let(:plan) { 'premium' }
let(:feature) { nil }
it 'raises KeyError' do
expect { subject }.to raise_error(KeyError)
end
end
end
......@@ -307,7 +345,7 @@ describe License do
describe 'reading add-ons' do
describe '#add_ons' do
context "without add-ons" do
context 'without add-ons' do
it 'returns an empty Hash' do
license = build_license_with_add_ons({})
......@@ -315,47 +353,53 @@ describe License do
end
end
context "with add-ons" do
context 'with add-ons' do
it 'returns all available add-ons' do
license = build_license_with_add_ons({ 'support' => 1, 'custom-domain' => 2 })
license = build_license_with_add_ons({ License::DEPLOY_BOARD_FEATURE => 1, License::FILE_LOCK_FEATURE => 2 })
expect(license.add_ons.keys).to include('support', 'custom-domain')
expect(license.add_ons.keys).to include(License::DEPLOY_BOARD_FEATURE, License::FILE_LOCK_FEATURE)
end
it 'can return details about a single add-on' do
license = build_license_with_add_ons({ 'custom-domain' => 2 })
license = build_license_with_add_ons({ License::DEPLOY_BOARD_FEATURE => 2 })
expect(license.add_ons['custom-domain']).to eq(2)
expect(license.add_ons[License::DEPLOY_BOARD_FEATURE]).to eq(2)
end
end
context 'with extra features mapped by plan' do
it 'returns all available add-ons and extra features' do
license = build_license_with_add_ons({ 'support' => 1 }, plan: 'premium')
license = build_license_with_add_ons({ License::DEPLOY_BOARD_FEATURE => 1 }, plan: License::PREMIUM_PLAN)
eep_features = License::EEP_FEATURES.reduce({}, :merge).keys
expect(license.add_ons.keys).to include('support', *eep_features)
expect(license.add_ons.keys).to include(License::DEPLOY_BOARD_FEATURE, *eep_features)
end
end
end
describe '#add_on?' do
describe '#feature_available?' do
it 'returns true if add-on exists and have a quantity greater than 0' do
license = build_license_with_add_ons({ 'support' => 1 })
license = build_license_with_add_ons({ License::DEPLOY_BOARD_FEATURE => 1 })
expect(license.add_on?('support')).to eq(true)
expect(license.feature_available?(:deploy_board)).to eq(true)
end
it 'returns false if add-on exists but have a quantity of 0' do
license = build_license_with_add_ons({ 'support' => 0 })
license = build_license_with_add_ons({ License::DEPLOY_BOARD_FEATURE => 0 })
expect(license.add_on?('support')).to eq(false)
expect(license.feature_available?(:deploy_board)).to eq(false)
end
it 'returns false if add-on does not exists' do
license = build_license_with_add_ons({})
expect(license.add_on?('support')).to eq(false)
expect(license.feature_available?(:deploy_board)).to eq(false)
end
it 'raises error if invalid symbol is sent' do
license = build_license_with_add_ons({})
expect { license.feature_available?(:invalid) }.to raise_error(KeyError)
end
end
......
......@@ -1671,7 +1671,7 @@ describe User, models: true do
before do
# `auditor?` returns true only when the user is an auditor _and_ the auditor license
# add-on is present. We aren't testing this here, so we can assume that the add-on exists.
allow_any_instance_of(License).to receive(:add_on?).with('GitLab_Auditor_User') { true }
allow_any_instance_of(License).to receive(:feature_available?).with(:auditor_user) { true }
end
it 'does nothing for an invalid access level' do
......@@ -1751,7 +1751,7 @@ describe User, models: true do
context 'creating an auditor user' do
it "does not allow creating an auditor user if the addon isn't enabled" do
allow_any_instance_of(License).to receive(:add_on?).with('GitLab_Auditor_User') { false }
allow_any_instance_of(License).to receive(:feature_available?).with(:auditor_user) { false }
expect(build(:user, :auditor)).to be_invalid
end
......@@ -1763,13 +1763,13 @@ describe User, models: true do
end
it "allows creating an auditor user if the addon is enabled" do
allow_any_instance_of(License).to receive(:add_on?).with('GitLab_Auditor_User') { true }
allow_any_instance_of(License).to receive(:feature_available?).with(:auditor_user) { true }
expect(build(:user, :auditor)).to be_valid
end
it "allows creating a regular user if the addon isn't enabled" do
allow_any_instance_of(License).to receive(:add_on?).with('GitLab_Auditor_User') { false }
allow_any_instance_of(License).to receive(:feature_available?).with(:auditor_user) { false }
expect(build(:user)).to be_valid
end
......@@ -1777,25 +1777,25 @@ describe User, models: true do
context '#auditor?' do
it "returns true for an auditor user if the addon is enabled" do
allow_any_instance_of(License).to receive(:add_on?).with('GitLab_Auditor_User') { true }
allow_any_instance_of(License).to receive(:feature_available?).with(:auditor_user) { true }
expect(build(:user, :auditor)).to be_auditor
end
it "returns false for an auditor user if the addon is not enabled" do
allow_any_instance_of(License).to receive(:add_on?).with('GitLab_Auditor_User') { false }
allow_any_instance_of(License).to receive(:feature_available?).with(:auditor_user) { false }
expect(build(:user, :auditor)).not_to be_auditor
end
it "returns false for an auditor user if a license is not present" do
allow_any_instance_of(License).to receive(:add_on?).with('GitLab_Auditor_User') { false }
allow_any_instance_of(License).to receive(:feature_available?).with(:auditor_user) { false }
expect(build(:user, :auditor)).not_to be_auditor
end
it "returns false for a non-auditor user even if the addon is present" do
allow_any_instance_of(License).to receive(:add_on?).with('GitLab_Auditor_User') { true }
allow_any_instance_of(License).to receive(:feature_available?).with(:auditor_user) { true }
expect(build(:user)).not_to be_auditor
end
......
......@@ -56,4 +56,48 @@ describe API::Namespaces do
end
end
end
describe 'PUT /namespaces/:id' do
context 'when authenticated as admin' do
it 'updates plan using full_path' do
put api("/namespaces/#{group1.full_path}", admin), plan: 'silver'
expect(response).to have_http_status(200)
expect(json_response['plan']).to eq('silver')
end
it 'updates plan using id' do
put api("/namespaces/#{group1.id}", admin), plan: 'silver'
expect(response).to have_http_status(200)
expect(json_response['plan']).to eq('silver')
end
end
context 'when not authenticated as admin' do
it 'retuns 403' do
put api("/namespaces/#{group1.id}", user), plan: 'silver'
expect(response).to have_http_status(403)
end
end
context 'when namespace not found' do
it 'returns 404' do
put api("/namespaces/12345", admin), plan: 'silver'
expect(response).to have_http_status(404)
expect(json_response).to eq('message' => '404 Namespace Not Found')
end
end
context 'when invalid params' do
it 'returns validation error' do
put api("/namespaces/#{group1.id}", admin), plan: 'unknown'
expect(response).to have_http_status(400)
expect(json_response['message']).to eq('plan' => ['is not included in the list'])
end
end
end
end
......@@ -11,7 +11,7 @@ describe EnvironmentEntity do
subject { entity.as_json }
before do
allow_any_instance_of(License).to receive(:add_on?).and_return(false)
allow_any_instance_of(License).to receive(:feature_available?).and_return(false)
environment.project.team << [user, :master]
end
......@@ -46,7 +46,7 @@ describe EnvironmentEntity do
context 'with deployment service ready' do
before do
allow_any_instance_of(License).to receive(:add_on?).with('GitLab_DeployBoard').and_return(true)
allow_any_instance_of(License).to receive(:feature_available?).with(:deploy_board).and_return(true)
allow(environment).to receive(:deployment_service_ready?).and_return(true)
end
......@@ -59,7 +59,7 @@ describe EnvironmentEntity do
context 'when license does not has the GitLab_DeployBoard add-on' do
before do
allow_any_instance_of(License).to receive(:add_on?).with('GitLab_DeployBoard').and_return(false)
allow_any_instance_of(License).to receive(:feature_available?).with(:deploy_board).and_return(false)
allow(environment).to receive(:deployment_service_ready?).and_return(true)
end
......
......@@ -7,8 +7,8 @@ describe EE::NotificationService do
allow(Notify).to receive(:service_desk_new_note_email)
.with(kind_of(Integer), kind_of(Integer)).and_return(double(deliver_later: true))
allow_any_instance_of(License).to receive(:add_on?).and_call_original
allow_any_instance_of(License).to receive(:add_on?).with('GitLab_ServiceDesk') { true }
allow_any_instance_of(License).to receive(:feature_available?).and_call_original
allow_any_instance_of(License).to receive(:feature_available?).with(:service_desk) { true }
allow(::Gitlab::IncomingEmail).to receive(:enabled?) { true }
allow(::Gitlab::IncomingEmail).to receive(:supports_wildcard?) { true }
end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment