Commit 28d96459 authored by Robert Speicher's avatar Robert Speicher

Merge branch '21992-disable-access-requests-by-default' into 'master'

Disable the "request access" functionality by default for new groups and projects

Currently this feature is enabled by default, and additional action is required to disable it.

Closes #21992 
Closes !7011

See merge request !7425
parents 3255401e d2110116
---
title: Disable "Request Access" functionality by default for new projects and groups
merge_request: 7425
author:
class DefaultRequestAccessGroups < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def up
change_column_default :namespaces, :request_access_enabled, false
end
def down
change_column_default :namespaces, :request_access_enabled, true
end
end
class DefaultRequestAccessProjects < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def up
change_column_default :projects, :request_access_enabled, false
end
def down
change_column_default :projects, :request_access_enabled, true
end
end
...@@ -723,7 +723,7 @@ ActiveRecord::Schema.define(version: 20161109150329) do ...@@ -723,7 +723,7 @@ ActiveRecord::Schema.define(version: 20161109150329) do
t.string "avatar" t.string "avatar"
t.boolean "share_with_group_lock", default: false t.boolean "share_with_group_lock", default: false
t.integer "visibility_level", default: 20, null: false t.integer "visibility_level", default: 20, null: false
t.boolean "request_access_enabled", default: true, null: false t.boolean "request_access_enabled", default: false, null: false
t.datetime "deleted_at" t.datetime "deleted_at"
t.boolean "lfs_enabled" t.boolean "lfs_enabled"
t.text "description_html" t.text "description_html"
...@@ -911,7 +911,7 @@ ActiveRecord::Schema.define(version: 20161109150329) do ...@@ -911,7 +911,7 @@ ActiveRecord::Schema.define(version: 20161109150329) do
t.boolean "only_allow_merge_if_build_succeeds", default: false, null: false t.boolean "only_allow_merge_if_build_succeeds", default: false, null: false
t.boolean "has_external_issue_tracker" t.boolean "has_external_issue_tracker"
t.string "repository_storage", default: "default", null: false t.string "repository_storage", default: "default", null: false
t.boolean "request_access_enabled", default: true, null: false t.boolean "request_access_enabled", default: false, null: false
t.boolean "has_external_wiki" t.boolean "has_external_wiki"
t.boolean "lfs_enabled" t.boolean "lfs_enabled"
t.text "description_html" t.text "description_html"
......
...@@ -2,7 +2,7 @@ require 'spec_helper' ...@@ -2,7 +2,7 @@ require 'spec_helper'
describe Groups::GroupMembersController do describe Groups::GroupMembersController do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:group) { create(:group, :public) } let(:group) { create(:group, :public, :access_requestable) }
describe 'GET index' do describe 'GET index' do
it 'renders index with 200 status code' do it 'renders index with 200 status code' do
......
...@@ -2,7 +2,7 @@ require('spec_helper') ...@@ -2,7 +2,7 @@ require('spec_helper')
describe Projects::ProjectMembersController do describe Projects::ProjectMembersController do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project) { create(:project, :public) } let(:project) { create(:empty_project, :public, :access_requestable) }
describe 'GET index' do describe 'GET index' do
it 'renders index with 200 status code' do it 'renders index with 200 status code' do
......
...@@ -15,5 +15,9 @@ FactoryGirl.define do ...@@ -15,5 +15,9 @@ FactoryGirl.define do
trait :private do trait :private do
visibility_level Gitlab::VisibilityLevel::PRIVATE visibility_level Gitlab::VisibilityLevel::PRIVATE
end end
trait :access_requestable do
request_access_enabled true
end
end end
end end
...@@ -24,6 +24,10 @@ FactoryGirl.define do ...@@ -24,6 +24,10 @@ FactoryGirl.define do
visibility_level Gitlab::VisibilityLevel::PRIVATE visibility_level Gitlab::VisibilityLevel::PRIVATE
end end
trait :access_requestable do
request_access_enabled true
end
trait :empty_repo do trait :empty_repo do
after(:create) do |project| after(:create) do |project|
project.create_repository project.create_repository
......
...@@ -3,7 +3,7 @@ require 'spec_helper' ...@@ -3,7 +3,7 @@ require 'spec_helper'
feature 'Groups > Members > Owner manages access requests', feature: true do feature 'Groups > Members > Owner manages access requests', feature: true do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:owner) { create(:user) } let(:owner) { create(:user) }
let(:group) { create(:group, :public) } let(:group) { create(:group, :public, :access_requestable) }
background do background do
group.request_access(user) group.request_access(user)
......
...@@ -3,7 +3,7 @@ require 'spec_helper' ...@@ -3,7 +3,7 @@ require 'spec_helper'
feature 'Groups > Members > User requests access', feature: true do feature 'Groups > Members > User requests access', feature: true do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:owner) { create(:user) } let(:owner) { create(:user) }
let(:group) { create(:group, :public) } let(:group) { create(:group, :public, :access_requestable) }
let!(:project) { create(:project, :private, namespace: group) } let!(:project) { create(:project, :private, namespace: group) }
background do background do
......
...@@ -3,8 +3,8 @@ require 'spec_helper' ...@@ -3,8 +3,8 @@ require 'spec_helper'
feature 'Projects > Members > Group requester cannot request access to project', feature: true do feature 'Projects > Members > Group requester cannot request access to project', feature: true do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:owner) { create(:user) } let(:owner) { create(:user) }
let(:group) { create(:group, :public) } let(:group) { create(:group, :public, :access_requestable) }
let(:project) { create(:project, :public, namespace: group) } let(:project) { create(:project, :public, :access_requestable, namespace: group) }
background do background do
group.add_owner(owner) group.add_owner(owner)
......
...@@ -3,7 +3,7 @@ require 'spec_helper' ...@@ -3,7 +3,7 @@ require 'spec_helper'
feature 'Projects > Members > Master manages access requests', feature: true do feature 'Projects > Members > Master manages access requests', feature: true do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:master) { create(:user) } let(:master) { create(:user) }
let(:project) { create(:project, :public) } let(:project) { create(:empty_project, :public, :access_requestable) }
background do background do
project.request_access(user) project.request_access(user)
......
...@@ -3,7 +3,7 @@ require 'spec_helper' ...@@ -3,7 +3,7 @@ require 'spec_helper'
feature 'Projects > Members > User requests access', feature: true do feature 'Projects > Members > User requests access', feature: true do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:master) { create(:user) } let(:master) { create(:user) }
let(:project) { create(:project, :public) } let(:project) { create(:project, :public, :access_requestable) }
background do background do
project.team << [master, :master] project.team << [master, :master]
......
...@@ -3,12 +3,17 @@ require 'spec_helper' ...@@ -3,12 +3,17 @@ require 'spec_helper'
describe AccessRequestsFinder, services: true do describe AccessRequestsFinder, services: true do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:access_requester) { create(:user) } let(:access_requester) { create(:user) }
let(:project) { create(:project, :public) }
let(:group) { create(:group, :public) }
before do let(:project) do
project.request_access(access_requester) create(:empty_project, :public, :access_requestable) do |project|
group.request_access(access_requester) project.request_access(access_requester)
end
end
let(:group) do
create(:group, :public, :access_requestable) do |group|
group.request_access(access_requester)
end
end end
shared_examples 'a finder returning access requesters' do |method_name| shared_examples 'a finder returning access requesters' do |method_name|
......
...@@ -11,11 +11,11 @@ describe MembersHelper do ...@@ -11,11 +11,11 @@ describe MembersHelper do
describe '#remove_member_message' do describe '#remove_member_message' do
let(:requester) { build(:user) } let(:requester) { build(:user) }
let(:project) { create(:empty_project, :public) } let(:project) { create(:empty_project, :public, :access_requestable) }
let(:project_member) { build(:project_member, project: project) } let(:project_member) { build(:project_member, project: project) }
let(:project_member_invite) { build(:project_member, project: project).tap { |m| m.generate_invite_token! } } let(:project_member_invite) { build(:project_member, project: project).tap { |m| m.generate_invite_token! } }
let(:project_member_request) { project.request_access(requester) } let(:project_member_request) { project.request_access(requester) }
let(:group) { create(:group) } let(:group) { create(:group, :access_requestable) }
let(:group_member) { build(:group_member, group: group) } let(:group_member) { build(:group_member, group: group) }
let(:group_member_invite) { build(:group_member, group: group).tap { |m| m.generate_invite_token! } } let(:group_member_invite) { build(:group_member, group: group).tap { |m| m.generate_invite_token! } }
let(:group_member_request) { group.request_access(requester) } let(:group_member_request) { group.request_access(requester) }
...@@ -32,10 +32,10 @@ describe MembersHelper do ...@@ -32,10 +32,10 @@ describe MembersHelper do
describe '#remove_member_title' do describe '#remove_member_title' do
let(:requester) { build(:user) } let(:requester) { build(:user) }
let(:project) { create(:empty_project, :public) } let(:project) { create(:empty_project, :public, :access_requestable) }
let(:project_member) { build(:project_member, project: project) } let(:project_member) { build(:project_member, project: project) }
let(:project_member_request) { project.request_access(requester) } let(:project_member_request) { project.request_access(requester) }
let(:group) { create(:group) } let(:group) { create(:group, :access_requestable) }
let(:group_member) { build(:group_member, group: group) } let(:group_member) { build(:group_member, group: group) }
let(:group_member_request) { group.request_access(requester) } let(:group_member_request) { group.request_access(requester) }
......
...@@ -401,7 +401,12 @@ describe Notify do ...@@ -401,7 +401,12 @@ describe Notify do
describe 'project access requested' do describe 'project access requested' do
context 'for a project in a user namespace' do context 'for a project in a user namespace' do
let(:project) { create(:project, :public).tap { |p| p.team << [p.owner, :master, p.owner] } } let(:project) do
create(:empty_project, :public, :access_requestable) do |project|
project.team << [project.owner, :master, project.owner]
end
end
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project_member) do let(:project_member) do
project.request_access(user) project.request_access(user)
...@@ -428,7 +433,7 @@ describe Notify do ...@@ -428,7 +433,7 @@ describe Notify do
context 'for a project in a group' do context 'for a project in a group' do
let(:group_owner) { create(:user) } let(:group_owner) { create(:user) }
let(:group) { create(:group).tap { |g| g.add_owner(group_owner) } } let(:group) { create(:group).tap { |g| g.add_owner(group_owner) } }
let(:project) { create(:project, :public, namespace: group) } let(:project) { create(:empty_project, :public, :access_requestable, namespace: group) }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project_member) do let(:project_member) do
project.request_access(user) project.request_access(user)
...@@ -454,7 +459,7 @@ describe Notify do ...@@ -454,7 +459,7 @@ describe Notify do
end end
describe 'project access denied' do describe 'project access denied' do
let(:project) { create(:project) } let(:project) { create(:empty_project, :public, :access_requestable) }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project_member) do let(:project_member) do
project.request_access(user) project.request_access(user)
...@@ -474,7 +479,7 @@ describe Notify do ...@@ -474,7 +479,7 @@ describe Notify do
end end
describe 'project access changed' do describe 'project access changed' do
let(:project) { create(:project) } let(:project) { create(:empty_project, :public, :access_requestable) }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project_member) { create(:project_member, project: project, user: user) } let(:project_member) { create(:project_member, project: project, user: user) }
subject { Notify.member_access_granted_email('project', project_member.id) } subject { Notify.member_access_granted_email('project', project_member.id) }
...@@ -685,7 +690,7 @@ describe Notify do ...@@ -685,7 +690,7 @@ describe Notify do
context 'for a group' do context 'for a group' do
describe 'group access requested' do describe 'group access requested' do
let(:group) { create(:group) } let(:group) { create(:group, :public, :access_requestable) }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:group_member) do let(:group_member) do
group.request_access(user) group.request_access(user)
......
...@@ -3,7 +3,7 @@ require 'spec_helper' ...@@ -3,7 +3,7 @@ require 'spec_helper'
describe AccessRequestable do describe AccessRequestable do
describe 'Group' do describe 'Group' do
describe '#request_access' do describe '#request_access' do
let(:group) { create(:group, :public) } let(:group) { create(:group, :public, :access_requestable) }
let(:user) { create(:user) } let(:user) { create(:user) }
it { expect(group.request_access(user)).to be_a(GroupMember) } it { expect(group.request_access(user)).to be_a(GroupMember) }
...@@ -11,7 +11,7 @@ describe AccessRequestable do ...@@ -11,7 +11,7 @@ describe AccessRequestable do
end end
describe '#access_requested?' do describe '#access_requested?' do
let(:group) { create(:group, :public) } let(:group) { create(:group, :public, :access_requestable) }
let(:user) { create(:user) } let(:user) { create(:user) }
before { group.request_access(user) } before { group.request_access(user) }
...@@ -22,14 +22,14 @@ describe AccessRequestable do ...@@ -22,14 +22,14 @@ describe AccessRequestable do
describe 'Project' do describe 'Project' do
describe '#request_access' do describe '#request_access' do
let(:project) { create(:empty_project, :public) } let(:project) { create(:empty_project, :public, :access_requestable) }
let(:user) { create(:user) } let(:user) { create(:user) }
it { expect(project.request_access(user)).to be_a(ProjectMember) } it { expect(project.request_access(user)).to be_a(ProjectMember) }
end end
describe '#access_requested?' do describe '#access_requested?' do
let(:project) { create(:empty_project, :public) } let(:project) { create(:empty_project, :public, :access_requestable) }
let(:user) { create(:user) } let(:user) { create(:user) }
before { project.request_access(user) } before { project.request_access(user) }
......
require 'spec_helper' require 'spec_helper'
describe Group, models: true do describe Group, models: true do
let!(:group) { create(:group) } let!(:group) { create(:group, :access_requestable) }
describe 'associations' do describe 'associations' do
it { is_expected.to have_many :projects } it { is_expected.to have_many :projects }
......
...@@ -57,7 +57,7 @@ describe Member, models: true do ...@@ -57,7 +57,7 @@ describe Member, models: true do
describe 'Scopes & finders' do describe 'Scopes & finders' do
before do before do
project = create(:empty_project, :public) project = create(:empty_project, :public, :access_requestable)
group = create(:group) group = create(:group)
@owner_user = create(:user).tap { |u| group.add_owner(u) } @owner_user = create(:user).tap { |u| group.add_owner(u) }
@owner = group.members.find_by(user_id: @owner_user.id) @owner = group.members.find_by(user_id: @owner_user.id)
...@@ -174,7 +174,7 @@ describe Member, models: true do ...@@ -174,7 +174,7 @@ describe Member, models: true do
describe '.add_user' do describe '.add_user' do
%w[project group].each do |source_type| %w[project group].each do |source_type|
context "when source is a #{source_type}" do context "when source is a #{source_type}" do
let!(:source) { create(source_type, :public) } let!(:source) { create(source_type, :public, :access_requestable) }
let!(:user) { create(:user) } let!(:user) { create(:user) }
let!(:admin) { create(:admin) } let!(:admin) { create(:admin) }
......
...@@ -76,7 +76,7 @@ describe Project, models: true do ...@@ -76,7 +76,7 @@ describe Project, models: true do
end end
describe '#members & #requesters' do describe '#members & #requesters' do
let(:project) { create(:project, :public) } let(:project) { create(:empty_project, :public, :access_requestable) }
let(:requester) { create(:user) } let(:requester) { create(:user) }
let(:developer) { create(:user) } let(:developer) { create(:user) }
before do before do
......
...@@ -137,7 +137,7 @@ describe ProjectTeam, models: true do ...@@ -137,7 +137,7 @@ describe ProjectTeam, models: true do
describe '#find_member' do describe '#find_member' do
context 'personal project' do context 'personal project' do
let(:project) { create(:empty_project, :public) } let(:project) { create(:empty_project, :public, :access_requestable) }
let(:requester) { create(:user) } let(:requester) { create(:user) }
before do before do
...@@ -155,7 +155,7 @@ describe ProjectTeam, models: true do ...@@ -155,7 +155,7 @@ describe ProjectTeam, models: true do
end end
context 'group project' do context 'group project' do
let(:group) { create(:group) } let(:group) { create(:group, :access_requestable) }
let(:project) { create(:empty_project, group: group) } let(:project) { create(:empty_project, group: group) }
let(:requester) { create(:user) } let(:requester) { create(:user) }
...@@ -200,7 +200,7 @@ describe ProjectTeam, models: true do ...@@ -200,7 +200,7 @@ describe ProjectTeam, models: true do
let(:requester) { create(:user) } let(:requester) { create(:user) }
context 'personal project' do context 'personal project' do
let(:project) { create(:empty_project, :public) } let(:project) { create(:empty_project, :public, :access_requestable) }
context 'when project is not shared with group' do context 'when project is not shared with group' do
before do before do
...@@ -243,7 +243,7 @@ describe ProjectTeam, models: true do ...@@ -243,7 +243,7 @@ describe ProjectTeam, models: true do
end end
context 'group project' do context 'group project' do
let(:group) { create(:group) } let(:group) { create(:group, :access_requestable) }
let(:project) { create(:empty_project, group: group) } let(:project) { create(:empty_project, group: group) }
before do before do
......
...@@ -37,7 +37,7 @@ describe User, models: true do ...@@ -37,7 +37,7 @@ describe User, models: true do
describe '#group_members' do describe '#group_members' do
it 'does not include group memberships for which user is a requester' do it 'does not include group memberships for which user is a requester' do
user = create(:user) user = create(:user)
group = create(:group, :public) group = create(:group, :public, :access_requestable)
group.request_access(user) group.request_access(user)
expect(user.group_members).to be_empty expect(user.group_members).to be_empty
...@@ -47,7 +47,7 @@ describe User, models: true do ...@@ -47,7 +47,7 @@ describe User, models: true do
describe '#project_members' do describe '#project_members' do
it 'does not include project memberships for which user is a requester' do it 'does not include project memberships for which user is a requester' do
user = create(:user) user = create(:user)
project = create(:project, :public) project = create(:project, :public, :access_requestable)
project.request_access(user) project.request_access(user)
expect(user.project_members).to be_empty expect(user.project_members).to be_empty
......
...@@ -9,19 +9,19 @@ describe API::AccessRequests, api: true do ...@@ -9,19 +9,19 @@ describe API::AccessRequests, api: true do
let(:stranger) { create(:user) } let(:stranger) { create(:user) }
let(:project) do let(:project) do
project = create(:project, :public, creator_id: master.id, namespace: master.namespace) create(:project, :public, :access_requestable, creator_id: master.id, namespace: master.namespace) do |project|
project.team << [developer, :developer] project.team << [developer, :developer]
project.team << [master, :master] project.team << [master, :master]
project.request_access(access_requester) project.request_access(access_requester)
project end
end end
let(:group) do let(:group) do
group = create(:group, :public) create(:group, :public, :access_requestable) do |group|
group.add_developer(developer) group.add_developer(developer)
group.add_owner(master) group.add_owner(master)
group.request_access(access_requester) group.request_access(access_requester)
group end
end end
shared_examples 'GET /:sources/:id/access_requests' do |source_type| shared_examples 'GET /:sources/:id/access_requests' do |source_type|
...@@ -89,7 +89,7 @@ describe API::AccessRequests, api: true do ...@@ -89,7 +89,7 @@ describe API::AccessRequests, api: true do
context 'when authenticated as a stranger' do context 'when authenticated as a stranger' do
context "when access request is disabled for the #{source_type}" do context "when access request is disabled for the #{source_type}" do
before do before do
source.update(request_access_enabled: false) source.update_attributes(request_access_enabled: false)
end end
it 'returns 403' do it 'returns 403' do
......
...@@ -9,19 +9,19 @@ describe API::Members, api: true do ...@@ -9,19 +9,19 @@ describe API::Members, api: true do
let(:stranger) { create(:user) } let(:stranger) { create(:user) }
let(:project) do let(:project) do
project = create(:project, :public, creator_id: master.id, namespace: master.namespace) create(:project, :public, :access_requestable, creator_id: master.id, namespace: master.namespace) do |project|
project.team << [developer, :developer] project.team << [developer, :developer]
project.team << [master, :master] project.team << [master, :master]
project.request_access(access_requester) project.request_access(access_requester)
project end
end end
let!(:group) do let!(:group) do
group = create(:group, :public) create(:group, :public, :access_requestable) do |group|
group.add_developer(developer) group.add_developer(developer)
group.add_owner(master) group.add_owner(master)
group.request_access(access_requester) group.request_access(access_requester)
group end
end end
shared_examples 'GET /:sources/:id/members' do |source_type| shared_examples 'GET /:sources/:id/members' do |source_type|
......
...@@ -3,8 +3,8 @@ require 'spec_helper' ...@@ -3,8 +3,8 @@ require 'spec_helper'
describe Members::ApproveAccessRequestService, services: true do describe Members::ApproveAccessRequestService, services: true do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:access_requester) { create(:user) } let(:access_requester) { create(:user) }
let(:project) { create(:project, :public) } let(:project) { create(:empty_project, :public, :access_requestable) }
let(:group) { create(:group, :public) } let(:group) { create(:group, :public, :access_requestable) }
let(:opts) { {} } let(:opts) { {} }
shared_examples 'a service raising ActiveRecord::RecordNotFound' do shared_examples 'a service raising ActiveRecord::RecordNotFound' do
......
...@@ -26,6 +26,7 @@ describe Members::DestroyService, services: true do ...@@ -26,6 +26,7 @@ describe Members::DestroyService, services: true do
context 'when the given member is an access requester' do context 'when the given member is an access requester' do
before do before do
source.members.find_by(user_id: member_user).destroy source.members.find_by(user_id: member_user).destroy
source.update_attributes(request_access_enabled: true)
source.request_access(member_user) source.request_access(member_user)
end end
let(:access_requester) { source.requesters.find_by(user_id: member_user) } let(:access_requester) { source.requesters.find_by(user_id: member_user) }
......
...@@ -2,8 +2,6 @@ require 'spec_helper' ...@@ -2,8 +2,6 @@ require 'spec_helper'
describe Members::RequestAccessService, services: true do describe Members::RequestAccessService, services: true do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project) { create(:project, :private) }
let(:group) { create(:group, :private) }
shared_examples 'a service raising Gitlab::Access::AccessDeniedError' do shared_examples 'a service raising Gitlab::Access::AccessDeniedError' do
it 'raises Gitlab::Access::AccessDeniedError' do it 'raises Gitlab::Access::AccessDeniedError' do
...@@ -31,27 +29,26 @@ describe Members::RequestAccessService, services: true do ...@@ -31,27 +29,26 @@ describe Members::RequestAccessService, services: true do
end end
context 'when current user cannot request access to the project' do context 'when current user cannot request access to the project' do
it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError' do %i[project group].each do |source_type|
let(:source) { project } it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError' do
let(:source) { create(source_type, :private) }
end
end end
end
it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError' do context 'when access requests are disabled' do
let(:source) { group } %i[project group].each do |source_type|
it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError' do
let(:source) { create(source_type, :public) }
end
end end
end end
context 'when current user can request access to the project' do context 'when current user can request access to the project' do
before do %i[project group].each do |source_type|
project.update(visibility_level: Gitlab::VisibilityLevel::PUBLIC) it_behaves_like 'a service creating a access request' do
group.update(visibility_level: Gitlab::VisibilityLevel::PUBLIC) let(:source) { create(source_type, :public, :access_requestable) }
end end
it_behaves_like 'a service creating a access request' do
let(:source) { project }
end
it_behaves_like 'a service creating a access request' do
let(:source) { group }
end end
end end
end 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