Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ce
This commit is contained in:
commit
1165d3dce1
|
@ -5,6 +5,7 @@ v 7.13.0 (unreleased)
|
||||||
- Fix external issue tracker hook/test for HTTPS URLs (Daniel Gerhardt)
|
- Fix external issue tracker hook/test for HTTPS URLs (Daniel Gerhardt)
|
||||||
- Remove link leading to a 404 error in Deploy Keys page (Stan Hu)
|
- Remove link leading to a 404 error in Deploy Keys page (Stan Hu)
|
||||||
- Add support for unlocking users in admin settings (Stan Hu)
|
- Add support for unlocking users in admin settings (Stan Hu)
|
||||||
|
- Add Irker service configuration options (Stan Hu)
|
||||||
- Fix order of issues imported form GitHub (Hiroyuki Sato)
|
- Fix order of issues imported form GitHub (Hiroyuki Sato)
|
||||||
- Bump rugments to 1.0.0beta8 to fix C prototype function highlighting (Jonathon Reinhart)
|
- Bump rugments to 1.0.0beta8 to fix C prototype function highlighting (Jonathon Reinhart)
|
||||||
- Fix Merge Request webhook to properly fire "merge" action when accepted from the web UI
|
- Fix Merge Request webhook to properly fire "merge" action when accepted from the web UI
|
||||||
|
@ -39,6 +40,8 @@ v 7.13.0 (unreleased)
|
||||||
|
|
||||||
v 7.12.2
|
v 7.12.2
|
||||||
- Correctly show anonymous authorized applications under Profile > Applications.
|
- Correctly show anonymous authorized applications under Profile > Applications.
|
||||||
|
- Faster automerge check and merge itself when source and target branches are in same repository
|
||||||
|
- Audit log for user authentication
|
||||||
|
|
||||||
v 7.12.1
|
v 7.12.1
|
||||||
- Fix error when deleting a user who has projects (Stan Hu)
|
- Fix error when deleting a user who has projects (Stan Hu)
|
||||||
|
|
|
@ -28,6 +28,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
||||||
|
|
||||||
# Do additional LDAP checks for the user filter and EE features
|
# Do additional LDAP checks for the user filter and EE features
|
||||||
if @user.allowed?
|
if @user.allowed?
|
||||||
|
log_audit_event(gl_user, with: :ldap)
|
||||||
sign_in_and_redirect(gl_user)
|
sign_in_and_redirect(gl_user)
|
||||||
else
|
else
|
||||||
flash[:alert] = "Access denied for your LDAP account."
|
flash[:alert] = "Access denied for your LDAP account."
|
||||||
|
@ -47,6 +48,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
||||||
if current_user
|
if current_user
|
||||||
# Add new authentication method
|
# Add new authentication method
|
||||||
current_user.identities.find_or_create_by(extern_uid: oauth['uid'], provider: oauth['provider'])
|
current_user.identities.find_or_create_by(extern_uid: oauth['uid'], provider: oauth['provider'])
|
||||||
|
log_audit_event(current_user, with: oauth['provider'])
|
||||||
redirect_to profile_account_path, notice: 'Authentication method updated'
|
redirect_to profile_account_path, notice: 'Authentication method updated'
|
||||||
else
|
else
|
||||||
@user = Gitlab::OAuth::User.new(oauth)
|
@user = Gitlab::OAuth::User.new(oauth)
|
||||||
|
@ -54,6 +56,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
||||||
|
|
||||||
# Only allow properly saved users to login.
|
# Only allow properly saved users to login.
|
||||||
if @user.persisted? && @user.valid?
|
if @user.persisted? && @user.valid?
|
||||||
|
log_audit_event(@user.gl_user, with: oauth['provider'])
|
||||||
sign_in_and_redirect(@user.gl_user)
|
sign_in_and_redirect(@user.gl_user)
|
||||||
else
|
else
|
||||||
error_message =
|
error_message =
|
||||||
|
@ -83,4 +86,9 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
||||||
def oauth
|
def oauth
|
||||||
@oauth ||= request.env['omniauth.auth']
|
@oauth ||= request.env['omniauth.auth']
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def log_audit_event(user, options = {})
|
||||||
|
AuditEventService.new(user, user, options).
|
||||||
|
for_authentication.security_event
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -38,8 +38,11 @@ class ProfilesController < Profiles::ApplicationController
|
||||||
redirect_to profile_account_path
|
redirect_to profile_account_path
|
||||||
end
|
end
|
||||||
|
|
||||||
def history
|
def audit_log
|
||||||
@events = current_user.recent_events.page(params[:page]).per(PER_PAGE)
|
@events = AuditEvent.where(entity_type: "User", entity_id: current_user.id).
|
||||||
|
order("created_at DESC").
|
||||||
|
page(params[:page]).
|
||||||
|
per(PER_PAGE)
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_username
|
def update_username
|
||||||
|
|
|
@ -7,7 +7,8 @@ class Projects::ServicesController < Projects::ApplicationController
|
||||||
:colorize_messages, :channels,
|
:colorize_messages, :channels,
|
||||||
:push_events, :issues_events, :merge_requests_events, :tag_push_events,
|
:push_events, :issues_events, :merge_requests_events, :tag_push_events,
|
||||||
:note_events, :send_from_committer_email, :disable_diffs, :external_wiki_url,
|
:note_events, :send_from_committer_email, :disable_diffs, :external_wiki_url,
|
||||||
:notify, :color]
|
:notify, :color,
|
||||||
|
:server_host, :server_port, :default_irc_uri]
|
||||||
# Authorize
|
# Authorize
|
||||||
before_action :authorize_admin_project!
|
before_action :authorize_admin_project!
|
||||||
before_action :service, only: [:edit, :update, :test]
|
before_action :service, only: [:edit, :update, :test]
|
||||||
|
|
|
@ -37,6 +37,8 @@ class SessionsController < Devise::SessionsController
|
||||||
resource.update_attributes(reset_password_token: nil,
|
resource.update_attributes(reset_password_token: nil,
|
||||||
reset_password_sent_at: nil)
|
reset_password_sent_at: nil)
|
||||||
end
|
end
|
||||||
|
authenticated_with = user_params[:otp_attempt] ? "two-factor" : "standard"
|
||||||
|
log_audit_event(current_user, with: authenticated_with)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -95,4 +97,9 @@ class SessionsController < Devise::SessionsController
|
||||||
user.valid_otp?(user_params[:otp_attempt]) ||
|
user.valid_otp?(user_params[:otp_attempt]) ||
|
||||||
user.invalidate_otp_backup_code!(user_params[:otp_attempt])
|
user.invalidate_otp_backup_code!(user_params[:otp_attempt])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def log_audit_event(user, options = {})
|
||||||
|
AuditEventService.new(user, user, options).
|
||||||
|
for_authentication.security_event
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
class AuditEvent < ActiveRecord::Base
|
||||||
|
serialize :details, Hash
|
||||||
|
|
||||||
|
belongs_to :user, foreign_key: :author_id
|
||||||
|
|
||||||
|
validates :author_id, presence: true
|
||||||
|
validates :entity_id, presence: true
|
||||||
|
validates :entity_type, presence: true
|
||||||
|
|
||||||
|
after_initialize :initialize_details
|
||||||
|
|
||||||
|
def initialize_details
|
||||||
|
self.details = {} if details.nil?
|
||||||
|
end
|
||||||
|
|
||||||
|
def author_name
|
||||||
|
self.user.name
|
||||||
|
end
|
||||||
|
end
|
|
@ -21,26 +21,11 @@
|
||||||
require 'uri'
|
require 'uri'
|
||||||
|
|
||||||
class IrkerService < Service
|
class IrkerService < Service
|
||||||
|
prop_accessor :server_host, :server_port, :default_irc_uri
|
||||||
prop_accessor :colorize_messages, :recipients, :channels
|
prop_accessor :colorize_messages, :recipients, :channels
|
||||||
validates :recipients, presence: true, if: :activated?
|
validates :recipients, presence: true, if: :activated?
|
||||||
validate :check_recipients_count, if: :activated?
|
|
||||||
|
|
||||||
before_validation :get_channels
|
before_validation :get_channels
|
||||||
after_initialize :initialize_settings
|
|
||||||
|
|
||||||
# Writer for RSpec tests
|
|
||||||
attr_writer :settings
|
|
||||||
|
|
||||||
def initialize_settings
|
|
||||||
# See the documentation (doc/project_services/irker.md) for possible values
|
|
||||||
# here
|
|
||||||
@settings ||= {
|
|
||||||
server_ip: 'localhost',
|
|
||||||
server_port: 6659,
|
|
||||||
max_channels: 3,
|
|
||||||
default_irc_uri: nil
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def title
|
def title
|
||||||
'Irker (IRC gateway)'
|
'Irker (IRC gateway)'
|
||||||
|
@ -51,20 +36,6 @@ class IrkerService < Service
|
||||||
'gateway.'
|
'gateway.'
|
||||||
end
|
end
|
||||||
|
|
||||||
def help
|
|
||||||
msg = 'Recipients have to be specified with a full URI: '\
|
|
||||||
'irc[s]://irc.network.net[:port]/#channel. Special cases: if you want '\
|
|
||||||
'the channel to be a nickname instead, append ",isnick" to the channel '\
|
|
||||||
'name; if the channel is protected by a secret password, append '\
|
|
||||||
'"?key=secretpassword" to the URI.'
|
|
||||||
|
|
||||||
unless @settings[:default_irc].nil?
|
|
||||||
msg += ' Note that a default IRC URI is provided by this service\'s '\
|
|
||||||
"administrator: #{default_irc}. You can thus just give a channel name."
|
|
||||||
end
|
|
||||||
msg
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_param
|
def to_param
|
||||||
'irker'
|
'irker'
|
||||||
end
|
end
|
||||||
|
@ -77,31 +48,46 @@ class IrkerService < Service
|
||||||
return unless supported_events.include?(data[:object_kind])
|
return unless supported_events.include?(data[:object_kind])
|
||||||
|
|
||||||
IrkerWorker.perform_async(project_id, channels,
|
IrkerWorker.perform_async(project_id, channels,
|
||||||
colorize_messages, data, @settings)
|
colorize_messages, data, settings)
|
||||||
|
end
|
||||||
|
|
||||||
|
def settings
|
||||||
|
{ server_host: server_host.present? ? server_host : 'localhost',
|
||||||
|
server_port: server_port.present? ? server_port : 6659
|
||||||
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def fields
|
def fields
|
||||||
[
|
[
|
||||||
|
{ type: 'text', name: 'server_host', placeholder: 'localhost',
|
||||||
|
help: 'Irker daemon hostname (defaults to localhost)' },
|
||||||
|
{ type: 'text', name: 'server_port', placeholder: 6659,
|
||||||
|
help: 'Irker daemon port (defaults to 6659)' },
|
||||||
|
{ type: 'text', name: 'default_irc_uri',
|
||||||
|
help: 'A default IRC URI to prepend before each recipient (optional)',
|
||||||
|
placeholder: 'irc://irc.network.net:6697/' },
|
||||||
{ type: 'textarea', name: 'recipients',
|
{ type: 'textarea', name: 'recipients',
|
||||||
placeholder: 'Recipients/channels separated by whitespaces' },
|
placeholder: 'Recipients/channels separated by whitespaces',
|
||||||
|
help: 'Recipients have to be specified with a full URI: '\
|
||||||
|
'irc[s]://irc.network.net[:port]/#channel. Special cases: if '\
|
||||||
|
'you want the channel to be a nickname instead, append ",isnick" to ' \
|
||||||
|
'the channel name; if the channel is protected by a secret password, ' \
|
||||||
|
' append "?key=secretpassword" to the URI. Note that if you specify a ' \
|
||||||
|
' default IRC URI to prepend before each recipient, you can just give ' \
|
||||||
|
' a channel name.' },
|
||||||
{ type: 'checkbox', name: 'colorize_messages' },
|
{ type: 'checkbox', name: 'colorize_messages' },
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def help
|
||||||
|
' NOTE: Irker does NOT have built-in authentication, which makes it' \
|
||||||
|
' vulnerable to spamming IRC channels if it is hosted outside of a ' \
|
||||||
|
' firewall. Please make sure you run the daemon within a secured network ' \
|
||||||
|
' to prevent abuse. For more details, read: http://www.catb.org/~esr/irker/security.html.'
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def check_recipients_count
|
|
||||||
return true if recipients.nil? || recipients.empty?
|
|
||||||
|
|
||||||
if recipients.split(/\s+/).count > max_chans
|
|
||||||
errors.add(:recipients, "are limited to #{max_chans}")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def max_chans
|
|
||||||
@settings[:max_channels]
|
|
||||||
end
|
|
||||||
|
|
||||||
def get_channels
|
def get_channels
|
||||||
return true unless :activated?
|
return true unless :activated?
|
||||||
return true if recipients.nil? || recipients.empty?
|
return true if recipients.nil? || recipients.empty?
|
||||||
|
@ -114,40 +100,35 @@ class IrkerService < Service
|
||||||
|
|
||||||
def map_recipients
|
def map_recipients
|
||||||
self.channels = recipients.split(/\s+/).map do |recipient|
|
self.channels = recipients.split(/\s+/).map do |recipient|
|
||||||
format_channel default_irc_uri, recipient
|
format_channel(recipient)
|
||||||
end
|
end
|
||||||
channels.reject! &:nil?
|
channels.reject! &:nil?
|
||||||
end
|
end
|
||||||
|
|
||||||
def default_irc_uri
|
def format_channel(recipient)
|
||||||
default_irc = @settings[:default_irc_uri]
|
uri = nil
|
||||||
if !(default_irc.nil? || default_irc[-1] == '/')
|
|
||||||
default_irc += '/'
|
|
||||||
end
|
|
||||||
default_irc
|
|
||||||
end
|
|
||||||
|
|
||||||
def format_channel(default_irc, recipient)
|
|
||||||
cnt = 0
|
|
||||||
url = nil
|
|
||||||
|
|
||||||
# Try to parse the chan as a full URI
|
# Try to parse the chan as a full URI
|
||||||
begin
|
begin
|
||||||
uri = URI.parse(recipient)
|
uri = consider_uri(URI.parse(recipient))
|
||||||
raise URI::InvalidURIError if uri.scheme.nil? && cnt == 0
|
|
||||||
rescue URI::InvalidURIError
|
rescue URI::InvalidURIError
|
||||||
unless default_irc.nil?
|
|
||||||
cnt += 1
|
|
||||||
recipient = "#{default_irc}#{recipient}"
|
|
||||||
retry if cnt == 1
|
|
||||||
end
|
end
|
||||||
else
|
|
||||||
url = consider_uri uri
|
unless uri.present? and default_irc_uri.nil?
|
||||||
|
begin
|
||||||
|
new_recipient = URI.join(default_irc_uri, '/', recipient).to_s
|
||||||
|
uri = consider_uri(URI.parse(new_recipient))
|
||||||
|
rescue
|
||||||
|
Rails.logger.error("Unable to create a valid URL from #{default_irc_uri} and #{recipient}")
|
||||||
end
|
end
|
||||||
url
|
end
|
||||||
|
|
||||||
|
uri
|
||||||
end
|
end
|
||||||
|
|
||||||
def consider_uri(uri)
|
def consider_uri(uri)
|
||||||
|
return nil if uri.scheme.nil?
|
||||||
|
|
||||||
# Authorize both irc://domain.com/#chan and irc://domain.com/chan
|
# Authorize both irc://domain.com/#chan and irc://domain.com/chan
|
||||||
if uri.is_a?(URI) && uri.scheme[/^ircs?\z/] && !uri.path.nil?
|
if uri.is_a?(URI) && uri.scheme[/^ircs?\z/] && !uri.path.nil?
|
||||||
# Do not authorize irc://domain.com/
|
# Do not authorize irc://domain.com/
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
class SecurityEvent < AuditEvent
|
||||||
|
end
|
|
@ -0,0 +1,25 @@
|
||||||
|
class AuditEventService
|
||||||
|
def initialize(author, entity, details = {})
|
||||||
|
@author, @entity, @details = author, entity, details
|
||||||
|
end
|
||||||
|
|
||||||
|
def for_authentication
|
||||||
|
@details = {
|
||||||
|
with: @details[:with],
|
||||||
|
target_id: @author.id,
|
||||||
|
target_type: "User",
|
||||||
|
target_details: @author.name,
|
||||||
|
}
|
||||||
|
|
||||||
|
self
|
||||||
|
end
|
||||||
|
|
||||||
|
def security_event
|
||||||
|
SecurityEvent.create(
|
||||||
|
author_id: @author.id,
|
||||||
|
entity_id: @entity.id,
|
||||||
|
entity_type: @entity.class.name,
|
||||||
|
details: @details
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
|
@ -44,8 +44,8 @@
|
||||||
= icon('image fw')
|
= icon('image fw')
|
||||||
%span
|
%span
|
||||||
Preferences
|
Preferences
|
||||||
= nav_link(path: 'profiles#history') do
|
= nav_link(path: 'profiles#audit_log') do
|
||||||
= link_to history_profile_path, title: 'History', data: {placement: 'right'} do
|
= link_to audit_log_profile_path, title: 'Audit Log', data: {placement: 'right'} do
|
||||||
= icon('history fw')
|
= icon('history fw')
|
||||||
%span
|
%span
|
||||||
History
|
Audit Log
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
%table.table#audits
|
||||||
|
%thead
|
||||||
|
%tr
|
||||||
|
%th Action
|
||||||
|
%th When
|
||||||
|
|
||||||
|
%tbody
|
||||||
|
- events.each do |event|
|
||||||
|
%tr
|
||||||
|
%td
|
||||||
|
%span
|
||||||
|
Signed in with
|
||||||
|
%b= event.details[:with]
|
||||||
|
authentication
|
||||||
|
%td #{time_ago_in_words event.created_at} ago
|
||||||
|
= paginate events, theme: "gitlab"
|
|
@ -0,0 +1,5 @@
|
||||||
|
- page_title "Audit Log"
|
||||||
|
%h3.page-title Audit Log
|
||||||
|
%p.light History of authentications
|
||||||
|
|
||||||
|
= render 'event_table', events: @events
|
|
@ -1,11 +0,0 @@
|
||||||
- page_title "History"
|
|
||||||
%h3.page-title
|
|
||||||
Your Account History
|
|
||||||
%p.light
|
|
||||||
All events created by your account are listed below.
|
|
||||||
%hr
|
|
||||||
.profile_history
|
|
||||||
= render @events
|
|
||||||
%hr
|
|
||||||
= paginate @events, theme: "gitlab"
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ class IrkerWorker
|
||||||
branch = "\x0305#{branch}\x0f"
|
branch = "\x0305#{branch}\x0f"
|
||||||
end
|
end
|
||||||
|
|
||||||
# Firsts messages are for branch creation/deletion
|
# First messages are for branch creation/deletion
|
||||||
send_branch_updates push_data, project, repo_name, committer, branch
|
send_branch_updates push_data, project, repo_name, committer, branch
|
||||||
|
|
||||||
# Next messages are for commits
|
# Next messages are for commits
|
||||||
|
@ -34,7 +34,7 @@ class IrkerWorker
|
||||||
def init_perform(set, chans, colors)
|
def init_perform(set, chans, colors)
|
||||||
@colors = colors
|
@colors = colors
|
||||||
@channels = chans
|
@channels = chans
|
||||||
start_connection set['server_ip'], set['server_port']
|
start_connection set['server_host'], set['server_port']
|
||||||
end
|
end
|
||||||
|
|
||||||
def start_connection(irker_server, irker_port)
|
def start_connection(irker_server, irker_port)
|
||||||
|
|
|
@ -208,7 +208,7 @@ Gitlab::Application.routes.draw do
|
||||||
#
|
#
|
||||||
resource :profile, only: [:show, :update] do
|
resource :profile, only: [:show, :update] do
|
||||||
member do
|
member do
|
||||||
get :history
|
get :audit_log
|
||||||
get :applications
|
get :applications
|
||||||
|
|
||||||
put :reset_private_token
|
put :reset_private_token
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
class AddAuditEvent < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
create_table :audit_events do |t|
|
||||||
|
t.integer :author_id, null: false
|
||||||
|
t.string :type, null: false
|
||||||
|
|
||||||
|
# "Namespace" where the change occurs
|
||||||
|
# eg. On a project, group or user
|
||||||
|
t.integer :entity_id, null: false
|
||||||
|
t.string :entity_type, null: false
|
||||||
|
|
||||||
|
# Details for the event
|
||||||
|
t.text :details
|
||||||
|
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
|
||||||
|
add_index :audit_events, :author_id
|
||||||
|
add_index :audit_events, :type
|
||||||
|
add_index :audit_events, [:entity_id, :entity_type]
|
||||||
|
end
|
||||||
|
end
|
18
db/schema.rb
18
db/schema.rb
|
@ -28,16 +28,30 @@ ActiveRecord::Schema.define(version: 20150620233230) do
|
||||||
t.integer "default_branch_protection", default: 2
|
t.integer "default_branch_protection", default: 2
|
||||||
t.boolean "twitter_sharing_enabled", default: true
|
t.boolean "twitter_sharing_enabled", default: true
|
||||||
t.text "restricted_visibility_levels"
|
t.text "restricted_visibility_levels"
|
||||||
t.boolean "version_check_enabled", default: true
|
|
||||||
t.integer "max_attachment_size", default: 10, null: false
|
t.integer "max_attachment_size", default: 10, null: false
|
||||||
t.integer "default_project_visibility"
|
t.integer "default_project_visibility"
|
||||||
t.integer "default_snippet_visibility"
|
t.integer "default_snippet_visibility"
|
||||||
t.text "restricted_signup_domains"
|
t.text "restricted_signup_domains"
|
||||||
|
t.boolean "version_check_enabled", default: true
|
||||||
t.boolean "user_oauth_applications", default: true
|
t.boolean "user_oauth_applications", default: true
|
||||||
t.string "after_sign_out_path"
|
t.string "after_sign_out_path"
|
||||||
t.integer "session_expire_delay", default: 10080, null: false
|
t.integer "session_expire_delay", default: 10080, null: false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
create_table "audit_events", force: true do |t|
|
||||||
|
t.integer "author_id", null: false
|
||||||
|
t.string "type", null: false
|
||||||
|
t.integer "entity_id", null: false
|
||||||
|
t.string "entity_type", null: false
|
||||||
|
t.text "details"
|
||||||
|
t.datetime "created_at"
|
||||||
|
t.datetime "updated_at"
|
||||||
|
end
|
||||||
|
|
||||||
|
add_index "audit_events", ["author_id"], name: "index_audit_events_on_author_id", using: :btree
|
||||||
|
add_index "audit_events", ["entity_id", "entity_type"], name: "index_audit_events_on_entity_id_and_entity_type", using: :btree
|
||||||
|
add_index "audit_events", ["type"], name: "index_audit_events_on_type", using: :btree
|
||||||
|
|
||||||
create_table "broadcast_messages", force: true do |t|
|
create_table "broadcast_messages", force: true do |t|
|
||||||
t.text "message", null: false
|
t.text "message", null: false
|
||||||
t.datetime "starts_at"
|
t.datetime "starts_at"
|
||||||
|
@ -496,12 +510,12 @@ ActiveRecord::Schema.define(version: 20150620233230) do
|
||||||
t.string "bitbucket_access_token"
|
t.string "bitbucket_access_token"
|
||||||
t.string "bitbucket_access_token_secret"
|
t.string "bitbucket_access_token_secret"
|
||||||
t.string "location"
|
t.string "location"
|
||||||
|
t.string "public_email", default: "", null: false
|
||||||
t.string "encrypted_otp_secret"
|
t.string "encrypted_otp_secret"
|
||||||
t.string "encrypted_otp_secret_iv"
|
t.string "encrypted_otp_secret_iv"
|
||||||
t.string "encrypted_otp_secret_salt"
|
t.string "encrypted_otp_secret_salt"
|
||||||
t.boolean "otp_required_for_login", default: false, null: false
|
t.boolean "otp_required_for_login", default: false, null: false
|
||||||
t.text "otp_backup_codes"
|
t.text "otp_backup_codes"
|
||||||
t.string "public_email", default: "", null: false
|
|
||||||
t.integer "dashboard", default: 0
|
t.integer "dashboard", default: 0
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -343,6 +343,36 @@ Strikethrough uses two tildes. ~~Scratch this.~~
|
||||||
- Or minuses
|
- Or minuses
|
||||||
+ Or pluses
|
+ Or pluses
|
||||||
|
|
||||||
|
If a list item contains multiple paragraphs,
|
||||||
|
each subsequent paragraph should be indented with four spaces.
|
||||||
|
|
||||||
|
```no-highlight
|
||||||
|
1. First ordered list item
|
||||||
|
|
||||||
|
Second paragraph of first item.
|
||||||
|
2. Another item
|
||||||
|
```
|
||||||
|
|
||||||
|
1. First ordered list item
|
||||||
|
|
||||||
|
Second paragraph of first item.
|
||||||
|
2. Another item
|
||||||
|
|
||||||
|
If the second paragraph isn't indented with four spaces,
|
||||||
|
the second list item will be incorrectly labeled as `1`.
|
||||||
|
|
||||||
|
```no-highlight
|
||||||
|
1. First ordered list item
|
||||||
|
|
||||||
|
Second paragraph of first item.
|
||||||
|
2. Another item
|
||||||
|
```
|
||||||
|
|
||||||
|
1. First ordered list item
|
||||||
|
|
||||||
|
Second paragraph of first item.
|
||||||
|
2. Another item
|
||||||
|
|
||||||
## Links
|
## Links
|
||||||
|
|
||||||
There are two ways to create links, inline-style and reference-style.
|
There are two ways to create links, inline-style and reference-style.
|
||||||
|
|
|
@ -9,38 +9,43 @@ See the project homepage for further info: https://gitlab.com/esr/irker
|
||||||
## Needed setup
|
## Needed setup
|
||||||
|
|
||||||
You will first need an Irker daemon. You can download the Irker code from its
|
You will first need an Irker daemon. You can download the Irker code from its
|
||||||
gitorious repository on https://gitorious.org/irker: `git clone
|
repository on https://gitlab.com/esr/irker:
|
||||||
git@gitorious.org:irker/irker.git`. Once you have downloaded the code, you can
|
|
||||||
run the python script named `irkerd`. This script is the gateway script, it acts
|
```
|
||||||
both as an IRC client, for sending messages to an IRC server obviously, and as a
|
git clone https://gitlab.com/esr/irker.git
|
||||||
TCP server, for receiving messages from the GitLab service.
|
```
|
||||||
|
|
||||||
|
Once you have downloaded the code, you can run the python script named `irkerd`.
|
||||||
|
This script is the gateway script, it acts both as an IRC client, for sending
|
||||||
|
messages to an IRC server obviously, and as a TCP server, for receiving messages
|
||||||
|
from the GitLab service.
|
||||||
|
|
||||||
If the Irker server runs on the same machine, you are done. If not, you will
|
If the Irker server runs on the same machine, you are done. If not, you will
|
||||||
need to follow the firsts steps of the next section.
|
need to follow the firsts steps of the next section.
|
||||||
|
|
||||||
## Optional setup
|
## Complete these steps in GitLab:
|
||||||
|
|
||||||
In the `app/models/project_services/irker_service.rb` file, you can modify some
|
1. Navigate to the project you want to configure for notifications.
|
||||||
options in the `initialize_settings` method:
|
1. Select "Settings" in the top navigation.
|
||||||
- **server_ip** (defaults to `localhost`): the server IP address where the
|
1. Select "Services" in the left navigation.
|
||||||
`irkerd` daemon runs;
|
1. Click "Irker".
|
||||||
- **server_port** (defaults to `6659`): the server port of the `irkerd` daemon;
|
1. Select the "Active" checkbox.
|
||||||
- **max_channels** (defaults to `3`): the maximum number of recipients the
|
1. Enter the server host address where `irkerd` runs (defaults to `localhost`)
|
||||||
client is authorized to join, per project;
|
in the `Server host` field on the Web page
|
||||||
- **default_irc_uri** (no default) : if this option is set, it has to be in the
|
1. Enter the server port of `irkerd` (e.g. defaults to 6659) in the
|
||||||
format `irc[s]://domain.name` and will be prepend to each and every channel
|
`Server port` field on the Web page.
|
||||||
provided by the user which is not a full URI.
|
1. Optional: if `Default irc uri` is set, it has to be in the format
|
||||||
|
`irc[s]://domain.name` and will be prepend to each and every channel provided
|
||||||
If the Irker server and the GitLab application do not run on the same host, you
|
by the user which is not a full URI.
|
||||||
will **need** to setup at least the **server_ip** option.
|
1. Specify the recipients (e.g. #channel1, user1, etc.)
|
||||||
|
1. Save or optionally click "Test Settings".
|
||||||
|
|
||||||
## Note on Irker recipients
|
## Note on Irker recipients
|
||||||
|
|
||||||
Irker accepts channel names of the form `chan` and `#chan`, both for the
|
Irker accepts channel names of the form `chan` and `#chan`, both for the
|
||||||
`#chan` channel. If you want to send messages in query, you will need to add
|
`#chan` channel. If you want to send messages in query, you will need to add
|
||||||
`,isnick` avec the channel name, in this form: `Aorimn,isnick`. In this latter
|
`,isnick` after the channel name, in this form: `Aorimn,isnick`. In this latter
|
||||||
case, `Aorimn` is treated as a nick and no more as a channel name.
|
case, `Aorimn` is treated as a nick and no more as a channel name.
|
||||||
|
|
||||||
Irker can also join password-protected channels. Users need to append
|
Irker can also join password-protected channels. Users need to append
|
||||||
`?key=thesecretpassword` to the chan name.
|
`?key=thesecretpassword` to the chan name.
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ Feature: Profile Active Tab
|
||||||
Then the active main tab should be Preferences
|
Then the active main tab should be Preferences
|
||||||
And no other main tabs should be active
|
And no other main tabs should be active
|
||||||
|
|
||||||
Scenario: On Profile History
|
Scenario: On Profile Audit Log
|
||||||
Given I visit profile history page
|
Given I visit Audit Log page
|
||||||
Then the active main tab should be History
|
Then the active main tab should be Audit Log
|
||||||
And no other main tabs should be active
|
And no other main tabs should be active
|
||||||
|
|
|
@ -63,7 +63,7 @@ Feature: Profile
|
||||||
|
|
||||||
Scenario: I visit history tab
|
Scenario: I visit history tab
|
||||||
Given I have activity
|
Given I have activity
|
||||||
When I visit profile history page
|
When I visit Audit Log page
|
||||||
Then I should see my activity
|
Then I should see my activity
|
||||||
|
|
||||||
Scenario: I visit my user page
|
Scenario: I visit my user page
|
||||||
|
|
|
@ -19,7 +19,7 @@ class Spinach::Features::ProfileActiveTab < Spinach::FeatureSteps
|
||||||
ensure_active_main_tab('Preferences')
|
ensure_active_main_tab('Preferences')
|
||||||
end
|
end
|
||||||
|
|
||||||
step 'the active main tab should be History' do
|
step 'the active main tab should be Audit Log' do
|
||||||
ensure_active_main_tab('History')
|
ensure_active_main_tab('Audit Log')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -115,7 +115,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
|
||||||
end
|
end
|
||||||
|
|
||||||
step 'I should see my activity' do
|
step 'I should see my activity' do
|
||||||
expect(page).to have_content "#{current_user.name} closed issue"
|
expect(page).to have_content "Signed in with standard authentication"
|
||||||
end
|
end
|
||||||
|
|
||||||
step 'my password is expired' do
|
step 'my password is expired' do
|
||||||
|
|
|
@ -127,8 +127,8 @@ module SharedPaths
|
||||||
visit profile_preferences_path
|
visit profile_preferences_path
|
||||||
end
|
end
|
||||||
|
|
||||||
step 'I visit profile history page' do
|
step 'I visit Audit Log page' do
|
||||||
visit history_profile_path
|
visit audit_log_profile_path
|
||||||
end
|
end
|
||||||
|
|
||||||
# ----------------------------------------
|
# ----------------------------------------
|
||||||
|
|
|
@ -45,8 +45,8 @@ describe "Profile access", feature: true do
|
||||||
it { is_expected.to be_denied_for :visitor }
|
it { is_expected.to be_denied_for :visitor }
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "GET /profile/history" do
|
describe "GET /profile/audit_log" do
|
||||||
subject { history_profile_path }
|
subject { audit_log_profile_path }
|
||||||
|
|
||||||
it { is_expected.to be_allowed_for @u1 }
|
it { is_expected.to be_allowed_for @u1 }
|
||||||
it { is_expected.to be_allowed_for :admin }
|
it { is_expected.to be_allowed_for :admin }
|
||||||
|
|
|
@ -38,22 +38,6 @@ describe IrkerService do
|
||||||
let(:_recipients) { nil }
|
let(:_recipients) { nil }
|
||||||
it { should validate_presence_of :recipients }
|
it { should validate_presence_of :recipients }
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'too many recipients' do
|
|
||||||
let(:_recipients) { 'a b c d' }
|
|
||||||
it 'should add an error if there is too many recipients' do
|
|
||||||
subject.send :check_recipients_count
|
|
||||||
expect(subject.errors).not_to be_blank
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context '3 recipients' do
|
|
||||||
let(:_recipients) { 'a b c' }
|
|
||||||
it 'should not add an error if there is 3 recipients' do
|
|
||||||
subject.send :check_recipients_count
|
|
||||||
expect(subject.errors).to be_blank
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'Execute' do
|
describe 'Execute' do
|
||||||
|
@ -62,7 +46,7 @@ describe IrkerService do
|
||||||
let(:project) { create(:project) }
|
let(:project) { create(:project) }
|
||||||
let(:sample_data) { Gitlab::PushDataBuilder.build_sample(project, user) }
|
let(:sample_data) { Gitlab::PushDataBuilder.build_sample(project, user) }
|
||||||
|
|
||||||
let(:recipients) { '#commits' }
|
let(:recipients) { '#commits irc://test.net/#test ftp://bad' }
|
||||||
let(:colorize_messages) { '1' }
|
let(:colorize_messages) { '1' }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
|
@ -71,17 +55,12 @@ describe IrkerService do
|
||||||
project: project,
|
project: project,
|
||||||
project_id: project.id,
|
project_id: project.id,
|
||||||
service_hook: true,
|
service_hook: true,
|
||||||
properties: {
|
server_host: 'localhost',
|
||||||
'recipients' => recipients,
|
|
||||||
'colorize_messages' => colorize_messages
|
|
||||||
}
|
|
||||||
)
|
|
||||||
irker.settings = {
|
|
||||||
server_ip: 'localhost',
|
|
||||||
server_port: 6659,
|
server_port: 6659,
|
||||||
max_channels: 3,
|
default_irc_uri: 'irc://chat.freenode.net/',
|
||||||
default_irc_uri: 'irc://chat.freenode.net/'
|
recipients: recipients,
|
||||||
}
|
colorize_messages: colorize_messages)
|
||||||
|
|
||||||
irker.valid?
|
irker.valid?
|
||||||
@irker_server = TCPServer.new 'localhost', 6659
|
@irker_server = TCPServer.new 'localhost', 6659
|
||||||
end
|
end
|
||||||
|
@ -97,11 +76,8 @@ describe IrkerService do
|
||||||
conn.readlines.each do |line|
|
conn.readlines.each do |line|
|
||||||
msg = JSON.load(line.chomp("\n"))
|
msg = JSON.load(line.chomp("\n"))
|
||||||
expect(msg.keys).to match_array(['to', 'privmsg'])
|
expect(msg.keys).to match_array(['to', 'privmsg'])
|
||||||
if msg['to'].is_a?(String)
|
expect(msg['to']).to match_array(["irc://chat.freenode.net/#commits",
|
||||||
expect(msg['to']).to eq 'irc://chat.freenode.net/#commits'
|
"irc://test.net/#test"])
|
||||||
else
|
|
||||||
expect(msg['to']).to match_array(['irc://chat.freenode.net/#commits'])
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
conn.close
|
conn.close
|
||||||
end
|
end
|
||||||
|
|
|
@ -108,8 +108,8 @@ describe ProfilesController, "routing" do
|
||||||
expect(get("/profile/account")).to route_to('profiles/accounts#show')
|
expect(get("/profile/account")).to route_to('profiles/accounts#show')
|
||||||
end
|
end
|
||||||
|
|
||||||
it "to #history" do
|
it "to #audit_log" do
|
||||||
expect(get("/profile/history")).to route_to('profiles#history')
|
expect(get("/profile/audit_log")).to route_to('profiles#audit_log')
|
||||||
end
|
end
|
||||||
|
|
||||||
it "to #reset_private_token" do
|
it "to #reset_private_token" do
|
||||||
|
|
Loading…
Reference in New Issue