Commit ea9dda95 authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ce

parents 791bf115 07956981
Please view this file on the master branch, on stable branches it's out of date. Please view this file on the master branch, on stable branches it's out of date.
v 7.13.0 (unreleased) v 7.13.0 (unreleased)
- Update maintenance documentation to explain no need to recompile asssets for omnibus installations (Stan Hu)
- Support commenting on diffs in side-by-side mode (Stan Hu) - Support commenting on diffs in side-by-side mode (Stan Hu)
- Fix JavaScript error when clicking on the comment button on a diff line that has a comment already (Stan Hu) - Fix JavaScript error when clicking on the comment button on a diff line that has a comment already (Stan Hu)
- Remove project visibility icons from dashboard projects list - Remove project visibility icons from dashboard projects list
...@@ -9,6 +10,8 @@ v 7.13.0 (unreleased) ...@@ -9,6 +10,8 @@ v 7.13.0 (unreleased)
- Update ssl_ciphers in Nginx example to remove DHE settings. This will deny forward secrecy for Android 2.3.7, Java 6 and OpenSSL 0.9.8 - Update ssl_ciphers in Nginx example to remove DHE settings. This will deny forward secrecy for Android 2.3.7, Java 6 and OpenSSL 0.9.8
v 7.12.0 (unreleased) v 7.12.0 (unreleased)
- Fix Error 500 when one user attempts to access a personal, internal snippet (Stan Hu)
- Disable changing of target branch in new merge request page when a branch has already been specified (Stan Hu)
- Fix post-receive errors on a push when an external issue tracker is configured (Stan Hu) - Fix post-receive errors on a push when an external issue tracker is configured (Stan Hu)
- Update oauth button logos for Twitter and Google to recommended assets - Update oauth button logos for Twitter and Google to recommended assets
- Fix hooks for web based events with external issue references (Daniel Gerhardt) - Fix hooks for web based events with external issue references (Daniel Gerhardt)
......
class @BlobView
constructor: ->
# handle multi-line select
handleMultiSelect = (e) ->
[ first_line, last_line ] = parseSelectedLines()
[ line_number ] = parseSelectedLines($(this).attr("id"))
hash = "L#{line_number}"
if e.shiftKey and not isNaN(first_line) and not isNaN(line_number)
if line_number < first_line
last_line = first_line
first_line = line_number
else
last_line = line_number
hash = if first_line == last_line then "L#{first_line}" else "L#{first_line}-#{last_line}"
setHash(hash)
e.preventDefault()
# See if there are lines selected
# "#L12" and "#L34-56" supported
highlightBlobLines = (e) ->
[ first_line, last_line ] = parseSelectedLines()
unless isNaN first_line
$("#tree-content-holder .highlight .line").removeClass("hll")
$("#LC#{line}").addClass("hll") for line in [first_line..last_line]
$.scrollTo("#L#{first_line}", offset: -50) unless e?
# parse selected lines from hash
# always return first and last line (initialized to NaN)
parseSelectedLines = (str) ->
first_line = NaN
last_line = NaN
hash = str || window.location.hash
if hash isnt ""
matches = hash.match(/\#?L(\d+)(\-(\d+))?/)
first_line = parseInt(matches?[1])
last_line = parseInt(matches?[3])
last_line = first_line if isNaN(last_line)
[ first_line, last_line ]
setHash = (hash) ->
hash = hash.replace(/^\#/, "")
nodes = $("#" + hash)
# if any nodes are using this id, they must be temporarily changed
# also, add a temporary div at the top of the screen to prevent scrolling
if nodes.length > 0
scroll_top = $(document).scrollTop()
nodes.attr("id", "")
tmp = $("<div></div>")
.css({ position: "absolute", visibility: "hidden", top: scroll_top + "px" })
.attr("id", hash)
.appendTo(document.body)
window.location.hash = hash
# restore the nodes
if nodes.length > 0
tmp.remove()
nodes.attr("id", hash)
# initialize multi-line select
$("#tree-content-holder .line-numbers a[id^=L]").on("click", handleMultiSelect)
# Highlight the correct lines on load
highlightBlobLines()
# Highlight the correct lines when the hash part of the URL changes
$(window).on("hashchange", highlightBlobLines)
...@@ -87,7 +87,7 @@ class Dispatcher ...@@ -87,7 +87,7 @@ class Dispatcher
new TreeView() new TreeView()
shortcut_handler = new ShortcutsNavigation() shortcut_handler = new ShortcutsNavigation()
when 'projects:blob:show' when 'projects:blob:show'
new BlobView() new LineHighlighter()
shortcut_handler = new ShortcutsNavigation() shortcut_handler = new ShortcutsNavigation()
when 'projects:labels:new', 'projects:labels:edit' when 'projects:labels:new', 'projects:labels:edit'
new Labels() new Labels()
......
# LineHighlighter
#
# Handles single- and multi-line selection and highlight for blob views.
#
#= require jquery.scrollTo
#
# ### Example Markup
#
# <div id="tree-content-holder">
# <div class="file-content">
# <div class="line-numbers">
# <a href="#L1" id="L1" data-line-number="1">1</a>
# <a href="#L2" id="L2" data-line-number="2">2</a>
# <a href="#L3" id="L3" data-line-number="3">3</a>
# <a href="#L4" id="L4" data-line-number="4">4</a>
# <a href="#L5" id="L5" data-line-number="5">5</a>
# </div>
# <pre class="code highlight">
# <code>
# <span id="LC1" class="line">...</span>
# <span id="LC2" class="line">...</span>
# <span id="LC3" class="line">...</span>
# <span id="LC4" class="line">...</span>
# <span id="LC5" class="line">...</span>
# </code>
# </pre>
# </div>
# </div>
#
class @LineHighlighter
# CSS class applied to highlighted lines
highlightClass: 'hll'
# Internal copy of location.hash so we're not dependent on `location` in tests
_hash: ''
# Initialize a LineHighlighter object
#
# hash - String URL hash for dependency injection in tests
constructor: (hash = location.hash) ->
@_hash = hash
@bindEvents()
unless hash == ''
range = @hashToRange(hash)
if range[0]
@highlightRange(range)
# Scroll to the first highlighted line on initial load
# Offset -50 for the sticky top bar, and another -100 for some context
$.scrollTo("#L#{range[0]}", offset: -150)
bindEvents: ->
$('#tree-content-holder').on 'mousedown', 'a[data-line-number]', @clickHandler
# While it may seem odd to bind to the mousedown event and then throw away
# the click event, there is a method to our madness.
#
# If not done this way, the line number anchor will sometimes keep its
# active state even when the event is cancelled, resulting in an ugly border
# around the link and/or a persisted underline text decoration.
$('#tree-content-holder').on 'click', 'a[data-line-number]', (event) ->
event.preventDefault()
clickHandler: (event) =>
event.preventDefault()
@clearHighlight()
lineNumber = $(event.target).data('line-number')
current = @hashToRange(@_hash)
unless current[0] && event.shiftKey
# If there's no current selection, or there is but Shift wasn't held,
# treat this like a single-line selection.
@setHash(lineNumber)
@highlightLine(lineNumber)
else if event.shiftKey
if lineNumber < current[0]
range = [lineNumber, current[0]]
else
range = [current[0], lineNumber]
@setHash(range[0], range[1])
@highlightRange(range)
# Unhighlight previously highlighted lines
clearHighlight: ->
$(".#{@highlightClass}").removeClass(@highlightClass)
# Convert a URL hash String into line numbers
#
# hash - Hash String
#
# Examples:
#
# hashToRange('#L5') # => [5, null]
# hashToRange('#L5-15') # => [5, 15]
# hashToRange('#foo') # => [null, null]
#
# Returns an Array
hashToRange: (hash) ->
matches = hash.match(/^#?L(\d+)(?:-(\d+))?$/)
if matches && matches.length
first = parseInt(matches[1])
last = if matches[2] then parseInt(matches[2]) else null
[first, last]
else
[null, null]
# Highlight a single line
#
# lineNumber - Line number to highlight
highlightLine: (lineNumber) =>
$("#LC#{lineNumber}").addClass(@highlightClass)
# Highlight all lines within a range
#
# range - Array containing the starting and ending line numbers
highlightRange: (range) ->
if range[1]
for lineNumber in [range[0]..range[1]]
@highlightLine(lineNumber)
else
@highlightLine(range[0])
# Set the URL hash string
setHash: (firstLineNumber, lastLineNumber) =>
if lastLineNumber
hash = "#L#{firstLineNumber}-#{lastLineNumber}"
else
hash = "#L#{firstLineNumber}"
@_hash = hash
@__setLocationHash__(hash)
# Make the actual hash change in the browser
#
# This method is stubbed in tests.
__setLocationHash__: (value) ->
# We're using pushState instead of assigning location.hash directly to
# prevent the page from scrolling on the hashchange event
history.pushState({turbolinks: false, url: value}, document.title, value)
...@@ -24,7 +24,7 @@ class PasswordsController < Devise::PasswordsController ...@@ -24,7 +24,7 @@ class PasswordsController < Devise::PasswordsController
super do |resource| super do |resource|
# TODO (rspeicher): In Devise master (> 3.4.1), we can set # TODO (rspeicher): In Devise master (> 3.4.1), we can set
# `Devise.sign_in_after_reset_password = false` and avoid this mess. # `Devise.sign_in_after_reset_password = false` and avoid this mess.
if resource.errors.empty? && resource.try(:otp_required_for_login?) if resource.errors.empty? && resource.try(:two_factor_enabled?)
resource.unlock_access! if unlockable?(resource) resource.unlock_access! if unlockable?(resource)
# Since we are not signing this user in, we use the :updated_not_active # Since we are not signing this user in, we use the :updated_not_active
......
...@@ -10,7 +10,7 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController ...@@ -10,7 +10,7 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
def create def create
if current_user.valid_otp?(params[:pin_code]) if current_user.valid_otp?(params[:pin_code])
current_user.otp_required_for_login = true current_user.two_factor_enabled = true
@codes = current_user.generate_otp_backup_codes! @codes = current_user.generate_otp_backup_codes!
current_user.save! current_user.save!
...@@ -30,7 +30,7 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController ...@@ -30,7 +30,7 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
def destroy def destroy
current_user.update_attributes({ current_user.update_attributes({
otp_required_for_login: false, two_factor_enabled: false,
encrypted_otp_secret: nil, encrypted_otp_secret: nil,
encrypted_otp_secret_iv: nil, encrypted_otp_secret_iv: nil,
encrypted_otp_secret_salt: nil, encrypted_otp_secret_salt: nil,
......
...@@ -57,7 +57,7 @@ class SessionsController < Devise::SessionsController ...@@ -57,7 +57,7 @@ class SessionsController < Devise::SessionsController
def authenticate_with_two_factor def authenticate_with_two_factor
user = self.resource = find_user user = self.resource = find_user
return unless user && user.otp_required_for_login return unless user && user.two_factor_enabled?
if user_params[:otp_attempt].present? && session[:otp_user_id] if user_params[:otp_attempt].present? && session[:otp_user_id]
if valid_otp_attempt?(user) if valid_otp_attempt?(user)
......
...@@ -263,7 +263,7 @@ class Ability ...@@ -263,7 +263,7 @@ class Ability
:"modify_#{name}", :"modify_#{name}",
] ]
else else
if subject.respond_to?(:project) if subject.respond_to?(:project) && subject.project
project_abilities(user, subject.project) project_abilities(user, subject.project)
else else
[] []
......
...@@ -172,6 +172,9 @@ class User < ActiveRecord::Base ...@@ -172,6 +172,9 @@ class User < ActiveRecord::Base
after_create :post_create_hook after_create :post_create_hook
after_destroy :post_destroy_hook after_destroy :post_destroy_hook
# User's Dashboard preference
# Note: When adding an option, it MUST go on the end of the array.
enum dashboard: [:projects, :stars]
alias_attribute :private_token, :authentication_token alias_attribute :private_token, :authentication_token
...@@ -297,6 +300,18 @@ class User < ActiveRecord::Base ...@@ -297,6 +300,18 @@ class User < ActiveRecord::Base
@reset_token @reset_token
end end
# Check if the user has enabled Two-factor Authentication
def two_factor_enabled?
otp_required_for_login
end
# Set whether or not Two-factor Authentication is enabled for the current user
#
# setting - Boolean
def two_factor_enabled=(setting)
self.otp_required_for_login = setting
end
def namespace_uniq def namespace_uniq
namespace_name = self.username namespace_name = self.username
existing_namespace = Namespace.by_path(namespace_name) existing_namespace = Namespace.by_path(namespace_name)
...@@ -704,8 +719,4 @@ class User < ActiveRecord::Base ...@@ -704,8 +719,4 @@ class User < ActiveRecord::Base
def can_be_removed? def can_be_removed?
!solo_owned_groups.present? !solo_owned_groups.present?
end end
# User's Dashboard preference
# Note: When adding an option, it MUST go on the end of the array.
enum dashboard: [:projects, :stars]
end end
...@@ -50,6 +50,14 @@ ...@@ -50,6 +50,14 @@
= link_to remove_email_admin_user_path(@user, email), data: { confirm: "Are you sure you want to remove #{email.email}?" }, method: :delete, class: "btn-xs btn btn-remove pull-right", title: 'Remove secondary email', id: "remove_email_#{email.id}" do = link_to remove_email_admin_user_path(@user, email), data: { confirm: "Are you sure you want to remove #{email.email}?" }, method: :delete, class: "btn-xs btn btn-remove pull-right", title: 'Remove secondary email', id: "remove_email_#{email.id}" do
%i.fa.fa-times %i.fa.fa-times
%li.two-factor-status
%span.light Two-factor Authentication:
%strong{class: @user.two_factor_enabled? ? 'cgreen' : 'cred'}
- if @user.two_factor_enabled?
Enabled
- else
Disabled
%li %li
%span.light Can create groups: %span.light Can create groups:
%strong %strong
......
...@@ -36,7 +36,7 @@ ...@@ -36,7 +36,7 @@
.panel-heading .panel-heading
Two-factor Authentication Two-factor Authentication
.panel-body .panel-body
- if current_user.otp_required_for_login - if current_user.two_factor_enabled?
.pull-right .pull-right
= link_to 'Disable Two-factor Authentication', profile_two_factor_auth_path, method: :delete, class: 'btn btn-close btn-sm', = link_to 'Disable Two-factor Authentication', profile_two_factor_auth_path, method: :delete, class: 'btn btn-close btn-sm',
data: { confirm: 'Are you sure?' } data: { confirm: 'Are you sure?' }
......
...@@ -81,21 +81,22 @@ ...@@ -81,21 +81,22 @@
- if issuable.is_a?(MergeRequest) - if issuable.is_a?(MergeRequest)
%hr %hr
- unless @merge_request.persisted? - if @merge_request.new_record?
.form-group .form-group
= f.label :source_branch, class: 'control-label' do = f.label :source_branch, class: 'control-label' do
%i.fa.fa-code-fork %i.fa.fa-code-fork
Source Branch Source Branch
.col-sm-10 .col-sm-10
= f.select(:source_branch, [@merge_request.source_branch], { }, { class: 'source_branch select2 span2', disabled: true }) = f.select(:source_branch, [@merge_request.source_branch], { }, { class: 'source_branch select2 span2', disabled: true })
%p.help-block
= link_to 'Change source branch', mr_change_branches_path(@merge_request)
.form-group .form-group
= f.label :target_branch, class: 'control-label' do = f.label :target_branch, class: 'control-label' do
%i.fa.fa-code-fork %i.fa.fa-code-fork
Target Branch Target Branch
.col-sm-10 .col-sm-10
= f.select(:target_branch, @merge_request.target_branches, { include_blank: "Select branch" }, { class: 'target_branch select2 span2' }) = f.select(:target_branch, @merge_request.target_branches, { include_blank: "Select branch" }, { class: 'target_branch select2 span2', disabled: @merge_request.new_record? })
- if @merge_request.new_record?
%p.help-block
= link_to 'Change branches', mr_change_branches_path(@merge_request)
.form-actions .form-actions
- if !issuable.project.empty_repo? && (guide_url = contribution_guide_url(issuable.project)) && !issuable.persisted? - if !issuable.project.empty_repo? && (guide_url = contribution_guide_url(issuable.project)) && !issuable.persisted?
......
.file-content.code{class: user_color_scheme_class} .file-content.code{class: user_color_scheme_class}
.line-numbers .line-numbers
- if blob.data.present? - if blob.data.present?
- blob.data.lines.to_a.size.times do |index| - blob.data.lines.each_index do |index|
- offset = defined?(first_line_number) ? first_line_number : 1 - offset = defined?(first_line_number) ? first_line_number : 1
- i = index + offset - i = index + offset
/ We're not using `link_to` because it is too slow once we get to thousands of lines. -# We're not using `link_to` because it is too slow once we get to thousands of lines.
%a{href: "#L#{i}", id: "L#{i}", rel: "#L#{i}"} %a{href: "#L#{i}", id: "L#{i}", 'data-line-number' => i}
%i.fa.fa-link %i.fa.fa-link
= i = i
:preserve :preserve
......
...@@ -165,13 +165,18 @@ sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production ...@@ -165,13 +165,18 @@ sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production
Sometimes during version upgrades you might end up with some wrong CSS or Sometimes during version upgrades you might end up with some wrong CSS or
missing some icons. In that case, try to precompile the assets again. missing some icons. In that case, try to precompile the assets again.
For Omnibus-packages: Note that this only applies to source installations and does NOT apply to
``` omnibus packages.
sudo gitlab-rake assets:precompile
```
For installations from source: For installations from source:
``` ```
cd /home/git/gitlab cd /home/git/gitlab
sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production
``` ```
For omnibus versions, the unoptimized assets (JavaScript, CSS) are frozen at
the release of upstream GitLab. The omnibus version includes optimized versions
of those assets. Unless you are modifying the JavaScript / CSS code on your
production machine after installing the package, there should be no reason to redo
rake assets:precompile on the production machine. If you suspect that assets
have been corrupted, you should reinstall the omnibus package.
...@@ -77,3 +77,31 @@ information. ...@@ -77,3 +77,31 @@ information.
### Eclipse ### Eclipse
How to add your ssh key to Eclipse: http://wiki.eclipse.org/EGit/User_Guide#Eclipse_SSH_Configuration How to add your ssh key to Eclipse: http://wiki.eclipse.org/EGit/User_Guide#Eclipse_SSH_Configuration
## Tip: Non-default OpenSSH key file names or locations
If, for whatever reason, you decide to specify a non-default location and filename for your Gitlab SSH key pair, you must configure your SSH client to find your Gitlab SSH private key for connections to your Gitlab server (perhaps gitlab.com). For OpenSSH clients, this is handled in the `~/.ssh/config` file with a stanza similar to the following:
```
#
# Main gitlab.com server
#
Host gitlab.com
RSAAuthentication yes
IdentityFile ~/my-ssh-key-directory/my-gitlab-private-key-filename
User mygitlabusername
```
Another example
```
#
# Our company's internal Gitlab server
#
Host my-gitlab.company.com
RSAAuthentication yes
IdentityFile ~/my-ssh-key-directory/company-com-private-key-filename
```
Note in the gitlab.com example above a username was specified to override the default chosen by OpenSSH (your local username). This is only required if your local and remote usernames differ.
Due to the wide variety of SSH clients and their very large number of configuration options, further explanation of these topics is beyond the scope of this document.
Feature: Project Source Multiselect Blob
Background:
Given I sign in as a user
And I own project "Shop"
And I visit ".gitignore" file in repo
@javascript
Scenario: I click line 1 in file
When I click line 1 in file
Then I should see "L1" as URI fragment
And I should see line 1 highlighted
@javascript
Scenario: I shift-click line 1 in file
When I shift-click line 1 in file
Then I should see "L1" as URI fragment
And I should see line 1 highlighted
@javascript
Scenario: I click line 1 then click line 2 in file
When I click line 1 in file
Then I should see "L1" as URI fragment
And I should see line 1 highlighted
Then I click line 2 in file
Then I should see "L2" as URI fragment
And I should see line 2 highlighted
@javascript
Scenario: I click various line numbers to test multiselect
Then I click line 1 in file
Then I should see "L1" as URI fragment
And I should see line 1 highlighted
Then I shift-click line 2 in file
Then I should see "L1-2" as URI fragment
And I should see lines 1-2 highlighted
Then I shift-click line 3 in file
Then I should see "L1-3" as URI fragment
And I should see lines 1-3 highlighted
Then I click line 3 in file
Then I should see "L3" as URI fragment
And I should see line 3 highlighted
Then I shift-click line 1 in file
Then I should see "L1-3" as URI fragment
And I should see lines 1-3 highlighted
Then I shift-click line 5 in file
Then I should see "L1-5" as URI fragment
And I should see lines 1-5 highlighted
Then I shift-click line 4 in file
Then I should see "L1-4" as URI fragment
And I should see lines 1-4 highlighted
Then I click line 5 in file
Then I should see "L5" as URI fragment
And I should see line 5 highlighted
Then I shift-click line 3 in file
Then I should see "L3-5" as URI fragment
And I should see lines 3-5 highlighted
Then I shift-click line 1 in file
Then I should see "L1-3" as URI fragment
And I should see lines 1-3 highlighted
Then I shift-click line 1 in file
Then I should see "L1" as URI fragment
And I should see line 1 highlighted
@javascript
Scenario: I multiselect lines 1-5 and then go back and forward in history
When I click line 1 in file
And I shift-click line 3 in file
And I shift-click line 2 in file
And I shift-click line 5 in file
Then I should see "L1-5" as URI fragment
And I should see lines 1-5 highlighted
Then I go back in history
Then I should see "L1-2" as URI fragment
And I should see lines 1-2 highlighted
Then I go back in history
Then I should see "L1-3" as URI fragment
And I should see lines 1-3 highlighted
Then I go back in history
Then I should see "L1" as URI fragment
And I should see line 1 highlighted
Then I go forward in history
And I go forward in history
And I go forward in history
Then I should see "L1-5" as URI fragment
And I should see lines 1-5 highlighted
...@@ -26,3 +26,14 @@ Feature: Snippets ...@@ -26,3 +26,14 @@ Feature: Snippets
Given I visit snippet page "Personal snippet one" Given I visit snippet page "Personal snippet one"
And I click link "Destroy" And I click link "Destroy"
Then I should not see "Personal snippet one" in snippets Then I should not see "Personal snippet one" in snippets
Scenario: I create new internal snippet
Given I logout directly
And I sign in as an admin
Then I visit new snippet page
And I submit new internal snippet
Then I visit snippet page "Internal personal snippet one"
And I logout directly
Then I sign in as a user
Given I visit new snippet page
Then I visit snippet page "Internal personal snippet one"
class Spinach::Features::ProjectSourceMultiselectBlob < Spinach::FeatureSteps
include SharedAuthentication
include SharedProject
include SharedPaths
class << self
def click_line_steps(*line_numbers)
line_numbers.each do |line_number|
step "I click line #{line_number} in file" do
find("#L#{line_number}").click
end
step "I shift-click line #{line_number} in file" do
script = "$('#L#{line_number}').trigger($.Event('click', { shiftKey: true }));"
execute_script(script)
end
end
end
def check_state_steps(*ranges)
ranges.each do |range|
fragment = range.kind_of?(Array) ? "L#{range.first}-#{range.last}" : "L#{range}"
pluralization = range.kind_of?(Array) ? "s" : ""
step "I should see \"#{fragment}\" as URI fragment" do
expect(URI.parse(current_url).fragment).to eq fragment
end
step "I should see line#{pluralization} #{fragment[1..-1]} highlighted" do
ids = Array(range).map { |n| "LC#{n}" }
extra = false
highlighted = page.all("#tree-content-holder .highlight .line.hll")
highlighted.each do |element|
extra ||= ids.delete(element[:id]).nil?
end
expect(extra).to be_false and ids.should be_empty
end
end
end
end
click_line_steps *Array(1..5)
check_state_steps *Array(1..5), Array(1..2), Array(1..3), Array(1..4), Array(1..5), Array(3..5)
step 'I go back in history' do
go_back
end
step 'I go forward in history' do
go_forward
end
step 'I click on ".gitignore" file in repo' do
click_link ".gitignore"
end
end
...@@ -28,6 +28,10 @@ module SharedAuthentication ...@@ -28,6 +28,10 @@ module SharedAuthentication
logout logout
end end
step "I logout directly" do
logout_direct
end
def current_user def current_user
@user || User.first @user || User.first
end end
......
...@@ -31,6 +31,18 @@ class Spinach::Features::Snippets < Spinach::FeatureSteps ...@@ -31,6 +31,18 @@ class Spinach::Features::Snippets < Spinach::FeatureSteps
click_button "Create snippet" click_button "Create snippet"
end end
step 'I submit new internal snippet' do
fill_in "personal_snippet_title", :with => "Internal personal snippet one"
fill_in "personal_snippet_file_name", :with => "my_snippet.rb"
choose 'personal_snippet_visibility_level_10'
page.within('.file-editor') do
find(:xpath, "//input[@id='personal_snippet_content']").set 'Content of internal snippet'
end
click_button "Create snippet"
end
step 'I should see snippet "Personal snippet three"' do step 'I should see snippet "Personal snippet three"' do
expect(page).to have_content "Personal snippet three" expect(page).to have_content "Personal snippet three"
expect(page).to have_content "Content of snippet three" expect(page).to have_content "Content of snippet three"
...@@ -58,7 +70,15 @@ class Spinach::Features::Snippets < Spinach::FeatureSteps ...@@ -58,7 +70,15 @@ class Spinach::Features::Snippets < Spinach::FeatureSteps
visit snippet_path(snippet) visit snippet_path(snippet)
end end
step 'I visit snippet page "Internal personal snippet one"' do
visit snippet_path(internal_snippet)
end
def snippet def snippet
@snippet ||= PersonalSnippet.find_by!(title: "Personal snippet one") @snippet ||= PersonalSnippet.find_by!(title: "Personal snippet one")
end end
def internal_snippet
@snippet ||= PersonalSnippet.find_by!(title: "Internal personal snippet one")
end
end end
...@@ -40,11 +40,11 @@ describe Profiles::TwoFactorAuthsController do ...@@ -40,11 +40,11 @@ describe Profiles::TwoFactorAuthsController do
expect(user).to receive(:valid_otp?).with(pin).and_return(true) expect(user).to receive(:valid_otp?).with(pin).and_return(true)
end end
it 'sets otp_required_for_login' do it 'sets two_factor_enabled' do
go go
user.reload user.reload
expect(user.otp_required_for_login).to eq true expect(user).to be_two_factor_enabled
end end
it 'presents plaintext codes for the user to save' do it 'presents plaintext codes for the user to save' do
...@@ -109,13 +109,13 @@ describe Profiles::TwoFactorAuthsController do ...@@ -109,13 +109,13 @@ describe Profiles::TwoFactorAuthsController do
let!(:codes) { user.generate_otp_backup_codes! } let!(:codes) { user.generate_otp_backup_codes! }
it 'clears all 2FA-related fields' do it 'clears all 2FA-related fields' do
expect(user.otp_required_for_login).to eq true expect(user).to be_two_factor_enabled
expect(user.otp_backup_codes).not_to be_nil expect(user.otp_backup_codes).not_to be_nil
expect(user.encrypted_otp_secret).not_to be_nil expect(user.encrypted_otp_secret).not_to be_nil
delete :destroy delete :destroy
expect(user.otp_required_for_login).to eq false expect(user).not_to be_two_factor_enabled
expect(user.otp_backup_codes).to be_nil expect(user.otp_backup_codes).to be_nil
expect(user.encrypted_otp_secret).to be_nil expect(user.encrypted_otp_secret).to be_nil
end end
......
...@@ -30,7 +30,7 @@ FactoryGirl.define do ...@@ -30,7 +30,7 @@ FactoryGirl.define do
trait :two_factor do trait :two_factor do
before(:create) do |user| before(:create) do |user|
user.otp_required_for_login = true user.two_factor_enabled = true
user.otp_secret = User.generate_otp_secret(32) user.otp_secret = User.generate_otp_secret(32)
end end
end end
......
...@@ -63,15 +63,35 @@ describe "Admin::Users", feature: true do ...@@ -63,15 +63,35 @@ describe "Admin::Users", feature: true do
end end
describe "GET /admin/users/:id" do describe "GET /admin/users/:id" do
before do it "should have user info" do
visit admin_users_path visit admin_users_path
click_link "#{@user.name}" click_link @user.name
end
it "should have user info" do
expect(page).to have_content(@user.email) expect(page).to have_content(@user.email)
expect(page).to have_content(@user.name) expect(page).to have_content(@user.name)
end end
describe 'Two-factor Authentication status' do
it 'shows when enabled' do
@user.update_attribute(:two_factor_enabled, true)
visit admin_user_path(@user)
expect_two_factor_status('Enabled')
end
it 'shows when disabled' do
visit admin_user_path(@user)
expect_two_factor_status('Disabled')
end
def expect_two_factor_status(status)
page.within('.two-factor-status') do
expect(page).to have_content(status)
end
end
end
end end
describe "GET /admin/users/:id/edit" do describe "GET /admin/users/:id/edit" do
......
#tree-content-holder
.file-content
.line-numbers
- 1.upto(25) do |i|
%a{href: "#L#{i}", id: "L#{i}", 'data-line-number' => i}= i
%pre.code.highlight
%code
- 1.upto(25) do |i|
%span.line{id: "LC#{i}"}= "Line #{i}"
#= require line_highlighter
describe 'LineHighlighter', ->
fixture.preload('line_highlighter.html')
clickLine = (number, eventData = {}) ->
if $.isEmptyObject(eventData)
$("#L#{number}").mousedown().click()
else
e = $.Event 'mousedown', eventData
$("#L#{number}").trigger(e).click()
beforeEach ->
fixture.load('line_highlighter.html')
@class = new LineHighlighter()
@css = @class.highlightClass
@spies = {
__setLocationHash__: spyOn(@class, '__setLocationHash__').and.callFake ->
}
describe 'behavior', ->
it 'highlights one line given in the URL hash', ->
new LineHighlighter('#L13')
expect($('#LC13')).toHaveClass(@css)
it 'highlights a range of lines given in the URL hash', ->
new LineHighlighter('#L5-25')
expect($(".#{@css}").length).toBe(21)
expect($("#LC#{line}")).toHaveClass(@css) for line in [5..25]
it 'scrolls to the first highlighted line on initial load', ->
spy = spyOn($, 'scrollTo')
new LineHighlighter('#L5-25')
expect(spy).toHaveBeenCalledWith('#L5', jasmine.anything())
it 'discards click events', ->
spy = spyOnEvent('a[data-line-number]', 'click')
clickLine(13)
expect(spy).toHaveBeenPrevented()
it 'handles garbage input from the hash', ->
func = -> new LineHighlighter('#tree-content-holder')
expect(func).not.toThrow()
describe '#clickHandler', ->
it 'discards the mousedown event', ->
spy = spyOnEvent('a[data-line-number]', 'mousedown')
clickLine(13)
expect(spy).toHaveBeenPrevented()
describe 'without shiftKey', ->
it 'highlights one line when clicked', ->
clickLine(13)
expect($('#LC13')).toHaveClass(@css)
it 'unhighlights previously highlighted lines', ->
clickLine(13)
clickLine(20)
expect($('#LC13')).not.toHaveClass(@css)
expect($('#LC20')).toHaveClass(@css)
it 'sets the hash', ->
spy = spyOn(@class, 'setHash').and.callThrough()
clickLine(13)
expect(spy).toHaveBeenCalledWith(13)
describe 'with shiftKey', ->
it 'sets the hash', ->
spy = spyOn(@class, 'setHash').and.callThrough()
clickLine(13)
clickLine(20, shiftKey: true)
expect(spy).toHaveBeenCalledWith(13)
expect(spy).toHaveBeenCalledWith(13, 20)
describe 'without existing highlight', ->
it 'highlights the clicked line', ->
clickLine(13, shiftKey: true)
expect($('#LC13')).toHaveClass(@css)
expect($(".#{@css}").length).toBe(1)
it 'sets the hash', ->
spy = spyOn(@class, 'setHash')
clickLine(13, shiftKey: true)
expect(spy).toHaveBeenCalledWith(13)
describe 'with existing single-line highlight', ->
it 'uses existing line as last line when target is lesser', ->
clickLine(20)
clickLine(15, shiftKey: true)
expect($(".#{@css}").length).toBe(6)
expect($("#LC#{line}")).toHaveClass(@css) for line in [15..20]
it 'uses existing line as first line when target is greater', ->
clickLine(5)
clickLine(10, shiftKey: true)
expect($(".#{@css}").length).toBe(6)
expect($("#LC#{line}")).toHaveClass(@css) for line in [5..10]
describe 'with existing multi-line highlight', ->
beforeEach ->
clickLine(10, shiftKey: true)
clickLine(13, shiftKey: true)
it 'uses target as first line when it is less than existing first line', ->
clickLine(5, shiftKey: true)
expect($(".#{@css}").length).toBe(6)
expect($("#LC#{line}")).toHaveClass(@css) for line in [5..10]
it 'uses target as last line when it is greater than existing first line', ->
clickLine(15, shiftKey: true)
expect($(".#{@css}").length).toBe(6)
expect($("#LC#{line}")).toHaveClass(@css) for line in [10..15]
describe '#hashToRange', ->
beforeEach ->
@subject = @class.hashToRange
it 'extracts a single line number from the hash', ->
expect(@subject('#L5')).toEqual([5, null])
it 'extracts a range of line numbers from the hash', ->
expect(@subject('#L5-15')).toEqual([5, 15])
it 'returns [null, null] when the hash is not a line number', ->
expect(@subject('#foo')).toEqual([null, null])
describe '#highlightLine', ->
beforeEach ->
@subject = @class.highlightLine
it 'highlights the specified line', ->
@subject(13)
expect($('#LC13')).toHaveClass(@css)
it 'accepts a String-based number', ->
@subject('13')
expect($('#LC13')).toHaveClass(@css)
describe '#setHash', ->
beforeEach ->
@subject = @class.setHash
it 'sets the location hash for a single line', ->
@subject(5)
expect(@spies.__setLocationHash__).toHaveBeenCalledWith('#L5')
it 'sets the location hash for a range', ->
@subject(5, 15)
expect(@spies.__setLocationHash__).toHaveBeenCalledWith('#L5-15')
...@@ -210,6 +210,30 @@ describe User do ...@@ -210,6 +210,30 @@ describe User do
end end
end end
describe '#two_factor_enabled' do
it 'returns two-factor authentication status' do
enabled = build_stubbed(:user, two_factor_enabled: true)
disabled = build_stubbed(:user)
expect(enabled).to be_two_factor_enabled
expect(disabled).not_to be_two_factor_enabled
end
end
describe '#two_factor_enabled=' do
it 'enables two-factor authentication' do
user = build_stubbed(:user, two_factor_enabled: false)
expect { user.two_factor_enabled = true }.
to change { user.two_factor_enabled? }.to(true)
end
it 'disables two-factor authentication' do
user = build_stubbed(:user, two_factor_enabled: true)
expect { user.two_factor_enabled = false }.
to change { user.two_factor_enabled? }.to(false)
end
end
describe 'authentication token' do describe 'authentication token' do
it "should have authentication token" do it "should have authentication token" do
user = create(:user) user = create(:user)
......
...@@ -39,4 +39,9 @@ module LoginHelpers ...@@ -39,4 +39,9 @@ module LoginHelpers
def logout def logout
find(:css, ".fa.fa-sign-out").click find(:css, ".fa.fa-sign-out").click
end end
# Logout without JavaScript driver
def logout_direct
page.driver.submit :delete, '/users/sign_out', {}
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