Commit 405b82af authored by Tomasz Maczukin's avatar Tomasz Maczukin

Merge branch 'master' into ci/api-builds

* master: (51 commits)
  Fix version
  Fix specs and rubocop warnings
  Improve the consistency of commit titles, branch names, tag names, issue/MR titles, on their respective project pages
  fixed LDAP activation on login to use new ldap_blocked state
  Fix Admin/Users view to position buttons without spacing magic
  Update to Go 1.5.3
  Fix the undefinded variable error in Project's safe_import_url method
  Update CHANGELOG [ci skip]
  Add some cosmetic changes to variables API documentation [ci skip]
  Fix misaligned edit button in milestone collection partial
  Update button styles for Milestones#show
  Modify builds API documentation style [ci skip]
  Modify :ci_variable factory
  Ensure the API doesn't return notes that the current user shouldn't see
  Add 'Build' prefix to Variables entry name in API docs index
  Fix some typos
  Add spec for Note#cross_reference_not_visible_for?
  Remove (invalid) timestamp formatting
  Move `BroadcastMessage#status` to a helper since it's presentational
  Update CHANGELOG
  ...

Conflicts:
	doc/api/README.md
	lib/api/api.rb
	lib/api/entities.rb
parents 6a98fb03 f981da44
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 8.4.0 (unreleased) v 8.4.0 (unreleased)
- Improve the consistency of commit titles, branch names, tag names, issue/MR titles, on their respective project pages
- Autocomplete data is now always loaded, instead of when focusing a comment text area (Yorick Peterse) - Autocomplete data is now always loaded, instead of when focusing a comment text area (Yorick Peterse)
- Improved performance of finding issues for an entire group (Yorick Peterse) - Improved performance of finding issues for an entire group (Yorick Peterse)
- Added custom application performance measuring system powered by InfluxDB (Yorick Peterse) - Added custom application performance measuring system powered by InfluxDB (Yorick Peterse)
...@@ -42,8 +43,11 @@ v 8.4.0 (unreleased) ...@@ -42,8 +43,11 @@ v 8.4.0 (unreleased)
- Ajax filter by message for commits page - Ajax filter by message for commits page
- API: Add support for deleting a tag via the API (Robert Schilling) - API: Add support for deleting a tag via the API (Robert Schilling)
- Allow subsequent validations in CI Linter - Allow subsequent validations in CI Linter
- Show referenced MRs & Issues only when the current viewer can access them
- Fix Encoding::CompatibilityError bug when markdown content has some complex URL (Jason Lee) - Fix Encoding::CompatibilityError bug when markdown content has some complex URL (Jason Lee)
- Add API support for managing builds of a project - Add API support for managing builds of a project
- Add API support for managing build variables of project
- Allow broadcast messages to be edited
v 8.3.4 v 8.3.4
- Use gitlab-workhorse 0.5.4 (fixes API routing bug) - Use gitlab-workhorse 0.5.4 (fixes API routing bug)
......
...@@ -10,19 +10,19 @@ class @Admin ...@@ -10,19 +10,19 @@ class @Admin
$('body').on 'click', '.js-toggle-colors-link', (e) -> $('body').on 'click', '.js-toggle-colors-link', (e) ->
e.preventDefault() e.preventDefault()
$('.js-toggle-colors-link').hide() $('.js-toggle-colors-container').toggle()
$('.js-toggle-colors-container').show()
$('input#broadcast_message_color').on 'input', -> $('input#broadcast_message_color').on 'input', ->
previewColor = $('input#broadcast_message_color').val() previewColor = $(@).val()
$('div.broadcast-message-preview').css('background-color', previewColor) $('div.broadcast-message-preview').css('background-color', previewColor)
$('input#broadcast_message_font').on 'input', -> $('input#broadcast_message_font').on 'input', ->
previewColor = $('input#broadcast_message_font').val() previewColor = $(@).val()
$('div.broadcast-message-preview').css('color', previewColor) $('div.broadcast-message-preview').css('color', previewColor)
$('textarea#broadcast_message_message').on 'input', -> $('textarea#broadcast_message_message').on 'input', ->
previewMessage = $('textarea#broadcast_message_message').val() previewMessage = $(@).val()
previewMessage = "Your message here" if previewMessage.trim() == ''
$('div.broadcast-message-preview span').text(previewMessage) $('div.broadcast-message-preview span').text(previewMessage)
$('.log-tabs a').click (e) -> $('.log-tabs a').click (e) ->
......
...@@ -131,6 +131,12 @@ ...@@ -131,6 +131,12 @@
&:last-child { &:last-child {
margin-right: 0px; margin-right: 0px;
} }
&.btn-xs {
margin-right: 3px;
}
}
&.disabled {
pointer-events: auto !important;
} }
} }
......
...@@ -78,6 +78,10 @@ label { ...@@ -78,6 +78,10 @@ label {
padding: 8px $gl-padding; padding: 8px $gl-padding;
} }
.form-control-inline {
display: inline;
}
.wiki-content { .wiki-content {
margin-top: 35px; margin-top: 35px;
} }
......
.branch-name{
font-weight: 600;
}
...@@ -2,6 +2,10 @@ ...@@ -2,6 +2,10 @@
display: block; display: block;
} }
.commit-row-title .commit-title {
font-weight: 600;
}
.commit-author, .commit-committer{ .commit-author, .commit-committer{
display: block; display: block;
color: #999; color: #999;
......
...@@ -11,3 +11,8 @@ ...@@ -11,3 +11,8 @@
height: 42px; height: 42px;
} }
} }
.content-list .group-name {
font-weight: 600;
color: #4c4e54;
}
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
.issue-title { .issue-title {
margin-bottom: 5px; margin-bottom: 5px;
font-size: $list-font-size; font-size: $list-font-size;
font-weight: bold; font-weight: 600;
} }
.issue-info { .issue-info {
......
...@@ -150,7 +150,7 @@ ...@@ -150,7 +150,7 @@
.merge-request-title { .merge-request-title {
margin-bottom: 5px; margin-bottom: 5px;
font-size: $list-font-size; font-size: $list-font-size;
font-weight: bold; font-weight: 600;
} }
.merge-request-info { .merge-request-info {
......
.tag-name{
font-weight: 600;
}
class Admin::BroadcastMessagesController < Admin::ApplicationController class Admin::BroadcastMessagesController < Admin::ApplicationController
before_action :broadcast_messages before_action :finder, only: [:edit, :update, :destroy]
def index def index
@broadcast_messages = BroadcastMessage.reorder("starts_at ASC").page(params[:page])
@broadcast_message = BroadcastMessage.new @broadcast_message = BroadcastMessage.new
end end
def edit
end
def create def create
@broadcast_message = BroadcastMessage.new(broadcast_message_params) @broadcast_message = BroadcastMessage.new(broadcast_message_params)
...@@ -15,8 +19,16 @@ class Admin::BroadcastMessagesController < Admin::ApplicationController ...@@ -15,8 +19,16 @@ class Admin::BroadcastMessagesController < Admin::ApplicationController
end end
end end
def update
if @broadcast_message.update(broadcast_message_params)
redirect_to admin_broadcast_messages_path, notice: 'Broadcast Message was successfully updated.'
else
render :edit
end
end
def destroy def destroy
BroadcastMessage.find(params[:id]).destroy @broadcast_message.destroy
respond_to do |format| respond_to do |format|
format.html { redirect_back_or_default(default: { action: 'index' }) } format.html { redirect_back_or_default(default: { action: 'index' }) }
...@@ -26,14 +38,17 @@ class Admin::BroadcastMessagesController < Admin::ApplicationController ...@@ -26,14 +38,17 @@ class Admin::BroadcastMessagesController < Admin::ApplicationController
protected protected
def broadcast_messages def finder
@broadcast_messages ||= BroadcastMessage.order("starts_at DESC").page(params[:page]) @broadcast_message = BroadcastMessage.find(params[:id])
end end
def broadcast_message_params def broadcast_message_params
params.require(:broadcast_message).permit( params.require(:broadcast_message).permit(%i(
:alert_type, :color, :ends_at, :font, color
:message, :starts_at ends_at
) font
message
starts_at
))
end end
end end
...@@ -26,6 +26,7 @@ class Admin::IdentitiesController < Admin::ApplicationController ...@@ -26,6 +26,7 @@ class Admin::IdentitiesController < Admin::ApplicationController
def update def update
if @identity.update_attributes(identity_params) if @identity.update_attributes(identity_params)
RepairLdapBlockedUserService.new(@user).execute
redirect_to admin_user_identities_path(@user), notice: 'User identity was successfully updated.' redirect_to admin_user_identities_path(@user), notice: 'User identity was successfully updated.'
else else
render :edit render :edit
...@@ -34,6 +35,7 @@ class Admin::IdentitiesController < Admin::ApplicationController ...@@ -34,6 +35,7 @@ class Admin::IdentitiesController < Admin::ApplicationController
def destroy def destroy
if @identity.destroy if @identity.destroy
RepairLdapBlockedUserService.new(@user).execute
redirect_to admin_user_identities_path(@user), notice: 'User identity was successfully removed.' redirect_to admin_user_identities_path(@user), notice: 'User identity was successfully removed.'
else else
redirect_to admin_user_identities_path(@user), alert: 'Failed to remove user identity.' redirect_to admin_user_identities_path(@user), alert: 'Failed to remove user identity.'
......
...@@ -40,7 +40,9 @@ class Admin::UsersController < Admin::ApplicationController ...@@ -40,7 +40,9 @@ class Admin::UsersController < Admin::ApplicationController
end end
def unblock def unblock
if user.activate if user.ldap_blocked?
redirect_back_or_admin_user(alert: "This user cannot be unlocked manually from GitLab")
elsif user.activate
redirect_back_or_admin_user(notice: "Successfully unblocked") redirect_back_or_admin_user(notice: "Successfully unblocked")
else else
redirect_back_or_admin_user(alert: "Error occurred. User was not unblocked") redirect_back_or_admin_user(alert: "Error occurred. User was not unblocked")
......
...@@ -61,7 +61,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -61,7 +61,7 @@ class Projects::IssuesController < Projects::ApplicationController
@note = @project.notes.new(noteable: @issue) @note = @project.notes.new(noteable: @issue)
@notes = @issue.notes.nonawards.with_associations.fresh @notes = @issue.notes.nonawards.with_associations.fresh
@noteable = @issue @noteable = @issue
@merge_requests = @issue.referenced_merge_requests @merge_requests = @issue.referenced_merge_requests(current_user)
respond_with(@issue) respond_with(@issue)
end end
......
...@@ -181,10 +181,6 @@ module ApplicationHelper ...@@ -181,10 +181,6 @@ module ApplicationHelper
end end
end end
def broadcast_message
BroadcastMessage.current
end
# Render a `time` element with Javascript-based relative date and tooltip # Render a `time` element with Javascript-based relative date and tooltip
# #
# time - Time object # time - Time object
......
module BroadcastMessagesHelper module BroadcastMessagesHelper
def broadcast_styling(broadcast_message) def broadcast_message(message = BroadcastMessage.current)
styling = '' return unless message.present?
content_tag :div, class: 'broadcast-message', style: broadcast_message_style(message) do
icon('bullhorn') << ' ' << message.message
end
end
def broadcast_message_style(broadcast_message)
style = ''
if broadcast_message.color.present? if broadcast_message.color.present?
styling << "background-color: #{broadcast_message.color}" style << "background-color: #{broadcast_message.color}"
styling << '; ' if broadcast_message.font.present? style << '; ' if broadcast_message.font.present?
end end
if broadcast_message.font.present? if broadcast_message.font.present?
styling << "color: #{broadcast_message.font}" style << "color: #{broadcast_message.font}"
end end
styling style
end
def broadcast_message_status(broadcast_message)
if broadcast_message.active?
'Active'
elsif broadcast_message.ended?
'Expired'
else
'Pending'
end
end end
end end
...@@ -6,7 +6,6 @@ ...@@ -6,7 +6,6 @@
# message :text not null # message :text not null
# starts_at :datetime # starts_at :datetime
# ends_at :datetime # ends_at :datetime
# alert_type :integer
# created_at :datetime # created_at :datetime
# updated_at :datetime # updated_at :datetime
# color :string(255) # color :string(255)
...@@ -23,7 +22,22 @@ class BroadcastMessage < ActiveRecord::Base ...@@ -23,7 +22,22 @@ class BroadcastMessage < ActiveRecord::Base
validates :color, allow_blank: true, color: true validates :color, allow_blank: true, color: true
validates :font, allow_blank: true, color: true validates :font, allow_blank: true, color: true
default_value_for :color, '#E75E40'
default_value_for :font, '#FFFFFF'
def self.current def self.current
where("ends_at > :now AND starts_at < :now", now: Time.zone.now).last where("ends_at > :now AND starts_at <= :now", now: Time.zone.now).last
end
def active?
started? && !ended?
end
def started?
Time.zone.now >= starts_at
end
def ended?
ends_at < Time.zone.now
end end
end end
...@@ -18,8 +18,12 @@ module Ci ...@@ -18,8 +18,12 @@ module Ci
belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id
validates_presence_of :key
validates_uniqueness_of :key, scope: :gl_project_id validates_uniqueness_of :key, scope: :gl_project_id
validates :key,
presence: true,
length: { within: 0..255 },
format: { with: /\A[a-zA-Z0-9_]+\z/,
message: "can contain only letters, digits and '_'." }
attr_encrypted :value, mode: :per_attribute_iv_and_salt, key: Gitlab::Application.secrets.db_key_base attr_encrypted :value, mode: :per_attribute_iv_and_salt, key: Gitlab::Application.secrets.db_key_base
end end
......
...@@ -18,4 +18,8 @@ class Identity < ActiveRecord::Base ...@@ -18,4 +18,8 @@ class Identity < ActiveRecord::Base
validates :provider, presence: true validates :provider, presence: true
validates :extern_uid, allow_blank: true, uniqueness: { scope: :provider } validates :extern_uid, allow_blank: true, uniqueness: { scope: :provider }
validates :user_id, uniqueness: { scope: :provider } validates :user_id, uniqueness: { scope: :provider }
def ldap?
provider.starts_with?('ldap')
end
end end
...@@ -85,10 +85,10 @@ class Issue < ActiveRecord::Base ...@@ -85,10 +85,10 @@ class Issue < ActiveRecord::Base
reference reference
end end
def referenced_merge_requests def referenced_merge_requests(current_user = nil)
Gitlab::ReferenceExtractor.lazily do Gitlab::ReferenceExtractor.lazily do
[self, *notes].flat_map do |note| [self, *notes].flat_map do |note|
note.all_references.merge_requests note.all_references(current_user).merge_requests
end end
end.sort_by(&:iid) end.sort_by(&:iid)
end end
......
...@@ -358,6 +358,10 @@ class Note < ActiveRecord::Base ...@@ -358,6 +358,10 @@ class Note < ActiveRecord::Base
!system? && !is_award !system? && !is_award
end end
def cross_reference_not_visible_for?(user)
cross_reference? && referenced_mentionables(user).empty?
end
# Checks if note is an award added as a comment # Checks if note is an award added as a comment
# #
# If note is an award, this method sets is_award to true # If note is an award, this method sets is_award to true
......
...@@ -397,7 +397,7 @@ class Project < ActiveRecord::Base ...@@ -397,7 +397,7 @@ class Project < ActiveRecord::Base
result.password = '*****' unless result.password.nil? result.password = '*****' unless result.password.nil?
result.to_s result.to_s
rescue rescue
original_url self.import_url
end end
def check_limit def check_limit
......
...@@ -196,10 +196,22 @@ class User < ActiveRecord::Base ...@@ -196,10 +196,22 @@ class User < ActiveRecord::Base
state_machine :state, initial: :active do state_machine :state, initial: :active do
event :block do event :block do
transition active: :blocked transition active: :blocked
transition ldap_blocked: :blocked
end
event :ldap_block do
transition active: :ldap_blocked
end end
event :activate do event :activate do
transition blocked: :active transition blocked: :active
transition ldap_blocked: :active
end
state :blocked, :ldap_blocked do
def blocked?
true
end
end end
end end
...@@ -207,7 +219,7 @@ class User < ActiveRecord::Base ...@@ -207,7 +219,7 @@ class User < ActiveRecord::Base
# Scopes # Scopes
scope :admins, -> { where(admin: true) } scope :admins, -> { where(admin: true) }
scope :blocked, -> { with_state(:blocked) } scope :blocked, -> { with_states(:blocked, :ldap_blocked) }
scope :active, -> { with_state(:active) } scope :active, -> { with_state(:active) }
scope :not_in_project, ->(project) { project.users.present? ? where("id not in (:ids)", ids: project.users.map(&:id) ) : all } scope :not_in_project, ->(project) { project.users.present? ? where("id not in (:ids)", ids: project.users.map(&:id) ) : all }
scope :without_projects, -> { where('id NOT IN (SELECT DISTINCT(user_id) FROM members)') } scope :without_projects, -> { where('id NOT IN (SELECT DISTINCT(user_id) FROM members)') }
......
class RepairLdapBlockedUserService
attr_accessor :user
def initialize(user)
@user = user
end
def execute
user.block if ldap_hard_blocked?
end
private
def ldap_hard_blocked?
user.ldap_blocked? && !user.ldap_user?
end
end
.broadcast-message-preview{ style: broadcast_message_style(@broadcast_message) }
= icon('bullhorn')
%span= @broadcast_message.message || "Your message here"
= form_for [:admin, @broadcast_message], html: { class: 'broadcast-message-form form-horizontal js-requires-input'} do |f|
-if @broadcast_message.errors.any?
.alert.alert-danger
- @broadcast_message.errors.full_messages.each do |msg|
%p= msg
.form-group
= f.label :message, class: 'control-label'
.col-sm-10
= f.text_area :message, class: "form-control js-quick-submit", rows: 2, required: true
.form-group.js-toggle-colors-container
.col-sm-10.col-sm-offset-2
= link_to 'Customize colors', '#', class: 'js-toggle-colors-link'
.form-group.js-toggle-colors-container.hide
= f.label :color, "Background Color", class: 'control-label'
.col-sm-10
= f.color_field :color, class: "form-control"
.form-group.js-toggle-colors-container.hide
= f.label :font, "Font Color", class: 'control-label'
.col-sm-10
= f.color_field :font, class: "form-control"
.form-group
= f.label :starts_at, class: 'control-label'
.col-sm-10.datetime-controls
= f.datetime_select :starts_at, {}, class: 'form-control form-control-inline'
.form-group
= f.label :ends_at, class: 'control-label'
.col-sm-10.datetime-controls
= f.datetime_select :ends_at, {}, class: 'form-control form-control-inline'
.form-actions
- if @broadcast_message.persisted?
= f.submit "Update broadcast message", class: "btn btn-create"
- else
= f.submit "Add broadcast message", class: "btn btn-create"
- page_title "Broadcast Messages"
= render 'form'
- page_title "Broadcast Messages" - page_title "Broadcast Messages"
%h3.page-title %h3.page-title
Broadcast Messages Broadcast Messages
%p.light %p.light
Broadcast messages are displayed for every user and can be used to notify users about scheduled maintenance, recent upgrades and more. Broadcast messages are displayed for every user and can be used to notify
.broadcast-message-preview users about scheduled maintenance, recent upgrades and more.
%i.fa.fa-bullhorn
%span Your message here
= form_for [:admin, @broadcast_message], html: { class: 'broadcast-message-form form-horizontal'} do |f|
-if @broadcast_message.errors.any?
.alert.alert-danger
- @broadcast_message.errors.full_messages.each do |msg|
%p= msg
.form-group
= f.label :message, class: 'control-label'
.col-sm-10
= f.text_area :message, class: "form-control", rows: 2, required: true
%div
= link_to '#', class: 'js-toggle-colors-link' do
Customize colors
.form-group.js-toggle-colors-container.hide
= f.label :color, "Background Color", class: 'control-label'
.col-sm-10
= f.color_field :color, value: "#eb9532", class: "form-control"
.form-group.js-toggle-colors-container.hide
= f.label :font, "Font Color", class: 'control-label'
.col-sm-10
= f.color_field :font, value: "#FFFFFF", class: "form-control"
.form-group
= f.label :starts_at, class: 'control-label'
.col-sm-10.datetime-controls
= f.datetime_select :starts_at
.form-group
= f.label :ends_at, class: 'control-label'
.col-sm-10.datetime-controls
= f.datetime_select :ends_at
.form-actions
= f.submit "Add broadcast message", class: "btn btn-create"
-if @broadcast_messages.any? = render 'form'
%ul.bordered-list.broadcast-messages
- @broadcast_messages.each do |broadcast_message|
%li
.pull-right
- if broadcast_message.starts_at
%strong
#{broadcast_message.starts_at.to_s(:short)}
\...
- if broadcast_message.ends_at
%strong
#{broadcast_message.ends_at.to_s(:short)}
&nbsp;
= link_to [:admin, broadcast_message], method: :delete, remote: true, class: 'remove-row btn btn-xs' do
%i.fa.fa-times.cred
.message= broadcast_message.message %br.clearfix
-if @broadcast_messages.any?
%table.table
%thead
%tr
%th Status
%th Preview
%th Starts
%th Ends
%th &nbsp;
%tbody
- @broadcast_messages.each do |message|
%tr
%td
= broadcast_message_status(message)
%td
= broadcast_message(message)
%td
= message.starts_at
%td
= message.ends_at
%td
= link_to icon('pencil-square-o'), edit_admin_broadcast_message_path(message), title: 'Edit', class: 'btn btn-xs'
= link_to icon('times'), admin_broadcast_message_path(message), method: :delete, remote: true, title: 'Remove', class: 'js-remove-tr btn btn-xs btn-danger'
= paginate @broadcast_messages = paginate @broadcast_messages
...@@ -88,14 +88,19 @@ ...@@ -88,14 +88,19 @@
%i.fa.fa-envelope %i.fa.fa-envelope
= mail_to user.email, user.email, class: 'light' = mail_to user.email, user.email, class: 'light'
&nbsp; &nbsp;
= link_to 'Edit', edit_admin_user_path(user), id: "edit_#{dom_id(user)}", class: "btn btn-xs" .pull-right
= link_to 'Edit', edit_admin_user_path(user), id: "edit_#{dom_id(user)}", class: 'btn-grouped btn btn-xs'
- unless user == current_user - unless user == current_user
- if user.blocked? - if user.ldap_blocked?
= link_to 'Unblock', unblock_admin_user_path(user), method: :put, class: "btn btn-xs btn-success" = link_to '#', title: 'Cannot unblock LDAP blocked users', data: {toggle: 'tooltip'}, class: 'btn-grouped btn btn-xs btn-success disabled' do
%i.fa.fa-lock
Unblock
- elsif user.blocked?
= link_to 'Unblock', unblock_admin_user_path(user), method: :put, class: 'btn-grouped btn btn-xs btn-success'
- else - else
= link_to 'Block', block_admin_user_path(user), data: {confirm: 'USER WILL BE BLOCKED! Are you sure?'}, method: :put, class: "btn btn-xs btn-warning" = link_to 'Block', block_admin_user_path(user), data: {confirm: 'USER WILL BE BLOCKED! Are you sure?'}, method: :put, class: 'btn-grouped btn btn-xs btn-warning'
- if user.access_locked? - if user.access_locked?
= link_to 'Unlock', unlock_admin_user_path(user), method: :put, class: "btn btn-xs btn-success", data: { confirm: 'Are you sure?' } = link_to 'Unlock', unlock_admin_user_path(user), method: :put, class: 'btn-grouped btn btn-xs btn-success', data: { confirm: 'Are you sure?' }
- if user.can_be_removed? - if user.can_be_removed?
= link_to 'Destroy', [:admin, user], data: { confirm: "USER #{user.name} WILL BE REMOVED! All issues, merge requests and groups linked to this user will also be removed! Maybe block the user instead? Are you sure?" }, method: :delete, class: "btn btn-xs btn-remove" = link_to 'Destroy', [:admin, user], data: { confirm: "USER #{user.name} WILL BE REMOVED! All issues, merge requests and groups linked to this user will also be removed! Maybe block the user instead? Are you sure?" }, method: :delete, class: 'btn-grouped btn btn-xs btn-remove'
= paginate @users, theme: "gitlab" = paginate @users, theme: "gitlab"
- if broadcast_message.present? = broadcast_message
.broadcast-message{ style: broadcast_styling(broadcast_message) }
%i.fa.fa-bullhorn
= broadcast_message.message
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
%li(class="js-branch-#{branch.name}") %li(class="js-branch-#{branch.name}")
%div %div
= link_to namespace_project_tree_path(@project.namespace, @project, branch.name) do = link_to namespace_project_tree_path(@project.namespace, @project, branch.name) do
%strong.str-truncated= branch.name .branch-name.str-truncated= branch.name
&nbsp; &nbsp;
- if branch.name == @repository.root_ref - if branch.name == @repository.root_ref
%span.label.label-primary default %span.label.label-primary default
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
= cache(cache_key) do = cache(cache_key) do
%li.commit.js-toggle-container{ id: "commit-#{commit.short_id}" } %li.commit.js-toggle-container{ id: "commit-#{commit.short_id}" }
.commit-row-title .commit-row-title
%strong.str-truncated .commit-title.str-truncated
= link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message" = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message"
- if commit.description? - if commit.description?
%a.text-expander.js-toggle-button ... %a.text-expander.js-toggle-button ...
......
...@@ -21,10 +21,11 @@ ...@@ -21,10 +21,11 @@
= render 'shared/milestone_expired', milestone: milestone = render 'shared/milestone_expired', milestone: milestone
.col-sm-6 .col-sm-6
- if can?(current_user, :admin_milestone, milestone.project) and milestone.active? - if can?(current_user, :admin_milestone, milestone.project) and milestone.active?
= link_to edit_namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), class: "btn btn-xs edit-milestone-link btn-grouped" do = link_to edit_namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), class: "btn btn-xs" do
%i.fa.fa-pencil-square-o = icon('pencil-square-o')
Edit Edit
\
= link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-xs btn-close" = link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-xs btn-close"
= link_to namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-xs btn-remove" do = link_to namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-xs btn-remove" do
%i.fa.fa-trash-o = icon('trash-o')
Delete Delete
...@@ -20,16 +20,16 @@ ...@@ -20,16 +20,16 @@
.pull-right .pull-right
- if can?(current_user, :admin_milestone, @project) - if can?(current_user, :admin_milestone, @project)
- if @milestone.active? - if @milestone.active?
= link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :close }), method: :put, class: "btn btn-close btn-grouped" = link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :close }), method: :put, class: "btn btn-close btn-nr btn-grouped"
- else - else
= link_to 'Reopen Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-reopen btn-grouped" = link_to 'Reopen Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-reopen btn-nr btn-grouped"
= link_to namespace_project_milestone_path(@project.namespace, @project, @milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-grouped btn-remove" do = link_to namespace_project_milestone_path(@project.namespace, @project, @milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-grouped btn-nr btn-remove" do
%i.fa.fa-trash-o = icon('trash-o')
Delete Delete
= link_to edit_namespace_project_milestone_path(@project.namespace, @project, @milestone), class: "btn btn-grouped" do = link_to edit_namespace_project_milestone_path(@project.namespace, @project, @milestone), class: "btn btn-grouped btn-nr" do
%i.fa.fa-pencil-square-o = icon('pencil-square-o')
Edit Edit
.detail-page-description.gray-content-block.second-block .detail-page-description.gray-content-block.second-block
......
...@@ -2,10 +2,14 @@ ...@@ -2,10 +2,14 @@
- @discussions.each do |discussion_notes| - @discussions.each do |discussion_notes|
- note = discussion_notes.first - note = discussion_notes.first
- if note_for_main_target?(note) - if note_for_main_target?(note)
- next if note.cross_reference_not_visible_for?(current_user)
= render discussion_notes = render discussion_notes
- else - else
= render 'projects/notes/discussion', discussion_notes: discussion_notes = render 'projects/notes/discussion', discussion_notes: discussion_notes
- else - else
- @notes.each do |note| - @notes.each do |note|
- next unless note.author - next unless note.author
- next if note.cross_reference_not_visible_for?(current_user)
= render note = render note
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
%li %li
%div %div
= link_to namespace_project_tag_path(@project.namespace, @project, tag.name) do = link_to namespace_project_tag_path(@project.namespace, @project, tag.name) do
%strong .tag-name
= icon('tag') = icon('tag')
= tag.name = tag.name
- if tag.message.present? - if tag.message.present?
......
...@@ -17,8 +17,8 @@ ...@@ -17,8 +17,8 @@
.pull-right .pull-right
= link_to namespace_project_tag_path(@project.namespace, @project, @tag.name), class: 'btn btn-remove remove-row grouped has_tooltip', title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{@tag.name}' tag cannot be undone. Are you sure?" } do = link_to namespace_project_tag_path(@project.namespace, @project, @tag.name), class: 'btn btn-remove remove-row grouped has_tooltip', title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{@tag.name}' tag cannot be undone. Are you sure?" } do
%i.fa.fa-trash-o %i.fa.fa-trash-o
.title .tag-name.title
%strong= @tag.name = @tag.name
- if @tag.message.present? - if @tag.message.present?
%span.light %span.light
&nbsp; &nbsp;
......
...@@ -10,8 +10,7 @@ ...@@ -10,8 +10,7 @@
%i.fa.fa-sign-out %i.fa.fa-sign-out
= image_tag group_icon(group), class: "avatar s46 hidden-xs" = image_tag group_icon(group), class: "avatar s46 hidden-xs"
= link_to group, class: 'group-name' do = link_to group.name, group, class: 'group-name'
%strong= group.name
- if group_member - if group_member
as as
......
...@@ -219,7 +219,7 @@ Rails.application.routes.draw do ...@@ -219,7 +219,7 @@ Rails.application.routes.draw do
get :test get :test
end end
resources :broadcast_messages, only: [:index, :create, :destroy] resources :broadcast_messages, only: [:index, :edit, :create, :update, :destroy]
resource :logs, only: [:show] resource :logs, only: [:show]
resource :background_jobs, controller: 'background_jobs', only: [:show] resource :background_jobs, controller: 'background_jobs', only: [:show]
......
class RemoveAlertTypeFromBroadcastMessages < ActiveRecord::Migration
def change
remove_column :broadcast_messages, :alert_type, :integer
end
end
...@@ -82,7 +82,6 @@ ActiveRecord::Schema.define(version: 20160113111034) do ...@@ -82,7 +82,6 @@ ActiveRecord::Schema.define(version: 20160113111034) do
t.text "message", null: false t.text "message", null: false
t.datetime "starts_at" t.datetime "starts_at"
t.datetime "ends_at" t.datetime "ends_at"
t.integer "alert_type"
t.datetime "created_at" t.datetime "created_at"
t.datetime "updated_at" t.datetime "updated_at"
t.string "color" t.string "color"
......
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,7 @@
- [Settings](settings.md) - [Settings](settings.md)
- [Keys](keys.md) - [Keys](keys.md)
- [Builds](builds.md) - [Builds](builds.md)
- [Build Variables](build_variables.md)
## Clients ## Clients
......
# Build Variables
## List project variables
Get list of a project's build variables.
```
GET /projects/:id/variables
```
| Attribute | Type | required | Description |
|-----------|---------|----------|---------------------|
| `id` | integer | yes | The ID of a project |
```
curl -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/variables"
```
```json
[
{
"key": "TEST_VARIABLE_1",
"value": "TEST_1"
},
{
"key": "TEST_VARIABLE_2",
"value": "TEST_2"
}
]
```
## Show variable details
Get the details of a project's specific build variable.
```
GET /projects/:id/variables/:key
```
| Attribute | Type | required | Description |
|-----------|---------|----------|-----------------------|
| `id` | integer | yes | The ID of a project |
| `key` | string | yes | The `key` of a variable |
```
curl -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/variables/TEST_VARIABLE_1"
```
```json
{
"key": "TEST_VARIABLE_1",
"value": "TEST_1"
}
```
## Create variable
Create a new build variable.
```
POST /projects/:id/variables
```
| Attribute | Type | required | Description |
|-----------|---------|----------|-----------------------|
| `id` | integer | yes | The ID of a project |
| `key` | string | yes | The `key` of a variable; must have no more than 255 characters; only `A-Z`, `a-z`, `0-9`, and `_` are allowed |
| `value` | string | yes | The `value` of a variable |
```
curl -X POST -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/variables" -F "key=NEW_VARIABLE" -F "value=new value"
```
```json
{
"key": "NEW_VARIABLE",
"value": "new value"
}
```
## Update variable
Update a project's build variable.
```
PUT /projects/:id/variables/:key
```
| Attribute | Type | required | Description |
|-----------|---------|----------|-------------------------|
| `id` | integer | yes | The ID of a project |
| `key` | string | yes | The `key` of a variable |
| `value` | string | yes | The `value` of a variable |
```
curl -X PUT -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/variables/NEW_VARIABLE" -F "value=updated value"
```
```json
{
"key": "NEW_VARIABLE",
"value": "updated value"
}
```
## Remove variable
Remove a project's build variable.
```
DELETE /projects/:id/variables/:key
```
| Attribute | Type | required | Description |
|-----------|---------|----------|-------------------------|
| `id` | integer | yes | The ID of a project |
| `key` | string | yes | The `key` of a variable |
```
curl -X DELETE -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/variables/VARIABLE_1"
```
```json
{
"key": "VARIABLE_1",
"value": "VALUE_1"
}
```
...@@ -558,7 +558,8 @@ Parameters: ...@@ -558,7 +558,8 @@ Parameters:
- `uid` (required) - id of specified user - `uid` (required) - id of specified user
Will return `200 OK` on success, or `404 User Not Found` is user cannot be found. Will return `200 OK` on success, `404 User Not Found` is user cannot be found or
`403 Forbidden` when trying to block an already blocked user by LDAP synchronization.
## Unblock user ## Unblock user
...@@ -572,4 +573,5 @@ Parameters: ...@@ -572,4 +573,5 @@ Parameters:
- `uid` (required) - id of specified user - `uid` (required) - id of specified user
Will return `200 OK` on success, or `404 User Not Found` is user cannot be found. Will return `200 OK` on success, `404 User Not Found` is user cannot be found or
`403 Forbidden` when trying to unblock a user blocked by LDAP synchronization.
...@@ -135,11 +135,11 @@ gitlab-workhorse we need a Go compiler. The instructions below assume you ...@@ -135,11 +135,11 @@ gitlab-workhorse we need a Go compiler. The instructions below assume you
use 64-bit Linux. You can find downloads for other platforms at the [Go download use 64-bit Linux. You can find downloads for other platforms at the [Go download
page](https://golang.org/dl). page](https://golang.org/dl).
curl -O --progress https://storage.googleapis.com/golang/go1.5.1.linux-amd64.tar.gz curl -O --progress https://storage.googleapis.com/golang/go1.5.3.linux-amd64.tar.gz
echo '46eecd290d8803887dec718c691cc243f2175fe0 go1.5.1.linux-amd64.tar.gz' | shasum -c - && \ echo '43afe0c5017e502630b1aea4d44b8a7f059bf60d7f29dfd58db454d4e4e0ae53 go1.5.3.linux-amd64.tar.gz' | shasum -c - && \
sudo tar -C /usr/local -xzf go1.5.1.linux-amd64.tar.gz sudo tar -C /usr/local -xzf go1.5.3.linux-amd64.tar.gz
sudo ln -sf /usr/local/go/bin/{go,godoc,gofmt} /usr/local/bin/ sudo ln -sf /usr/local/go/bin/{go,godoc,gofmt} /usr/local/bin/
rm go1.5.1.linux-amd64.tar.gz rm go1.5.3.linux-amd64.tar.gz
## 4. System Users ## 4. System Users
......
...@@ -2,16 +2,11 @@ ...@@ -2,16 +2,11 @@
Feature: Admin Broadcast Messages Feature: Admin Broadcast Messages
Background: Background:
Given I sign in as an admin Given I sign in as an admin
And application already has admin messages And application already has a broadcast message
And I visit admin messages page And I visit admin messages page
Scenario: See broadcast messages list Scenario: See broadcast messages list
Then I should be all broadcast messages Then I should see all broadcast messages
Scenario: Create a broadcast message
When submit form with new broadcast message
Then I should be redirected to admin messages page
And I should see newly created broadcast message
Scenario: Create a customized broadcast message Scenario: Create a customized broadcast message
When submit form with new customized broadcast message When submit form with new customized broadcast message
...@@ -19,3 +14,14 @@ Feature: Admin Broadcast Messages ...@@ -19,3 +14,14 @@ Feature: Admin Broadcast Messages
And I should see newly created broadcast message And I should see newly created broadcast message
Then I visit dashboard page Then I visit dashboard page
And I should see a customized broadcast message And I should see a customized broadcast message
Scenario: Edit an existing broadcast message
When I edit an existing broadcast message
And I change the broadcast message text
Then I should be redirected to admin messages page
And I should see the updated broadcast message
Scenario: Remove an existing broadcast message
When I remove an existing broadcast message
Then I should be redirected to admin messages page
And I should not see the removed broadcast message
@project_issues
Feature: Project Issues References
Background:
Given I sign in as "John Doe"
And public project "Community"
And "John Doe" owns public project "Community"
And project "Community" has "Community issue" open issue
And I logout
And I sign in as "Mary Jane"
And private project "Enterprise"
And "Mary Jane" owns private project "Enterprise"
And project "Enterprise" has "Enterprise issue" open issue
And project "Enterprise" has "Enterprise fix" open merge request
And I visit issue page "Enterprise issue"
And I leave a comment referencing issue "Community issue"
And I visit merge request page "Enterprise fix"
And I leave a comment referencing issue "Community issue"
And I logout
@javascript
Scenario: Viewing the public issue as a "John Doe"
Given I sign in as "John Doe"
When I visit issue page "Community issue"
Then I should not see any related merge requests
And I should see no notes at all
@javascript
Scenario: Viewing the public issue as "Mary Jane"
Given I sign in as "Mary Jane"
When I visit issue page "Community issue"
Then I should see the "Enterprise fix" related merge request
And I should see a note linking to "Enterprise fix" merge request
And I should see a note linking to "Enterprise issue" issue
@project_merge_requests
Feature: Project Merge Requests References
Background:
Given I sign in as "John Doe"
And public project "Community"
And "John Doe" owns public project "Community"
And project "Community" has "Community fix" open merge request
And I logout
And I sign in as "Mary Jane"
And private project "Enterprise"
And "Mary Jane" owns private project "Enterprise"
And project "Enterprise" has "Enterprise issue" open issue
And project "Enterprise" has "Enterprise fix" open merge request
And I visit issue page "Enterprise issue"
And I leave a comment referencing issue "Community fix"
And I visit merge request page "Enterprise fix"
And I leave a comment referencing issue "Community fix"
And I logout
@javascript
Scenario: Viewing the public issue as a "John Doe"
Given I sign in as "John Doe"
When I visit issue page "Community fix"
Then I should see no notes at all
@javascript
Scenario: Viewing the public issue as "Mary Jane"
Given I sign in as "Mary Jane"
When I visit issue page "Community fix"
And I should see a note linking to "Enterprise fix" merge request
And I should see a note linking to "Enterprise issue" issue
class Spinach::Features::AdminBroadcastMessages < Spinach::FeatureSteps class Spinach::Features::AdminBroadcastMessages < Spinach::FeatureSteps
include SharedAuthentication include SharedAuthentication
include SharedPaths include SharedPaths
include SharedAdmin
step 'application already has admin messages' do step 'application already has a broadcast message' do
FactoryGirl.create(:broadcast_message, message: "Migration to new server") FactoryGirl.create(:broadcast_message, :expired, message: "Migration to new server")
end end
step 'I should be all broadcast messages' do step 'I should see all broadcast messages' do
expect(page).to have_content "Migration to new server" expect(page).to have_content "Migration to new server"
end end
step 'submit form with new broadcast message' do
fill_in 'broadcast_message_message', with: 'Application update from 4:00 CST to 5:00 CST'
select '2018', from: "broadcast_message_ends_at_1i"
click_button "Add broadcast message"
end
step 'I should be redirected to admin messages page' do step 'I should be redirected to admin messages page' do
expect(current_path).to eq admin_broadcast_messages_path expect(current_path).to eq admin_broadcast_messages_path
end end
...@@ -27,10 +20,9 @@ class Spinach::Features::AdminBroadcastMessages < Spinach::FeatureSteps ...@@ -27,10 +20,9 @@ class Spinach::Features::AdminBroadcastMessages < Spinach::FeatureSteps
step 'submit form with new customized broadcast message' do step 'submit form with new customized broadcast message' do
fill_in 'broadcast_message_message', with: 'Application update from 4:00 CST to 5:00 CST' fill_in 'broadcast_message_message', with: 'Application update from 4:00 CST to 5:00 CST'
click_link "Customize colors"
fill_in 'broadcast_message_color', with: '#f2dede' fill_in 'broadcast_message_color', with: '#f2dede'
fill_in 'broadcast_message_font', with: '#b94a48' fill_in 'broadcast_message_font', with: '#b94a48'
select '2018', from: "broadcast_message_ends_at_1i" select Date.today.next_year.year, from: "broadcast_message_ends_at_1i"
click_button "Add broadcast message" click_button "Add broadcast message"
end end
...@@ -38,4 +30,25 @@ class Spinach::Features::AdminBroadcastMessages < Spinach::FeatureSteps ...@@ -38,4 +30,25 @@ class Spinach::Features::AdminBroadcastMessages < Spinach::FeatureSteps
expect(page).to have_content 'Application update from 4:00 CST to 5:00 CST' expect(page).to have_content 'Application update from 4:00 CST to 5:00 CST'
expect(page).to have_selector %(div[style="background-color: #f2dede; color: #b94a48"]) expect(page).to have_selector %(div[style="background-color: #f2dede; color: #b94a48"])
end end
step 'I edit an existing broadcast message' do
click_link 'Edit'
end
step 'I change the broadcast message text' do
fill_in 'broadcast_message_message', with: 'Application update RIGHT NOW'
click_button 'Update broadcast message'
end
step 'I should see the updated broadcast message' do
expect(page).to have_content "Application update RIGHT NOW"
end
step 'I remove an existing broadcast message' do
click_link 'Remove'
end
step 'I should not see the removed broadcast message' do
expect(page).not_to have_content 'Migration to new server'
end
end end
class Spinach::Features::ProjectIssuesReferences < Spinach::FeatureSteps
include SharedAuthentication
include SharedIssuable
include SharedNote
include SharedProject
include SharedUser
end
class Spinach::Features::ProjectMergeRequestsReferences < Spinach::FeatureSteps
include SharedAuthentication
include SharedIssuable
include SharedNote
include SharedProject
include SharedUser
end
...@@ -5,6 +5,99 @@ module SharedIssuable ...@@ -5,6 +5,99 @@ module SharedIssuable
find(:css, '.issuable-edit').click find(:css, '.issuable-edit').click
end end
step 'project "Community" has "Community issue" open issue' do
create_issuable_for_project(
project_name: 'Community',
title: 'Community issue'
)
end
step 'project "Community" has "Community fix" open merge request' do
create_issuable_for_project(
project_name: 'Community',
type: :merge_request,
title: 'Community fix'
)
end
step 'project "Enterprise" has "Enterprise issue" open issue' do
create_issuable_for_project(
project_name: 'Enterprise',
title: 'Enterprise issue'
)
end
step 'project "Enterprise" has "Enterprise fix" open merge request' do
create_issuable_for_project(
project_name: 'Enterprise',
type: :merge_request,
title: 'Enterprise fix'
)
end
step 'I leave a comment referencing issue "Community issue"' do
leave_reference_comment(
issuable: Issue.find_by(title: 'Community issue'),
from_project_name: 'Enterprise'
)
end
step 'I leave a comment referencing issue "Community fix"' do
leave_reference_comment(
issuable: MergeRequest.find_by(title: 'Community fix'),
from_project_name: 'Enterprise'
)
end
step 'I visit issue page "Enterprise issue"' do
issue = Issue.find_by(title: 'Enterprise issue')
visit namespace_project_issue_path(issue.project.namespace, issue.project, issue)
end
step 'I visit merge request page "Enterprise fix"' do
mr = MergeRequest.find_by(title: 'Enterprise fix')
visit namespace_project_merge_request_path(mr.target_project.namespace, mr.target_project, mr)
end
step 'I visit issue page "Community issue"' do
issue = Issue.find_by(title: 'Community issue')
visit namespace_project_issue_path(issue.project.namespace, issue.project, issue)
end
step 'I visit issue page "Community fix"' do
mr = MergeRequest.find_by(title: 'Community fix')
visit namespace_project_merge_request_path(mr.target_project.namespace, mr.target_project, mr)
end
step 'I should not see any related merge requests' do
page.within '.issue-details' do
expect(page).not_to have_content('.merge-requests')
end
end
step 'I should see the "Enterprise fix" related merge request' do
page.within '.merge-requests' do
expect(page).to have_content('1 Related Merge Request')
expect(page).to have_content('Enterprise fix')
end
end
step 'I should see a note linking to "Enterprise fix" merge request' do
visible_note(
issuable: MergeRequest.find_by(title: 'Enterprise fix'),
from_project_name: 'Community',
user_name: 'Mary Jane'
)
end
step 'I should see a note linking to "Enterprise issue" issue' do
visible_note(
issuable: Issue.find_by(title: 'Enterprise issue'),
from_project_name: 'Community',
user_name: 'Mary Jane'
)
end
step 'I click link "Edit" for the merge request' do step 'I click link "Edit" for the merge request' do
edit_issuable edit_issuable
end end
...@@ -12,4 +105,45 @@ module SharedIssuable ...@@ -12,4 +105,45 @@ module SharedIssuable
step 'I click link "Edit" for the issue' do step 'I click link "Edit" for the issue' do
edit_issuable edit_issuable
end end
def create_issuable_for_project(project_name:, title:, type: :issue)
project = Project.find_by(name: project_name)
attrs = {
title: title,
author: project.users.first,
description: '# Description header'
}
case type
when :issue
attrs.merge!(project: project)
when :merge_request
attrs.merge!(
source_project: project,
target_project: project,
source_branch: 'fix',
target_branch: 'master'
)
end
create(type, attrs)
end
def leave_reference_comment(issuable:, from_project_name:)
project = Project.find_by(name: from_project_name)
page.within('.js-main-target-form') do
fill_in 'note[note]', with: "##{issuable.to_reference(project)}"
click_button 'Add Comment'
end
end
def visible_note(issuable:, from_project_name:, user_name:)
project = Project.find_by(name: from_project_name)
expect(page).to have_content(user_name)
expect(page).to have_content("mentioned in #{issuable.class.to_s.titleize.downcase} #{issuable.to_reference(project)}")
end
end end
...@@ -106,6 +106,10 @@ module SharedNote ...@@ -106,6 +106,10 @@ module SharedNote
end end
end end
step 'I should see no notes at all' do
expect(page).to_not have_css('.note')
end
# Markdown # Markdown
step 'I leave a comment with a header containing "Comment with a header"' do step 'I leave a comment with a header containing "Comment with a header"' do
......
...@@ -161,24 +161,33 @@ module SharedProject ...@@ -161,24 +161,33 @@ module SharedProject
end end
step '"John Doe" owns private project "Enterprise"' do step '"John Doe" owns private project "Enterprise"' do
user = user_exists("John Doe", username: "john_doe") user_owns_project(
project = Project.find_by(name: "Enterprise") user_name: 'John Doe',
project ||= create(:empty_project, name: "Enterprise", namespace: user.namespace) project_name: 'Enterprise'
project.team << [user, :master] )
end
step '"Mary Jane" owns private project "Enterprise"' do
user_owns_project(
user_name: 'Mary Jane',
project_name: 'Enterprise'
)
end end
step '"John Doe" owns internal project "Internal"' do step '"John Doe" owns internal project "Internal"' do
user = user_exists("John Doe", username: "john_doe") user_owns_project(
project = Project.find_by(name: "Internal") user_name: 'John Doe',
project ||= create :empty_project, :internal, name: 'Internal', namespace: user.namespace project_name: 'Internal',
project.team << [user, :master] visibility: :internal
)
end end
step '"John Doe" owns public project "Community"' do step '"John Doe" owns public project "Community"' do
user = user_exists("John Doe", username: "john_doe") user_owns_project(
project = Project.find_by(name: "Community") user_name: 'John Doe',
project ||= create :empty_project, :public, name: 'Community', namespace: user.namespace project_name: 'Community',
project.team << [user, :master] visibility: :public
)
end end
step 'public empty project "Empty Public Project"' do step 'public empty project "Empty Public Project"' do
...@@ -213,4 +222,12 @@ module SharedProject ...@@ -213,4 +222,12 @@ module SharedProject
expect(page).to have_content("skipped") expect(page).to have_content("skipped")
end end
end end
def user_owns_project(user_name:, project_name:, visibility: :private)
user = user_exists(user_name, username: user_name.gsub(/\s/, '').underscore)
project = Project.find_by(name: project_name)
project ||= create(:empty_project, visibility, name: project_name, namespace: user.namespace)
project.team << [user, :master]
end
end end
...@@ -55,5 +55,6 @@ module API ...@@ -55,5 +55,6 @@ module API
mount Tags mount Tags
mount Triggers mount Triggers
mount Builds mount Builds
mount Variables
end end
end end
...@@ -393,5 +393,9 @@ module API ...@@ -393,5 +393,9 @@ module API
end end
expose :runner, with: Runner expose :runner, with: Runner
end end
class Variable < Grape::Entity
expose :key, :value
end
end end
end end
...@@ -20,7 +20,19 @@ module API ...@@ -20,7 +20,19 @@ module API
# GET /projects/:id/snippets/:noteable_id/notes # GET /projects/:id/snippets/:noteable_id/notes
get ":id/#{noteables_str}/:#{noteable_id_str}/notes" do get ":id/#{noteables_str}/:#{noteable_id_str}/notes" do
@noteable = user_project.send(:"#{noteables_str}").find(params[:"#{noteable_id_str}"]) @noteable = user_project.send(:"#{noteables_str}").find(params[:"#{noteable_id_str}"])
present paginate(@noteable.notes), with: Entities::Note
# We exclude notes that are cross-references and that cannot be viewed
# by the current user. By doing this exclusion at this level and not
# at the DB query level (which we cannot in that case), the current
# page can have less elements than :per_page even if
# there's more than one page.
notes =
# paginate() only works with a relation. This could lead to a
# mismatch between the pagination headers info and the actual notes
# array returned, but this is really a edge-case.
paginate(@noteable.notes).
reject { |n| n.cross_reference_not_visible_for?(current_user) }
present notes, with: Entities::Note
end end
# Get a single +noteable+ note # Get a single +noteable+ note
...@@ -35,8 +47,13 @@ module API ...@@ -35,8 +47,13 @@ module API
get ":id/#{noteables_str}/:#{noteable_id_str}/notes/:note_id" do get ":id/#{noteables_str}/:#{noteable_id_str}/notes/:note_id" do
@noteable = user_project.send(:"#{noteables_str}").find(params[:"#{noteable_id_str}"]) @noteable = user_project.send(:"#{noteables_str}").find(params[:"#{noteable_id_str}"])
@note = @noteable.notes.find(params[:note_id]) @note = @noteable.notes.find(params[:note_id])
if @note.cross_reference_not_visible_for?(current_user)
not_found!("Note")
else
present @note, with: Entities::Note present @note, with: Entities::Note
end end
end
# Create a new +noteable+ note # Create a new +noteable+ note
# #
......
...@@ -284,10 +284,12 @@ module API ...@@ -284,10 +284,12 @@ module API
authenticated_as_admin! authenticated_as_admin!
user = User.find_by(id: params[:id]) user = User.find_by(id: params[:id])
if user if !user
not_found!('User')
elsif !user.ldap_blocked?
user.block user.block
else else
not_found!('User') forbidden!('LDAP blocked users cannot be modified by the API')
end end
end end
...@@ -299,10 +301,12 @@ module API ...@@ -299,10 +301,12 @@ module API
authenticated_as_admin! authenticated_as_admin!
user = User.find_by(id: params[:id]) user = User.find_by(id: params[:id])
if user if !user
user.activate
else
not_found!('User') not_found!('User')
elsif user.ldap_blocked?
forbidden!('LDAP blocked users cannot be unblocked by the API')
else
user.activate
end end
end end
end end
......
module API
# Projects variables API
class Variables < Grape::API
before { authenticate! }
before { authorize_admin_project }
resource :projects do
# Get project variables
#
# Parameters:
# id (required) - The ID of a project
# page (optional) - The page number for pagination
# per_page (optional) - The value of items per page to show
# Example Request:
# GET /projects/:id/variables
get ':id/variables' do
variables = user_project.variables
present paginate(variables), with: Entities::Variable
end
# Get specific variable of a project
#
# Parameters:
# id (required) - The ID of a project
# key (required) - The `key` of variable
# Example Request:
# GET /projects/:id/variables/:key
get ':id/variables/:key' do
key = params[:key]
variable = user_project.variables.find_by(key: key.to_s)
return not_found!('Variable') unless variable
present variable, with: Entities::Variable
end
# Create a new variable in project
#
# Parameters:
# id (required) - The ID of a project
# key (required) - The key of variable
# value (required) - The value of variable
# Example Request:
# POST /projects/:id/variables
post ':id/variables' do
required_attributes! [:key, :value]
variable = user_project.variables.create(key: params[:key], value: params[:value])
if variable.valid?
present variable, with: Entities::Variable
else
render_validation_error!(variable)
end
end
# Update existing variable of a project
#
# Parameters:
# id (required) - The ID of a project
# key (optional) - The `key` of variable
# value (optional) - New value for `value` field of variable
# Example Request:
# PUT /projects/:id/variables/:key
put ':id/variables/:key' do
variable = user_project.variables.find_by(key: params[:key].to_s)
return not_found!('Variable') unless variable
attrs = attributes_for_keys [:value]
if variable.update(attrs)
present variable, with: Entities::Variable
else
render_validation_error!(variable)
end
end
# Delete existing variable of a project
#
# Parameters:
# id (required) - The ID of a project
# key (required) - The ID of a variable
# Example Request:
# DELETE /projects/:id/variables/:key
delete ':id/variables/:key' do
variable = user_project.variables.find_by(key: params[:key].to_s)
return not_found!('Variable') unless variable
variable.destroy
present variable, with: Entities::Variable
end
end
end
end
...@@ -37,15 +37,15 @@ module Gitlab ...@@ -37,15 +37,15 @@ module Gitlab
# Block user in GitLab if he/she was blocked in AD # Block user in GitLab if he/she was blocked in AD
if Gitlab::LDAP::Person.disabled_via_active_directory?(user.ldap_identity.extern_uid, adapter) if Gitlab::LDAP::Person.disabled_via_active_directory?(user.ldap_identity.extern_uid, adapter)
user.block user.ldap_block
false false
else else
user.activate if user.blocked? && !ldap_config.block_auto_created_users user.activate if user.ldap_blocked?
true true
end end
else else
# Block the user if they no longer exist in LDAP/AD # Block the user if they no longer exist in LDAP/AD
user.block user.ldap_block
false false
end end
rescue rescue
......
require 'spec_helper'
describe Admin::IdentitiesController do
let(:admin) { create(:admin) }
before { sign_in(admin) }
describe 'UPDATE identity' do
let(:user) { create(:omniauth_user, provider: 'ldapmain', extern_uid: 'uid=myuser,ou=people,dc=example,dc=com') }
it 'repairs ldap blocks' do
expect_any_instance_of(RepairLdapBlockedUserService).to receive(:execute)
put :update, user_id: user.username, id: user.ldap_identity.id, identity: { provider: 'twitter' }
end
end
describe 'DELETE identity' do
let(:user) { create(:omniauth_user, provider: 'ldapmain', extern_uid: 'uid=myuser,ou=people,dc=example,dc=com') }
it 'repairs ldap blocks' do
expect_any_instance_of(RepairLdapBlockedUserService).to receive(:execute)
delete :destroy, user_id: user.username, id: user.ldap_identity.id
end
end
end
...@@ -34,6 +34,22 @@ describe Admin::UsersController do ...@@ -34,6 +34,22 @@ describe Admin::UsersController do
end end
describe 'PUT unblock/:id' do describe 'PUT unblock/:id' do
context 'ldap blocked users' do
let(:user) { create(:omniauth_user, provider: 'ldapmain') }
before do
user.ldap_block
end
it 'will not unblock user' do
put :unblock, id: user.username
user.reload
expect(user.blocked?).to be_truthy
expect(flash[:alert]).to eq 'This user cannot be unlocked manually from GitLab'
end
end
context 'manually blocked users' do
let(:user) { create(:user) } let(:user) { create(:user) }
before do before do
...@@ -47,6 +63,7 @@ describe Admin::UsersController do ...@@ -47,6 +63,7 @@ describe Admin::UsersController do
expect(flash[:notice]).to eq 'Successfully unblocked' expect(flash[:notice]).to eq 'Successfully unblocked'
end end
end end
end
describe 'PUT unlock/:id' do describe 'PUT unlock/:id' do
let(:user) { create(:user) } let(:user) { create(:user) }
......
...@@ -6,7 +6,6 @@ ...@@ -6,7 +6,6 @@
# message :text not null # message :text not null
# starts_at :datetime # starts_at :datetime
# ends_at :datetime # ends_at :datetime
# alert_type :integer
# created_at :datetime # created_at :datetime
# updated_at :datetime # updated_at :datetime
# color :string(255) # color :string(255)
...@@ -18,10 +17,17 @@ ...@@ -18,10 +17,17 @@
FactoryGirl.define do FactoryGirl.define do
factory :broadcast_message do factory :broadcast_message do
message "MyText" message "MyText"
starts_at "2013-11-12 13:43:25" starts_at Date.today
ends_at "2013-11-12 13:43:25" ends_at Date.tomorrow
alert_type 1
color "#555555" trait :expired do
font "#BBBBBB" starts_at 5.days.ago
ends_at 3.days.ago
end
trait :future do
starts_at 5.days.from_now
ends_at 6.days.from_now
end
end end
end end
# == Schema Information
#
# Table name: ci_variables
#
# id :integer not null, primary key
# project_id :integer not null
# key :string(255)
# value :text
# encrypted_value :text
# encrypted_value_salt :string(255)
# encrypted_value_iv :string(255)
# gl_project_id :integer
#
# Read about factories at https://github.com/thoughtbot/factory_girl
FactoryGirl.define do
factory :ci_variable, class: Ci::Variable do
sequence(:key) { |n| "VARIABLE_#{n}" }
value 'VARIABLE_VALUE'
end
end
require 'spec_helper' require 'spec_helper'
describe BroadcastMessagesHelper do describe BroadcastMessagesHelper do
describe 'broadcast_styling' do describe 'broadcast_message' do
let(:broadcast_message) { double(color: '', font: '') } it 'returns nil when no current message' do
expect(helper.broadcast_message(nil)).to be_nil
end
it 'includes the current message' do
current = double(message: 'Current Message')
allow(helper).to receive(:broadcast_message_style).and_return(nil)
expect(helper.broadcast_message(current)).to include 'Current Message'
end
context "default style" do it 'includes custom style' do
it "should have no style" do current = double(message: 'Current Message')
expect(broadcast_styling(broadcast_message)).to eq ''
allow(helper).to receive(:broadcast_message_style).and_return('foo')
expect(helper.broadcast_message(current)).to include 'style="foo"'
end
end end
describe 'broadcast_message_style' do
it 'defaults to no style' do
broadcast_message = spy
expect(helper.broadcast_message_style(broadcast_message)).to eq ''
end end
context "customized style" do it 'allows custom style' do
let(:broadcast_message) { double(color: "#f2dede", font: '#b94a48') } broadcast_message = double(color: '#f2dede', font: '#b94a48')
it "should have a customized style" do expect(helper.broadcast_message_style(broadcast_message)).
expect(broadcast_styling(broadcast_message)).
to match('background-color: #f2dede; color: #b94a48') to match('background-color: #f2dede; color: #b94a48')
end end
end end
describe 'broadcast_message_status' do
it 'returns Active' do
message = build(:broadcast_message)
expect(helper.broadcast_message_status(message)).to eq 'Active'
end
it 'returns Expired' do
message = build(:broadcast_message, :expired)
expect(helper.broadcast_message_status(message)).to eq 'Expired'
end
it 'returns Pending' do
message = build(:broadcast_message, :future)
expect(helper.broadcast_message_status(message)).to eq 'Pending'
end
end end
end end
...@@ -17,60 +17,54 @@ describe Gitlab::LDAP::Access, lib: true do ...@@ -17,60 +17,54 @@ describe Gitlab::LDAP::Access, lib: true do
it 'should block user in GitLab' do it 'should block user in GitLab' do
access.allowed? access.allowed?
expect(user).to be_blocked expect(user).to be_blocked
expect(user).to be_ldap_blocked
end end
end end
context 'when the user is found' do context 'when the user is found' do
before do before do
allow(Gitlab::LDAP::Person). allow(Gitlab::LDAP::Person).to receive(:find_by_dn).and_return(:ldap_user)
to receive(:find_by_dn).and_return(:ldap_user)
end end
context 'and the user is disabled via active directory' do context 'and the user is disabled via active directory' do
before do before do
allow(Gitlab::LDAP::Person). allow(Gitlab::LDAP::Person).to receive(:disabled_via_active_directory?).and_return(true)
to receive(:disabled_via_active_directory?).and_return(true)
end end
it { is_expected.to be_falsey } it { is_expected.to be_falsey }
it "should block user in GitLab" do it 'should block user in GitLab' do
access.allowed? access.allowed?
expect(user).to be_blocked expect(user).to be_blocked
expect(user).to be_ldap_blocked
end end
end end
context 'and has no disabled flag in active diretory' do context 'and has no disabled flag in active diretory' do
before do before do
user.block allow(Gitlab::LDAP::Person).to receive(:disabled_via_active_directory?).and_return(false)
allow(Gitlab::LDAP::Person).
to receive(:disabled_via_active_directory?).and_return(false)
end end
it { is_expected.to be_truthy } it { is_expected.to be_truthy }
context 'when auto-created users are blocked' do context 'when auto-created users are blocked' do
before do before do
allow_any_instance_of(Gitlab::LDAP::Config). user.block
to receive(:block_auto_created_users).and_return(true)
end end
it "does not unblock user in GitLab" do it 'does not unblock user in GitLab' do
access.allowed? access.allowed?
expect(user).to be_blocked expect(user).to be_blocked
expect(user).not_to be_ldap_blocked # this block is handled by omniauth not by our internal logic
end end
end end
context "when auto-created users are not blocked" do context 'when auto-created users are not blocked' do
before do before do
allow_any_instance_of(Gitlab::LDAP::Config). user.ldap_block
to receive(:block_auto_created_users).and_return(false)
end end
it "should unblock user in GitLab" do it 'should unblock user in GitLab' do
access.allowed? access.allowed?
expect(user).not_to be_blocked expect(user).not_to be_blocked
end end
...@@ -80,8 +74,7 @@ describe Gitlab::LDAP::Access, lib: true do ...@@ -80,8 +74,7 @@ describe Gitlab::LDAP::Access, lib: true do
context 'without ActiveDirectory enabled' do context 'without ActiveDirectory enabled' do
before do before do
allow(Gitlab::LDAP::Config).to receive(:enabled?).and_return(true) allow(Gitlab::LDAP::Config).to receive(:enabled?).and_return(true)
allow_any_instance_of(Gitlab::LDAP::Config). allow_any_instance_of(Gitlab::LDAP::Config).to receive(:active_directory).and_return(false)
to receive(:active_directory).and_return(false)
end end
it { is_expected.to be_truthy } it { is_expected.to be_truthy }
......
...@@ -6,7 +6,6 @@ ...@@ -6,7 +6,6 @@
# message :text not null # message :text not null
# starts_at :datetime # starts_at :datetime
# ends_at :datetime # ends_at :datetime
# alert_type :integer
# created_at :datetime # created_at :datetime
# updated_at :datetime # updated_at :datetime
# color :string(255) # color :string(255)
...@@ -16,6 +15,8 @@ ...@@ -16,6 +15,8 @@
require 'spec_helper' require 'spec_helper'
describe BroadcastMessage, models: true do describe BroadcastMessage, models: true do
include ActiveSupport::Testing::TimeHelpers
subject { create(:broadcast_message) } subject { create(:broadcast_message) }
it { is_expected.to be_valid } it { is_expected.to be_valid }
...@@ -35,20 +36,79 @@ describe BroadcastMessage, models: true do ...@@ -35,20 +36,79 @@ describe BroadcastMessage, models: true do
it { is_expected.not_to allow_value('000').for(:font) } it { is_expected.not_to allow_value('000').for(:font) }
end end
describe :current do describe '.current' do
it "should return last message if time match" do it "should return last message if time match" do
broadcast_message = create(:broadcast_message, starts_at: Time.now.yesterday, ends_at: Time.now.tomorrow) message = create(:broadcast_message)
expect(BroadcastMessage.current).to eq(broadcast_message)
expect(BroadcastMessage.current).to eq message
end end
it "should return nil if time not come" do it "should return nil if time not come" do
create(:broadcast_message, starts_at: Time.now.tomorrow, ends_at: Time.now + 2.days) create(:broadcast_message, :future)
expect(BroadcastMessage.current).to be_nil expect(BroadcastMessage.current).to be_nil
end end
it "should return nil if time has passed" do it "should return nil if time has passed" do
create(:broadcast_message, starts_at: Time.now - 2.days, ends_at: Time.now.yesterday) create(:broadcast_message, :expired)
expect(BroadcastMessage.current).to be_nil expect(BroadcastMessage.current).to be_nil
end end
end end
describe '#active?' do
it 'is truthy when started and not ended' do
message = build(:broadcast_message)
expect(message).to be_active
end
it 'is falsey when ended' do
message = build(:broadcast_message, :expired)
expect(message).not_to be_active
end
it 'is falsey when not started' do
message = build(:broadcast_message, :future)
expect(message).not_to be_active
end
end
describe '#started?' do
it 'is truthy when starts_at has passed' do
message = build(:broadcast_message)
travel_to(3.days.from_now) do
expect(message).to be_started
end
end
it 'is falsey when starts_at is in the future' do
message = build(:broadcast_message)
travel_to(3.days.ago) do
expect(message).not_to be_started
end
end
end
describe '#ended?' do
it 'is truthy when ends_at has passed' do
message = build(:broadcast_message)
travel_to(3.days.from_now) do
expect(message).to be_ended
end
end
it 'is falsey when ends_at is in the future' do
message = build(:broadcast_message)
travel_to(3.days.ago) do
expect(message).not_to be_ended
end
end
end
end end
# == Schema Information
#
# Table name: identities
#
# id :integer not null, primary key
# extern_uid :string(255)
# provider :string(255)
# user_id :integer
# created_at :datetime
# updated_at :datetime
#
require 'spec_helper'
RSpec.describe Identity, models: true do
describe 'relations' do
it { is_expected.to belong_to(:user) }
end
describe 'fields' do
it { is_expected.to respond_to(:provider) }
it { is_expected.to respond_to(:extern_uid) }
end
describe '#is_ldap?' do
let(:ldap_identity) { create(:identity, provider: 'ldapmain') }
let(:other_identity) { create(:identity, provider: 'twitter') }
it 'returns true if it is a ldap identity' do
expect(ldap_identity.ldap?).to be_truthy
end
it 'returns false if it is not a ldap identity' do
expect(other_identity.ldap?).to be_falsey
end
end
end
...@@ -178,6 +178,30 @@ describe Note, models: true do ...@@ -178,6 +178,30 @@ describe Note, models: true do
end end
end end
describe "cross_reference_not_visible_for?" do
let(:private_user) { create(:user) }
let(:private_project) { create(:project, namespace: private_user.namespace).tap { |p| p.team << [private_user, :master] } }
let(:private_issue) { create(:issue, project: private_project) }
let(:ext_proj) { create(:project, :public) }
let(:ext_issue) { create(:issue, project: ext_proj) }
let(:note) do
create :note,
noteable: ext_issue, project: ext_proj,
note: "mentioned in issue #{private_issue.to_reference(ext_proj)}",
system: true
end
it "returns true" do
expect(note.cross_reference_not_visible_for?(ext_issue.author)).to be_truthy
end
it "returns false" do
expect(note.cross_reference_not_visible_for?(private_user)).to be_falsy
end
end
describe "set_award!" do describe "set_award!" do
let(:issue) { create :issue } let(:issue) { create :issue }
......
...@@ -569,30 +569,42 @@ describe User, models: true do ...@@ -569,30 +569,42 @@ describe User, models: true do
end end
end end
context 'ldap synchronized user' do
describe :ldap_user? do describe :ldap_user? do
it "is true if provider name starts with ldap" do it 'is true if provider name starts with ldap' do
user = create(:omniauth_user, provider: 'ldapmain') user = create(:omniauth_user, provider: 'ldapmain')
expect( user.ldap_user? ).to be_truthy expect(user.ldap_user?).to be_truthy
end end
it "is false for other providers" do it 'is false for other providers' do
user = create(:omniauth_user, provider: 'other-provider') user = create(:omniauth_user, provider: 'other-provider')
expect( user.ldap_user? ).to be_falsey expect(user.ldap_user?).to be_falsey
end end
it "is false if no extern_uid is provided" do it 'is false if no extern_uid is provided' do
user = create(:omniauth_user, extern_uid: nil) user = create(:omniauth_user, extern_uid: nil)
expect( user.ldap_user? ).to be_falsey expect(user.ldap_user?).to be_falsey
end end
end end
describe :ldap_identity do describe :ldap_identity do
it "returns ldap identity" do it 'returns ldap identity' do
user = create :omniauth_user user = create :omniauth_user
expect(user.ldap_identity.provider).not_to be_empty expect(user.ldap_identity.provider).not_to be_empty
end end
end end
describe '#ldap_block' do
let(:user) { create(:omniauth_user, provider: 'ldapmain', name: 'John Smith') }
it 'blocks user flaging the action caming from ldap' do
user.ldap_block
expect(user.blocked?).to be_truthy
expect(user.ldap_blocked?).to be_truthy
end
end
end
describe '#full_website_url' do describe '#full_website_url' do
let(:user) { create(:user) } let(:user) { create(:user) }
......
...@@ -10,6 +10,25 @@ describe API::API, api: true do ...@@ -10,6 +10,25 @@ describe API::API, api: true do
let!(:issue_note) { create(:note, noteable: issue, project: project, author: user) } let!(:issue_note) { create(:note, noteable: issue, project: project, author: user) }
let!(:merge_request_note) { create(:note, noteable: merge_request, project: project, author: user) } let!(:merge_request_note) { create(:note, noteable: merge_request, project: project, author: user) }
let!(:snippet_note) { create(:note, noteable: snippet, project: project, author: user) } let!(:snippet_note) { create(:note, noteable: snippet, project: project, author: user) }
# For testing the cross-reference of a private issue in a public issue
let(:private_user) { create(:user) }
let(:private_project) do
create(:project, namespace: private_user.namespace).
tap { |p| p.team << [private_user, :master] }
end
let(:private_issue) { create(:issue, project: private_project) }
let(:ext_proj) { create(:project, :public) }
let(:ext_issue) { create(:issue, project: ext_proj) }
let!(:cross_reference_note) do
create :note,
noteable: ext_issue, project: ext_proj,
note: "mentioned in issue #{private_issue.to_reference(ext_proj)}",
system: true
end
before { project.team << [user, :reporter] } before { project.team << [user, :reporter] }
describe "GET /projects/:id/noteable/:noteable_id/notes" do describe "GET /projects/:id/noteable/:noteable_id/notes" do
...@@ -25,6 +44,24 @@ describe API::API, api: true do ...@@ -25,6 +44,24 @@ describe API::API, api: true do
get api("/projects/#{project.id}/issues/123/notes", user) get api("/projects/#{project.id}/issues/123/notes", user)
expect(response.status).to eq(404) expect(response.status).to eq(404)
end end
context "that references a private issue" do
it "should return an empty array" do
get api("/projects/#{ext_proj.id}/issues/#{ext_issue.id}/notes", user)
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(json_response).to be_empty
end
context "and current user can view the note" do
it "should return an empty array" do
get api("/projects/#{ext_proj.id}/issues/#{ext_issue.id}/notes", private_user)
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(json_response.first['body']).to eq(cross_reference_note.note)
end
end
end
end end
context "when noteable is a Snippet" do context "when noteable is a Snippet" do
...@@ -68,6 +105,21 @@ describe API::API, api: true do ...@@ -68,6 +105,21 @@ describe API::API, api: true do
get api("/projects/#{project.id}/issues/#{issue.id}/notes/123", user) get api("/projects/#{project.id}/issues/#{issue.id}/notes/123", user)
expect(response.status).to eq(404) expect(response.status).to eq(404)
end end
context "that references a private issue" do
it "should return a 404 error" do
get api("/projects/#{ext_proj.id}/issues/#{ext_issue.id}/notes/#{cross_reference_note.id}", user)
expect(response.status).to eq(404)
end
context "and current user can view the note" do
it "should return an issue note by id" do
get api("/projects/#{ext_proj.id}/issues/#{ext_issue.id}/notes/#{cross_reference_note.id}", private_user)
expect(response.status).to eq(200)
expect(json_response['body']).to eq(cross_reference_note.note)
end
end
end
end end
context "when noteable is a Snippet" do context "when noteable is a Snippet" do
......
...@@ -8,6 +8,8 @@ describe API::API, api: true do ...@@ -8,6 +8,8 @@ describe API::API, api: true do
let(:key) { create(:key, user: user) } let(:key) { create(:key, user: user) }
let(:email) { create(:email, user: user) } let(:email) { create(:email, user: user) }
let(:omniauth_user) { create(:omniauth_user) } let(:omniauth_user) { create(:omniauth_user) }
let(:ldap_user) { create(:omniauth_user, provider: 'ldapmain') }
let(:ldap_blocked_user) { create(:omniauth_user, provider: 'ldapmain', state: 'ldap_blocked') }
describe "GET /users" do describe "GET /users" do
context "when unauthenticated" do context "when unauthenticated" do
...@@ -783,6 +785,12 @@ describe API::API, api: true do ...@@ -783,6 +785,12 @@ describe API::API, api: true do
expect(user.reload.state).to eq('blocked') expect(user.reload.state).to eq('blocked')
end end
it 'should not re-block ldap blocked users' do
put api("/users/#{ldap_blocked_user.id}/block", admin)
expect(response.status).to eq(403)
expect(ldap_blocked_user.reload.state).to eq('ldap_blocked')
end
it 'should not be available for non admin users' do it 'should not be available for non admin users' do
put api("/users/#{user.id}/block", user) put api("/users/#{user.id}/block", user)
expect(response.status).to eq(403) expect(response.status).to eq(403)
...@@ -797,7 +805,9 @@ describe API::API, api: true do ...@@ -797,7 +805,9 @@ describe API::API, api: true do
end end
describe 'PUT /user/:id/unblock' do describe 'PUT /user/:id/unblock' do
let(:blocked_user) { create(:user, state: 'blocked') }
before { admin } before { admin }
it 'should unblock existing user' do it 'should unblock existing user' do
put api("/users/#{user.id}/unblock", admin) put api("/users/#{user.id}/unblock", admin)
expect(response.status).to eq(200) expect(response.status).to eq(200)
...@@ -805,12 +815,15 @@ describe API::API, api: true do ...@@ -805,12 +815,15 @@ describe API::API, api: true do
end end
it 'should unblock a blocked user' do it 'should unblock a blocked user' do
put api("/users/#{user.id}/block", admin) put api("/users/#{blocked_user.id}/unblock", admin)
expect(response.status).to eq(200)
expect(user.reload.state).to eq('blocked')
put api("/users/#{user.id}/unblock", admin)
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(user.reload.state).to eq('active') expect(blocked_user.reload.state).to eq('active')
end
it 'should not unblock ldap blocked users' do
put api("/users/#{ldap_blocked_user.id}/unblock", admin)
expect(response.status).to eq(403)
expect(ldap_blocked_user.reload.state).to eq('ldap_blocked')
end end
it 'should not be available for non admin users' do it 'should not be available for non admin users' do
......
require 'spec_helper'
describe API::API, api: true do
include ApiHelpers
let(:user) { create(:user) }
let(:user2) { create(:user) }
let!(:project) { create(:project, creator_id: user.id) }
let!(:master) { create(:project_member, user: user, project: project, access_level: ProjectMember::MASTER) }
let!(:developer) { create(:project_member, user: user2, project: project, access_level: ProjectMember::DEVELOPER) }
let!(:variable) { create(:ci_variable, project: project) }
describe 'GET /projects/:id/variables' do
context 'authorized user with proper permissions' do
it 'should return project variables' do
get api("/projects/#{project.id}/variables", user)
expect(response.status).to eq(200)
expect(json_response).to be_a(Array)
end
end
context 'authorized user with invalid permissions' do
it 'should not return project variables' do
get api("/projects/#{project.id}/variables", user2)
expect(response.status).to eq(403)
end
end
context 'unauthorized user' do
it 'should not return project variables' do
get api("/projects/#{project.id}/variables")
expect(response.status).to eq(401)
end
end
end
describe 'GET /projects/:id/variables/:key' do
context 'authorized user with proper permissions' do
it 'should return project variable details' do
get api("/projects/#{project.id}/variables/#{variable.key}", user)
expect(response.status).to eq(200)
expect(json_response['value']).to eq(variable.value)
end
it 'should respond with 404 Not Found if requesting non-existing variable' do
get api("/projects/#{project.id}/variables/non_existing_variable", user)
expect(response.status).to eq(404)
end
end
context 'authorized user with invalid permissions' do
it 'should not return project variable details' do
get api("/projects/#{project.id}/variables/#{variable.key}", user2)
expect(response.status).to eq(403)
end
end
context 'unauthorized user' do
it 'should not return project variable details' do
get api("/projects/#{project.id}/variables/#{variable.key}")
expect(response.status).to eq(401)
end
end
end
describe 'POST /projects/:id/variables' do
context 'authorized user with proper permissions' do
it 'should create variable' do
expect do
post api("/projects/#{project.id}/variables", user), key: 'TEST_VARIABLE_2', value: 'VALUE_2'
end.to change{project.variables.count}.by(1)
expect(response.status).to eq(201)
expect(json_response['key']).to eq('TEST_VARIABLE_2')
expect(json_response['value']).to eq('VALUE_2')
end
it 'should not allow to duplicate variable key' do
expect do
post api("/projects/#{project.id}/variables", user), key: variable.key, value: 'VALUE_2'
end.to change{project.variables.count}.by(0)
expect(response.status).to eq(400)
end
end
context 'authorized user with invalid permissions' do
it 'should not create variable' do
post api("/projects/#{project.id}/variables", user2)
expect(response.status).to eq(403)
end
end
context 'unauthorized user' do
it 'should not create variable' do
post api("/projects/#{project.id}/variables")
expect(response.status).to eq(401)
end
end
end
describe 'PUT /projects/:id/variables/:key' do
context 'authorized user with proper permissions' do
it 'should update variable data' do
initial_variable = project.variables.first
value_before = initial_variable.value
put api("/projects/#{project.id}/variables/#{variable.key}", user), value: 'VALUE_1_UP'
updated_variable = project.variables.first
expect(response.status).to eq(200)
expect(value_before).to eq(variable.value)
expect(updated_variable.value).to eq('VALUE_1_UP')
end
it 'should responde with 404 Not Found if requesting non-existing variable' do
put api("/projects/#{project.id}/variables/non_existing_variable", user)
expect(response.status).to eq(404)
end
end
context 'authorized user with invalid permissions' do
it 'should not update variable' do
put api("/projects/#{project.id}/variables/#{variable.key}", user2)
expect(response.status).to eq(403)
end
end
context 'unauthorized user' do
it 'should not update variable' do
put api("/projects/#{project.id}/variables/#{variable.key}")
expect(response.status).to eq(401)
end
end
end
describe 'DELETE /projects/:id/variables/:key' do
context 'authorized user with proper permissions' do
it 'should delete variable' do
expect do
delete api("/projects/#{project.id}/variables/#{variable.key}", user)
end.to change{project.variables.count}.by(-1)
expect(response.status).to eq(200)
end
it 'should responde with 404 Not Found if requesting non-existing variable' do
delete api("/projects/#{project.id}/variables/non_existing_variable", user)
expect(response.status).to eq(404)
end
end
context 'authorized user with invalid permissions' do
it 'should not delete variable' do
delete api("/projects/#{project.id}/variables/#{variable.key}", user2)
expect(response.status).to eq(403)
end
end
context 'unauthorized user' do
it 'should not delete variable' do
delete api("/projects/#{project.id}/variables/#{variable.key}")
expect(response.status).to eq(401)
end
end
end
end
require 'spec_helper'
describe RepairLdapBlockedUserService, services: true do
let(:user) { create(:omniauth_user, provider: 'ldapmain', state: 'ldap_blocked') }
let(:identity) { user.ldap_identity }
subject(:service) { RepairLdapBlockedUserService.new(user) }
describe '#execute' do
it 'change to normal block after destroying last ldap identity' do
identity.destroy
service.execute
expect(user.reload).not_to be_ldap_blocked
end
it 'change to normal block after changing last ldap identity to another provider' do
identity.update_attribute(:provider, 'twitter')
service.execute
expect(user.reload).not_to be_ldap_blocked
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