Commit adb0044a authored by Philip Cunningham's avatar Philip Cunningham Committed by Luke Duncalfe

Return additional site profile config from GraphQL

- Extend auth type
- Extend profile type
- Rework specs
- Add new specs
- Add model methods

https://gitlab.com/gitlab-org/gitlab/-/issues/277353
https://gitlab.com/gitlab-org/gitlab/-/issues/323449
parent fe41d72c
......@@ -2,6 +2,8 @@
module Resolvers
class DastSiteProfileResolver < BaseResolver
include LooksAhead
alias_method :project, :object
type Types::DastSiteProfileType.connection_type, null: true
......@@ -12,7 +14,20 @@ module Resolvers
description: "ID of the site profile."
end
def resolve(**args)
def resolve_with_lookahead(**args)
apply_lookahead(find_dast_site_profiles(args))
end
private
def preloads
{
request_headers: [:secret_variables],
auth: [:secret_variables]
}
end
def find_dast_site_profiles(args)
if args[:id]
# TODO: remove this coercion when the compatibility layer is removed
# See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883
......
......@@ -3,6 +3,8 @@
module Types
module Dast
class SiteProfileAuthType < BaseObject
REDACTED_PASSWORD = '••••••••'
graphql_name 'DastSiteProfileAuth'
description 'Input type for DastSiteProfile authentication'
......@@ -39,7 +41,9 @@ module Types
description: 'Redacted password to authenticate with on the target website.'
def password
nil
return unless object.secret_variables.any? { |variable| variable.key == ::Dast::SiteProfileSecretVariable::PASSWORD }
REDACTED_PASSWORD
end
end
end
......
......@@ -2,6 +2,8 @@
module Types
class DastSiteProfileType < BaseObject
REDACTED_REQUEST_HEADERS = '[Redacted]'
graphql_name 'DastSiteProfile'
description 'Represents a DAST Site Profile'
......@@ -68,7 +70,10 @@ module Types
end
def request_headers
nil
return unless Feature.enabled?(:security_dast_site_profiles_additional_fields, object.project, default_enabled: :yaml)
return unless object.secret_variables.any? { |variable| variable.key == ::Dast::SiteProfileSecretVariable::REQUEST_HEADERS }
REDACTED_REQUEST_HEADERS
end
def normalized_target_url
......
......@@ -2,6 +2,9 @@
module Dast
class SiteProfileSecretVariable < ApplicationRecord
REQUEST_HEADERS = 'DAST_REQUEST_HEADERS_BASE64'
PASSWORD = 'DAST_PASSWORD_BASE64'
include Ci::HasVariable
include Ci::Maskable
......
......@@ -2,9 +2,66 @@
require 'spec_helper'
RSpec.describe Types::Dast::SiteProfileAuthType do
RSpec.describe GitlabSchema.types['DastSiteProfileAuth'] do
include GraphqlHelpers
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user, developer_projects: [project]) }
let_it_be(:object, reload: true) { create(:dast_site_profile, project: project) }
let_it_be(:fields) { %i[enabled url usernameField passwordField username password] }
before do
stub_licensed_features(security_on_demand_scans: true)
end
specify { expect(described_class.graphql_name).to eq('DastSiteProfileAuth') }
specify { expect(described_class).to require_graphql_authorizations(:read_on_demand_scans) }
it { expect(described_class).to have_graphql_fields(%w[enabled url usernameField passwordField username password]) }
it { expect(described_class).to have_graphql_fields(fields) }
describe 'enabled field' do
it 'is auth_enabled' do
expect(resolve_field(:enabled, object, current_user: user)).to eq(object.auth_enabled)
end
end
describe 'url field' do
it 'is auth_url' do
expect(resolve_field(:url, object, current_user: user)).to eq(object.auth_url)
end
end
describe 'usernameField field' do
it 'is auth_username_field' do
expect(resolve_field(:username_field, object, current_user: user)).to eq(object.auth_username_field)
end
end
describe 'passwordField field' do
it 'is auth_password_field' do
expect(resolve_field(:password_field, object, current_user: user)).to eq(object.auth_password_field)
end
end
describe 'username field' do
it 'is auth_username' do
expect(resolve_field(:username, object, current_user: user)).to eq(object.auth_username)
end
end
describe 'password field' do
context 'when there is no associated secret variable' do
it 'is nil' do
expect(resolve_field(:password, object, current_user: user)).to be_nil
end
end
context 'when there an associated secret variable' do
it 'is redacted' do
create(:dast_site_profile_secret_variable, dast_site_profile: object, key: Dast::SiteProfileSecretVariable::PASSWORD)
expect(resolve_field(:password, object, current_user: user)).to eq('••••••••')
end
end
end
end
......@@ -5,23 +5,11 @@ require 'spec_helper'
RSpec.describe GitlabSchema.types['DastSiteProfile'] do
include GraphqlHelpers
let_it_be(:dast_site_profile) { create(:dast_site_profile) }
let_it_be(:project) { dast_site_profile.project }
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user, developer_projects: [project]) }
let_it_be(:object, reload: true) { create(:dast_site_profile, project: project) }
let_it_be(:fields) { %i[id profileName targetUrl editPath excludedUrls requestHeaders validationStatus userPermissions normalizedTargetUrl auth referencedInSecurityPolicies] }
subject do
GitlabSchema.execute(
query,
context: {
current_user: user
},
variables: {
fullPath: project.full_path
}
).as_json
end
before do
stub_licensed_features(security_on_demand_scans: true)
end
......@@ -31,51 +19,47 @@ RSpec.describe GitlabSchema.types['DastSiteProfile'] do
specify { expect(described_class).to expose_permissions_using(Types::PermissionTypes::DastSiteProfile) }
it { expect(described_class).to have_graphql_fields(fields) }
describe 'dast_site_profiles' do
before do
project.add_developer(user)
end
let(:query) do
%(
query project($fullPath: ID!) {
project(fullPath: $fullPath) {
dastSiteProfiles(first: 1) {
nodes { #{all_graphql_fields_for('DastSiteProfile')} }
}
}
}
)
end
let(:first_dast_site_profile) do
subject.dig('data', 'project', 'dastSiteProfiles', 'nodes', 0)
end
it { expect(described_class).to have_graphql_field(:referenced_in_security_policies, calls_gitaly?: true, complexity: 10) }
describe 'id field' do
it 'is a global id' do
expect(first_dast_site_profile['id']).to eq(dast_site_profile.to_global_id.to_s)
it 'is the global id' do
expect(resolve_field(:id, object, current_user: user)).to eq(object.to_global_id)
end
end
describe 'profile_name field' do
describe 'profileName field' do
it 'is the name' do
expect(first_dast_site_profile['profileName']).to eq(dast_site_profile.name)
expect(resolve_field(:profile_name, object, current_user: user)).to eq(object.name)
end
end
describe 'target_url field' do
describe 'targetUrl field' do
it 'is the url of the associated dast_site' do
expect(first_dast_site_profile['targetUrl']).to eq(dast_site_profile.dast_site.url)
expect(resolve_field(:target_url, object, current_user: user)).to eq(object.dast_site.url)
end
end
describe 'edit_path field' do
describe 'editPath field' do
it 'is the relative path to edit the dast_site_profile' do
path = "/#{project.full_path}/-/security/configuration/dast_scans/dast_site_profiles/#{dast_site_profile.id}/edit"
path = "/#{project.full_path}/-/security/configuration/dast_scans/dast_site_profiles/#{object.id}/edit"
expect(first_dast_site_profile['editPath']).to eq(path)
expect(resolve_field(:edit_path, object, current_user: user)).to eq(path)
end
end
describe 'auth field' do
context 'when the feature flag is disabled' do
it 'is nil' do
stub_feature_flags(security_dast_site_profiles_additional_fields: false)
expect(resolve_field(:auth, object, current_user: user)).to be_nil
end
end
context 'when the feature flag is enabled' do
it 'is the dast_site_profile' do
expect(resolve_field(:auth, object, current_user: user)).to eq(object)
end
end
end
......@@ -84,64 +68,60 @@ RSpec.describe GitlabSchema.types['DastSiteProfile'] do
it 'is nil' do
stub_feature_flags(security_dast_site_profiles_additional_fields: false)
expect(first_dast_site_profile['excludedUrls']).to eq(nil)
expect(resolve_field(:excluded_urls, object, current_user: user)).to be_nil
end
end
context 'when the feature flag is enabled' do
it 'is the excluded urls' do
expect(first_dast_site_profile['excludedUrls']).to eq(dast_site_profile.excluded_urls)
expect(resolve_field(:excluded_urls, object, current_user: user)).to eq(object.excluded_urls)
end
end
end
describe 'auth field' do
describe 'requestHeaders field' do
context 'when the feature flag is disabled' do
it 'is nil' do
stub_feature_flags(security_dast_site_profiles_additional_fields: false)
expect(first_dast_site_profile['auth']).to eq(nil)
expect(resolve_field(:request_headers, object, current_user: user)).to be_nil
end
end
context 'when the feature flag is enabled' do
it 'includes the correct values' do
auth = first_dast_site_profile['auth']
context 'when there is no associated secret variable' do
it 'is nil' do
expect(resolve_field(:request_headers, object, current_user: user)).to be_nil
end
end
expect(auth).to include(
'enabled' => false,
'url' => dast_site_profile.auth_url,
'usernameField' => dast_site_profile.auth_username_field,
'passwordField' => dast_site_profile.auth_password_field,
'username' => dast_site_profile.auth_username,
'password' => nil
)
context 'when there an associated secret variable' do
it 'is redacted' do
create(:dast_site_profile_secret_variable, dast_site_profile: object, key: Dast::SiteProfileSecretVariable::REQUEST_HEADERS)
expect(resolve_field(:request_headers, object, current_user: user)).to eq('[Redacted]')
end
end
end
end
describe 'validation_status field' do
it 'is the validation status' do
expect(first_dast_site_profile['validationStatus']).to eq('NONE')
expect(resolve_field(:validation_status, object, current_user: user)).to eq('none')
end
end
describe 'normalized_target_url field' do
describe 'normalizedTargetUrl field' do
it 'is the normalized url of the associated dast_site' do
normalized_url = DastSiteValidation.get_normalized_url_base(dast_site_profile.dast_site.url)
normalized_url = DastSiteValidation.get_normalized_url_base(object.dast_site.url)
expect(first_dast_site_profile['normalizedTargetUrl']).to eq(normalized_url)
expect(resolve_field(:normalized_target_url, object, current_user: user)).to eq(normalized_url)
end
end
context 'when there are no dast_site_profiles' do
let(:project) { create(:project) }
it 'has no nodes' do
nodes = subject.dig('data', 'project', 'dastSiteProfiles', 'nodes')
expect(nodes).to be_empty
end
describe 'referencedInSecurityPolicies field' do
it 'is the policies' do
expect(resolve_field(:referenced_in_security_policies, object, current_user: user)).to eq(object.referenced_in_security_policies)
end
end
end
......@@ -122,6 +122,7 @@ RSpec.describe DastSiteProfile, type: :model do
end
end
describe 'instance methods' do
describe '#destroy!' do
context 'when the associated dast_site has no dast_site_profiles' do
it 'is also destroyed' do
......@@ -184,4 +185,5 @@ RSpec.describe DastSiteProfile, type: :model do
end
end
end
end
end
......@@ -70,7 +70,7 @@ RSpec.describe 'Query.project(fullPath).dastSiteProfiles' do
expect(first_dast_site_profile_response['id']).to eq(dast_site_profile.to_global_id.to_s)
end
it 'eager loads the dast site and dast site validation' do
it 'avoids N+1 queries' do
control = ActiveRecord::QueryRecorder.new do
post_graphql(
query,
......
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