diff --git a/CHANGELOG b/CHANGELOG index ae8de1df27..6c28a57370 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -18,8 +18,9 @@ v 7.6.0 - - - In the docker directory is a container template based on the Omnibus packages. + - Update Sidekiq to version 2.17.8 - - - + - Atom feed for user activity v 7.5.2 - Don't log Sidekiq arguments by default diff --git a/Gemfile b/Gemfile index 613ef11cf4..b4ca596927 100644 --- a/Gemfile +++ b/Gemfile @@ -112,7 +112,7 @@ gem "acts-as-taggable-on" # Background jobs gem 'slim' gem 'sinatra', require: nil -gem 'sidekiq', '2.17.0' +gem 'sidekiq', '2.17.8' # HTTP requests gem "httparty" diff --git a/Gemfile.lock b/Gemfile.lock index 7871f49d0b..4bcb1eb0de 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -78,7 +78,7 @@ GEM coffee-script-source (1.6.3) colored (1.2) colorize (0.5.8) - connection_pool (1.2.0) + connection_pool (2.1.0) coveralls (0.7.0) multi_json (~> 1.3) rest-client @@ -402,7 +402,7 @@ GEM rdoc (3.12.2) json (~> 1.4) redcarpet (3.1.2) - redis (3.0.6) + redis (3.1.0) redis-actionpack (4.0.0) actionpack (~> 4) redis-rack (~> 1.5.0) @@ -410,8 +410,8 @@ GEM redis-activesupport (4.0.0) activesupport (~> 4) redis-store (~> 1.1.0) - redis-namespace (1.4.1) - redis (~> 3.0.4) + redis-namespace (1.5.1) + redis (~> 3.0, >= 3.0.4) redis-rack (1.5.0) rack (~> 1.5) redis-store (~> 1.1.0) @@ -470,12 +470,12 @@ GEM sexp_processor (4.4.0) shoulda-matchers (2.1.0) activesupport (>= 3.0.0) - sidekiq (2.17.0) - celluloid (>= 0.15.2) - connection_pool (>= 1.0.0) + sidekiq (2.17.8) + celluloid (= 0.15.2) + connection_pool (~> 2.0) json - redis (>= 3.0.4) - redis-namespace (>= 1.3.1) + redis (~> 3.1) + redis-namespace (~> 1.3) simple_oauth (0.1.9) simplecov (0.9.0) docile (~> 1.1.0) @@ -684,7 +684,7 @@ DEPENDENCIES semantic-ui-sass (~> 0.16.1.0) settingslogic shoulda-matchers (~> 2.1.0) - sidekiq (= 2.17.0) + sidekiq (= 2.17.8) simplecov sinatra six diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb index bd4b310fcb..3e984e5007 100644 --- a/app/controllers/omniauth_callbacks_controller.rb +++ b/app/controllers/omniauth_callbacks_controller.rb @@ -42,10 +42,8 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController def handle_omniauth if current_user - # Change a logged-in user's authentication method: - current_user.extern_uid = oauth['uid'] - current_user.provider = oauth['provider'] - current_user.save + # Add new authentication method + current_user.identities.find_or_create_by(extern_uid: oauth['uid'], provider: oauth['provider']) redirect_to profile_path else @user = Gitlab::OAuth::User.new(oauth) @@ -67,8 +65,8 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController redirect_to omniauth_error_path(oauth['provider'], error: error_message) and return end end - rescue StandardError - flash[:notice] = "There's no such user!" + rescue ForbiddenAction => e + flash[:notice] = e.message redirect_to new_user_session_path end diff --git a/app/controllers/projects/hooks_controller.rb b/app/controllers/projects/hooks_controller.rb index cab8fd76e6..2d6c311119 100644 --- a/app/controllers/projects/hooks_controller.rb +++ b/app/controllers/projects/hooks_controller.rb @@ -26,6 +26,7 @@ class Projects::HooksController < Projects::ApplicationController def test if !@project.empty_repo? status = TestHookService.new.execute(hook, current_user) + if status flash[:notice] = 'Hook successfully executed.' else diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 0b442f5383..67af1801bd 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -20,9 +20,14 @@ class UsersController < ApplicationController # Get user activity feed for projects common for both users @events = @user.recent_events. - where(project_id: authorized_projects_ids).limit(20) + where(project_id: authorized_projects_ids).limit(30) @title = @user.name + + respond_to do |format| + format.html + format.atom { render layout: false } + end end def determine_layout diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb index 71f97fbb8c..a3136926b3 100644 --- a/app/helpers/events_helper.rb +++ b/app/helpers/events_helper.rb @@ -145,4 +145,26 @@ module EventsHelper rescue "--broken encoding" end + + def event_to_atom(xml, event) + if event.proper? + xml.entry do + event_link = event_feed_url(event) + event_title = event_feed_title(event) + event_summary = event_feed_summary(event) + + xml.id "tag:#{request.host},#{event.created_at.strftime("%Y-%m-%d")}:#{event.id}" + xml.link href: event_link + xml.title truncate(event_title, length: 80) + xml.updated event.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") + xml.media :thumbnail, width: "40", height: "40", url: avatar_icon(event.author_email) + xml.author do |author| + xml.name event.author_name + xml.email event.author_email + end + + xml.summary(type: "xhtml") { |x| x << event_summary unless event_summary.nil? } + end + end + end end diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index d513e0ba58..a5b393c1e3 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -113,4 +113,19 @@ module IssuesHelper 'issue-box-open' end end + + def issue_to_atom(xml, issue) + xml.entry do + xml.id project_issue_url(issue.project, issue) + xml.link href: project_issue_url(issue.project, issue) + xml.title truncate(issue.title, length: 80) + xml.updated issue.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") + xml.media :thumbnail, width: "40", height: "40", url: avatar_icon(issue.author_email) + xml.author do |author| + xml.name issue.author_name + xml.email issue.author_email + end + xml.summary issue.title + end + end end diff --git a/app/helpers/oauth_helper.rb b/app/helpers/oauth_helper.rb index 7024483b8b..df18db71c8 100644 --- a/app/helpers/oauth_helper.rb +++ b/app/helpers/oauth_helper.rb @@ -16,4 +16,8 @@ module OauthHelper [:twitter, :github, :google_oauth2].include?(name.to_sym) end end + + def additional_providers + enabled_oauth_providers.reject{|provider| provider.to_s.starts_with?('ldap')} + end end diff --git a/app/helpers/profile_helper.rb b/app/helpers/profile_helper.rb index 0b37555830..6480fd3886 100644 --- a/app/helpers/profile_helper.rb +++ b/app/helpers/profile_helper.rb @@ -1,6 +1,6 @@ module ProfileHelper def oauth_active_class(provider) - if current_user.provider == provider.to_s + if current_user.identities.exists?(provider: provider.to_s) 'active' end end @@ -10,10 +10,10 @@ module ProfileHelper end def show_profile_social_tab? - enabled_social_providers.any? && !current_user.ldap_user? + enabled_social_providers.any? end def show_profile_remove_tab? - gitlab_config.signup_enabled && !current_user.ldap_user? + gitlab_config.signup_enabled end end diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb index 0ee1983662..6d671e6e0b 100644 --- a/app/mailers/notify.rb +++ b/app/mailers/notify.rb @@ -26,6 +26,14 @@ class Notify < ActionMailer::Base delay_for(2.seconds) end + def test_email(recepient_email, subject, body) + mail(to: recepient_email, + subject: subject, + body: body.html_safe, + content_type: 'text/html' + ) + end + private # The default email address to send emails from diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb index 23fa01e0b7..8479d4aecf 100644 --- a/app/models/hooks/web_hook.rb +++ b/app/models/hooks/web_hook.rb @@ -32,7 +32,10 @@ class WebHook < ActiveRecord::Base def execute(data) parsed_url = URI.parse(url) if parsed_url.userinfo.blank? - WebHook.post(url, body: data.to_json, headers: { "Content-Type" => "application/json" }, verify: false) + WebHook.post(url, + body: data.to_json, + headers: { "Content-Type" => "application/json" }, + verify: false) else post_url = url.gsub("#{parsed_url.userinfo}@", "") auth = { @@ -45,6 +48,9 @@ class WebHook < ActiveRecord::Base verify: false, basic_auth: auth) end + rescue SocketError, Errno::ECONNREFUSED => e + logger.error("WebHook Error => #{e}") + false end def async_execute(data) diff --git a/app/models/identity.rb b/app/models/identity.rb new file mode 100644 index 0000000000..5fb1850c30 --- /dev/null +++ b/app/models/identity.rb @@ -0,0 +1,5 @@ +class Identity < ActiveRecord::Base + belongs_to :user + + validates :extern_uid, allow_blank: true, uniqueness: {scope: :provider} +end \ No newline at end of file diff --git a/app/models/user.rb b/app/models/user.rb index 1cddd85ada..7faeef1b5b 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -79,6 +79,7 @@ class User < ActiveRecord::Base # Profile has_many :keys, dependent: :destroy has_many :emails, dependent: :destroy + has_many :identities, dependent: :destroy # Groups has_many :members, dependent: :destroy @@ -113,7 +114,6 @@ class User < ActiveRecord::Base validates :name, presence: true validates :email, presence: true, email: {strict_mode: true}, uniqueness: true validates :bio, length: { maximum: 255 }, allow_blank: true - validates :extern_uid, allow_blank: true, uniqueness: {scope: :provider} validates :projects_limit, presence: true, numericality: {greater_than_or_equal_to: 0} validates :username, presence: true, uniqueness: { case_sensitive: false }, exclusion: { in: Gitlab::Blacklist.path }, @@ -178,7 +178,6 @@ class User < ActiveRecord::Base scope :not_in_team, ->(team){ where('users.id NOT IN (:ids)', ids: team.member_ids) } 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 :ldap, -> { where('provider LIKE ?', 'ldap%') } scope :potential_team_members, ->(team) { team.members.any? ? active.not_in_team(team) : active } # @@ -407,7 +406,11 @@ class User < ActiveRecord::Base end def ldap_user? - extern_uid && provider.start_with?('ldap') + identities.exists?(["provider LIKE ? AND extern_uid IS NOT NULL", "ldap%"]) + end + + def ldap_identity + @ldap_identity ||= identities.find_by(["provider LIKE ?", "ldap%"]) end def accessible_deploy_keys diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index 2b6217e2e2..d1aadd741e 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -107,7 +107,7 @@ class NotificationService # Notify new user with email after creation def new_user(user, token = nil) # Don't email omniauth created users - mailer.new_user_email(user.id, token) unless user.extern_uid? + mailer.new_user_email(user.id, token) unless user.identities.any? end # Notify users on new note in system diff --git a/app/services/test_hook_service.rb b/app/services/test_hook_service.rb index b6b1ef29b5..17d86a7a27 100644 --- a/app/services/test_hook_service.rb +++ b/app/services/test_hook_service.rb @@ -2,8 +2,5 @@ class TestHookService def execute(hook, current_user) data = GitPushService.new.sample_data(hook.project, current_user) hook.execute(data) - true - rescue SocketError - false end end diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml index 211d77d518..29717aedd8 100644 --- a/app/views/admin/users/show.html.haml +++ b/app/views/admin/users/show.html.haml @@ -95,7 +95,7 @@ %li %span.light LDAP uid: %strong - = @user.extern_uid + = @user.ldap_identity.extern_uid - if @user.created_by %li diff --git a/app/views/dashboard/issues.atom.builder b/app/views/dashboard/issues.atom.builder index f541355778..6638131022 100644 --- a/app/views/dashboard/issues.atom.builder +++ b/app/views/dashboard/issues.atom.builder @@ -1,24 +1,13 @@ xml.instruct! -xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do +xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlnsmedia" => "http://search.yahoo.com/mrss/" do xml.title "#{current_user.name} issues" - xml.link :href => issues_dashboard_url(:atom, :private_token => current_user.private_token), :rel => "self", :type => "application/atom+xml" - xml.link :href => issues_dashboard_url(:private_token => current_user.private_token), :rel => "alternate", :type => "text/html" - xml.id issues_dashboard_url(:private_token => current_user.private_token) + xml.link href: issues_dashboard_url(:atom, private_token: current_user.private_token), rel: "self", type: "application/atom+xml" + xml.link href: issues_dashboard_url(private_token: current_user.private_token), rel: "alternate", type: "text/html" + xml.id issues_dashboard_url(private_token: current_user.private_token) xml.updated @issues.first.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") if @issues.any? @issues.each do |issue| - xml.entry do - xml.id project_issue_url(issue.project, issue) - xml.link :href => project_issue_url(issue.project, issue) - xml.title truncate(issue.title, :length => 80) - xml.updated issue.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") - xml.media :thumbnail, :width => "40", :height => "40", :url => avatar_icon(issue.author_email) - xml.author do |author| - xml.name issue.author_name - xml.email issue.author_email - end - xml.summary issue.title - end + issue_to_atom(xml, issue) end end diff --git a/app/views/dashboard/show.atom.builder b/app/views/dashboard/show.atom.builder index f4cf24ccd9..70ac66f801 100644 --- a/app/views/dashboard/show.atom.builder +++ b/app/views/dashboard/show.atom.builder @@ -1,29 +1,12 @@ xml.instruct! -xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do +xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlnsmedia" => "http://search.yahoo.com/mrss/" do xml.title "Dashboard feed#{" - #{current_user.name}" if current_user.name.present?}" - xml.link :href => dashboard_url(:atom), :rel => "self", :type => "application/atom+xml" - xml.link :href => dashboard_url, :rel => "alternate", :type => "text/html" + xml.link href: dashboard_url(:atom), rel: "self", type: "application/atom+xml" + xml.link href: dashboard_url, rel: "alternate", type: "text/html" xml.id projects_url xml.updated @events.maximum(:updated_at).strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any? @events.each do |event| - if event.proper? - xml.entry do - event_link = event_feed_url(event) - event_title = event_feed_title(event) - event_summary = event_feed_summary(event) - - xml.id "tag:#{request.host},#{event.created_at.strftime("%Y-%m-%d")}:#{event.id}" - xml.link :href => event_link - xml.title truncate(event_title, :length => 80) - xml.updated event.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") - xml.media :thumbnail, :width => "40", :height => "40", :url => avatar_icon(event.author_email) - xml.author do |author| - xml.name event.author_name - xml.email event.author_email - end - xml.summary(:type => "xhtml") { |x| x << event_summary unless event_summary.nil? } - end - end + event_to_atom(xml, event) end end diff --git a/app/views/devise/sessions/_oauth_providers.html.haml b/app/views/devise/sessions/_oauth_providers.html.haml index 15048a7806..8d6aaefb9f 100644 --- a/app/views/devise/sessions/_oauth_providers.html.haml +++ b/app/views/devise/sessions/_oauth_providers.html.haml @@ -1,4 +1,4 @@ -- providers = (enabled_oauth_providers - [:ldap]) +- providers = additional_providers - if providers.present? .bs-callout.bs-callout-info{:'data-no-turbolink' => 'data-no-turbolink'} %span Sign in with:   diff --git a/app/views/groups/issues.atom.builder b/app/views/groups/issues.atom.builder index f2005193f8..240001967f 100644 --- a/app/views/groups/issues.atom.builder +++ b/app/views/groups/issues.atom.builder @@ -7,18 +7,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear xml.updated @issues.first.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") if @issues.any? @issues.each do |issue| - xml.entry do - xml.id project_issue_url(issue.project, issue) - xml.link :href => project_issue_url(issue.project, issue) - xml.title truncate(issue.title, :length => 80) - xml.updated issue.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") - xml.media :thumbnail, :width => "40", :height => "40", :url => avatar_icon(issue.author_email) - xml.author do |author| - xml.name issue.author_name - xml.email issue.author_email - end - xml.summary issue.title - end + issue_to_atom(xml, issue) end end diff --git a/app/views/groups/show.atom.builder b/app/views/groups/show.atom.builder index e07bb7d2fb..e765ea8338 100644 --- a/app/views/groups/show.atom.builder +++ b/app/views/groups/show.atom.builder @@ -1,28 +1,12 @@ xml.instruct! -xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do +xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlnsmedia" => "http://search.yahoo.com/mrss/" do xml.title "Group feed - #{@group.name}" - xml.link :href => group_path(@group, :atom), :rel => "self", :type => "application/atom+xml" - xml.link :href => group_path(@group), :rel => "alternate", :type => "text/html" + xml.link href: group_path(@group, :atom), rel: "self", type: "application/atom+xml" + xml.link href: group_path(@group), rel: "alternate", type: "text/html" xml.id projects_url xml.updated @events.maximum(:updated_at).strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any? @events.each do |event| - if event.proper? - xml.entry do - event_link = event_feed_url(event) - event_title = event_feed_title(event) - - xml.id "tag:#{request.host},#{event.created_at.strftime("%Y-%m-%d")}:#{event.id}" - xml.link :href => event_link - xml.title truncate(event_title, :length => 80) - xml.updated event.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") - xml.media :thumbnail, :width => "40", :height => "40", :url => avatar_icon(event.author_email) - xml.author do |author| - xml.name event.author_name - xml.email event.author_email - end - xml.summary event_title - end - end + event_to_atom(xml, event) end end diff --git a/app/views/projects/issues/index.atom.builder b/app/views/projects/issues/index.atom.builder index 012ba23595..61e651da93 100644 --- a/app/views/projects/issues/index.atom.builder +++ b/app/views/projects/issues/index.atom.builder @@ -7,17 +7,6 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear xml.updated @issues.first.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") if @issues.any? @issues.each do |issue| - xml.entry do - xml.id project_issue_url(@project, issue) - xml.link :href => project_issue_url(@project, issue) - xml.title truncate(issue.title, :length => 80) - xml.updated issue.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") - xml.media :thumbnail, :width => "40", :height => "40", :url => avatar_icon(issue.author_email) - xml.author do |author| - xml.name issue.author_name - xml.email issue.author_email - end - xml.summary issue.title - end + issue_to_atom(xml, issue) end end diff --git a/app/views/users/show.atom.builder b/app/views/users/show.atom.builder new file mode 100644 index 0000000000..b7216a8876 --- /dev/null +++ b/app/views/users/show.atom.builder @@ -0,0 +1,12 @@ +xml.instruct! +xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlnsmedia" => "http://search.yahoo.com/mrss/" do + xml.title "Activity feed for #{@user.name}" + xml.link href: user_url(@user, :atom), rel: "self", type: "application/atom+xml" + xml.link href: user_url(@user), rel: "alternate", type: "text/html" + xml.id projects_url + xml.updated @events.maximum(:updated_at).strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any? + + @events.each do |event| + event_to_atom(xml, event) + end +end diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index cb49c030af..54f2666ce5 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -18,7 +18,15 @@ %h4 Groups: = render 'groups', groups: @groups %hr - %h4 User Activity: + %h4 + User Activity: + + - if current_user + %span.rss-icon.pull-right + = link_to user_path(@user, :atom, { private_token: current_user.private_token }) do + %strong + %i.fa.fa-rss + = render @events .col-md-4 = render 'profile', user: @user diff --git a/config/routes.rb b/config/routes.rb index 723104daf1..f2984069b7 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -137,7 +137,8 @@ Gitlab::Application.routes.draw do end end - match "/u/:username" => "users#show", as: :user, constraints: { username: /.*/ }, via: :get + match "/u/:username" => "users#show", as: :user, + constraints: {username: /(?:[^.]|\.(?!atom$))+/, format: /atom/}, via: :get # # Dashboard Area diff --git a/db/migrate/20141121161704_add_identity_table.rb b/db/migrate/20141121161704_add_identity_table.rb new file mode 100644 index 0000000000..243958039a --- /dev/null +++ b/db/migrate/20141121161704_add_identity_table.rb @@ -0,0 +1,32 @@ +class AddIdentityTable < ActiveRecord::Migration + def up + create_table :identities do |t| + t.string :extern_uid + t.string :provider + t.references :user + end + + add_index :identities, :user_id + + User.where("provider IS NOT NULL").find_each do |user| + execute "INSERT INTO identities(provider, extern_uid, user_id) VALUES('#{user.provider}', '#{user.extern_uid}', '#{user.id}')" + end + + remove_column :users, :extern_uid + remove_column :users, :provider + end + + def down + add_column :users, :extern_uid, :string + add_column :users, :provider, :string + + User.where("id IN(SELECT user_id FROM identities)").find_each do |user| + identity = user.identities.last + user.extern_uid = identity.extern_uid + user.provider = identity.provider + user.save + end + + drop_table :identities + end +end diff --git a/db/schema.rb b/db/schema.rb index 68d1080b6e..ec211901e4 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20141121133009) do +ActiveRecord::Schema.define(version: 20141121161704) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -74,6 +74,14 @@ ActiveRecord::Schema.define(version: 20141121133009) do add_index "forked_project_links", ["forked_to_project_id"], name: "index_forked_project_links_on_forked_to_project_id", unique: true, using: :btree + create_table "identities", force: true do |t| + t.string "extern_uid" + t.string "provider" + t.integer "user_id" + end + + add_index "identities", ["user_id"], name: "index_identities_on_user_id", using: :btree + create_table "issues", force: true do |t| t.string "title" t.integer "assignee_id" @@ -350,8 +358,6 @@ ActiveRecord::Schema.define(version: 20141121133009) do t.string "bio" t.integer "failed_attempts", default: 0 t.datetime "locked_at" - t.string "extern_uid" - t.string "provider" t.string "username" t.boolean "can_create_group", default: true, null: false t.boolean "can_create_team", default: true, null: false @@ -360,6 +366,7 @@ ActiveRecord::Schema.define(version: 20141121133009) do t.integer "notification_level", default: 1, null: false t.datetime "password_expires_at" t.integer "created_by_id" + t.datetime "last_credential_check_at" t.string "avatar" t.string "confirmation_token" t.datetime "confirmed_at" @@ -367,7 +374,6 @@ ActiveRecord::Schema.define(version: 20141121133009) do t.string "unconfirmed_email" t.boolean "hide_no_ssh_key", default: false t.string "website_url", default: "", null: false - t.datetime "last_credential_check_at" end add_index "users", ["admin"], name: "index_users_on_admin", using: :btree @@ -375,7 +381,6 @@ ActiveRecord::Schema.define(version: 20141121133009) do add_index "users", ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true, using: :btree add_index "users", ["current_sign_in_at"], name: "index_users_on_current_sign_in_at", using: :btree add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree - add_index "users", ["extern_uid", "provider"], name: "index_users_on_extern_uid_and_provider", unique: true, using: :btree add_index "users", ["name"], name: "index_users_on_name", using: :btree add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree add_index "users", ["username"], name: "index_users_on_username", using: :btree diff --git a/doc/development/rake_tasks.md b/doc/development/rake_tasks.md index 6d9ac161e9..53f8095cb1 100644 --- a/doc/development/rake_tasks.md +++ b/doc/development/rake_tasks.md @@ -1,6 +1,6 @@ # Rake tasks for developers -## Setup db with developer seeds: +## Setup db with developer seeds Note that if your db user does not have advanced privileges you must create the db manually before running this command. @@ -8,6 +8,10 @@ Note that if your db user does not have advanced privileges you must create the bundle exec rake setup ``` +The `setup` task is a alias for `gitlab:setup`. +This tasks calls `db:setup` to create the database, calls `add_limits_mysql` that adds limits to the database schema in case of a MySQL database and fianlly it calls `db:seed_fu` to seed the database. +Note: `db:setup` calls `db:seed` but this does nothing. + ## Run tests This runs all test suites present in GitLab. diff --git a/doc/install/requirements.md b/doc/install/requirements.md index 2b60c3560a..660c1adb80 100644 --- a/doc/install/requirements.md +++ b/doc/install/requirements.md @@ -50,12 +50,12 @@ We love [JRuby](http://jruby.org/) and [Rubinius](http://rubini.us/) but GitLab ### Memory -- 512MB is the absolute minimum but we do not recommend this amount of memory. -You will need to configure minimum 1.5GB of swap space. -With 1.5GB of swap space you must configure only one unicorn worker. -With one unicorn worker only git over ssh access will work because the git over HTTP access requires two running workers (one worker to receive the user request and one worker for the authorization check). -If you use SSD storage and configure 1.5GB of swap space you can use two Unicorn workers, this will allow HTTP access but it will still be slow. -Consider installing GitLab on Ubuntu as installation on CentOS could be unsuccessful with this amount of memory. +- 512MB is the absolute minimum but we strongly **advise against** this amount of memory. +You will need to configure a minimum of 1.5GB of swap space to make the Omnibus package reconfigure run succeed. +If you use a magnetic (non-SSD) swap drive we recommend to configure only one Unicorn worker. +With one Unicorn worker only git over ssh access will work because the git over HTTP access requires two running workers (one worker to receive the user request and one worker for the authorization check). +If you use a SSD drive you can use two Unicorn workers, this will allow HTTP access although it will be slow. +Consider installing GitLab on Ubuntu instead of CentOS because sometimes CentOS gives errors during installation and usage with this amount of memory. - 1GB RAM + 1GB swap supports up to 100 users - **2GB RAM** is the **recommended** memory size and supports up to 500 users - 4GB RAM supports up to 2,000 users diff --git a/doc/integration/gitlab_buttons_in_gmail.md b/doc/integration/gitlab_buttons_in_gmail.md index 5cfea5a90f..0816509c55 100644 --- a/doc/integration/gitlab_buttons_in_gmail.md +++ b/doc/integration/gitlab_buttons_in_gmail.md @@ -9,3 +9,20 @@ If correctly setup, emails that require an action will be marked in Gmail. To get this functioning, you need to be registered with Google. [See how to register with google in this document.](https://developers.google.com/gmail/markup/registering-with-google) +To aid the registering with google, GitLab offers a rake task that will send an email to google whitelisting email address from your GitLab server. + +To check what would be sent to the google email address, run the rake task: + +```bash +bundle exec rake gitlab:mail_google_schema_whitelisting RAILS_ENV=production +``` + +**This will not send the email but give you the output of how the mail will look.** + +Copy the output of the rake task to [google email markup tester](https://www.google.com/webmasters/markup-tester/u/0/) and press "Validate". + +If you receive "No errors detected" message from the tester you can send the email using: + +```bash +bundle exec rake gitlab:mail_google_schema_whitelisting RAILS_ENV=production SEND=true +`` diff --git a/features/steps/profile/profile.rb b/features/steps/profile/profile.rb index 6d747b65ba..38aaadcd28 100644 --- a/features/steps/profile/profile.rb +++ b/features/steps/profile/profile.rb @@ -170,7 +170,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps end step "I am not an ldap user" do - current_user.update_attributes(extern_uid: nil, provider: '') + current_user.identities.delete current_user.ldap_user?.should be_false end diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 42e4442365..2fea151aeb 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -14,10 +14,14 @@ module API expose :bio, :skype, :linkedin, :twitter, :website_url end + class Identity < Grape::Entity + expose :provider, :extern_uid + end + class UserFull < User expose :email - expose :theme_id, :color_scheme_id, :extern_uid, :provider, \ - :projects_limit + expose :theme_id, :color_scheme_id, :projects_limit + expose :identities, using: Entities::Identity expose :can_create_group?, as: :can_create_group expose :can_create_project?, as: :can_create_project end diff --git a/lib/api/users.rb b/lib/api/users.rb index d07815a8a9..37b36ddcf9 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -59,10 +59,16 @@ module API post do authenticated_as_admin! required_attributes! [:email, :password, :name, :username] - attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :projects_limit, :username, :extern_uid, :provider, :bio, :can_create_group, :admin] + attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :projects_limit, :username, :bio, :can_create_group, :admin] user = User.build_user(attrs) admin = attrs.delete(:admin) user.admin = admin unless admin.nil? + + identity_attrs = attributes_for_keys [:provider, :extern_uid] + if identity_attrs.any? + user.identities.build(identity_attrs) + end + if user.save present user, with: Entities::UserFull else @@ -89,8 +95,6 @@ module API # twitter - Twitter account # website_url - Website url # projects_limit - Limit projects each user can create - # extern_uid - External authentication provider UID - # provider - External provider # bio - Bio # admin - User is admin - true or false (default) # can_create_group - User can create groups - true or false @@ -99,7 +103,7 @@ module API put ":id" do authenticated_as_admin! - attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :website_url, :projects_limit, :username, :extern_uid, :provider, :bio, :can_create_group, :admin] + attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :website_url, :projects_limit, :username, :bio, :can_create_group, :admin] user = User.find(params[:id]) not_found!('User') unless user diff --git a/lib/gitlab/ldap/access.rb b/lib/gitlab/ldap/access.rb index eb2c4e48ff..0c85acf7e6 100644 --- a/lib/gitlab/ldap/access.rb +++ b/lib/gitlab/ldap/access.rb @@ -8,7 +8,7 @@ module Gitlab attr_reader :adapter, :provider, :user def self.open(user, &block) - Gitlab::LDAP::Adapter.open(user.provider) do |adapter| + Gitlab::LDAP::Adapter.open(user.ldap_identity.provider) do |adapter| block.call(self.new(user, adapter)) end end @@ -28,13 +28,13 @@ module Gitlab def initialize(user, adapter=nil) @adapter = adapter @user = user - @provider = user.provider + @provider = user.ldap_identity.provider end def allowed? - if Gitlab::LDAP::Person.find_by_dn(user.extern_uid, adapter) + if Gitlab::LDAP::Person.find_by_dn(user.ldap_identity.extern_uid, adapter) return true unless ldap_config.active_directory - !Gitlab::LDAP::Person.disabled_via_active_directory?(user.extern_uid, adapter) + !Gitlab::LDAP::Person.disabled_via_active_directory?(user.ldap_identity.extern_uid, adapter) else false end diff --git a/lib/gitlab/ldap/user.rb b/lib/gitlab/ldap/user.rb index 3176e9790a..3ef494ba13 100644 --- a/lib/gitlab/ldap/user.rb +++ b/lib/gitlab/ldap/user.rb @@ -12,9 +12,10 @@ module Gitlab class << self def find_by_uid_and_provider(uid, provider) # LDAP distinguished name is case-insensitive - ::User. + identity = ::Identity. where(provider: [provider, :ldap]). where('lower(extern_uid) = ?', uid.downcase).last + identity && identity.user end end @@ -34,15 +35,13 @@ module Gitlab end def find_by_email - model.find_by(email: auth_hash.email) + ::User.find_by(email: auth_hash.email) end def update_user_attributes - gl_user.attributes = { - extern_uid: auth_hash.uid, - provider: auth_hash.provider, - email: auth_hash.email - } + gl_user.email = auth_hash.email + gl_user.identities.build(provider: auth_hash.provider, extern_uid: auth_hash.uid) + gl_user end def changed? diff --git a/lib/gitlab/oauth/user.rb b/lib/gitlab/oauth/user.rb index 47f62153a5..6861427864 100644 --- a/lib/gitlab/oauth/user.rb +++ b/lib/gitlab/oauth/user.rb @@ -5,6 +5,8 @@ # module Gitlab module OAuth + class ForbiddenAction < StandardError; end + class User attr_accessor :auth_hash, :gl_user @@ -70,24 +72,24 @@ module Gitlab end def find_by_uid_and_provider - model.where(provider: auth_hash.provider, extern_uid: auth_hash.uid).last + identity = Identity.find_by(provider: auth_hash.provider, extern_uid: auth_hash.uid) + identity && identity.user end def build_new_user - model.new(user_attributes).tap do |user| - user.skip_confirmation! - end + user = ::User.new(user_attributes) + user.skip_confirmation! + user.identities.new(extern_uid: auth_hash.uid, provider: auth_hash.provider) + user end def user_attributes { - extern_uid: auth_hash.uid, - provider: auth_hash.provider, name: auth_hash.name, username: auth_hash.username, email: auth_hash.email, password: auth_hash.password, - password_confirmation: auth_hash.password, + password_confirmation: auth_hash.password } end @@ -95,12 +97,8 @@ module Gitlab Gitlab::AppLogger end - def model - ::User - end - - def raise_unauthorized_to_create - raise StandardError.new("Unauthorized to create user, signup disabled for #{auth_hash.provider}") + def unauthorized_to_create + raise ForbiddenAction.new("Unauthorized to create user, signup disabled for #{auth_hash.provider}") end end end diff --git a/lib/tasks/gitlab/mail_google_schema_whitelisting.rake b/lib/tasks/gitlab/mail_google_schema_whitelisting.rake new file mode 100644 index 0000000000..f40bba24da --- /dev/null +++ b/lib/tasks/gitlab/mail_google_schema_whitelisting.rake @@ -0,0 +1,73 @@ +require "#{Rails.root}/app/helpers/emails_helper" +require 'action_view/helpers' +extend ActionView::Helpers + +include ActionView::Context +include EmailsHelper + +namespace :gitlab do + desc "Email google whitelisting email with example email for actions in inbox" + task mail_google_schema_whitelisting: :environment do + subject = "Rails | Implemented feature" + url = "#{Gitlab.config.gitlab.url}/base/rails-project/issues/#{rand(1..100)}#note_#{rand(10..1000)}" + schema = email_action(url) + body = email_template(schema, url) + mail = Notify.test_email("schema.whitelisting+sample@gmail.com", subject, body.html_safe) + if send_now + mail.deliver + else + puts "WOULD SEND:" + end + puts mail + end + + def email_template(schema, url) + " + + + + GitLab + + + + + +
+
+

I like it :+1:

+
+
+ + + + " + end + + def send_now + if ENV['SEND'] == "true" + true + else + false + end + end +end diff --git a/spec/factories.rb b/spec/factories.rb index 15899d8c3c..5806013163 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -18,15 +18,24 @@ FactoryGirl.define do password "12345678" password_confirmation { password } confirmed_at { Time.now } - confirmation_token { nil } + confirmation_token { nil } trait :admin do admin true end - trait :ldap do - provider 'ldapmain' - extern_uid 'my-ldap-id' + factory :omniauth_user do + ignore do + extern_uid '123456' + provider 'ldapmain' + end + + after(:create) do |user, evaluator| + user.identities << create(:identity, + provider: evaluator.provider, + extern_uid: evaluator.extern_uid + ) + end end factory :admin, traits: [:admin] @@ -182,4 +191,9 @@ FactoryGirl.define do deploy_key project end + + factory :identity do + provider 'ldapmain' + extern_uid 'my-ldap-id' + end end diff --git a/spec/features/atom/users_spec.rb b/spec/features/atom/users_spec.rb new file mode 100644 index 0000000000..746b6fc1ac --- /dev/null +++ b/spec/features/atom/users_spec.rb @@ -0,0 +1,43 @@ +require 'spec_helper' + +describe "User Feed", feature: true do + describe "GET /" do + let!(:user) { create(:user) } + + context "user atom feed via private token" do + it "should render user atom feed" do + visit user_path(user, :atom, private_token: user.private_token) + body.should have_selector("feed title") + end + end + + context 'feed content' do + let(:project) { create(:project) } + let(:issue) { create(:issue, project: project, author: user, description: '') } + let(:note) { create(:note, noteable: issue, author: user, note: 'Bug confirmed', project: project) } + + before do + project.team << [user, :master] + issue_event(issue, user) + note_event(note, user) + visit user_path(user, :atom, private_token: user.private_token) + end + + it "should have issue opened event" do + body.should have_content("#{user.name} opened issue ##{issue.iid}") + end + + it "should have issue comment event" do + body.should have_content("#{user.name} commented on issue ##{issue.iid}") + end + end + end + + def issue_event(issue, user) + EventCreateService.new.open_issue(issue, user) + end + + def note_event(note, user) + EventCreateService.new.leave_note(note, user) + end +end diff --git a/spec/helpers/oauth_helper_spec.rb b/spec/helpers/oauth_helper_spec.rb new file mode 100644 index 0000000000..453699136e --- /dev/null +++ b/spec/helpers/oauth_helper_spec.rb @@ -0,0 +1,20 @@ +require "spec_helper" + +describe OauthHelper do + describe "additional_providers" do + it 'returns all enabled providers' do + allow(helper).to receive(:enabled_oauth_providers) { [:twitter, :github] } + helper.additional_providers.should include(*[:twitter, :github]) + end + + it 'does not return ldap provider' do + allow(helper).to receive(:enabled_oauth_providers) { [:twitter, :ldapmain] } + helper.additional_providers.should include(:twitter) + end + + it 'returns empty array' do + allow(helper).to receive(:enabled_oauth_providers) { [] } + helper.additional_providers.should == [] + end + end +end \ No newline at end of file diff --git a/spec/lib/gitlab/ldap/access_spec.rb b/spec/lib/gitlab/ldap/access_spec.rb index f4d5a92739..4573b8696c 100644 --- a/spec/lib/gitlab/ldap/access_spec.rb +++ b/spec/lib/gitlab/ldap/access_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Gitlab::LDAP::Access do let(:access) { Gitlab::LDAP::Access.new user } - let(:user) { create(:user, :ldap) } + let(:user) { create(:omniauth_user) } describe :allowed? do subject { access.allowed? } diff --git a/spec/lib/gitlab/ldap/authentication_spec.rb b/spec/lib/gitlab/ldap/authentication_spec.rb index 0eb7c443b8..11fdf10875 100644 --- a/spec/lib/gitlab/ldap/authentication_spec.rb +++ b/spec/lib/gitlab/ldap/authentication_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Gitlab::LDAP::Authentication do let(:klass) { Gitlab::LDAP::Authentication } - let(:user) { create(:user, :ldap, extern_uid: dn) } + let(:user) { create(:omniauth_user, extern_uid: dn) } let(:dn) { 'uid=john,ou=people,dc=example,dc=com' } let(:login) { 'john' } let(:password) { 'password' } diff --git a/spec/lib/gitlab/ldap/user_spec.rb b/spec/lib/gitlab/ldap/user_spec.rb index 726c9764e3..f73884e644 100644 --- a/spec/lib/gitlab/ldap/user_spec.rb +++ b/spec/lib/gitlab/ldap/user_spec.rb @@ -15,18 +15,18 @@ describe Gitlab::LDAP::User do describe :find_or_create do it "finds the user if already existing" do - existing_user = create(:user, extern_uid: 'my-uid', provider: 'ldapmain') + existing_user = create(:omniauth_user, extern_uid: 'my-uid', provider: 'ldapmain') expect{ gl_user.save }.to_not change{ User.count } end it "connects to existing non-ldap user if the email matches" do - existing_user = create(:user, email: 'john@example.com') + existing_user = create(:omniauth_user, email: 'john@example.com', provider: "twitter") expect{ gl_user.save }.to_not change{ User.count } existing_user.reload - expect(existing_user.extern_uid).to eql 'my-uid' - expect(existing_user.provider).to eql 'ldapmain' + expect(existing_user.ldap_identity.extern_uid).to eql 'my-uid' + expect(existing_user.ldap_identity.provider).to eql 'ldapmain' end it "creates a new user if not found" do diff --git a/spec/lib/gitlab/oauth/user_spec.rb b/spec/lib/gitlab/oauth/user_spec.rb index 8a83a1b258..8830751578 100644 --- a/spec/lib/gitlab/oauth/user_spec.rb +++ b/spec/lib/gitlab/oauth/user_spec.rb @@ -15,7 +15,7 @@ describe Gitlab::OAuth::User do end describe :persisted? do - let!(:existing_user) { create(:user, extern_uid: 'my-uid', provider: 'my-provider') } + let!(:existing_user) { create(:omniauth_user, extern_uid: 'my-uid', provider: 'my-provider') } it "finds an existing user based on uid and provider (facebook)" do auth = double(info: double(name: 'John'), uid: 'my-uid', provider: 'my-provider') @@ -39,8 +39,9 @@ describe Gitlab::OAuth::User do oauth_user.save expect(gl_user).to be_valid - expect(gl_user.extern_uid).to eql uid - expect(gl_user.provider).to eql 'twitter' + identity = gl_user.identities.first + expect(identity.extern_uid).to eql uid + expect(identity.provider).to eql 'twitter' end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 6d865cfc69..8be7f733a5 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -62,6 +62,7 @@ describe User do it { should have_many(:assigned_issues).dependent(:destroy) } it { should have_many(:merge_requests).dependent(:destroy) } it { should have_many(:assigned_merge_requests).dependent(:destroy) } + it { should have_many(:identities).dependent(:destroy) } end describe "Mass assignment" do @@ -361,24 +362,29 @@ describe User do end describe :ldap_user? do - let(:user) { build(:user, :ldap) } - it "is true if provider name starts with ldap" do - user.provider = 'ldapmain' + user = create(:omniauth_user, provider: 'ldapmain') expect( user.ldap_user? ).to be_true end it "is false for other providers" do - user.provider = 'other-provider' + user = create(:omniauth_user, provider: 'other-provider') expect( user.ldap_user? ).to be_false end it "is false if no extern_uid is provided" do - user.extern_uid = nil + user = create(:omniauth_user, extern_uid: nil) expect( user.ldap_user? ).to be_false end end + describe :ldap_identity do + it "returns ldap identity" do + user = create :omniauth_user + user.ldap_identity.provider.should_not be_empty + end + end + describe '#full_website_url' do let(:user) { create(:user) } diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index 113a39b870..1ecc79ea7e 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -33,7 +33,7 @@ describe API::API, api: true do response.status.should == 200 json_response.should be_an Array json_response.first.keys.should include 'email' - json_response.first.keys.should include 'extern_uid' + json_response.first.keys.should include 'identities' json_response.first.keys.should include 'can_create_project' end end diff --git a/spec/tasks/gitlab/mail_google_schema_whitelisting.rb b/spec/tasks/gitlab/mail_google_schema_whitelisting.rb new file mode 100644 index 0000000000..45aaf0fc90 --- /dev/null +++ b/spec/tasks/gitlab/mail_google_schema_whitelisting.rb @@ -0,0 +1,27 @@ +require 'spec_helper' +require 'rake' + +describe 'gitlab:mail_google_schema_whitelisting rake task' do + before :all do + Rake.application.rake_require "tasks/gitlab/task_helpers" + Rake.application.rake_require "tasks/gitlab/mail_google_schema_whitelisting" + # empty task as env is already loaded + Rake::Task.define_task :environment + end + + describe 'call' do + before do + # avoid writing task output to spec progress + $stdout.stub :write + end + + let :run_rake_task do + Rake::Task["gitlab:mail_google_schema_whitelisting"].reenable + Rake.application.invoke_task "gitlab:mail_google_schema_whitelisting" + end + + it 'should run the task without errors' do + expect { run_rake_task }.to_not raise_error + end + end +end