Skip to content
Snippets Groups Projects
Commit c84b60b1 authored by Tuomo Ala-Vannesluoma's avatar Tuomo Ala-Vannesluoma Committed by Nick Thomas
Browse files

Make GitLab pages support access control

parent c972f2e4
No related branches found
No related tags found
1 merge request!10495Merge Requests - Assignee
Showing
with 151 additions and 16 deletions
1.1.0
1.2.0
Loading
Loading
@@ -52,6 +52,21 @@
required: false,
default: '',
},
pagesAvailable: {
type: Boolean,
required: false,
default: false,
},
pagesAccessControlEnabled: {
type: Boolean,
required: false,
default: false,
},
pagesHelpPath: {
type: String,
required: false,
default: '',
},
},
 
data() {
Loading
Loading
@@ -64,6 +79,7 @@
buildsAccessLevel: 20,
wikiAccessLevel: 20,
snippetsAccessLevel: 20,
pagesAccessLevel: 20,
containerRegistryEnabled: true,
lfsEnabled: true,
requestAccessEnabled: true,
Loading
Loading
@@ -90,6 +106,13 @@
);
},
 
pagesFeatureAccessLevelOptions() {
if (this.visibilityLevel !== visibilityOptions.PUBLIC) {
return this.featureAccessLevelOptions.concat([[30, 'Everyone']]);
}
return this.featureAccessLevelOptions;
},
repositoryEnabled() {
return this.repositoryAccessLevel > 0;
},
Loading
Loading
@@ -109,6 +132,10 @@
this.buildsAccessLevel = Math.min(10, this.buildsAccessLevel);
this.wikiAccessLevel = Math.min(10, this.wikiAccessLevel);
this.snippetsAccessLevel = Math.min(10, this.snippetsAccessLevel);
if (this.pagesAccessLevel === 20) {
// When from Internal->Private narrow access for only members
this.pagesAccessLevel = 10;
}
this.highlightChanges();
} else if (oldValue === visibilityOptions.PRIVATE) {
// if changing away from private, make enabled features more permissive
Loading
Loading
@@ -118,6 +145,7 @@
if (this.buildsAccessLevel > 0) this.buildsAccessLevel = 20;
if (this.wikiAccessLevel > 0) this.wikiAccessLevel = 20;
if (this.snippetsAccessLevel > 0) this.snippetsAccessLevel = 20;
if (this.pagesAccessLevel === 10) this.pagesAccessLevel = 20;
this.highlightChanges();
}
},
Loading
Loading
@@ -323,6 +351,18 @@
name="project[project_feature_attributes][snippets_access_level]"
/>
</project-setting-row>
<project-setting-row
v-if="pagesAvailable && pagesAccessControlEnabled"
:help-path="pagesHelpPath"
label="Pages"
help-text="Static website for the project."
>
<project-feature-setting
v-model="pagesAccessLevel"
:options="pagesFeatureAccessLevelOptions"
name="project[project_feature_attributes][pages_access_level]"
/>
</project-setting-row>
</div>
</div>
</template>
Loading
Loading
@@ -366,6 +366,7 @@ class ProjectsController < Projects::ApplicationController
repository_access_level
snippets_access_level
wiki_access_level
pages_access_level
]
]
end
Loading
Loading
Loading
Loading
@@ -16,7 +16,7 @@ module Types
:create_deployment, :push_to_delete_protected_branch,
:admin_wiki, :admin_project, :update_pages,
:admin_remote_mirror, :create_label, :update_wiki, :destroy_wiki,
:create_pages, :destroy_pages
:create_pages, :destroy_pages, :read_pages_content
end
end
end
Loading
Loading
@@ -454,6 +454,7 @@ module ProjectsHelper
buildsAccessLevel: feature.builds_access_level,
wikiAccessLevel: feature.wiki_access_level,
snippetsAccessLevel: feature.snippets_access_level,
pagesAccessLevel: feature.pages_access_level,
containerRegistryEnabled: !!project.container_registry_enabled,
lfsEnabled: !!project.lfs_enabled
}
Loading
Loading
@@ -468,7 +469,10 @@ module ProjectsHelper
registryAvailable: Gitlab.config.registry.enabled,
registryHelpPath: help_page_path('user/project/container_registry'),
lfsAvailable: Gitlab.config.lfs.enabled,
lfsHelpPath: help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs')
lfsHelpPath: help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs'),
pagesAvailable: Gitlab.config.pages.enabled,
pagesAccessControlEnabled: Gitlab.config.pages.access_control,
pagesHelpPath: help_page_path('user/project/pages/index.md')
}
end
 
Loading
Loading
Loading
Loading
@@ -55,8 +55,8 @@ class Project < ActiveRecord::Base
cache_markdown_field :description, pipeline: :description
 
delegate :feature_available?, :builds_enabled?, :wiki_enabled?,
:merge_requests_enabled?, :issues_enabled?, to: :project_feature,
allow_nil: true
:merge_requests_enabled?, :issues_enabled?, :pages_enabled?, :public_pages?,
to: :project_feature, allow_nil: true
 
delegate :base_dir, :disk_path, :ensure_storage_path_exists, to: :storage
 
Loading
Loading
@@ -356,7 +356,7 @@ class Project < ActiveRecord::Base
# "enabled" here means "not disabled". It includes private features!
scope :with_feature_enabled, ->(feature) {
access_level_attribute = ProjectFeature.access_level_attribute(feature)
with_project_feature.where(project_features: { access_level_attribute => [nil, ProjectFeature::PRIVATE, ProjectFeature::ENABLED] })
with_project_feature.where(project_features: { access_level_attribute => [nil, ProjectFeature::PRIVATE, ProjectFeature::ENABLED, ProjectFeature::PUBLIC] })
}
 
# Picks a feature where the level is exactly that given.
Loading
Loading
@@ -418,15 +418,15 @@ class Project < ActiveRecord::Base
end
end
 
# project features may be "disabled", "internal" or "enabled". If "internal",
# project features may be "disabled", "internal", "enabled" or "public". If "internal",
# they are only available to team members. This scope returns projects where
# the feature is either enabled, or internal with permission for the user.
# the feature is either public, enabled, or internal with permission for the user.
#
# This method uses an optimised version of `with_feature_access_level` for
# logged in users to more efficiently get private projects with the given
# feature.
def self.with_feature_available_for_user(feature, user)
visible = [nil, ProjectFeature::ENABLED]
visible = [nil, ProjectFeature::ENABLED, ProjectFeature::PUBLIC]
 
if user&.admin?
with_feature_enabled(feature)
Loading
Loading
Loading
Loading
@@ -13,14 +13,16 @@ class ProjectFeature < ActiveRecord::Base
# Disabled: not enabled for anyone
# Private: enabled only for team members
# Enabled: enabled for everyone able to access the project
# Public: enabled for everyone (only allowed for pages)
#
 
# Permission levels
DISABLED = 0
PRIVATE = 10
ENABLED = 20
PUBLIC = 30
 
FEATURES = %i(issues merge_requests wiki snippets builds repository).freeze
FEATURES = %i(issues merge_requests wiki snippets builds repository pages).freeze
 
class << self
def access_level_attribute(feature)
Loading
Loading
@@ -46,6 +48,7 @@ class ProjectFeature < ActiveRecord::Base
validates :project, presence: true
 
validate :repository_children_level
validate :allowed_access_levels
 
default_value_for :builds_access_level, value: ENABLED, allows_nil: false
default_value_for :issues_access_level, value: ENABLED, allows_nil: false
Loading
Loading
@@ -81,6 +84,16 @@ class ProjectFeature < ActiveRecord::Base
issues_access_level > DISABLED
end
 
def pages_enabled?
pages_access_level > DISABLED
end
def public_pages?
return true unless Gitlab.config.pages.access_control
pages_access_level == PUBLIC || pages_access_level == ENABLED && project.public?
end
private
 
# Validates builds and merge requests access level
Loading
Loading
@@ -95,6 +108,17 @@ class ProjectFeature < ActiveRecord::Base
%i(merge_requests_access_level builds_access_level).each(&validator)
end
 
# Validates access level for other than pages cannot be PUBLIC
def allowed_access_levels
validator = lambda do |field|
level = public_send(field) || ProjectFeature::ENABLED # rubocop:disable GitlabSecurity/PublicSend
not_allowed = level > ProjectFeature::ENABLED
self.errors.add(field, "cannot have public visibility level") if not_allowed
end
(FEATURES - %i(pages)).each {|f| validator.call("#{f}_access_level")}
end
def get_permission(user, level)
case level
when DISABLED
Loading
Loading
@@ -103,6 +127,8 @@ class ProjectFeature < ActiveRecord::Base
user && (project.team.member?(user) || user.full_private_access?)
when ENABLED
true
when PUBLIC
true
else
true
end
Loading
Loading
Loading
Loading
@@ -110,6 +110,7 @@ class ProjectPolicy < BasePolicy
snippets
wiki
builds
pages
]
 
features.each do |f|
Loading
Loading
@@ -167,6 +168,7 @@ class ProjectPolicy < BasePolicy
enable :upload_file
enable :read_cycle_analytics
enable :award_emoji
enable :read_pages_content
end
 
# These abilities are not allowed to admins that are not members of the project,
Loading
Loading
@@ -286,6 +288,8 @@ class ProjectPolicy < BasePolicy
prevent(*create_read_update_admin_destroy(:merge_request))
end
 
rule { pages_disabled }.prevent :read_pages_content
rule { issues_disabled & merge_requests_disabled }.policy do
prevent(*create_read_update_admin_destroy(:label))
prevent(*create_read_update_admin_destroy(:milestone))
Loading
Loading
@@ -345,6 +349,7 @@ class ProjectPolicy < BasePolicy
enable :download_code
enable :download_wiki_code
enable :read_cycle_analytics
enable :read_pages_content
 
# NOTE: may be overridden by IssuePolicy
enable :read_issue
Loading
Loading
Loading
Loading
@@ -21,7 +21,9 @@ module Projects
def pages_config
{
domains: pages_domains_config,
https_only: project.pages_https_only?
https_only: project.pages_https_only?,
id: project.project_id,
access_control: !project.public_pages?
}
end
 
Loading
Loading
@@ -31,7 +33,9 @@ module Projects
domain: domain.domain,
certificate: domain.certificate,
key: domain.key,
https_only: project.pages_https_only? && domain.https?
https_only: project.pages_https_only? && domain.https?,
id: project.project_id,
access_control: !project.public_pages?
}
end
end
Loading
Loading
Loading
Loading
@@ -72,7 +72,11 @@ module Projects
system_hook_service.execute_hooks_for(project, :update)
end
 
update_pages_config if changing_pages_https_only?
update_pages_config if changing_pages_related_config?
end
def changing_pages_related_config?
changing_pages_https_only? || changing_pages_access_level?
end
 
def update_failed!
Loading
Loading
@@ -102,6 +106,10 @@ module Projects
params.dig(:project_feature_attributes, :wiki_access_level).to_i > ProjectFeature::DISABLED
end
 
def changing_pages_access_level?
params.dig(:project_feature_attributes, :pages_access_level)
end
def ensure_wiki_exists
ProjectWiki.new(project, project.owner).wiki
rescue ProjectWiki::CouldNotCreateWikiError
Loading
Loading
---
title: Add access control to GitLab pages and make it possible to enable/disable it in project settings
merge_request: 18589
author: Tuomo Ala-Vannesluoma
type: added
Loading
Loading
@@ -210,6 +210,7 @@ production: &base
## GitLab Pages
pages:
enabled: false
access_control: false
# The location where pages are stored (default: shared/pages).
# path: shared/pages
 
Loading
Loading
Loading
Loading
@@ -200,6 +200,7 @@ Settings.registry['path'] = Settings.absolute(Settings.registry['path
#
Settings['pages'] ||= Settingslogic.new({})
Settings.pages['enabled'] = false if Settings.pages['enabled'].nil?
Settings.pages['access_control'] = false if Settings.pages['access_control'].nil?
Settings.pages['path'] = Settings.absolute(Settings.pages['path'] || File.join(Settings.shared['path'], "pages"))
Settings.pages['https'] = false if Settings.pages['https'].nil?
Settings.pages['host'] ||= "example.com"
Loading
Loading
class AddPagesAccessLevelToProjectFeature < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
DOWNTIME = false
def up
add_column_with_default(:project_features, :pages_access_level, :integer, default: ProjectFeature::PUBLIC, allow_null: false)
change_column_default(:project_features, :pages_access_level, ProjectFeature::ENABLED)
end
def down
remove_column :project_features, :pages_access_level
end
end
Loading
Loading
@@ -1578,6 +1578,7 @@ ActiveRecord::Schema.define(version: 20180924141949) do
t.datetime "created_at"
t.datetime "updated_at"
t.integer "repository_access_level", default: 20, null: false
t.integer "pages_access_level", default: 20, null: false
end
 
add_index "project_features", ["project_id"], name: "index_project_features_on_project_id", unique: true, using: :btree
Loading
Loading
Loading
Loading
@@ -110,6 +110,7 @@ which visibility level you select on project settings.
- Disabled: disabled for everyone
- Only team members: only team members will see even if your project is public or internal
- Everyone with access: everyone can see depending on your project visibility level
- Everyone: enabled for everyone (only available for GitLab Pages)
 
### Protected branches
 
Loading
Loading
@@ -242,6 +243,7 @@ which visibility level you select on project settings.
- Disabled: disabled for everyone
- Only team members: only team members will see even if your project is public or internal
- Everyone with access: everyone can see depending on your project visibility level
- Everyone: enabled for everyone (only available for GitLab Pages)
 
## GitLab CI/CD permissions
 
Loading
Loading
Loading
Loading
@@ -287,6 +287,12 @@ module API
present_projects forks
end
 
desc 'Check pages access of this project'
get ':id/pages_access' do
authorize! :read_pages_content, user_project unless user_project.public_pages?
status 200
end
desc 'Update an existing project' do
success Entities::Project
end
Loading
Loading
require_relative '../support/helpers/test_env'
 
FactoryBot.define do
PAGES_ACCESS_LEVEL_SCHEMA_VERSION = 20180423204600
# Project without repository
#
# Project does not have bare repository.
Loading
Loading
@@ -23,6 +25,7 @@ FactoryBot.define do
issues_access_level ProjectFeature::ENABLED
merge_requests_access_level ProjectFeature::ENABLED
repository_access_level ProjectFeature::ENABLED
pages_access_level ProjectFeature::ENABLED
 
# we can't assign the delegated `#ci_cd_settings` attributes directly, as the
# `#ci_cd_settings` relation needs to be created first
Loading
Loading
@@ -34,13 +37,20 @@ FactoryBot.define do
builds_access_level = [evaluator.builds_access_level, evaluator.repository_access_level].min
merge_requests_access_level = [evaluator.merge_requests_access_level, evaluator.repository_access_level].min
 
project.project_feature.update(
hash = {
wiki_access_level: evaluator.wiki_access_level,
builds_access_level: builds_access_level,
snippets_access_level: evaluator.snippets_access_level,
issues_access_level: evaluator.issues_access_level,
merge_requests_access_level: merge_requests_access_level,
repository_access_level: evaluator.repository_access_level)
repository_access_level: evaluator.repository_access_level
}
if ActiveRecord::Migrator.current_version >= PAGES_ACCESS_LEVEL_SCHEMA_VERSION
hash.store("pages_access_level", evaluator.pages_access_level)
end
project.project_feature.update(hash)
 
# Normally the class Projects::CreateService is used for creating
# projects, and this class takes care of making sure the owner and current
Loading
Loading
@@ -244,6 +254,10 @@ FactoryBot.define do
trait(:repository_enabled) { repository_access_level ProjectFeature::ENABLED }
trait(:repository_disabled) { repository_access_level ProjectFeature::DISABLED }
trait(:repository_private) { repository_access_level ProjectFeature::PRIVATE }
trait(:pages_public) { pages_access_level ProjectFeature::PUBLIC }
trait(:pages_enabled) { pages_access_level ProjectFeature::ENABLED }
trait(:pages_disabled) { pages_access_level ProjectFeature::DISABLED }
trait(:pages_private) { pages_access_level ProjectFeature::PRIVATE }
 
trait :auto_devops do
association :auto_devops, factory: :project_auto_devops
Loading
Loading
Loading
Loading
@@ -10,7 +10,7 @@ describe Types::PermissionTypes::Project do
:read_commit_status, :request_access, :create_pipeline, :create_pipeline_schedule,
:create_merge_request_from, :create_wiki, :push_code, :create_deployment, :push_to_delete_protected_branch,
:admin_wiki, :admin_project, :update_pages, :admin_remote_mirror, :create_label,
:update_wiki, :destroy_wiki, :create_pages, :destroy_pages
:update_wiki, :destroy_wiki, :create_pages, :destroy_pages, :read_pages_content
]
 
expect(described_class).to have_graphql_fields(expected_permissions)
Loading
Loading
Loading
Loading
@@ -492,6 +492,7 @@ ProjectFeature:
- snippets_access_level
- builds_access_level
- repository_access_level
- pages_access_level
- created_at
- updated_at
ProtectedBranch::MergeAccessLevel:
Loading
Loading
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment