Commit 7eb26c7f authored by Robin Bobbitt's avatar Robin Bobbitt

Provide hint to create a personal access token for Git over HTTP

If internal auth is disabled and user is not an LDAP user, present
the user with an alert to create a personal access token if he does
not have one already.
parent 4988c3a8
...@@ -50,10 +50,17 @@ module ButtonHelper ...@@ -50,10 +50,17 @@ module ButtonHelper
def http_clone_button(project, placement = 'right', append_link: true) def http_clone_button(project, placement = 'right', append_link: true)
klass = 'http-selector' klass = 'http-selector'
klass << ' has-tooltip' if current_user.try(:require_password?) klass << ' has-tooltip' if current_user.try(:require_password?) || current_user.try(:require_personal_access_token?)
protocol = gitlab_config.protocol.upcase protocol = gitlab_config.protocol.upcase
tooltip_title =
if current_user.try(:require_password?)
_("Set a password on your account to pull or push via %{protocol}.") % { protocol: protocol }
else
_("Create a personal access token on your account to pull or push via %{protocol}.") % { protocol: protocol }
end
content_tag (append_link ? :a : :span), protocol, content_tag (append_link ? :a : :span), protocol,
class: klass, class: klass,
href: (project.http_url_to_repo if append_link), href: (project.http_url_to_repo if append_link),
...@@ -61,7 +68,7 @@ module ButtonHelper ...@@ -61,7 +68,7 @@ module ButtonHelper
html: true, html: true,
placement: placement, placement: placement,
container: 'body', container: 'body',
title: _("Set a password on your account to pull or push via %{protocol}") % { protocol: protocol } title: tooltip_title
} }
end end
......
...@@ -198,6 +198,23 @@ module ProjectsHelper ...@@ -198,6 +198,23 @@ module ProjectsHelper
.load_in_batch_for_projects(projects) .load_in_batch_for_projects(projects)
end end
def show_no_ssh_key_message?
cookies[:hide_no_ssh_message].blank? && !current_user.hide_no_ssh_key && current_user.require_ssh_key?
end
def show_no_password_message?
cookies[:hide_no_password_message].blank? && !current_user.hide_no_password &&
( current_user.require_password? || current_user.require_personal_access_token? )
end
def link_to_set_password
if current_user.require_password?
link_to s_('SetPasswordToCloneLink|set a password'), edit_profile_password_path
else
link_to s_('CreateTokenToCloneLink|create a personal access token'), profile_personal_access_tokens_path
end
end
private private
def repo_children_classes(field) def repo_children_classes(field)
......
...@@ -570,7 +570,13 @@ class User < ActiveRecord::Base ...@@ -570,7 +570,13 @@ class User < ActiveRecord::Base
end end
def require_password? def require_password?
password_automatically_set? && !ldap_user? password_automatically_set? && !ldap_user? && current_application_settings.signin_enabled?
end
def require_personal_access_token?
return false if current_application_settings.signin_enabled? || ldap_user?
PersonalAccessTokensFinder.new(user: self, impersonation: false, state: 'active').execute.none?
end end
def can_change_username? def can_change_username?
......
- if cookies[:hide_no_password_message].blank? && !current_user.hide_no_password && current_user.require_password? - if show_no_password_message?
.no-password-message.alert.alert-warning .no-password-message.alert.alert-warning
- set_password_link = link_to s_('SetPasswordToCloneLink|set a password'), edit_profile_password_path - translation_params = { protocol: gitlab_config.protocol.upcase, set_password_link: link_to_set_password }
- translation_params = { protocol: gitlab_config.protocol.upcase, set_password_link: set_password_link }
- set_password_message = _("You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account") % translation_params - set_password_message = _("You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account") % translation_params
= set_password_message.html_safe
.alert-link-group .alert-link-group
= link_to _("Don't show again"), profile_path(user: {hide_no_password: true}), method: :put = link_to _("Don't show again"), profile_path(user: {hide_no_password: true}), method: :put
| |
......
- if cookies[:hide_no_ssh_message].blank? && !current_user.hide_no_ssh_key && current_user.require_ssh_key? - if show_no_ssh_key_message?
.no-ssh-key-message.alert.alert-warning .no-ssh-key-message.alert.alert-warning
- add_ssh_key_link = link_to s_('MissingSSHKeyWarningLink|add an SSH key'), profile_keys_path, class: 'alert-link' - add_ssh_key_link = link_to s_('MissingSSHKeyWarningLink|add an SSH key'), profile_keys_path, class: 'alert-link'
- ssh_message = _("You won't be able to pull or push project code via SSH until you %{add_ssh_key_link} to your profile") % { add_ssh_key_link: add_ssh_key_link } - ssh_message = _("You won't be able to pull or push project code via SSH until you %{add_ssh_key_link} to your profile") % { add_ssh_key_link: add_ssh_key_link }
#{ ssh_message.html_safe } = ssh_message.html_safe
.alert-link-group .alert-link-group
= link_to _("Don't show again"), profile_path(user: {hide_no_ssh_key: true}), method: :put, class: 'alert-link' = link_to _("Don't show again"), profile_path(user: {hide_no_ssh_key: true}), method: :put, class: 'alert-link'
| |
......
---
title: Provide hint to create a personal access token for Git over HTTP
merge_request: 12105
author: Robin Bobbitt
...@@ -143,29 +143,8 @@ feature 'Login', feature: true do ...@@ -143,29 +143,8 @@ feature 'Login', feature: true do
end end
context 'logging in via OAuth' do context 'logging in via OAuth' do
def saml_config
OpenStruct.new(name: 'saml', label: 'saml', args: {
assertion_consumer_service_url: 'https://localhost:3443/users/auth/saml/callback',
idp_cert_fingerprint: '26:43:2C:47:AF:F0:6B:D0:07:9C:AD:A3:74:FE:5D:94:5F:4E:9E:52',
idp_sso_target_url: 'https://idp.example.com/sso/saml',
issuer: 'https://localhost:3443/',
name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient'
})
end
def stub_omniauth_config(messages)
Rails.application.env_config['devise.mapping'] = Devise.mappings[:user]
Rails.application.routes.disable_clear_and_finalize = true
Rails.application.routes.draw do
post '/users/auth/saml' => 'omniauth_callbacks#saml'
end
allow(Gitlab::OAuth::Provider).to receive_messages(providers: [:saml], config_for: saml_config)
allow(Gitlab.config.omniauth).to receive_messages(messages)
expect_any_instance_of(Object).to receive(:omniauth_authorize_path).with(:user, "saml").and_return('/users/auth/saml')
end
it 'shows 2FA prompt after OAuth login' do it 'shows 2FA prompt after OAuth login' do
stub_omniauth_config(enabled: true, auto_link_saml_user: true, allow_single_sign_on: ['saml'], providers: [saml_config]) stub_omniauth_saml_config(enabled: true, auto_link_saml_user: true, allow_single_sign_on: ['saml'], providers: [mock_saml_config])
user = create(:omniauth_user, :two_factor, extern_uid: 'my-uid', provider: 'saml') user = create(:omniauth_user, :two_factor, extern_uid: 'my-uid', provider: 'saml')
gitlab_sign_in_via('saml', user, 'my-uid') gitlab_sign_in_via('saml', user, 'my-uid')
......
require 'spec_helper'
feature 'No Password Alert' do
let(:project) { create(:project, namespace: user.namespace) }
context 'with internal auth enabled' do
before do
sign_in(user)
visit namespace_project_path(project.namespace, project)
end
context 'when user has a password' do
let(:user) { create(:user) }
it 'shows no alert' do
expect(page).not_to have_content "You won't be able to pull or push project code via HTTP until you set a password on your account"
end
end
context 'when user has password automatically set' do
let(:user) { create(:user, password_automatically_set: true) }
it 'shows a password alert' do
expect(page).to have_content "You won't be able to pull or push project code via HTTP until you set a password on your account"
end
end
end
context 'with internal auth disabled' do
let(:user) { create(:omniauth_user, extern_uid: 'my-uid', provider: 'saml') }
before do
stub_application_setting(signin_enabled?: false)
stub_omniauth_saml_config(enabled: true, auto_link_saml_user: true, allow_single_sign_on: ['saml'], providers: [mock_saml_config])
end
context 'when user has no personal access tokens' do
it 'has a personal access token alert' do
gitlab_sign_in_via('saml', user, 'my-uid')
visit namespace_project_path(project.namespace, project)
expect(page).to have_content "You won't be able to pull or push project code via HTTP until you create a personal access token on your account"
end
end
context 'when user has a personal access token' do
it 'shows no alert' do
create(:personal_access_token, user: user)
gitlab_sign_in_via('saml', user, 'my-uid')
visit namespace_project_path(project.namespace, project)
expect(page).not_to have_content "You won't be able to pull or push project code via HTTP until you create a personal access token on your account"
end
end
end
context 'when user is ldap user' do
let(:user) { create(:omniauth_user, password_automatically_set: true) }
before do
sign_in(user)
visit namespace_project_path(project.namespace, project)
end
it 'shows no alert' do
expect(page).not_to have_content "You won't be able to pull or push project code via HTTP until you"
end
end
end
require 'spec_helper'
describe ButtonHelper do
describe 'http_clone_button' do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:has_tooltip_class) { 'has-tooltip' }
def element
element = helper.http_clone_button(project)
Nokogiri::HTML::DocumentFragment.parse(element).first_element_child
end
before do
allow(helper).to receive(:current_user).and_return(user)
end
context 'with internal auth enabled' do
context 'when user has a password' do
it 'shows no tooltip' do
expect(element.attr('class')).not_to include(has_tooltip_class)
end
end
context 'when user has password automatically set' do
let(:user) { create(:user, password_automatically_set: true) }
it 'shows a password tooltip' do
expect(element.attr('class')).to include(has_tooltip_class)
expect(element.attr('data-title')).to eq('Set a password on your account to pull or push via HTTP.')
end
end
end
context 'with internal auth disabled' do
before do
stub_application_setting(signin_enabled?: false)
end
context 'when user has no personal access tokens' do
it 'has a personal access token tooltip ' do
expect(element.attr('class')).to include(has_tooltip_class)
expect(element.attr('data-title')).to eq('Create a personal access token on your account to pull or push via HTTP.')
end
end
context 'when user has a personal access token' do
it 'shows no tooltip' do
create(:personal_access_token, user: user)
expect(element.attr('class')).not_to include(has_tooltip_class)
end
end
end
context 'when user is ldap user' do
let(:user) { create(:omniauth_user, password_automatically_set: true) }
it 'shows no tooltip' do
expect(element.attr('class')).not_to include(has_tooltip_class)
end
end
end
end
...@@ -115,6 +115,82 @@ describe ProjectsHelper do ...@@ -115,6 +115,82 @@ describe ProjectsHelper do
end end
end end
describe '#show_no_ssh_key_message?' do
let(:user) { create(:user) }
before do
allow(helper).to receive(:current_user).and_return(user)
end
context 'user has no keys' do
it 'returns true' do
expect(helper.show_no_ssh_key_message?).to be_truthy
end
end
context 'user has an ssh key' do
it 'returns false' do
create(:personal_key, user: user)
expect(helper.show_no_ssh_key_message?).to be_falsey
end
end
end
describe '#show_no_password_message?' do
let(:user) { create(:user) }
before do
allow(helper).to receive(:current_user).and_return(user)
end
context 'user has password set' do
it 'returns false' do
expect(helper.show_no_password_message?).to be_falsey
end
end
context 'user requires a password' do
let(:user) { create(:user, password_automatically_set: true) }
it 'returns true' do
expect(helper.show_no_password_message?).to be_truthy
end
end
context 'user requires a personal access token' do
it 'returns true' do
stub_application_setting(signin_enabled?: false)
expect(helper.show_no_password_message?).to be_truthy
end
end
end
describe '#link_to_set_password' do
before do
allow(helper).to receive(:current_user).and_return(user)
end
context 'user requires a password' do
let(:user) { create(:user, password_automatically_set: true) }
it 'returns link to set a password' do
expect(helper.link_to_set_password).to match %r{<a href="#{edit_profile_password_path}">set a password</a>}
end
end
context 'user requires a personal access token' do
let(:user) { create(:user) }
it 'returns link to create a personal access token' do
stub_application_setting(signin_enabled?: false)
expect(helper.link_to_set_password).to match %r{<a href="#{profile_personal_access_tokens_path}">create a personal access token</a>}
end
end
end
describe 'link_to_member' do describe 'link_to_member' do
let(:group) { create(:group) } let(:group) { create(:group) }
let(:project) { create(:empty_project, group: group) } let(:project) { create(:empty_project, group: group) }
......
...@@ -89,4 +89,25 @@ module LoginHelpers ...@@ -89,4 +89,25 @@ module LoginHelpers
}) })
Rails.application.env_config['omniauth.auth'] = OmniAuth.config.mock_auth[:saml] Rails.application.env_config['omniauth.auth'] = OmniAuth.config.mock_auth[:saml]
end end
def mock_saml_config
OpenStruct.new(name: 'saml', label: 'saml', args: {
assertion_consumer_service_url: 'https://localhost:3443/users/auth/saml/callback',
idp_cert_fingerprint: '26:43:2C:47:AF:F0:6B:D0:07:9C:AD:A3:74:FE:5D:94:5F:4E:9E:52',
idp_sso_target_url: 'https://idp.example.com/sso/saml',
issuer: 'https://localhost:3443/',
name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient'
})
end
def stub_omniauth_saml_config(messages)
Rails.application.env_config['devise.mapping'] = Devise.mappings[:user]
Rails.application.routes.disable_clear_and_finalize = true
Rails.application.routes.draw do
post '/users/auth/saml' => 'omniauth_callbacks#saml'
end
allow(Gitlab::OAuth::Provider).to receive_messages(providers: [:saml], config_for: mock_saml_config)
stub_omniauth_setting(messages)
expect_any_instance_of(Object).to receive(:omniauth_authorize_path).with(:user, "saml").and_return('/users/auth/saml')
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