mirror of
https://github.com/wahyd4/gitlabhq.git
synced 2026-08-25 20:46:12 +10:00
Merge branch 'license' into 'master'
Allow license to be uploaded and enforce its validity. Addresses #274. Still a work in progress, there are no tests yet, some things need extra validation or prettier error messages, and we're not doing anything to enforce active user count yet. Still, the flow stands! Let me know what you think :) - [x] Check active user count at moment of upload - [x] Prettier validation / error message when license is invalid - [x] Prettier error when public key is invalid - [x] Tests - [x] Work on copy These will go into separate MRs as they require more discussion: - Check historic active user count over past year - Add application setting to disallow creating/activating new user when limit is reached ----- No license:  Also shown when pushing code:  The "Upload a new license in the admin area" part is only for admins, regular users will see "Ask an admin to upload a new license". Upload page in admin area:  License details in admin area:  Notice of a license that will expire: Also notice the license history at the bottom.  Notice of an expired license:  cc @dzaporozhets @sytse See merge request !383
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0Hxv3MkkZbMrKtIs6np9
|
||||
ccP4OwGBkNhIvhPjcQP48hbbascv5RqsOquQGrYSD2ZrE/kbkRdkIcoHEeTZLif+
|
||||
bDKFZFI7o5x0H92o9/GSvxHJhQ8mkmvwxD7lssGShwZEm8WG+U7BZqUV/gGmCDqe
|
||||
9W8H8Fq2B0ck8IXjbQ4Zz+JlyV/NHZTZcs69plFiLKh4N6GYVftOVwSomh0bbypP
|
||||
OB9WnLC7RC9a2LRrhtf8sqa2rRFmtyMMfgFFzLMzS+w+1K4+QLnWP1gKQVzaFnzk
|
||||
pnwKPrqbGFYbRztIVEWbs8jPYlLkGb8ME4C84YVtQgbQcbyisU/VW3wUGkhT+J0k
|
||||
xwIDAQAB
|
||||
-----END PUBLIC KEY-----
|
||||
@@ -203,6 +203,8 @@ gem 'request_store'
|
||||
gem "virtus"
|
||||
gem 'addressable'
|
||||
|
||||
gem "gitlab-license", "~> 0.0.2"
|
||||
|
||||
group :development do
|
||||
gem 'brakeman', require: false
|
||||
gem "annotate", "~> 2.6.0.beta2"
|
||||
|
||||
@@ -209,6 +209,7 @@ GEM
|
||||
diff-lcs (~> 1.1)
|
||||
mime-types (~> 1.15)
|
||||
posix-spawn (~> 0.3)
|
||||
gitlab-license (0.0.2)
|
||||
gitlab-linguist (3.0.1)
|
||||
charlock_holmes (~> 0.6.6)
|
||||
escape_utils (~> 0.2.4)
|
||||
@@ -708,6 +709,7 @@ DEPENDENCIES
|
||||
github-markup
|
||||
gitlab-flowdock-git-hook (~> 0.4.2)
|
||||
gitlab-grack (~> 2.0.2)
|
||||
gitlab-license (~> 0.0.2)
|
||||
gitlab-linguist (~> 3.0.1)
|
||||
gitlab_emoji (~> 0.1)
|
||||
gitlab_git (~> 7.1.10)
|
||||
|
||||
@@ -114,6 +114,8 @@ if location.hash
|
||||
setTimeout shiftWindow, 1
|
||||
window.addEventListener "hashchange", shiftWindow
|
||||
|
||||
$.timeago.settings.allowFuture = true
|
||||
|
||||
$ ->
|
||||
# Click a .js-select-on-focus field, select the contents
|
||||
$(".js-select-on-focus").on "focusin", -> $(this).select()
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
class Admin::LicensesController < Admin::ApplicationController
|
||||
before_action :license, only: [:show, :download, :destroy]
|
||||
before_action :require_license, only: [:show, :download, :destroy]
|
||||
|
||||
respond_to :html
|
||||
|
||||
def show
|
||||
@previous_licenses = License.previous
|
||||
end
|
||||
|
||||
def download
|
||||
send_data @license.data, filename: @license.data_filename, disposition: 'attachment'
|
||||
end
|
||||
|
||||
def new
|
||||
@license = License.new
|
||||
end
|
||||
|
||||
def create
|
||||
unless params[:license]
|
||||
flash.now[:alert] = "No license was selected."
|
||||
|
||||
@license = License.new
|
||||
render :new
|
||||
return
|
||||
end
|
||||
|
||||
@license = License.new(license_params)
|
||||
|
||||
respond_with(@license, location: admin_license_path) do
|
||||
if @license.save
|
||||
flash[:notice] = "The license was successfully uploaded and is now active. You can see the details below."
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
license.destroy
|
||||
|
||||
message = "The license was removed. "
|
||||
if License.current
|
||||
flash[:notice] = "The license was removed. GitLab has fallen back on the previous license."
|
||||
else
|
||||
flash[:alert] = "The license was removed. GitLab now no longer has a valid license."
|
||||
end
|
||||
|
||||
redirect_to admin_license_path
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def license
|
||||
@license ||= begin
|
||||
License.reset_current
|
||||
License.current
|
||||
end
|
||||
end
|
||||
|
||||
def require_license
|
||||
return if license
|
||||
|
||||
flash.keep
|
||||
redirect_to new_admin_license_path
|
||||
end
|
||||
|
||||
def license_params
|
||||
params.require(:license).permit(:data_file)
|
||||
end
|
||||
end
|
||||
@@ -213,9 +213,15 @@ module ApplicationHelper
|
||||
|
||||
def time_ago_with_tooltip(date, placement = 'top', html_class = 'time_ago')
|
||||
capture_haml do
|
||||
haml_tag :time, date.to_s,
|
||||
class: html_class, datetime: date.getutc.iso8601, title: date.stamp('Aug 21, 2011 9:23pm'),
|
||||
data: { toggle: 'tooltip', placement: placement }
|
||||
if date.is_a?(Date)
|
||||
haml_tag :time, date.to_s,
|
||||
class: html_class, datetime: date.iso8601, title: date.stamp('Aug 21, 2011'),
|
||||
data: { toggle: 'tooltip', placement: placement }
|
||||
else
|
||||
haml_tag :time, date.to_s,
|
||||
class: html_class, datetime: date.getutc.iso8601, title: date.stamp('Aug 21, 2011 9:23pm'),
|
||||
data: { toggle: 'tooltip', placement: placement }
|
||||
end
|
||||
|
||||
haml_tag :script, "$('." + html_class + "').timeago().tooltip()"
|
||||
end.html_safe
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
module LicenseHelper
|
||||
def license_message(signed_in: signed_in?, is_admin: (current_user && current_user.is_admin?))
|
||||
@license_message ||=
|
||||
if License.current
|
||||
yes_license_message(signed_in, is_admin)
|
||||
else
|
||||
no_license_message(signed_in, is_admin)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def no_license_message(signed_in, is_admin)
|
||||
message = []
|
||||
|
||||
message << "No GitLab Enterprise Edition license has been provided yet."
|
||||
message << "Pushing code and creation of issues and merge requests has been disabled."
|
||||
|
||||
message <<
|
||||
if is_admin
|
||||
"Upload a license in the admin area"
|
||||
else
|
||||
"Ask an admin to upload a license"
|
||||
end
|
||||
|
||||
message << "to activate this functionality."
|
||||
|
||||
message.join(" ")
|
||||
end
|
||||
|
||||
def yes_license_message(signed_in, is_admin)
|
||||
license = License.current
|
||||
|
||||
return unless signed_in
|
||||
|
||||
return unless (license.notify_admins? && is_admin) || license.notify_users?
|
||||
|
||||
message = []
|
||||
|
||||
message << "The GitLab Enterprise Edition license"
|
||||
message << (license.expired? ? "expired" : "will expire")
|
||||
message << "on #{license.expires_at}."
|
||||
|
||||
if license.expired? && license.will_block_changes?
|
||||
message << "Pushing code and creation of issues and merge requests"
|
||||
|
||||
message <<
|
||||
if license.block_changes?
|
||||
"has been disabled."
|
||||
else
|
||||
"will be disabled on #{license.block_changes_at}."
|
||||
end
|
||||
end
|
||||
|
||||
message <<
|
||||
if is_admin
|
||||
"Upload a new license in the admin area"
|
||||
else
|
||||
"Ask an admin to upload a new license"
|
||||
end
|
||||
|
||||
message << "to"
|
||||
message << (license.block_changes? ? "restore" : "ensure uninterrupted")
|
||||
message << "service."
|
||||
|
||||
message.join(" ")
|
||||
end
|
||||
|
||||
extend self
|
||||
end
|
||||
+28
-12
@@ -5,18 +5,34 @@ class Ability
|
||||
return [] unless user.kind_of?(User)
|
||||
return [] if user.blocked?
|
||||
|
||||
case subject.class.name
|
||||
when "Project" then project_abilities(user, subject)
|
||||
when "Issue" then issue_abilities(user, subject)
|
||||
when "Note" then note_abilities(user, subject)
|
||||
when "ProjectSnippet" then project_snippet_abilities(user, subject)
|
||||
when "PersonalSnippet" then personal_snippet_abilities(user, subject)
|
||||
when "MergeRequest" then merge_request_abilities(user, subject)
|
||||
when "Group" then group_abilities(user, subject)
|
||||
when "Namespace" then namespace_abilities(user, subject)
|
||||
when "GroupMember" then group_member_abilities(user, subject)
|
||||
else []
|
||||
end.concat(global_abilities(user))
|
||||
abilities =
|
||||
case subject.class.name
|
||||
when "Project" then project_abilities(user, subject)
|
||||
when "Issue" then issue_abilities(user, subject)
|
||||
when "Note" then note_abilities(user, subject)
|
||||
when "ProjectSnippet" then project_snippet_abilities(user, subject)
|
||||
when "PersonalSnippet" then personal_snippet_abilities(user, subject)
|
||||
when "MergeRequest" then merge_request_abilities(user, subject)
|
||||
when "Group" then group_abilities(user, subject)
|
||||
when "Namespace" then namespace_abilities(user, subject)
|
||||
when "GroupMember" then group_member_abilities(user, subject)
|
||||
else []
|
||||
end
|
||||
|
||||
abilities.concat(global_abilities(user))
|
||||
|
||||
abilities -= license_blocked_abilities if License.block_changes?
|
||||
|
||||
abilities
|
||||
end
|
||||
|
||||
def license_blocked_abilities
|
||||
[
|
||||
:push_code,
|
||||
:push_code_to_protected_branches,
|
||||
:write_issue,
|
||||
:write_merge_request
|
||||
]
|
||||
end
|
||||
|
||||
# List of possible abilities
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
class License < ActiveRecord::Base
|
||||
validate :valid_license
|
||||
validate :active_user_count, unless: :persisted?
|
||||
validate :not_expired, unless: :persisted?
|
||||
|
||||
before_validation :reset_license, if: :data_changed?
|
||||
|
||||
after_create :reset_current
|
||||
after_destroy :reset_current
|
||||
|
||||
scope :previous, -> { order(created_at: :desc).offset(1) }
|
||||
|
||||
class << self
|
||||
def current
|
||||
return @current if @current
|
||||
|
||||
license = self.last
|
||||
return unless license && license.valid?
|
||||
|
||||
@current = license
|
||||
end
|
||||
|
||||
def reset_current
|
||||
@current = nil
|
||||
end
|
||||
|
||||
def block_changes?
|
||||
!current || current.block_changes?
|
||||
end
|
||||
end
|
||||
|
||||
def data_filename
|
||||
company_name = self.licensee["Company"] || self.licensee.values.first
|
||||
clean_company_name = company_name.gsub(/[^A-Za-z0-9]/, "")
|
||||
"#{clean_company_name}.gitlab-license"
|
||||
end
|
||||
|
||||
def data_file=(file)
|
||||
self.data = file.read
|
||||
end
|
||||
|
||||
def license
|
||||
return nil unless self.data
|
||||
|
||||
@license ||=
|
||||
begin
|
||||
Gitlab::License.import(self.data)
|
||||
rescue Gitlab::License::ImportError
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def license?
|
||||
self.license && self.license.valid?
|
||||
end
|
||||
|
||||
def method_missing(method_name, *arguments, &block)
|
||||
if License.column_names.include?(method_name.to_s)
|
||||
super
|
||||
elsif license && license.respond_to?(method_name)
|
||||
license.send(method_name, *arguments, &block)
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
def respond_to_missing?(method_name, include_private = false)
|
||||
if License.column_names.include?(method_name.to_s)
|
||||
super
|
||||
elsif license && license.respond_to?(method_name)
|
||||
true
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def reset_current
|
||||
self.class.reset_current
|
||||
end
|
||||
|
||||
def reset_license
|
||||
@license = nil
|
||||
end
|
||||
|
||||
def valid_license
|
||||
return if license?
|
||||
|
||||
self.errors.add(:base, "The license file is invalid. Make sure it is exactly as you received it from GitLab B.V.")
|
||||
end
|
||||
|
||||
def active_user_count
|
||||
return unless self.license? && self.restricted?(:active_user_count)
|
||||
|
||||
restricted_user_count = self.restrictions[:active_user_count]
|
||||
active_user_count = User.active.count
|
||||
|
||||
return if active_user_count <= restricted_user_count
|
||||
|
||||
message = "This license allows #{restricted_user_count} active users. "
|
||||
message << "This GitLab installation currently has #{active_user_count}, "
|
||||
message << "i.e. #{active_user_count - restricted_user_count} too many."
|
||||
self.errors.add(:base, message)
|
||||
end
|
||||
|
||||
def not_expired
|
||||
return unless self.license? && self.expired?
|
||||
|
||||
self.errors.add(:base, "This license has already expired.")
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,21 @@
|
||||
- page_title "Upload License"
|
||||
%h3.page-title Upload License
|
||||
|
||||
%p.light
|
||||
To #{License.current ? "continue" : "start"} using GitLab Enterprise Edition, upload the <code>.gitlab-license</code> file you have received from GitLab B.V..
|
||||
|
||||
%hr
|
||||
= form_for @license, url: admin_license_path, html: { multipart: true, class: 'form-horizontal fieldset-form' } do |f|
|
||||
- if @license.errors.any?
|
||||
#error_explanation
|
||||
.alert.alert-danger
|
||||
- @license.errors.full_messages.each do |msg|
|
||||
%p= msg
|
||||
|
||||
.form-group
|
||||
= f.label :data_file, "License", class: 'control-label col-sm-2'
|
||||
.col-sm-10
|
||||
= f.file_field :data_file, accept: ".gitlab-license,.gitlab_license,.txt"
|
||||
|
||||
.form-actions
|
||||
= f.submit 'Upload license', class: 'btn btn-primary'
|
||||
@@ -0,0 +1,121 @@
|
||||
- page_title "License"
|
||||
%h3.page-title
|
||||
Your License
|
||||
= link_to 'Upload New License', new_admin_license_path, class: "btn btn-new pull-right"
|
||||
|
||||
%hr
|
||||
|
||||
.row
|
||||
.col-md-6
|
||||
.panel.panel-default
|
||||
.panel-heading
|
||||
Licensed to
|
||||
%ul.well-list
|
||||
- @license.licensee.each do |label, value|
|
||||
%li
|
||||
%span.light #{label}:
|
||||
%strong= value
|
||||
|
||||
.panel.panel-default
|
||||
.panel-heading
|
||||
Details
|
||||
%ul.well-list
|
||||
%li
|
||||
%span.light Issued:
|
||||
%strong= time_ago_with_tooltip @license.issued_at
|
||||
%li
|
||||
%span.light Uploaded:
|
||||
%strong= time_ago_with_tooltip @license.created_at
|
||||
%li
|
||||
%span.light
|
||||
- if @license.expired?
|
||||
Expired:
|
||||
- else
|
||||
Expires:
|
||||
%strong
|
||||
- if @license.will_expire?
|
||||
= time_ago_with_tooltip @license.expires_at
|
||||
- if @license.expired?
|
||||
%span.label.label-danger.pull-right
|
||||
%strong Expired
|
||||
- else
|
||||
Never
|
||||
|
||||
.panel.panel-default
|
||||
.panel-heading
|
||||
Restrictions
|
||||
%ul.well-list
|
||||
%li
|
||||
%span.light Active users:
|
||||
%strong
|
||||
- if @license.restricted?(:active_user_count)
|
||||
- restricted_user_count = @license.restrictions[:active_user_count]
|
||||
- active_user_count = User.active.count
|
||||
#{restricted_user_count} users
|
||||
|
||||
- if active_user_count > restricted_user_count
|
||||
%span.label.label-danger.pull-right
|
||||
%strong Exceeded by #{active_user_count - restricted_user_count} users
|
||||
- elsif restricted_user_count > active_user_count
|
||||
%span.label.label-success.pull-right
|
||||
%strong #{restricted_user_count - active_user_count} more allowed
|
||||
- else
|
||||
%span.label.label-info.pull-right
|
||||
%strong Right at the limit
|
||||
|
||||
- else
|
||||
Unlimited
|
||||
|
||||
.col-md-6
|
||||
.panel.panel-info
|
||||
.panel-heading
|
||||
Download license
|
||||
.panel-body
|
||||
%p Your license will be included in your GitLab backup and will survive upgrades, so in normal usage you should never need to re-upload your <code>.gitlab-license</code> file.
|
||||
%p Still, we recommend keeping a backup saved somewhere. Otherwise, if you ever need it and have lost it, you will need to request GitLab B.V. to send it to you again.
|
||||
%br
|
||||
= link_to 'Download license', download_admin_license_path, class: "btn btn-info"
|
||||
|
||||
|
||||
.panel.panel-danger
|
||||
.panel-heading
|
||||
Remove license
|
||||
.panel-body
|
||||
%p If you remove this license, GitLab will fall back on the previous license, if any.
|
||||
%p If there is no previous license or if the previous license has expired, some GitLab functionality will be blocked until a new, valid license is uploaded.
|
||||
%br
|
||||
= link_to 'Remove license', admin_license_path, data: { confirm: "Are you sure you want to remove the license?" }, method: :delete, class: "btn btn-remove"
|
||||
|
||||
- if @previous_licenses.any?
|
||||
%h4 License History
|
||||
|
||||
.panel.panel-default#license_history
|
||||
%table.table
|
||||
%thead.panel-heading
|
||||
%tr
|
||||
- @license.licensee.keys.each do |label|
|
||||
%th= label
|
||||
%th Issued at
|
||||
%th Uploaded at
|
||||
%th Expired at
|
||||
%th Active users
|
||||
%tbody
|
||||
- @previous_licenses.each do |license|
|
||||
%tr
|
||||
- @license.licensee.keys.each do |label|
|
||||
%td= license.licensee[label]
|
||||
%td
|
||||
%span
|
||||
= license.issued_at
|
||||
%td
|
||||
%span
|
||||
= license.created_at
|
||||
%td
|
||||
%span
|
||||
= license.expires_at || "Never"
|
||||
%td
|
||||
%span
|
||||
- if license.restricted?(:active_user_count)
|
||||
#{license.restrictions[:active_user_count]} users
|
||||
- else
|
||||
Unlimited
|
||||
@@ -1,4 +1,9 @@
|
||||
- if broadcast_message.present?
|
||||
.broadcast-message{ style: broadcast_styling(broadcast_message) }
|
||||
%i.fa.fa-bullhorn
|
||||
= icon('bullhorn')
|
||||
= broadcast_message.message
|
||||
|
||||
- if license_message.present?
|
||||
.broadcast-message
|
||||
= icon('bullhorn')
|
||||
= license_message
|
||||
|
||||
@@ -72,3 +72,9 @@
|
||||
= icon('cogs fw')
|
||||
%span
|
||||
Settings
|
||||
|
||||
= nav_link(controller: :licenses) do
|
||||
= link_to admin_license_path, title: 'License', data: {placement: 'right'} do
|
||||
= icon('check fw')
|
||||
%span
|
||||
License
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
begin
|
||||
public_key_file = File.read(Rails.root.join(".license_encryption_key.pub"))
|
||||
public_key = OpenSSL::PKey::RSA.new(public_key_file)
|
||||
Gitlab::License.encryption_key = public_key
|
||||
rescue
|
||||
warn "WARNING: No valid license encryption key provided."
|
||||
end
|
||||
|
||||
# Needed to run migration
|
||||
if ActiveRecord::Base.connected? && ActiveRecord::Base.connection.table_exists?('licenses')
|
||||
message = LicenseHelper.license_message(signed_in: true, is_admin: true)
|
||||
if message.present?
|
||||
warn "WARNING: #{message}"
|
||||
end
|
||||
end
|
||||
@@ -211,6 +211,10 @@ Gitlab::Application.routes.draw do
|
||||
resources :services
|
||||
end
|
||||
|
||||
resource :license, only: [:show, :new, :create, :destroy] do
|
||||
get :download, on: :member
|
||||
end
|
||||
|
||||
root to: 'dashboard#index'
|
||||
end
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
class CreateLicenses < ActiveRecord::Migration
|
||||
def change
|
||||
create_table :licenses do |t|
|
||||
t.text :data, null: false
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -211,6 +211,12 @@ ActiveRecord::Schema.define(version: 20150502064022) do
|
||||
t.string "provider"
|
||||
end
|
||||
|
||||
create_table "licenses", force: true do |t|
|
||||
t.text "data", null: false
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
end
|
||||
|
||||
create_table "members", force: true do |t|
|
||||
t.integer "access_level", null: false
|
||||
t.integer "source_id", null: false
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
@admin
|
||||
Feature: Admin license
|
||||
Background:
|
||||
Given I sign in as an admin
|
||||
|
||||
Scenario: Viewing current license
|
||||
Given there is a license
|
||||
And I visit admin license page
|
||||
Then I should see to whom the license is licensed
|
||||
|
||||
Scenario: Viewing license when there is none
|
||||
Given I visit admin license page
|
||||
Then I should see a warning telling me there is no license
|
||||
And I should be redirected to the license upload page
|
||||
|
||||
Scenario: Viewing expired license
|
||||
Given there is a license
|
||||
And the current license is expired
|
||||
And I visit admin license page
|
||||
Then I should see a warning telling me the license has expired
|
||||
|
||||
Scenario: Viewing license that blocks changes
|
||||
Given there is a license
|
||||
And the current license is expired
|
||||
And the current license blocks changes
|
||||
And I visit admin license page
|
||||
Then I should see a warning telling me code pushes have been disabled
|
||||
|
||||
Scenario: Viewing license history
|
||||
Given there is a license
|
||||
And there are multiple licenses
|
||||
And I visit admin license page
|
||||
Then I should see to whom the licenses were licensed
|
||||
|
||||
Scenario: Uploading valid license
|
||||
Given I visit admin upload license page
|
||||
And I upload a valid license
|
||||
Then I should see a notice telling me the license was uploaded
|
||||
And I should see to whom the license is licensed
|
||||
|
||||
Scenario: Uploading invalid license
|
||||
Given I visit admin upload license page
|
||||
Then I upload an invalid license
|
||||
Then I should see a warning telling me it's invalid
|
||||
@@ -0,0 +1,86 @@
|
||||
class Spinach::Features::AdminLicense < Spinach::FeatureSteps
|
||||
include SharedAuthentication
|
||||
include SharedPaths
|
||||
|
||||
step 'I should see to whom the license is licensed' do
|
||||
expect(page).to have_content(license.licensee.values.first)
|
||||
end
|
||||
|
||||
step 'there is a license' do
|
||||
create(:license)
|
||||
end
|
||||
|
||||
step 'I should see a warning telling me there is no license' do
|
||||
expect(page).to have_content "No GitLab Enterprise Edition license has been provided yet."
|
||||
end
|
||||
|
||||
step 'I should be redirected to the license upload page' do
|
||||
expect(current_path).to eq(new_admin_license_path)
|
||||
end
|
||||
|
||||
step 'the current license is expired' do
|
||||
build(:license, data: build(:gitlab_license, expires_at: Date.yesterday).export).save(validate: false)
|
||||
end
|
||||
|
||||
step 'I should see a warning telling me the license has expired' do
|
||||
expect(page).to have_content "The GitLab Enterprise Edition license expired"
|
||||
end
|
||||
|
||||
step 'the current license blocks changes' do
|
||||
build(:license, data: build(:gitlab_license, expires_at: Date.yesterday, block_changes_at: Date.today).export).save(validate: false)
|
||||
end
|
||||
|
||||
step 'I should see a warning telling me code pushes have been disabled' do
|
||||
expect(page).to have_content "Pushing code and creation of issues and merge requests has been disabled."
|
||||
end
|
||||
|
||||
step 'there are multiple licenses' do
|
||||
create(:license)
|
||||
create(:license)
|
||||
end
|
||||
|
||||
step 'I should see to whom the licenses were licensed' do
|
||||
license_history = page.find("#license_history")
|
||||
|
||||
License.previous.each do |license|
|
||||
expect(license_history).to have_content(license.licensee.values.first)
|
||||
end
|
||||
end
|
||||
|
||||
step 'I visit admin upload license page' do
|
||||
visit new_admin_license_path
|
||||
end
|
||||
|
||||
step 'I upload a valid license' do
|
||||
path = Rails.root.join("tmp/valid_license.gitlab-license")
|
||||
|
||||
license = build(:gitlab_license)
|
||||
File.write(path, license.export)
|
||||
|
||||
attach_file 'license_data_file', path
|
||||
click_button "Upload license"
|
||||
end
|
||||
|
||||
step 'I should see a notice telling me the license was uploaded' do
|
||||
expect(page).to have_content "The license was successfully uploaded and is now active."
|
||||
end
|
||||
|
||||
step 'I upload an invalid license' do
|
||||
path = Rails.root.join("tmp/invalid_license.gitlab-license")
|
||||
|
||||
license = build(:gitlab_license, expires_at: Date.yesterday)
|
||||
File.write(path, license.export)
|
||||
|
||||
attach_file 'license_data_file', path
|
||||
click_button "Upload license"
|
||||
end
|
||||
|
||||
step "I should see a warning telling me it's invalid" do
|
||||
expect(page).to have_content "This license has already expired."
|
||||
end
|
||||
|
||||
def license
|
||||
License.reset_current
|
||||
License.current
|
||||
end
|
||||
end
|
||||
@@ -203,6 +203,10 @@ module SharedPaths
|
||||
visit admin_git_hooks_path
|
||||
end
|
||||
|
||||
step 'I visit admin license page' do
|
||||
visit admin_license_path
|
||||
end
|
||||
|
||||
# ----------------------------------------
|
||||
# Generic Project
|
||||
# ----------------------------------------
|
||||
|
||||
@@ -16,7 +16,7 @@ require 'sidekiq/testing/inline'
|
||||
require_relative 'capybara'
|
||||
require_relative 'db_cleaner'
|
||||
|
||||
%w(select2_helper test_env repo_helpers).each do |f|
|
||||
%w(select2_helper test_env repo_helpers license).each do |f|
|
||||
require Rails.root.join('spec', 'support', f)
|
||||
end
|
||||
|
||||
@@ -26,7 +26,9 @@ WebMock.allow_net_connect!
|
||||
|
||||
Spinach.hooks.before_run do
|
||||
include RSpec::Mocks::ExampleMethods
|
||||
|
||||
TestEnv.init(mailer: false)
|
||||
TestLicense.init
|
||||
|
||||
include FactoryGirl::Syntax::Methods
|
||||
end
|
||||
|
||||
@@ -113,6 +113,11 @@ module Gitlab
|
||||
unless project.repository.exists?
|
||||
return build_status_object(false, "Repository does not exist")
|
||||
end
|
||||
|
||||
if ::License.block_changes?
|
||||
message = ::LicenseHelper.license_message(signed_in: true, is_admin: (user && user.is_admin?))
|
||||
return build_status_object(false, message)
|
||||
end
|
||||
|
||||
changes = changes.lines if changes.kind_of?(String)
|
||||
|
||||
|
||||
@@ -202,4 +202,17 @@ FactoryGirl.define do
|
||||
provider 'ldapmain'
|
||||
extern_uid 'my-ldap-id'
|
||||
end
|
||||
|
||||
factory :gitlab_license, class: "Gitlab::License" do
|
||||
issued_at { Date.today }
|
||||
licensee do
|
||||
{ "Name" => Faker::Name.name }
|
||||
end
|
||||
notify_users_at { |l| l.expires_at }
|
||||
notify_admins_at { |l| l.expires_at }
|
||||
end
|
||||
|
||||
factory :license do
|
||||
data { build(:gitlab_license).export }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -224,4 +224,32 @@ describe "Internal Project Access", feature: true do
|
||||
it { is_expected.to be_denied_for :user }
|
||||
it { is_expected.to be_denied_for :visitor }
|
||||
end
|
||||
|
||||
context "when license blocks changes" do
|
||||
before do
|
||||
allow(License).to receive(:block_changes?).and_return(true)
|
||||
end
|
||||
|
||||
describe "GET /:project_path/issues/new" do
|
||||
subject { new_namespace_project_issue_path(project.namespace, project) }
|
||||
|
||||
it { is_expected.to be_denied_for master }
|
||||
it { is_expected.to be_denied_for reporter }
|
||||
it { is_expected.to be_denied_for :admin }
|
||||
it { is_expected.to be_denied_for guest }
|
||||
it { is_expected.to be_denied_for :user }
|
||||
it { is_expected.to be_denied_for :visitor }
|
||||
end
|
||||
|
||||
describe "GET /:project_path/merge_requests/new" do
|
||||
subject { new_namespace_project_merge_request_path(project.namespace, project) }
|
||||
|
||||
it { is_expected.to be_denied_for master }
|
||||
it { is_expected.to be_denied_for reporter }
|
||||
it { is_expected.to be_denied_for :admin }
|
||||
it { is_expected.to be_denied_for guest }
|
||||
it { is_expected.to be_denied_for :user }
|
||||
it { is_expected.to be_denied_for :visitor }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -202,4 +202,32 @@ describe "Private Project Access", feature: true do
|
||||
it { is_expected.to be_denied_for :user }
|
||||
it { is_expected.to be_denied_for :visitor }
|
||||
end
|
||||
|
||||
context "when license blocks changes" do
|
||||
before do
|
||||
allow(License).to receive(:block_changes?).and_return(true)
|
||||
end
|
||||
|
||||
describe "GET /:project_path/issues/new" do
|
||||
subject { new_namespace_project_issue_path(project.namespace, project) }
|
||||
|
||||
it { is_expected.to be_denied_for master }
|
||||
it { is_expected.to be_denied_for reporter }
|
||||
it { is_expected.to be_denied_for :admin }
|
||||
it { is_expected.to be_denied_for guest }
|
||||
it { is_expected.to be_denied_for :user }
|
||||
it { is_expected.to be_denied_for :visitor }
|
||||
end
|
||||
|
||||
describe "GET /:project_path/merge_requests/new" do
|
||||
subject { new_namespace_project_merge_request_path(project.namespace, project) }
|
||||
|
||||
it { is_expected.to be_denied_for master }
|
||||
it { is_expected.to be_denied_for reporter }
|
||||
it { is_expected.to be_denied_for :admin }
|
||||
it { is_expected.to be_denied_for guest }
|
||||
it { is_expected.to be_denied_for :user }
|
||||
it { is_expected.to be_denied_for :visitor }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -229,4 +229,32 @@ describe "Public Project Access", feature: true do
|
||||
it { is_expected.to be_denied_for :user }
|
||||
it { is_expected.to be_denied_for :visitor }
|
||||
end
|
||||
|
||||
context "when license blocks changes" do
|
||||
before do
|
||||
allow(License).to receive(:block_changes?).and_return(true)
|
||||
end
|
||||
|
||||
describe "GET /:project_path/issues/new" do
|
||||
subject { new_namespace_project_issue_path(project.namespace, project) }
|
||||
|
||||
it { is_expected.to be_denied_for master }
|
||||
it { is_expected.to be_denied_for reporter }
|
||||
it { is_expected.to be_denied_for :admin }
|
||||
it { is_expected.to be_denied_for guest }
|
||||
it { is_expected.to be_denied_for :user }
|
||||
it { is_expected.to be_denied_for :visitor }
|
||||
end
|
||||
|
||||
describe "GET /:project_path/merge_requests/new" do
|
||||
subject { new_namespace_project_merge_request_path(project.namespace, project) }
|
||||
|
||||
it { is_expected.to be_denied_for master }
|
||||
it { is_expected.to be_denied_for reporter }
|
||||
it { is_expected.to be_denied_for :admin }
|
||||
it { is_expected.to be_denied_for guest }
|
||||
it { is_expected.to be_denied_for :user }
|
||||
it { is_expected.to be_denied_for :visitor }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -231,6 +231,27 @@ describe Gitlab::GitAccess do
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when license blocks changes" do
|
||||
before do
|
||||
allow(License).to receive(:block_changes?).and_return(true)
|
||||
end
|
||||
|
||||
permissions_matrix.keys.each do |role|
|
||||
describe "#{role} access" do
|
||||
before { protect_feature_branch }
|
||||
before { project.team << [user, role] }
|
||||
|
||||
permissions_matrix[role].each do |action, allowed|
|
||||
context action do
|
||||
subject { access.push_access_check(changes[action]) }
|
||||
|
||||
it { expect(subject.allowed?).to be_falsey }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "git_hook_check" do
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
require "spec_helper"
|
||||
|
||||
describe License do
|
||||
let(:gl_license) { build(:gitlab_license) }
|
||||
let(:license) { build(:license, data: gl_license.export) }
|
||||
|
||||
describe "Validation" do
|
||||
describe "Valid license" do
|
||||
context "when the license is provided" do
|
||||
it "is valid" do
|
||||
expect(license).to be_valid
|
||||
end
|
||||
end
|
||||
|
||||
context "when no license is provided" do
|
||||
before do
|
||||
license.data = nil
|
||||
end
|
||||
|
||||
it "is invalid" do
|
||||
expect(license).to_not be_valid
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "Active user count" do
|
||||
context "when there is no active user count restriction" do
|
||||
it "is valid" do
|
||||
expect(license).to be_valid
|
||||
end
|
||||
end
|
||||
|
||||
context "when the active user count restriction is exceeded" do
|
||||
before do
|
||||
gl_license.restrictions = { active_user_count: User.active.count - 1 }
|
||||
end
|
||||
|
||||
it "is invalid" do
|
||||
expect(license).to_not be_valid
|
||||
end
|
||||
end
|
||||
|
||||
context "when the active user count restriction is not exceeded" do
|
||||
before do
|
||||
gl_license.restrictions = { active_user_count: User.active.count + 1 }
|
||||
end
|
||||
|
||||
it "is valid" do
|
||||
expect(license).to be_valid
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "Not expired" do
|
||||
context "when the license doesn't expire" do
|
||||
it "is valid" do
|
||||
expect(license).to be_valid
|
||||
end
|
||||
end
|
||||
|
||||
context "when the license has expired" do
|
||||
before do
|
||||
gl_license.expires_at = Date.yesterday
|
||||
end
|
||||
|
||||
it "is valid" do
|
||||
expect(license).to_not be_valid
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context "when the license has yet to expire" do
|
||||
before do
|
||||
gl_license.expires_at = Date.tomorrow
|
||||
end
|
||||
|
||||
it "is valid" do
|
||||
expect(license).to be_valid
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "Class methods" do
|
||||
let!(:license) { License.last }
|
||||
|
||||
before do
|
||||
License.reset_current
|
||||
allow(License).to receive(:last).and_return(license)
|
||||
end
|
||||
|
||||
describe ".current" do
|
||||
context "when there is no license" do
|
||||
let!(:license) { nil }
|
||||
|
||||
it "returns nil" do
|
||||
expect(License.current).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context "when the license is invalid" do
|
||||
before do
|
||||
allow(license).to receive(:valid?).and_return(false)
|
||||
end
|
||||
|
||||
it "returns nil" do
|
||||
expect(License.current).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context "when the license is valid" do
|
||||
it "returns the license" do
|
||||
expect(License.current)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe ".block_changes?" do
|
||||
context "when there is no current license" do
|
||||
before do
|
||||
allow(License).to receive(:current).and_return(nil)
|
||||
end
|
||||
|
||||
it "returns true" do
|
||||
expect(License.block_changes?).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
context "when the current license is set to block changes" do
|
||||
before do
|
||||
allow(license).to receive(:block_changes?).and_return(true)
|
||||
end
|
||||
|
||||
it "returns true" do
|
||||
expect(License.block_changes?).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
context "when the current license doesn't block changes" do
|
||||
it "returns false" do
|
||||
expect(License.block_changes?).to be_falsey
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#license" do
|
||||
context "when no data is provided" do
|
||||
before do
|
||||
license.data = nil
|
||||
end
|
||||
|
||||
it "returns nil" do
|
||||
expect(license.license).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context "when corrupt license data is provided" do
|
||||
before do
|
||||
license.data = "whatever"
|
||||
end
|
||||
|
||||
it "returns nil" do
|
||||
expect(license.license).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context "when valid license data is provided" do
|
||||
it "returns the license" do
|
||||
expect(license.license).to_not be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -37,6 +37,10 @@ RSpec.configure do |config|
|
||||
config.before(:suite) do
|
||||
TestEnv.init
|
||||
end
|
||||
|
||||
config.before(:all) do
|
||||
TestLicense.init
|
||||
end
|
||||
end
|
||||
|
||||
ActiveRecord::Migration.maintain_test_schema!
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
class TestLicense
|
||||
def self.init
|
||||
Gitlab::License.encryption_key = OpenSSL::PKey::RSA.generate(2048)
|
||||
|
||||
FactoryGirl.create(:license)
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user