Allow license to be uploaded and enforce its validity.

This commit is contained in:
Douwe Maan 2015-05-01 14:52:30 +02:00
parent e625cf685a
commit d1f2b09fad
18 changed files with 439 additions and 23 deletions

View file

@ -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-----

View file

@ -203,6 +203,8 @@ gem 'request_store'
gem "virtus"
gem 'addressable'
gem "gitlab-license"
group :development do
gem 'brakeman', require: false
gem "annotate", "~> 2.6.0.beta2"

View file

@ -209,6 +209,7 @@ GEM
diff-lcs (~> 1.1)
mime-types (~> 1.15)
posix-spawn (~> 0.3)
gitlab-license (0.0.1)
gitlab-linguist (3.0.1)
charlock_holmes (~> 0.6.6)
escape_utils (~> 0.2.4)
@ -706,6 +707,7 @@ DEPENDENCIES
github-markup
gitlab-flowdock-git-hook (~> 0.4.2)
gitlab-grack (~> 2.0.2)
gitlab-license
gitlab-linguist (~> 3.0.1)
gitlab_emoji (~> 0.1)
gitlab_git (~> 7.1.10)

View file

@ -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()

View file

@ -0,0 +1,54 @@
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.all.to_a[0..-2].reverse
end
def download
send_data @license.data, filename: @license.data_filename, disposition: 'attachment'
end
def new
@license = License.new
end
def create
@license = License.new
@license.data_file = license_params[:data_file]
respond_with(@license, location: admin_license_path) do
if @license.save
flash[:notice] = "The license was successfully uploaded."
end
end
end
def destroy
license.destroy
redirect_to admin_license_path, notice: "The license was removed."
end
private
def license
@license ||= begin
License.reset_current
License.current
end
end
def require_license
return if license
redirect_to new_admin_license_path
end
def license_params
params.require(:license).permit(:data_file)
end
end

View file

@ -213,9 +213,15 @@ def broadcast_message
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

View file

@ -0,0 +1,55 @@
module LicenseHelper
# better text
def license_message(signed_in: signed_in?, is_admin: (current_user && current_user.is_admin?))
message = []
license = License.current
if license
return unless signed_in
return unless (license.notify_admins? && is_admin) || license.notify_users?
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"
if license.block_changes?
message << "has been disabled."
else
message << "will be disabled on #{license.block_changes_at}."
end
end
if is_admin
message << "Upload a new license in the admin area"
else
message << "Ask an admin to upload a new license"
end
if license.block_changes?
message << "to restore service."
else
message << "to ensure uninterrupted service."
end
else
message << "No GitLab Enterprise Edition license has been provided yet."
message << "Pushing code and creation of issues and merge requests has been disabled."
if signed_in && is_admin
message << "Upload a license in the admin area"
else
message << "Ask an admin to upload a license"
end
message << "to restore service."
end
message.join(" ")
end
extend self
end

View file

@ -5,18 +5,28 @@ def allowed(user, subject)
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.concat(global_abilities(user))
if License.block_changes?
abilities.delete(:push_code)
abilities.delete(:push_code_to_protected_branches)
abilities.delete(:write_issue)
abilities.delete(:write_merge_request)
end
abilities
end
# List of possible abilities
@ -68,7 +78,7 @@ def global_abilities(user)
def project_abilities(user, project)
rules = []
key = "/user/#{user.id}/project/#{project.id}"
RequestStore.store[key] ||= begin
rules = RequestStore.store[key] ||= begin
team = project.team
# Rules based on role in project

93
app/models/license.rb Normal file
View file

@ -0,0 +1,93 @@
class License < ActiveRecord::Base
validates :data, presence: true
validate :valid_license
before_validation :reset_license, if: :data_changed?
after_create :reset_current
after_destroy :reset_current
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
clean_company_name = self.licensee.values.first.gsub(/[^A-Za-z0-9]/, "")
"#{clean_company_name}.gitlab-license"
end
def data_file
return nil unless self.data
Tempfile.new(self.data_filename) { |f| f.write(self.data) }
end
def data_file=(file)
self.data = file.read
end
def license
return nil unless self.data
@license ||= Gitlab::License.import(self.data)
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
(license && license.respond_to?(method_name)) || super
end
def active_user_restriction_exceeded?
return false unless self.restricted?(:active_user_count)
User.active.count > self.restrictions[:active_user_count]
end
private
def reset_current
self.class.reset_current
end
def reset_license
@license = nil
end
def valid_license
return if self.license && self.license.valid?
# TODO: Clearer message
self.errors.add(:license, "is invalid.")
end
end

View file

@ -0,0 +1,20 @@
%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"
.form-actions
= f.submit 'Upload', class: 'btn btn-primary'

View file

@ -0,0 +1,116 @@
%h3.page-title
Your License
= link_to 'Upload 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 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>.
%p Still, we recommend keeping it save somewhere, because 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: "LICENSE WILL BE REMOVED! Are you sure?" }, method: :delete, class: "btn btn-remove"
- if @previous_licenses.any?
%h4 Previous Licenses
.panel.panel-default
%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

View file

@ -2,3 +2,8 @@
.broadcast-message{ style: broadcast_styling(broadcast_message) }
%i.fa.fa-bullhorn
= broadcast_message.message
- if (message = license_message) && message.present?
.broadcast-message
%i.fa.fa-bullhorn
= message

View file

@ -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

View file

@ -0,0 +1,16 @@
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
# TODO: Validate encryptionkey
# 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?
# TODO: Change
warn "WARNING: #{message}"
end
# TODO: Warn about too many users
end

View file

@ -211,6 +211,10 @@
resources :services
end
resource :license, only: [:show, :new, :create, :destroy] do
get :download, on: :member
end
root to: 'dashboard#index'
end

View file

@ -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

View file

@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20150425173433) do
ActiveRecord::Schema.define(version: 20150501095306) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -210,6 +210,12 @@
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
@ -302,7 +308,7 @@
end
add_index "namespaces", ["created_at", "id"], name: "index_namespaces_on_created_at_and_id", using: :btree
add_index "namespaces", ["name"], name: "index_namespaces_on_name", unique: true, using: :btree
add_index "namespaces", ["name"], name: "index_namespaces_on_name", using: :btree
add_index "namespaces", ["owner_id"], name: "index_namespaces_on_owner_id", using: :btree
add_index "namespaces", ["path"], name: "index_namespaces_on_path", unique: true, using: :btree
add_index "namespaces", ["type"], name: "index_namespaces_on_type", using: :btree
@ -410,11 +416,11 @@
t.string "avatar"
t.string "import_status"
t.float "repository_size", default: 0.0
t.text "merge_requests_template"
t.integer "star_count", default: 0, null: false
t.boolean "merge_requests_rebase_enabled", default: false
t.string "import_type"
t.string "import_source"
t.text "merge_requests_template"
t.boolean "merge_requests_rebase_enabled", default: false
t.boolean "merge_requests_rebase_default", default: true
end
@ -541,7 +547,6 @@
t.string "unconfirmed_email"
t.boolean "hide_no_ssh_key", default: false
t.string "website_url", default: "", null: false
t.datetime "admin_email_unsubscribed_at"
t.string "github_access_token"
t.string "gitlab_access_token"
t.string "notification_email"
@ -549,6 +554,7 @@
t.boolean "password_automatically_set", default: false
t.string "bitbucket_access_token"
t.string "bitbucket_access_token_secret"
t.datetime "admin_email_unsubscribed_at"
t.string "location"
t.string "public_email", default: "", null: false
end

View file

@ -102,8 +102,9 @@ def deploy_key_download_access_check
end
def user_push_access_check(changes)
unless user && user_allowed?
return build_status_object(false, "You don't have access")
if ::License.block_changes?
message = ::LicenseHelper.license_message(signed_in: true, is_admin: (user && user.is_admin?))
return build_status_object(false, message)
end
if changes.blank?