Commit e712bbe8 authored by Roger Meier's avatar Roger Meier Committed by Nick Thomas

add sha256 fingerprint to SSH key view and api to query user by ssh fingerprint

parent 066e01de
# frozen_string_literal: true
class KeysFinder
InvalidFingerprint = Class.new(StandardError)
GitLabAccessDeniedError = Class.new(StandardError)
FINGERPRINT_ATTRIBUTES = {
'sha256' => 'fingerprint_sha256',
'md5' => 'fingerprint'
}.freeze
def initialize(current_user, params)
@current_user = current_user
@params = params
end
def execute
raise GitLabAccessDeniedError unless current_user.admin?
raise InvalidFingerprint unless valid_fingerprint_param?
Key.where(fingerprint_query).first # rubocop: disable CodeReuse/ActiveRecord
end
private
attr_reader :current_user, :params
def valid_fingerprint_param?
if fingerprint_type == "sha256"
Base64.decode64(fingerprint).length == 32
else
fingerprint =~ /^(\h{2}:){15}\h{2}/
end
end
def fingerprint_query
fingerprint_attribute = FINGERPRINT_ATTRIBUTES[fingerprint_type]
Key.arel_table[fingerprint_attribute].eq(fingerprint)
end
def fingerprint_type
if params[:fingerprint].start_with?(/sha256:|SHA256:/)
"sha256"
else
"md5"
end
end
def fingerprint
if fingerprint_type == "sha256"
params[:fingerprint].gsub(/sha256:|SHA256:/, "")
else
params[:fingerprint]
end
end
end
# frozen_string_literal: true
module Sha256Attribute
extend ActiveSupport::Concern
class_methods do
def sha256_attribute(name)
return if ENV['STATIC_VERIFICATION']
validate_binary_column_exists!(name) unless Rails.env.production?
attribute(name, Gitlab::Database::Sha256Attribute.new)
end
# This only gets executed in non-production environments as an additional check to ensure
# the column is the correct type. In production it should behave like any other attribute.
# See https://gitlab.com/gitlab-org/gitlab/merge_requests/5502 for more discussion
def validate_binary_column_exists!(name)
return unless database_exists?
unless table_exists?
warn "WARNING: sha256_attribute #{name.inspect} is invalid since the table doesn't exist - you may need to run database migrations"
return
end
column = columns.find { |c| c.name == name.to_s }
unless column
warn "WARNING: sha256_attribute #{name.inspect} is invalid since the column doesn't exist - you may need to run database migrations"
return
end
unless column.type == :binary
raise ArgumentError.new("sha256_attribute #{name.inspect} is invalid since the column type is not :binary")
end
rescue => error
Gitlab::AppLogger.error "Sha256Attribute initialization: #{error.message}"
raise
end
def database_exists?
ApplicationRecord.connection
true
rescue
false
end
end
end
...@@ -5,6 +5,9 @@ require 'digest/md5' ...@@ -5,6 +5,9 @@ require 'digest/md5'
class Key < ApplicationRecord class Key < ApplicationRecord
include AfterCommitQueue include AfterCommitQueue
include Sortable include Sortable
include Sha256Attribute
sha256_attribute :fingerprint_sha256
belongs_to :user belongs_to :user
...@@ -34,6 +37,8 @@ class Key < ApplicationRecord ...@@ -34,6 +37,8 @@ class Key < ApplicationRecord
after_destroy :post_destroy_hook after_destroy :post_destroy_hook
after_destroy :refresh_user_cache after_destroy :refresh_user_cache
alias_attribute :fingerprint_md5, :fingerprint
def self.regular_keys def self.regular_keys
where(type: ['Key', nil]) where(type: ['Key', nil])
end end
...@@ -114,10 +119,12 @@ class Key < ApplicationRecord ...@@ -114,10 +119,12 @@ class Key < ApplicationRecord
def generate_fingerprint def generate_fingerprint
self.fingerprint = nil self.fingerprint = nil
self.fingerprint_sha256 = nil
return unless public_key.valid? return unless public_key.valid?
self.fingerprint = public_key.fingerprint self.fingerprint_md5 = public_key.fingerprint
self.fingerprint_sha256 = public_key.fingerprint("SHA256").gsub("SHA256:", "")
end end
def key_meets_restrictions def key_meets_restrictions
......
...@@ -17,11 +17,21 @@ ...@@ -17,11 +17,21 @@
.col-md-8 .col-md-8
= form_errors(@key, type: 'key') unless @key.valid? = form_errors(@key, type: 'key') unless @key.valid?
%p
%span.light= _('Fingerprint:')
%code.key-fingerprint= @key.fingerprint
%pre.well-pre %pre.well-pre
= @key.key = @key.key
.card
.card-header
= _('Fingerprints')
%ul.content-list
%li
%span.light= 'MD5:'
%code.key-fingerprint= @key.fingerprint
- if @key.fingerprint_sha256.present?
%li
%span.light= 'SHA256:'
%code.key-fingerprint= @key.fingerprint_sha256
.col-md-12 .col-md-12
.float-right .float-right
- if @key.can_delete? - if @key.can_delete?
......
---
title: add sha256 fingerprint to keys model, view and extend users API to search user via fingerprint
merge_request: 19860
author: Roger Meier
type: added
# frozen_string_literal: true
class AddFingerprintSha256ToKey < ActiveRecord::Migration[5.0]
DOWNTIME = false
def up
add_column(:keys, :fingerprint_sha256, :binary)
end
def down
remove_column(:keys, :fingerprint_sha256) if column_exists?(:keys, :fingerprint_sha256)
end
end
# frozen_string_literal: true
class AddFingerprintSha256IndexToKey < ActiveRecord::Migration[5.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_concurrent_index(:keys, "fingerprint_sha256")
end
def down
remove_concurrent_index(:keys, "fingerprint_sha256")
end
end
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2019_12_06_122926) do ActiveRecord::Schema.define(version: 2019_12_08_071112) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "pg_trgm" enable_extension "pg_trgm"
...@@ -2206,7 +2206,9 @@ ActiveRecord::Schema.define(version: 2019_12_06_122926) do ...@@ -2206,7 +2206,9 @@ ActiveRecord::Schema.define(version: 2019_12_06_122926) do
t.string "fingerprint" t.string "fingerprint"
t.boolean "public", default: false, null: false t.boolean "public", default: false, null: false
t.datetime "last_used_at" t.datetime "last_used_at"
t.binary "fingerprint_sha256"
t.index ["fingerprint"], name: "index_keys_on_fingerprint", unique: true t.index ["fingerprint"], name: "index_keys_on_fingerprint", unique: true
t.index ["fingerprint_sha256"], name: "index_keys_on_fingerprint_sha256"
t.index ["id", "type"], name: "index_on_deploy_keys_id_and_type_and_public", unique: true, where: "(public = true)" t.index ["id", "type"], name: "index_on_deploy_keys_id_and_type_and_public", unique: true, where: "(public = true)"
t.index ["user_id"], name: "index_keys_on_user_id" t.index ["user_id"], name: "index_keys_on_user_id"
end end
......
...@@ -4,13 +4,19 @@ ...@@ -4,13 +4,19 @@
Get SSH key with user by ID of an SSH key. Note only administrators can lookup SSH key with user by ID of an SSH key. Get SSH key with user by ID of an SSH key. Note only administrators can lookup SSH key with user by ID of an SSH key.
``` ```text
GET /keys/:id GET /keys/:id
``` ```
Parameters: | Attribute | Type | Required | Description |
|:----------|:--------|:---------|:---------------------|
| `id` | integer | yes | The ID of an SSH key |
Example request:
- `id` (required) - The ID of an SSH key ```sh
curl --header "PRIVATE-TOKEN: <your_access_token>" 'https://gitlab.example.com/api/v4/keys/1
```
```json ```json
{ {
...@@ -51,3 +57,74 @@ Parameters: ...@@ -51,3 +57,74 @@ Parameters:
} }
} }
``` ```
## Get user by fingerprint of SSH key
You can search for a user that owns a specific SSH key. Note only administrators can lookup SSH key with the fingerprint of an SSH key.
```text
GET /keys
```
| Attribute | Type | Required | Description |
|:--------------|:-------|:---------|:------------------------------|
| `fingerprint` | string | yes | The fingerprint of an SSH key |
Example request:
```sh
curl --header "PRIVATE-TOKEN: <your_access_token>" 'https://gitlab.example.com/api/v4/keys?fingerprint=ba:81:59:68:d7:6c:cd:02:02:bf:6a:9b:55:4e:af:d1'
```
If using sha256 fingerprint API calls, make sure that the fingerprint is URL-encoded.
For example, `/` is represented by `%2F` and `:` is represented by`%3A`:
```sh
curl --header "PRIVATE-TOKEN: <your_access_token>" 'https://gitlab.example.com/api/v4/keys?fingerprint=SHA256%3AnUhzNyftwADy8AH3wFY31tAKs7HufskYTte2aXo%2FlCg
```
Example response:
```json
{
"id": 1,
"title": "Sample key 1",
"key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt1016k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=",
"created_at": "2019-11-14T15:11:13.222Z",
"user": {
"id": 1,
"name": "Administrator",
"username": "root",
"state": "active",
"avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
"web_url": "http://0.0.0.0:3000/root",
"created_at": "2019-11-14T15:09:34.831Z",
"bio": null,
"location": null,
"public_email": "",
"skype": "",
"linkedin": "",
"twitter": "",
"website_url": "",
"organization": null,
"last_sign_in_at": "2019-11-16T22:41:26.663Z",
"confirmed_at": "2019-11-14T15:09:34.575Z",
"last_activity_on": "2019-11-20",
"email": "admin@example.com",
"theme_id": 1,
"color_scheme_id": 1,
"projects_limit": 100000,
"current_sign_in_at": "2019-11-19T14:42:18.078Z",
"identities": [
],
"can_create_group": true,
"can_create_project": true,
"two_factor_enabled": false,
"external": false,
"private_profile": false,
"shared_runners_minutes_limit": null,
"extra_shared_runners_minutes_limit": null
}
}
```
...@@ -16,6 +16,23 @@ module API ...@@ -16,6 +16,23 @@ module API
present key, with: Entities::SSHKeyWithUser, current_user: current_user present key, with: Entities::SSHKeyWithUser, current_user: current_user
end end
desc 'Get SSH Key information' do
success Entities::UserWithAdmin
end
params do
requires :fingerprint, type: String, desc: 'Search for a SSH fingerprint'
end
get do
authenticated_with_full_private_access!
key = KeysFinder.new(current_user, params).execute
not_found!('Key') unless key
present key, with: Entities::SSHKeyWithUser, current_user: current_user
rescue KeysFinder::InvalidFingerprint
render_api_error!('Failed to return the key', 400)
end
end end
end end
end end
# frozen_string_literal: true
module Gitlab
module Database
# Class for casting binary data to hexadecimal SHA256 hashes (and vice-versa).
#
# Using Sha256Attribute allows you to store SHA256 values as binary while still
# using them as if they were stored as string values. This gives you the
# ease of use of string values, but without the storage overhead.
class Sha256Attribute < ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Bytea
# Casts binary data to a SHA256 and remove trailing = and newline from encode64
def deserialize(value)
value = super(value)
if value.present?
Base64.encode64(value).delete("=").chomp("\n")
else
nil
end
end
# Casts a SHA256 in a proper binary format. which is 32 bytes long
def serialize(value)
arg = if value.present?
Base64.decode64(value)
else
nil
end
super(arg)
end
end
end
end
...@@ -10,6 +10,7 @@ module Gitlab ...@@ -10,6 +10,7 @@ module Gitlab
# #
class InsecureKeyFingerprint class InsecureKeyFingerprint
attr_accessor :key attr_accessor :key
alias_attribute :fingerprint_md5, :fingerprint
# #
# Gets the base64 encoded string representing a rsa or dsa key # Gets the base64 encoded string representing a rsa or dsa key
...@@ -21,5 +22,9 @@ module Gitlab ...@@ -21,5 +22,9 @@ module Gitlab
def fingerprint def fingerprint
OpenSSL::Digest::MD5.hexdigest(Base64.decode64(@key)).scan(/../).join(':') OpenSSL::Digest::MD5.hexdigest(Base64.decode64(@key)).scan(/../).join(':')
end end
def fingerprint_sha256
Digest::SHA256.base64digest(Base64.decode64(@key)).scan(/../).join('').delete("=")
end
end end
end end
...@@ -7703,9 +7703,6 @@ msgstr "" ...@@ -7703,9 +7703,6 @@ msgstr ""
msgid "Fingerprint" msgid "Fingerprint"
msgstr "" msgstr ""
msgid "Fingerprint:"
msgstr ""
msgid "Fingerprints" msgid "Fingerprints"
msgstr "" msgstr ""
......
# frozen_string_literal: true
require 'spec_helper'
describe KeysFinder do
subject(:keys_finder) { described_class.new(user, params) }
let(:user) { create(:user) }
let(:fingerprint_type) { 'md5' }
let(:fingerprint) { 'ba:81:59:68:d7:6c:cd:02:02:bf:6a:9b:55:4e:af:d1' }
let(:params) do
{
type: fingerprint_type,
fingerprint: fingerprint
}
end
let!(:key) do
create(:key, user: user,
key: 'ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt1016k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=',
fingerprint: 'ba:81:59:68:d7:6c:cd:02:02:bf:6a:9b:55:4e:af:d1',
fingerprint_sha256: 'nUhzNyftwADy8AH3wFY31tAKs7HufskYTte2aXo/lCg'
)
end
context 'with a regular user' do
it 'raises GitLabAccessDeniedError' do
expect do
keys_finder.execute
end.to raise_error(KeysFinder::GitLabAccessDeniedError)
end
end
context 'with an admin user' do
let(:user) {create(:admin)}
context 'with invalid MD5 fingerprint' do
let(:fingerprint) { '11:11:11:11' }
it 'raises InvalidFingerprint' do
expect { keys_finder.execute }
.to raise_error(KeysFinder::InvalidFingerprint)
end
end
context 'with invalid SHA fingerprint' do
let(:fingerprint_type) { 'sha256' }
let(:fingerprint) { 'nUhzNyftwAAKs7HufskYTte2g' }
it 'raises InvalidFingerprint' do
expect { keys_finder.execute }
.to raise_error(KeysFinder::InvalidFingerprint)
end
end
context 'with valid MD5 params' do
it 'returns key if the fingerprint is found' do
result = keys_finder.execute
expect(result).to eq(key)
expect(key.user).to eq(user)
end
end
context 'with valid SHA256 params' do
let(:fingerprint) { 'ba:81:59:68:d7:6c:cd:02:02:bf:6a:9b:55:4e:af:d1' }
it 'returns key if the fingerprint is found' do
result = keys_finder.execute
expect(result).to eq(key)
expect(key.user).to eq(user)
end
end
end
end
...@@ -11,10 +11,17 @@ describe Gitlab::InsecureKeyFingerprint do ...@@ -11,10 +11,17 @@ describe Gitlab::InsecureKeyFingerprint do
end end
let(:fingerprint) { "3f:a2:ee:de:b5:de:53:c3:aa:2f:9c:45:24:4c:47:7b" } let(:fingerprint) { "3f:a2:ee:de:b5:de:53:c3:aa:2f:9c:45:24:4c:47:7b" }
let(:fingerprint_sha256) { "MQHWhS9nhzUezUdD42ytxubZoBKrZLbyBZzxCkmnxXc" }
describe "#fingerprint" do describe "#fingerprint" do
it "generates the key's fingerprint" do it "generates the key's fingerprint" do
expect(described_class.new(key.split[1]).fingerprint).to eq(fingerprint) expect(described_class.new(key.split[1]).fingerprint_md5).to eq(fingerprint)
end
end
describe "#fingerprint" do
it "generates the key's fingerprint" do
expect(described_class.new(key.split[1]).fingerprint_sha256).to eq(fingerprint_sha256)
end end
end end
end end
...@@ -183,6 +183,34 @@ describe Gitlab::SSHPublicKey, lib: true do ...@@ -183,6 +183,34 @@ describe Gitlab::SSHPublicKey, lib: true do
end end
end end
describe '#fingerprint in SHA256 format' do
subject { public_key.fingerprint("SHA256").gsub("SHA256:", "") if public_key.fingerprint("SHA256") }
where(:factory, :fingerprint_sha256) do
[
[:rsa_key_2048, 'GdtgO0eHbwLB+mK47zblkoXujkqKRZjgMQrHH6Kks3E'],
[:rsa_key_4096, 'ByDU7hQ1JB95l6p53rHrffc4eXvEtqGUtQhS+Dhyy7g'],
[:rsa_key_5120, 'PCCupLbFHScm4AbEufbGDvhBU27IM0MVAor715qKQK8'],
[:rsa_key_8192, 'CtHFQAS+9Hb8z4vrv4gVQPsHjNN0WIZhWODaB1mQLs4'],
[:dsa_key_2048, '+a3DQ7cU5GM+gaYOfmc0VWNnykHQSuth3VRcCpWuYNI'],
[:ecdsa_key_256, 'C+I5k3D+IGeM6k5iBR1ZsphqTKV+7uvL/XZ5hcrTr7g'],
[:ed25519_key_256, 'DCKAjzxWrdOTjaGKBBjtCW8qY5++GaiAJflrHPmp6W0']
]
end
with_them do
let(:key) { attributes_for(factory)[:key] }
it { is_expected.to eq(fingerprint_sha256) }
end
context 'with an invalid SSH key' do
let(:key) { 'this is not a key' }
it { is_expected.to be_nil }
end
end
describe '#key_text' do describe '#key_text' do
let(:key) { 'this is not a key' } let(:key) { 'this is not a key' }
......
# frozen_string_literal: true
require 'spec_helper'
describe Sha256Attribute do
let(:model) { Class.new { include Sha256Attribute } }
before do
columns = [
double(:column, name: 'name', type: :text),
double(:column, name: 'sha256', type: :binary)
]
allow(model).to receive(:columns).and_return(columns)
end
describe '#sha_attribute' do
context 'when in non-production' do
before do
stub_rails_env('development')
end
context 'when the table exists' do
before do
allow(model).to receive(:table_exists?).and_return(true)
end
it 'defines a SHA attribute for a binary column' do
expect(model).to receive(:attribute)
.with(:sha256, an_instance_of(Gitlab::Database::Sha256Attribute))
model.sha256_attribute(:sha256)
end
it 'raises ArgumentError when the column type is not :binary' do
expect { model.sha256_attribute(:name) }.to raise_error(ArgumentError)
end
end
context 'when the table does not exist' do
it 'allows the attribute to be added and issues a warning' do
allow(model).to receive(:table_exists?).and_return(false)
expect(model).not_to receive(:columns)
expect(model).to receive(:attribute)
expect(model).to receive(:warn)
model.sha256_attribute(:name)
end
end
context 'when the column does not exist' do
it 'allows the attribute to be added and issues a warning' do
allow(model).to receive(:table_exists?).and_return(true)
expect(model).to receive(:columns)
expect(model).to receive(:attribute)
expect(model).to receive(:warn)
model.sha256_attribute(:no_name)
end
end
context 'when other execeptions are raised' do
it 'logs and re-rasises the error' do
allow(model).to receive(:table_exists?).and_raise(ActiveRecord::NoDatabaseError.new('does not exist'))
expect(model).not_to receive(:columns)
expect(model).not_to receive(:attribute)
expect(Gitlab::AppLogger).to receive(:error)
expect { model.sha256_attribute(:name) }.to raise_error(ActiveRecord::NoDatabaseError)
end
end
end
context 'when in production' do
before do
stub_rails_env('production')
end
it 'defines a SHA attribute' do
expect(model).not_to receive(:table_exists?)
expect(model).not_to receive(:columns)
expect(model).to receive(:attribute).with(:sha256, an_instance_of(Gitlab::Database::Sha256Attribute))
model.sha256_attribute(:sha256)
end
end
end
end
...@@ -92,6 +92,7 @@ describe Key, :mailer do ...@@ -92,6 +92,7 @@ describe Key, :mailer do
with_them do with_them do
let!(:key) { create(factory) } let!(:key) { create(factory) }
let!(:original_fingerprint) { key.fingerprint } let!(:original_fingerprint) { key.fingerprint }
let!(:original_fingerprint_sha256) { key.fingerprint_sha256 }
it 'accepts a key with blank space characters after stripping them' do it 'accepts a key with blank space characters after stripping them' do
modified_key = key.key.insert(100, chars.first).insert(40, chars.last) modified_key = key.key.insert(100, chars.first).insert(40, chars.last)
...@@ -104,6 +105,8 @@ describe Key, :mailer do ...@@ -104,6 +105,8 @@ describe Key, :mailer do
expect(content).not_to match(/\s/) expect(content).not_to match(/\s/)
expect(original_fingerprint).to eq(key.fingerprint) expect(original_fingerprint).to eq(key.fingerprint)
expect(original_fingerprint).to eq(key.fingerprint_md5)
expect(original_fingerprint_sha256).to eq(key.fingerprint_sha256)
end end
end end
end end
......
...@@ -25,7 +25,6 @@ describe API::Keys do ...@@ -25,7 +25,6 @@ describe API::Keys do
it 'returns single ssh key with user information' do it 'returns single ssh key with user information' do
user.keys << key user.keys << key
user.save
get api("/keys/#{key.id}", admin) get api("/keys/#{key.id}", admin)
expect(response).to have_gitlab_http_status(200) expect(response).to have_gitlab_http_status(200)
expect(json_response['title']).to eq(key.title) expect(json_response['title']).to eq(key.title)
...@@ -40,4 +39,73 @@ describe API::Keys do ...@@ -40,4 +39,73 @@ describe API::Keys do
end end
end end
end end
describe 'GET /keys?fingerprint=' do
it 'returns authentication error' do
get api("/keys?fingerprint=#{key.fingerprint}")
expect(response).to have_gitlab_http_status(401)
end
it 'returns authentication error when authenticated as user' do
get api("/keys?fingerprint=#{key.fingerprint}", user)
expect(response).to have_gitlab_http_status(403)
end
context 'when authenticated as admin' do
it 'returns 404 for non-existing SSH md5 fingerprint' do
get api("/keys?fingerprint=11:11:11:11:11:11:11:11:11:11:11:11:11:11:11:11", admin)
expect(response).to have_gitlab_http_status(404)
expect(json_response['message']).to eq('404 Key Not Found')
end
it 'returns 404 for non-existing SSH sha256 fingerprint' do
get api("/keys?fingerprint=#{URI.encode_www_form_component("SHA256:nUhzNyftwADy8AH3wFY31tAKs7HufskYTte2aXo1lCg")}", admin)
expect(response).to have_gitlab_http_status(404)
expect(json_response['message']).to eq('404 Key Not Found')
end
it 'returns user if SSH md5 fingerprint found' do
user.keys << key
get api("/keys?fingerprint=#{key.fingerprint}", admin)
expect(response).to have_gitlab_http_status(200)
expect(json_response['title']).to eq(key.title)
expect(json_response['user']['id']).to eq(user.id)
expect(json_response['user']['username']).to eq(user.username)
end
it 'returns user if SSH sha256 fingerprint found' do
user.keys << key
get api("/keys?fingerprint=#{URI.encode_www_form_component("SHA256:" + key.fingerprint_sha256)}", admin)
expect(response).to have_gitlab_http_status(200)
expect(json_response['title']).to eq(key.title)
expect(json_response['user']['id']).to eq(user.id)
expect(json_response['user']['username']).to eq(user.username)
end
it 'returns user if SSH sha256 fingerprint found' do
user.keys << key
get api("/keys?fingerprint=#{URI.encode_www_form_component("sha256:" + key.fingerprint_sha256)}", admin)
expect(response).to have_gitlab_http_status(200)
expect(json_response['title']).to eq(key.title)
expect(json_response['user']['id']).to eq(user.id)
expect(json_response['user']['username']).to eq(user.username)
end
it "does not include the user's `is_admin` flag" do
get api("/keys?fingerprint=#{key.fingerprint}", admin)
expect(json_response['user']['is_admin']).to be_nil
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