Commit 74e3fc25 authored by Mark Chao's avatar Mark Chao Committed by Stan Hu

Admin: listing matching card details of an user

in order to find repeated use of forgery cards on multiple accounts.

Changelog: added
EE: true
parent 4c1f7449
...@@ -12,5 +12,13 @@ module Users ...@@ -12,5 +12,13 @@ module Users
validates :last_digits, allow_nil: true, numericality: { validates :last_digits, allow_nil: true, numericality: {
greater_than_or_equal_to: 0, less_than_or_equal_to: 9999 greater_than_or_equal_to: 0, less_than_or_equal_to: 9999
} }
def similar_records
self.class.where(
expiration_date: expiration_date,
last_digits: last_digits,
holder_name: holder_name
).order(credit_card_validated_at: :desc).includes(:user)
end
end end
end end
...@@ -61,7 +61,6 @@ ...@@ -61,7 +61,6 @@
= _('Disabled') = _('Disabled')
= render_if_exists 'admin/namespace_plan_info', namespace: @user.namespace = render_if_exists 'admin/namespace_plan_info', namespace: @user.namespace
= render_if_exists 'admin/users/credit_card_info', user: @user
%li %li
%span.light= _('External User:') %span.light= _('External User:')
...@@ -139,6 +138,8 @@ ...@@ -139,6 +138,8 @@
= render_if_exists 'namespaces/shared_runner_status', namespace: @user.namespace = render_if_exists 'namespaces/shared_runner_status', namespace: @user.namespace
= render_if_exists 'admin/users/credit_card_info', user: @user, link_to_match_page: true
= render 'shared/custom_attributes', custom_attributes: @user.custom_attributes = render 'shared/custom_attributes', custom_attributes: @user.custom_attributes
-# Rendered on desktop only so order of cards can be different on desktop vs mobile -# Rendered on desktop only so order of cards can be different on desktop vs mobile
......
...@@ -17,6 +17,18 @@ module EE ...@@ -17,6 +17,18 @@ module EE
end end
end end
def card_match
return render_404 unless ::Gitlab.com?
credit_card_validation = user.credit_card_validation
if credit_card_validation&.holder_name
@similar_credit_card_validations = credit_card_validation.similar_records.page(params[:page]).per(100)
else
redirect_to [:admin, @user], notice: _('No credit card data for matching')
end
end
private private
override :users_with_included_associations override :users_with_included_associations
......
- return unless Gitlab.com? - return if !Gitlab.com?
%li#credit-card-status - credit_card_validation = user.credit_card_validation
- if user.credit_card_validated_at .card
%span.light= _('Credit card validated at:') .card-header
%strong = _('Credit card:')
= user.credit_card_validated_at.to_s(:medium) - if local_assigns[:link_to_match_page] && credit_card_validation&.holder_name
- else .gl-float-right.small
%span.light= _('Credit card validated:') = link_to card_match_admin_user_path(@user) do
%strong = _('other card matches')
= _('No') %ul.content-list
- if credit_card_validation.nil?
%li#credit-card-status
%span.light= _('Validated:')
%strong= _('No')
- else
%li
- if credit_card_validation.holder_name
%span.light= _('Holder name:')
%strong
= credit_card_validation.holder_name
%li#credit-card-status
%span.light= _('Validated at:')
%strong
= credit_card_validation.credit_card_validated_at.to_s(:medium)
%li
- if credit_card_validation.last_digits
%span.light= _('Card number:')
%strong
= credit_card_validation.last_digits.to_s.rjust(4, '0')
%li
- if credit_card_validation.expiration_date
%span.light= _('Expiration date:')
%strong
= credit_card_validation.expiration_date
- add_to_breadcrumbs _('Users'), admin_users_path
- add_to_breadcrumbs @user.name, admin_user_path(@user)
- breadcrumb_title _('All users with matching cards')
- page_title @user.name, _('All users with matching cards')
- stripe_time_zone = 'America/Los_Angeles'
- stripe_time_format = '%b %-d, %-I:%M%P %Z'
.gl-display-flex.gl-flex-wrap.gl-justify-content-space-between.gl-align-items-center.gl-py-3.gl-mb-5.gl-border-b-solid.gl-border-gray-100.gl-border-b-1
.gl-my-3
%h3.page-title.gl-m-0
= @user.name
\-
= _("All users with matching cards") % { name: @user.name }
.row
- if @similar_credit_card_validations.present?
.col-md-12
%table.table
%thead
%th= _('ID')
%th= _('User')
%th.gl-text-right= _('Validated at')
%th.gl-text-right= _('User created at')
%th.gl-text-right= _('Current sign-in ip')
- @similar_credit_card_validations.each do |credit_card_validation|
- user = credit_card_validation.user
- validated_at = user.credit_card_validated_at
%tr
%td
= user.id
%td
= link_to(user.username, admin_user_path(user))
- if user == @user
= _('(target)')
%td.gl-text-right
= validated_at.to_s(:medium)
\/
= validated_at.in_time_zone(stripe_time_zone).strftime(stripe_time_format)
%td.gl-text-right= user.created_at.to_s(:medium)
%td.gl-text-right
- if user.current_sign_in_ip
= user.current_sign_in_ip
= link_to sprite_icon('earth'), "https://api.hostip.info/country.php?ip=#{user.current_sign_in_ip}", target: '_blank', rel: 'noreferrer'
* All times are in UTC unless specified
= paginate @similar_credit_card_validations, theme: 'gitlab'
.gl-float-right
= render 'admin/users/credit_card_info', user: @user
...@@ -4,6 +4,7 @@ namespace :admin do ...@@ -4,6 +4,7 @@ namespace :admin do
resources :users, only: [], constraints: { id: %r{[a-zA-Z./0-9_\-]+} } do resources :users, only: [], constraints: { id: %r{[a-zA-Z./0-9_\-]+} } do
member do member do
post :reset_runners_minutes post :reset_runners_minutes
get :card_match
end end
end end
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Admin::UsersController, :enable_admin_mode do
include AdminModeHelper
let_it_be(:admin) { create(:admin) }
let_it_be(:user) { create(:user) }
describe 'GET card_match' do
before do
sign_in(admin)
end
context 'when not SaaS' do
it 'responds with 404' do
send_request
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'when SaaS', :saas do
context 'when user has no credit card validation' do
it 'redirects back to #show' do
send_request
expect(response).to redirect_to(admin_user_path(user))
end
end
context 'when user has credit card validation' do
let!(:credit_card_validation) { create(:credit_card_validation, user: user) }
let(:card_details) { credit_card_validation.attributes.slice(:expiration_date, :last_digits, :holder_name) }
let!(:match) { create(:credit_card_validation, card_details) }
it 'displays its own and matching card details' do
send_request
expect(response).to have_gitlab_http_status(:ok)
expect(response.body).to include(credit_card_validation.holder_name)
expect(response.body).to include(match.holder_name)
end
end
end
def send_request
get card_match_admin_user_path(user)
end
end
end
...@@ -27,7 +27,7 @@ RSpec.describe 'admin/users/show.html.haml' do ...@@ -27,7 +27,7 @@ RSpec.describe 'admin/users/show.html.haml' do
it 'includes credit card validation status' do it 'includes credit card validation status' do
render render
expect(status).to match /Credit card validated:\s+No/ expect(status).to match /Validated:\s+No/
end end
context 'when user is validated' do context 'when user is validated' do
...@@ -36,7 +36,7 @@ RSpec.describe 'admin/users/show.html.haml' do ...@@ -36,7 +36,7 @@ RSpec.describe 'admin/users/show.html.haml' do
it 'includes credit card validation status' do it 'includes credit card validation status' do
render render
expect(status).to include 'Credit card validated at:' expect(status).to include 'Validated at:'
end end
end end
end end
......
...@@ -1141,6 +1141,9 @@ msgstr "" ...@@ -1141,6 +1141,9 @@ msgstr ""
msgid "(revoked)" msgid "(revoked)"
msgstr "" msgstr ""
msgid "(target)"
msgstr ""
msgid "(we need your current password to confirm your changes)" msgid "(we need your current password to confirm your changes)"
msgstr "" msgstr ""
...@@ -3341,6 +3344,9 @@ msgstr "" ...@@ -3341,6 +3344,9 @@ msgstr ""
msgid "All users must have a name." msgid "All users must have a name."
msgstr "" msgstr ""
msgid "All users with matching cards"
msgstr ""
msgid "Allow \"%{group_name}\" to sign you in" msgid "Allow \"%{group_name}\" to sign you in"
msgstr "" msgstr ""
...@@ -6309,6 +6315,9 @@ msgstr "" ...@@ -6309,6 +6315,9 @@ msgstr ""
msgid "Capacity threshold" msgid "Capacity threshold"
msgstr "" msgstr ""
msgid "Card number:"
msgstr ""
msgid "CascadingSettings|Enforce for all subgroups" msgid "CascadingSettings|Enforce for all subgroups"
msgstr "" msgstr ""
...@@ -9924,10 +9933,7 @@ msgstr "" ...@@ -9924,10 +9933,7 @@ msgstr ""
msgid "CredentialsInventory|SSH Keys" msgid "CredentialsInventory|SSH Keys"
msgstr "" msgstr ""
msgid "Credit card validated at:" msgid "Credit card:"
msgstr ""
msgid "Credit card validated:"
msgstr "" msgstr ""
msgid "Critical vulnerabilities present" msgid "Critical vulnerabilities present"
...@@ -9984,6 +9990,9 @@ msgstr "" ...@@ -9984,6 +9990,9 @@ msgstr ""
msgid "Current sign-in at:" msgid "Current sign-in at:"
msgstr "" msgstr ""
msgid "Current sign-in ip"
msgstr ""
msgid "Current vulnerabilities count" msgid "Current vulnerabilities count"
msgstr "" msgstr ""
...@@ -13806,6 +13815,9 @@ msgstr "" ...@@ -13806,6 +13815,9 @@ msgstr ""
msgid "Expiration date (optional)" msgid "Expiration date (optional)"
msgstr "" msgstr ""
msgid "Expiration date:"
msgstr ""
msgid "Expired" msgid "Expired"
msgstr "" msgstr ""
...@@ -16853,6 +16865,9 @@ msgstr "" ...@@ -16853,6 +16865,9 @@ msgstr ""
msgid "History of authentications" msgid "History of authentications"
msgstr "" msgstr ""
msgid "Holder name:"
msgstr ""
msgid "Home page URL" msgid "Home page URL"
msgstr "" msgstr ""
...@@ -22939,6 +22954,9 @@ msgstr "" ...@@ -22939,6 +22954,9 @@ msgstr ""
msgid "No contributions were found" msgid "No contributions were found"
msgstr "" msgstr ""
msgid "No credit card data for matching"
msgstr ""
msgid "No credit card required." msgid "No credit card required."
msgstr "" msgstr ""
...@@ -36953,6 +36971,9 @@ msgstr "" ...@@ -36953,6 +36971,9 @@ msgstr ""
msgid "User and IP rate limits" msgid "User and IP rate limits"
msgstr "" msgstr ""
msgid "User created at"
msgstr ""
msgid "User does not have a pending request" msgid "User does not have a pending request"
msgstr "" msgstr ""
...@@ -37313,6 +37334,15 @@ msgstr "" ...@@ -37313,6 +37334,15 @@ msgstr ""
msgid "Validate your GitLab CI configuration file" msgid "Validate your GitLab CI configuration file"
msgstr "" msgstr ""
msgid "Validated at"
msgstr ""
msgid "Validated at:"
msgstr ""
msgid "Validated:"
msgstr ""
msgid "Validations failed." msgid "Validations failed."
msgstr "" msgstr ""
...@@ -40851,6 +40881,9 @@ msgstr "" ...@@ -40851,6 +40881,9 @@ msgstr ""
msgid "originating vulnerability" msgid "originating vulnerability"
msgstr "" msgstr ""
msgid "other card matches"
msgstr ""
msgid "out of %d total test" msgid "out of %d total test"
msgid_plural "out of %d total tests" msgid_plural "out of %d total tests"
msgstr[0] "" msgstr[0] ""
......
...@@ -7,4 +7,19 @@ RSpec.describe Users::CreditCardValidation do ...@@ -7,4 +7,19 @@ RSpec.describe Users::CreditCardValidation do
it { is_expected.to validate_length_of(:holder_name).is_at_most(26) } it { is_expected.to validate_length_of(:holder_name).is_at_most(26) }
it { is_expected.to validate_numericality_of(:last_digits).is_less_than_or_equal_to(9999) } it { is_expected.to validate_numericality_of(:last_digits).is_less_than_or_equal_to(9999) }
describe '.similar_records' do
let(:card_details) { subject.attributes.slice(:expiration_date, :last_digits, :holder_name) }
subject(:credit_card_validation) { create(:credit_card_validation) }
let!(:match1) { create(:credit_card_validation, card_details) }
let!(:other1) { create(:credit_card_validation, card_details.merge(last_digits: 9)) }
let!(:match2) { create(:credit_card_validation, card_details) }
let!(:other2) { create(:credit_card_validation, card_details.merge(holder_name: 'foo bar')) }
it 'returns records with matching credit card, ordered by credit_card_validated_at' do
expect(subject.similar_records).to eq([match2, match1, subject])
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