...@@ -353,10 +353,6 @@ configuring a different storage driver. By default the GitLab Container Registry ...@@ -353,10 +353,6 @@ configuring a different storage driver. By default the GitLab Container Registry
is configured to use the filesystem driver, which makes use of [storage path](#container-registry-storage-path) is configured to use the filesystem driver, which makes use of [storage path](#container-registry-storage-path)
configuration. configuration.
NOTE: **Note:** Enabling a storage driver other than `filesystem` would mean
that your Docker client needs to be able to access the storage backend directly.
In that case, you must use an address that resolves and is accessible outside GitLab server. The Docker client will continue to authenticate via GitLab but data transfer will be direct to and from the storage backend.
The different supported drivers are: The different supported drivers are:
| Driver | Description | | Driver | Description |
...@@ -425,6 +421,55 @@ storage: ...@@ -425,6 +421,55 @@ storage:
NOTE: **Note:** NOTE: **Note:**
`your-s3-bucket` should only be the name of a bucket that exists, and can't include subdirectories. `your-s3-bucket` should only be the name of a bucket that exists, and can't include subdirectories.
### Disable redirect for storage driver
By default, users accessing a registry configured with a remote backend are redirected to the default backend for the storage driver. For example, registries can be configured using the `s3` storage driver, which redirects requests to a remote S3 bucket to alleviate load on the GitLab server.
However, this behaviour is undesirable for registries used by internal hosts that usually can't access public servers. To disable redirects, set the `disable` flag to true as follows. This makes all traffic to always go through the Registry service. This results in improved security (less surface attack as the storage backend is not publicly accessible), but worse performance (all traffic is redirected via the service).
**Omnibus GitLab installations**
1. Edit `/etc/gitlab/gitlab.rb`:
```ruby
registry['storage'] = {
's3' => {
'accesskey' => 's3-access-key',
'secretkey' => 's3-secret-key-for-access-key',
'bucket' => 'your-s3-bucket',
'region' => 'your-s3-region',
'regionendpoint' => 'your-s3-regionendpoint'
},
'redirect' => {
'disable' => true
}
}
```
1. Save the file and [reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
**Installations from source**
1. Add the `redirect` flag to your registry configuration YML file:
```yml
storage:
s3:
accesskey: 'AKIAKIAKI'
secretkey: 'secret123'
bucket: 'gitlab-registry-bucket-AKIAKIAKI'
region: 'your-s3-region'
regionendpoint: 'your-s3-regionendpoint'
redirect:
disable: true
cache:
blobdescriptor: inmemory
delete:
enabled: true
```
1. Save the file and [restart GitLab](../restart_gitlab.md#installations-from-source) for the changes to take effect.
### Storage limitations ### Storage limitations
Currently, there is no storage limitation, which means a user can upload an Currently, there is no storage limitation, which means a user can upload an
... ...
......
...@@ -840,7 +840,7 @@ GitLab Rails console: ...@@ -840,7 +840,7 @@ GitLab Rails console:
```ruby ```ruby
projects_and_size = [] projects_and_size = []
# a list of projects you want to look at, can get these however # You need to specify the projects that you want to look through. You can get these in any manner.
projects = Project.last(100) projects = Project.last(100)
projects.each do |p| projects.each do |p|
... ...
......
...@@ -7355,6 +7355,11 @@ type Snippet implements Noteable { ...@@ -7355,6 +7355,11 @@ type Snippet implements Noteable {
""" """
fileName: String fileName: String
"""
HTTP URL to the snippet repository
"""
httpUrlToRepo: String
""" """
Id of the snippet Id of the snippet
""" """
...@@ -7395,6 +7400,11 @@ type Snippet implements Noteable { ...@@ -7395,6 +7400,11 @@ type Snippet implements Noteable {
""" """
rawUrl: String! rawUrl: String!
"""
SSH URL to the snippet repository
"""
sshUrlToRepo: String
""" """
Title of the snippet Title of the snippet
""" """
... ...
......
...@@ -22213,6 +22213,20 @@ ...@@ -22213,6 +22213,20 @@
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
}, },
{
"name": "httpUrlToRepo",
"description": "HTTP URL to the snippet repository",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{ {
"name": "id", "name": "id",
"description": "Id of the snippet", "description": "Id of the snippet",
...@@ -22320,6 +22334,20 @@ ...@@ -22320,6 +22334,20 @@
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
}, },
{
"name": "sshUrlToRepo",
"description": "SSH URL to the snippet repository",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{ {
"name": "title", "name": "title",
"description": "Title of the snippet", "description": "Title of the snippet",
... ...
......
...@@ -1161,9 +1161,11 @@ Represents a snippet entry ...@@ -1161,9 +1161,11 @@ Represents a snippet entry
| `description` | String | Description of the snippet | | `description` | String | Description of the snippet |
| `descriptionHtml` | String | The GitLab Flavored Markdown rendering of `description` | | `descriptionHtml` | String | The GitLab Flavored Markdown rendering of `description` |
| `fileName` | String | File Name of the snippet | | `fileName` | String | File Name of the snippet |
| `httpUrlToRepo` | String | HTTP URL to the snippet repository |
| `id` | ID! | Id of the snippet | | `id` | ID! | Id of the snippet |
| `project` | Project | The project the snippet is associated with | | `project` | Project | The project the snippet is associated with |
| `rawUrl` | String! | Raw URL of the snippet | | `rawUrl` | String! | Raw URL of the snippet |
| `sshUrlToRepo` | String | SSH URL to the snippet repository |
| `title` | String! | Title of the snippet | | `title` | String! | Title of the snippet |
| `updatedAt` | Time! | Timestamp this snippet was updated | | `updatedAt` | Time! | Timestamp this snippet was updated |
| `userPermissions` | SnippetPermissions! | Permissions for the current user on the resource | | `userPermissions` | SnippetPermissions! | Permissions for the current user on the resource |
... ...
......
...@@ -19,6 +19,7 @@ type: index ...@@ -19,6 +19,7 @@ type: index
- [Send email confirmation on sign-up](user_email_confirmation.md) - [Send email confirmation on sign-up](user_email_confirmation.md)
- [Security of running jobs](https://docs.gitlab.com/runner/security/) - [Security of running jobs](https://docs.gitlab.com/runner/security/)
- [Proxying images](asset_proxy.md) - [Proxying images](asset_proxy.md)
- [CI/CD environment variables](cicd_environment_variables.md)
## Securing your GitLab installation ## Securing your GitLab installation
... ...
......
---
type: reference
---
# CI/CD Environment Variables
Environment variables are applied to environments via the runner and can be set from the project's **Settings > CI/CD** page.
The values are encrypted using [aes-256-cbc](https://en.wikipedia.org/wiki/Advanced_Encryption_Standard) and stored in the database.
This data can only be decrypted with a valid [secrets file](../raketasks/backup_restore.md#when-the-secrets-file-is-lost).
...@@ -36,7 +36,7 @@ A user can be deactivated from the Admin Area. To do this: ...@@ -36,7 +36,7 @@ A user can be deactivated from the Admin Area. To do this:
Please note that for the deactivation option to be visible to an admin, the user: Please note that for the deactivation option to be visible to an admin, the user:
- Must be currently active. - Must be currently active.
- Should not have any activity in the last 180 days. - Must not have any signins or activity in the last 180 days.
Users can also be deactivated using the [GitLab API](../../api/users.md#deactivate-user). Users can also be deactivated using the [GitLab API](../../api/users.md#deactivate-user).
... ...
......
...@@ -10,6 +10,7 @@ module API ...@@ -10,6 +10,7 @@ module API
expose :web_url do |snippet| expose :web_url do |snippet|
Gitlab::UrlBuilder.build(snippet) Gitlab::UrlBuilder.build(snippet)
end end
expose :ssh_url_to_repo, :http_url_to_repo, if: ->(snippet) { snippet.versioned_enabled_for?(options[:current_user]) }
end end
end end
end end
...@@ -75,6 +75,7 @@ module Gitlab ...@@ -75,6 +75,7 @@ module Gitlab
def find_metric(metrics, metric) def find_metric(metrics, metric)
return unless metrics return unless metrics
return unless metric.identifier
metrics.find { |m| m[:id] == metric.identifier } metrics.find { |m| m[:id] == metric.identifier }
end end
... ...
......
...@@ -4126,6 +4126,9 @@ msgstr "" ...@@ -4126,6 +4126,9 @@ msgstr ""
msgid "ClusterIntegration|Base domain" msgid "ClusterIntegration|Base domain"
msgstr "" msgstr ""
   
msgid "ClusterIntegration|Blocking mode"
msgstr ""
msgid "ClusterIntegration|CA Certificate" msgid "ClusterIntegration|CA Certificate"
msgstr "" msgstr ""
   
...@@ -4324,6 +4327,9 @@ msgstr "" ...@@ -4324,6 +4327,9 @@ msgstr ""
msgid "ClusterIntegration|Gitlab Integration" msgid "ClusterIntegration|Gitlab Integration"
msgstr "" msgstr ""
   
msgid "ClusterIntegration|Global default"
msgstr ""
msgid "ClusterIntegration|Google Cloud Platform project" msgid "ClusterIntegration|Google Cloud Platform project"
msgstr "" msgstr ""
   
...@@ -4483,6 +4489,9 @@ msgstr "" ...@@ -4483,6 +4489,9 @@ msgstr ""
msgid "ClusterIntegration|Loading subnetworks" msgid "ClusterIntegration|Loading subnetworks"
msgstr "" msgstr ""
   
msgid "ClusterIntegration|Logging mode"
msgstr ""
msgid "ClusterIntegration|Machine type" msgid "ClusterIntegration|Machine type"
msgstr "" msgstr ""
   
...@@ -4711,6 +4720,9 @@ msgstr "" ...@@ -4711,6 +4720,9 @@ msgstr ""
msgid "ClusterIntegration|Set a prefix for your namespaces. If not set, defaults to your project path. If modified, existing environments will use their current namespaces until the cluster cache is cleared." msgid "ClusterIntegration|Set a prefix for your namespaces. If not set, defaults to your project path. If modified, existing environments will use their current namespaces until the cluster cache is cleared."
msgstr "" msgstr ""
   
msgid "ClusterIntegration|Set the global mode for the WAF in this cluster. This can be overridden at the environmental level."
msgstr ""
msgid "ClusterIntegration|Show" msgid "ClusterIntegration|Show"
msgstr "" msgstr ""
   
... ...
......
...@@ -38,6 +38,7 @@ ...@@ -38,6 +38,7 @@
"email": { "type": ["string", "null"] }, "email": { "type": ["string", "null"] },
"stack": { "type": ["string", "null"] }, "stack": { "type": ["string", "null"] },
"modsecurity_enabled": { "type": ["boolean", "null"] }, "modsecurity_enabled": { "type": ["boolean", "null"] },
"modsecurity_mode": {"type": ["integer", "0"]},
"update_available": { "type": ["boolean", "null"] }, "update_available": { "type": ["boolean", "null"] },
"can_uninstall": { "type": "boolean" }, "can_uninstall": { "type": "boolean" },
"available_domains": { "available_domains": {
... ...
......
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import IngressModsecuritySettings from '~/clusters/components/ingress_modsecurity_settings.vue'; import IngressModsecuritySettings from '~/clusters/components/ingress_modsecurity_settings.vue';
import { APPLICATION_STATUS, INGRESS } from '~/clusters/constants'; import { APPLICATION_STATUS, INGRESS } from '~/clusters/constants';
import { GlAlert, GlToggle } from '@gitlab/ui'; import { GlAlert, GlToggle, GlDropdown } from '@gitlab/ui';
import eventHub from '~/clusters/event_hub'; import eventHub from '~/clusters/event_hub';
const { UPDATING } = APPLICATION_STATUS; const { UPDATING } = APPLICATION_STATUS;
...@@ -13,6 +13,7 @@ describe('IngressModsecuritySettings', () => { ...@@ -13,6 +13,7 @@ describe('IngressModsecuritySettings', () => {
modsecurity_enabled: false, modsecurity_enabled: false,
status: 'installable', status: 'installable',
installed: false, installed: false,
modsecurity_mode: 'logging',
}; };
const createComponent = (props = defaultProps) => { const createComponent = (props = defaultProps) => {
...@@ -29,6 +30,7 @@ describe('IngressModsecuritySettings', () => { ...@@ -29,6 +30,7 @@ describe('IngressModsecuritySettings', () => {
const findSaveButton = () => wrapper.find('.btn-success'); const findSaveButton = () => wrapper.find('.btn-success');
const findCancelButton = () => wrapper.find('[variant="secondary"]'); const findCancelButton = () => wrapper.find('[variant="secondary"]');
const findModSecurityToggle = () => wrapper.find(GlToggle); const findModSecurityToggle = () => wrapper.find(GlToggle);
const findModSecurityDropdown = () => wrapper.find(GlDropdown);
describe('when ingress is installed', () => { describe('when ingress is installed', () => {
beforeEach(() => { beforeEach(() => {
...@@ -44,6 +46,33 @@ describe('IngressModsecuritySettings', () => { ...@@ -44,6 +46,33 @@ describe('IngressModsecuritySettings', () => {
describe('with toggle changed by the user', () => { describe('with toggle changed by the user', () => {
beforeEach(() => { beforeEach(() => {
findModSecurityToggle().vm.$emit('change'); findModSecurityToggle().vm.$emit('change');
wrapper.setProps({
ingress: {
...defaultProps,
installed: true,
status: 'installed',
modsecurity_enabled: true,
},
});
});
it('renders save and cancel buttons', () => {
expect(findSaveButton().exists()).toBe(true);
expect(findCancelButton().exists()).toBe(true);
});
describe('with dropdown changed by the user', () => {
beforeEach(() => {
findModSecurityDropdown().vm.$children[1].$emit('click');
wrapper.setProps({
ingress: {
...defaultProps,
installed: true,
status: 'installed',
modsecurity_enabled: true,
modsecurity_mode: 'blocking',
},
});
}); });
it('renders both save and cancel buttons', () => { it('renders both save and cancel buttons', () => {
...@@ -59,7 +88,8 @@ describe('IngressModsecuritySettings', () => { ...@@ -59,7 +88,8 @@ describe('IngressModsecuritySettings', () => {
it('triggers save event and pass current modsecurity value', () => { it('triggers save event and pass current modsecurity value', () => {
expect(eventHub.$emit).toHaveBeenCalledWith('updateApplication', { expect(eventHub.$emit).toHaveBeenCalledWith('updateApplication', {
id: INGRESS, id: INGRESS,
params: { modsecurity_enabled: false }, params: { modsecurity_enabled: true, modsecurity_mode: 'blocking' },
});
}); });
}); });
}); });
...@@ -70,7 +100,7 @@ describe('IngressModsecuritySettings', () => { ...@@ -70,7 +100,7 @@ describe('IngressModsecuritySettings', () => {
}); });
it('triggers reset event and hides both cancel and save changes button', () => { it('triggers reset event and hides both cancel and save changes button', () => {
expect(eventHub.$emit).toHaveBeenCalledWith('resetIngressModSecurityEnabled', INGRESS); expect(eventHub.$emit).toHaveBeenCalledWith('resetIngressModSecurityChanges', INGRESS);
expect(findSaveButton().exists()).toBe(false); expect(findSaveButton().exists()).toBe(false);
expect(findCancelButton().exists()).toBe(false); expect(findCancelButton().exists()).toBe(false);
}); });
... ...
......
...@@ -82,6 +82,7 @@ describe('Clusters Store', () => { ...@@ -82,6 +82,7 @@ describe('Clusters Store', () => {
externalHostname: null, externalHostname: null,
installed: false, installed: false,
isEditingModSecurityEnabled: false, isEditingModSecurityEnabled: false,
isEditingModSecurityMode: false,
installFailed: true, installFailed: true,
uninstallable: false, uninstallable: false,
updateFailed: false, updateFailed: false,
...@@ -89,6 +90,7 @@ describe('Clusters Store', () => { ...@@ -89,6 +90,7 @@ describe('Clusters Store', () => {
uninstallFailed: false, uninstallFailed: false,
validationError: null, validationError: null,
modsecurity_enabled: false, modsecurity_enabled: false,
modsecurity_mode: undefined,
}, },
runner: { runner: {
title: 'GitLab Runner', title: 'GitLab Runner',
... ...
......
...@@ -3,12 +3,15 @@ ...@@ -3,12 +3,15 @@
require 'spec_helper' require 'spec_helper'
describe GitlabSchema.types['Snippet'] do describe GitlabSchema.types['Snippet'] do
let_it_be(:user) { create(:user) }
it 'has the correct fields' do it 'has the correct fields' do
expected_fields = [:id, :title, :project, :author, expected_fields = [:id, :title, :project, :author,
:file_name, :description, :file_name, :description,
:visibility_level, :created_at, :updated_at, :visibility_level, :created_at, :updated_at,
:web_url, :raw_url, :notes, :discussions, :web_url, :raw_url, :ssh_url_to_repo, :http_url_to_repo,
:user_permissions, :description_html, :blob] :notes, :discussions, :user_permissions,
:description_html, :blob]
expect(described_class).to have_graphql_fields(*expected_fields) expect(described_class).to have_graphql_fields(*expected_fields)
end end
...@@ -17,8 +20,55 @@ describe GitlabSchema.types['Snippet'] do ...@@ -17,8 +20,55 @@ describe GitlabSchema.types['Snippet'] do
it { expect(described_class).to require_graphql_authorizations(:read_snippet) } it { expect(described_class).to require_graphql_authorizations(:read_snippet) }
end end
shared_examples 'response without repository URLs' do
it 'does not respond with repository URLs' do
expect(response['sshUrlToRepo']).to be_nil
expect(response['httpUrlToRepo']).to be_nil
end
end
describe 'Repository URLs' do
let(:query) do
%(
{
snippets {
nodes {
sshUrlToRepo
httpUrlToRepo
}
}
}
)
end
let(:response) { subject.dig('data', 'snippets', 'nodes')[0] }
subject { GitlabSchema.execute(query, context: { current_user: user }).as_json }
context 'when snippet has repository' do
let!(:snippet) { create(:personal_snippet, :repository, :public, author: user) }
it 'responds with repository URLs' do
expect(response['sshUrlToRepo']).to eq(snippet.ssh_url_to_repo)
expect(response['httpUrlToRepo']).to eq(snippet.http_url_to_repo)
end
context 'when version_snippets feature is disabled' do
before do
stub_feature_flags(version_snippets: false)
end
it_behaves_like 'response without repository URLs'
end
end
context 'when snippet does not have a repository' do
let!(:snippet) { create(:personal_snippet, :public, author: user) }
it_behaves_like 'response without repository URLs'
end
end
describe '#blob' do describe '#blob' do
let_it_be(:user) { create(:user) }
let(:query_blob) { subject.dig('data', 'snippets', 'edges')[0]['node']['blob'] } let(:query_blob) { subject.dig('data', 'snippets', 'edges')[0]['node']['blob'] }
let(:query) do let(:query) do
%( %(
... ...
......
...@@ -74,6 +74,16 @@ describe Gitlab::Metrics::Dashboard::Processor do ...@@ -74,6 +74,16 @@ describe Gitlab::Metrics::Dashboard::Processor do
expect(actual_metrics_order).to eq expected_metrics_order expect(actual_metrics_order).to eq expected_metrics_order
end end
context 'when the project has multiple metrics in the same group' do
let!(:project_response_metric) { create(:prometheus_metric, project: project, group: :response) }
let!(:project_response_metric_2) { create(:prometheus_metric, project: project, group: :response) }
it 'includes multiple metrics' do
expect(all_metrics).to include get_metric_details(project_response_metric)
expect(all_metrics).to include get_metric_details(project_response_metric_2)
end
end
context 'when the dashboard should not include project metrics' do context 'when the dashboard should not include project metrics' do
let(:sequence) do let(:sequence) do
[ [
... ...
......
...@@ -140,13 +140,10 @@ describe Clusters::Applications::Ingress do ...@@ -140,13 +140,10 @@ describe Clusters::Applications::Ingress do
end end
describe '#values' do describe '#values' do
let(:project) { build(:project) } subject { ingress }
let(:cluster) { build(:cluster, projects: [project]) }
context 'when modsecurity_enabled is enabled' do context 'when modsecurity_enabled is enabled' do
before do before do
allow(subject).to receive(:cluster).and_return(cluster)
allow(subject).to receive(:modsecurity_enabled).and_return(true) allow(subject).to receive(:modsecurity_enabled).and_return(true)
end end
...@@ -154,8 +151,24 @@ describe Clusters::Applications::Ingress do ...@@ -154,8 +151,24 @@ describe Clusters::Applications::Ingress do
expect(subject.values).to include("enable-modsecurity: 'true'") expect(subject.values).to include("enable-modsecurity: 'true'")
end end
it 'includes modsecurity core ruleset enablement' do it 'includes modsecurity core ruleset enablement set to false' do
expect(subject.values).to include("enable-owasp-modsecurity-crs: 'true'") expect(subject.values).to include("enable-owasp-modsecurity-crs: 'false'")
end
it 'includes modsecurity snippet with information related to security rules' do
expect(subject.values).to include("SecRuleEngine DetectionOnly")
expect(subject.values).to include("Include #{described_class::MODSECURITY_OWASP_RULES_FILE}")
end
context 'when modsecurity_mode is set to :blocking' do
before do
subject.blocking!
end
it 'includes modsecurity snippet with information related to security rules' do
expect(subject.values).to include("SecRuleEngine On")
expect(subject.values).to include("Include #{described_class::MODSECURITY_OWASP_RULES_FILE}")
end
end end
it 'includes modsecurity.conf content' do it 'includes modsecurity.conf content' do
...@@ -176,7 +189,6 @@ describe Clusters::Applications::Ingress do ...@@ -176,7 +189,6 @@ describe Clusters::Applications::Ingress do
context 'when modsecurity_enabled is disabled' do context 'when modsecurity_enabled is disabled' do
before do before do
allow(subject).to receive(:cluster).and_return(cluster)
allow(subject).to receive(:modsecurity_enabled).and_return(false) allow(subject).to receive(:modsecurity_enabled).and_return(false)
end end
... ...
......
...@@ -713,4 +713,32 @@ describe Snippet do ...@@ -713,4 +713,32 @@ describe Snippet do
it { is_expected.to eq(Gitlab.config.gitlab_shell.ssh_path_prefix + "#{snippet.project.full_path}/snippets/#{snippet.id}.git") } it { is_expected.to eq(Gitlab.config.gitlab_shell.ssh_path_prefix + "#{snippet.project.full_path}/snippets/#{snippet.id}.git") }
end end
end end
describe '#versioned_enabled_for?' do
let_it_be(:user) { create(:user) }
subject { snippet.versioned_enabled_for?(user) }
context 'with repository and version_snippets enabled' do
let!(:snippet) { create(:personal_snippet, :repository, author: user) }
it { is_expected.to be_truthy }
end
context 'without repository' do
let!(:snippet) { create(:personal_snippet, author: user) }
it { is_expected.to be_falsy }
end
context 'without version_snippets feature disabled' do
let!(:snippet) { create(:personal_snippet, :repository, author: user) }
before do
stub_feature_flags(version_snippets: false)
end
it { is_expected.to be_falsy }
end
end
end end
...@@ -85,7 +85,7 @@ describe API::ProjectSnippets do ...@@ -85,7 +85,7 @@ describe API::ProjectSnippets do
describe 'GET /projects/:project_id/snippets/:id' do describe 'GET /projects/:project_id/snippets/:id' do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:snippet) { create(:project_snippet, :public, project: project) } let(:snippet) { create(:project_snippet, :public, :repository, project: project) }
it 'returns snippet json' do it 'returns snippet json' do
get api("/projects/#{project.id}/snippets/#{snippet.id}", user) get api("/projects/#{project.id}/snippets/#{snippet.id}", user)
...@@ -95,6 +95,18 @@ describe API::ProjectSnippets do ...@@ -95,6 +95,18 @@ describe API::ProjectSnippets do
expect(json_response['title']).to eq(snippet.title) expect(json_response['title']).to eq(snippet.title)
expect(json_response['description']).to eq(snippet.description) expect(json_response['description']).to eq(snippet.description)
expect(json_response['file_name']).to eq(snippet.file_name) expect(json_response['file_name']).to eq(snippet.file_name)
expect(json_response['ssh_url_to_repo']).to eq(snippet.ssh_url_to_repo)
expect(json_response['http_url_to_repo']).to eq(snippet.http_url_to_repo)
end
context 'when feature flag :version_snippets is disabled' do
before do
stub_feature_flags(version_snippets: false)
get api("/projects/#{project.id}/snippets/#{snippet.id}", user)
end
it_behaves_like 'snippet response without repository URLs'
end end
it 'returns 404 for invalid snippet id' do it 'returns 404 for invalid snippet id' do
... ...
......
...@@ -139,8 +139,8 @@ describe API::Snippets do ...@@ -139,8 +139,8 @@ describe API::Snippets do
describe 'GET /snippets/:id' do describe 'GET /snippets/:id' do
let_it_be(:admin) { create(:user, :admin) } let_it_be(:admin) { create(:user, :admin) }
let_it_be(:author) { create(:user) } let_it_be(:author) { create(:user) }
let_it_be(:private_snippet) { create(:personal_snippet, :private, author: author) } let_it_be(:private_snippet) { create(:personal_snippet, :repository, :private, author: author) }
let_it_be(:internal_snippet) { create(:personal_snippet, :internal, author: author) } let_it_be(:internal_snippet) { create(:personal_snippet, :repository, :internal, author: author) }
it 'requires authentication' do it 'requires authentication' do
get api("/snippets/#{private_snippet.id}", nil) get api("/snippets/#{private_snippet.id}", nil)
...@@ -157,6 +157,18 @@ describe API::Snippets do ...@@ -157,6 +157,18 @@ describe API::Snippets do
expect(json_response['description']).to eq(private_snippet.description) expect(json_response['description']).to eq(private_snippet.description)
expect(json_response['file_name']).to eq(private_snippet.file_name) expect(json_response['file_name']).to eq(private_snippet.file_name)
expect(json_response['visibility']).to eq(private_snippet.visibility) expect(json_response['visibility']).to eq(private_snippet.visibility)
expect(json_response['ssh_url_to_repo']).to eq(private_snippet.ssh_url_to_repo)
expect(json_response['http_url_to_repo']).to eq(private_snippet.http_url_to_repo)
end
context 'when feature flag :version_snippets is disabled' do
before do
stub_feature_flags(version_snippets: false)
get api("/snippets/#{private_snippet.id}", author)
end
it_behaves_like 'snippet response without repository URLs'
end end
it 'shows private snippets to an admin' do it 'shows private snippets to an admin' do
... ...
......