Commit 7dedd997 authored by Douwe Maan's avatar Douwe Maan

Merge branch 'master' into milestone-ref

parents ef8b1dbf ed777c7b
......@@ -3,6 +3,7 @@ Please view this file on the master branch, on stable branches it's out of date.
v 8.4.0 (unreleased)
- Implement new UI for group page
- Implement search inside emoji picker
- Add API support for looking up a user by username (Stan Hu)
- Add project permissions to all project API endpoints (Stan Hu)
- Link to milestone in "Milestone changed" system note
- Expose Git's version in the admin area
......@@ -10,11 +11,15 @@ v 8.4.0 (unreleased)
- Add CAS support (tduehr)
- Add link to merge request on build detail page.
v 8.3.2 (unreleased)
- Enable "Add key" button when user fills in a proper key
v 8.3.1
- Fix Error 500 when global milestones have slashes (Stan Hu)
- Fix Error 500 when doing a search in dashboard before visiting any project (Stan Hu)
- Fix LDAP identity and user retrieval when special characters are used
- Move Sidekiq-cron configuration to gitlab.yml
- Enable forcing Two-Factor authentication sitewide, with optional grace period
v 8.3.0
- Bump rack-attack to 4.3.1 for security fix (Stan Hu)
......
......@@ -49,6 +49,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:default_branch_protection,
:signup_enabled,
:signin_enabled,
:require_two_factor_authentication,
:two_factor_grace_period,
:gravatar_enabled,
:twitter_sharing_enabled,
:sign_in_text,
......
......@@ -13,6 +13,7 @@ class ApplicationController < ActionController::Base
before_action :validate_user_service_ticket!
before_action :reject_blocked!
before_action :check_password_expiration
before_action :check_2fa_requirement
before_action :ldap_security_check
before_action :default_headers
before_action :add_gon_variables
......@@ -223,6 +224,12 @@ class ApplicationController < ActionController::Base
end
end
def check_2fa_requirement
if two_factor_authentication_required? && current_user && !current_user.two_factor_enabled && !skip_two_factor?
redirect_to new_profile_two_factor_auth_path
end
end
def ldap_security_check
if current_user && current_user.requires_ldap_check?
unless Gitlab::LDAP::Access.allowed?(current_user)
......@@ -357,6 +364,23 @@ class ApplicationController < ActionController::Base
current_application_settings.import_sources.include?('git')
end
def two_factor_authentication_required?
current_application_settings.require_two_factor_authentication
end
def two_factor_grace_period
current_application_settings.two_factor_grace_period
end
def two_factor_grace_period_expired?
date = current_user.otp_grace_period_started_at
date && (date + two_factor_grace_period.hours) < Time.current
end
def skip_two_factor?
session[:skip_tfa] && session[:skip_tfa] > Time.current
end
def redirect_to_home_page_url?
# If user is not signed-in and tries to access root_path - redirect him to landing page
# Don't redirect to the default URL to prevent endless redirections
......
class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
skip_before_action :check_2fa_requirement
def new
unless current_user.otp_secret
current_user.otp_secret = User.generate_otp_secret(32)
current_user.save!
end
unless current_user.otp_grace_period_started_at && two_factor_grace_period
current_user.otp_grace_period_started_at = Time.current
end
current_user.save! if current_user.changed?
if two_factor_grace_period_expired?
flash.now[:alert] = 'You must configure Two-Factor Authentication in your account.'
else
grace_period_deadline = current_user.otp_grace_period_started_at + two_factor_grace_period.hours
flash.now[:alert] = "You must configure Two-Factor Authentication in your account until #{l(grace_period_deadline)}."
end
@qr_code = build_qr_code
......@@ -34,6 +48,15 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
redirect_to profile_account_path
end
def skip
if two_factor_grace_period_expired?
redirect_to new_profile_two_factor_auth_path, alert: 'Cannot skip two factor authentication setup'
else
session[:skip_tfa] = current_user.otp_grace_period_started_at + two_factor_grace_period.hours
redirect_to root_path
end
end
private
def build_qr_code
......
......@@ -50,5 +50,17 @@ module AuthHelper
current_user.identities.exists?(provider: provider.to_s)
end
def two_factor_skippable?
current_application_settings.require_two_factor_authentication &&
!current_user.two_factor_enabled &&
current_application_settings.two_factor_grace_period &&
!two_factor_grace_period_expired?
end
def two_factor_grace_period_expired?
current_user.otp_grace_period_started_at &&
(current_user.otp_grace_period_started_at + current_application_settings.two_factor_grace_period.hours) < Time.current
end
extend self
end
......@@ -2,32 +2,34 @@
#
# Table name: application_settings
#
# id :integer not null, primary key
# default_projects_limit :integer
# signup_enabled :boolean
# signin_enabled :boolean
# gravatar_enabled :boolean
# sign_in_text :text
# created_at :datetime
# updated_at :datetime
# home_page_url :string(255)
# default_branch_protection :integer default(2)
# twitter_sharing_enabled :boolean default(TRUE)
# restricted_visibility_levels :text
# version_check_enabled :boolean default(TRUE)
# max_attachment_size :integer default(10), not null
# default_project_visibility :integer
# default_snippet_visibility :integer
# restricted_signup_domains :text
# user_oauth_applications :boolean default(TRUE)
# after_sign_out_path :string(255)
# session_expire_delay :integer default(10080), not null
# import_sources :text
# help_page_text :text
# admin_notification_email :string(255)
# shared_runners_enabled :boolean default(TRUE), not null
# max_artifacts_size :integer default(100), not null
# runners_registration_token :string(255)
# id :integer not null, primary key
# default_projects_limit :integer
# signup_enabled :boolean
# signin_enabled :boolean
# gravatar_enabled :boolean
# sign_in_text :text
# created_at :datetime
# updated_at :datetime
# home_page_url :string(255)
# default_branch_protection :integer default(2)
# twitter_sharing_enabled :boolean default(TRUE)
# restricted_visibility_levels :text
# version_check_enabled :boolean default(TRUE)
# max_attachment_size :integer default(10), not null
# default_project_visibility :integer
# default_snippet_visibility :integer
# restricted_signup_domains :text
# user_oauth_applications :boolean default(TRUE)
# after_sign_out_path :string(255)
# session_expire_delay :integer default(10080), not null
# import_sources :text
# help_page_text :text
# admin_notification_email :string(255)
# shared_runners_enabled :boolean default(TRUE), not null
# max_artifacts_size :integer default(100), not null
# runners_registration_token :string(255)
# require_two_factor_authentication :boolean default(TRUE)
# two_factor_grace_period :integer default(48)
#
class ApplicationSetting < ActiveRecord::Base
......@@ -58,6 +60,9 @@ class ApplicationSetting < ActiveRecord::Base
allow_blank: true,
email: true
validates :two_factor_grace_period,
numericality: { greater_than_or_equal_to: 0 }
validates_each :restricted_visibility_levels do |record, attr, value|
unless value.nil?
value.each do |level|
......@@ -112,6 +117,8 @@ class ApplicationSetting < ActiveRecord::Base
import_sources: ['github','bitbucket','gitlab','gitorious','google_code','fogbugz','git'],
shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'],
max_artifacts_size: Settings.artifacts['max_size'],
require_two_factor_authentication: false,
two_factor_grace_period: 48
)
end
......
......@@ -104,6 +104,18 @@
= f.label :signin_enabled do
= f.check_box :signin_enabled
Sign-in enabled
.form-group
= f.label :two_factor_authentication, 'Two-Factor authentication', class: 'control-label col-sm-2'
.col-sm-10
.checkbox
= f.label :require_two_factor_authentication do
= f.check_box :require_two_factor_authentication
Require all users to setup Two-Factor authentication
.form-group
= f.label :two_factor_authentication, 'Two-Factor grace period (hours)', class: 'control-label col-sm-2'
.col-sm-10
= f.number_field :two_factor_grace_period, min: 0, class: 'form-control', placeholder: '0'
.help-block Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication
.form-group
= f.label :restricted_signup_domains, 'Restricted domains for sign-ups', class: 'control-label col-sm-2'
.col-sm-10
......
......@@ -12,6 +12,6 @@
comment = val.match(/^\S+ \S+ (.+)\n?$/);
if( comment && comment.length > 1 && title.val() == '' ){
$('#key_title').val( comment[1] );
$('#key_title').val( comment[1] ).change();
}
});
......@@ -38,3 +38,4 @@
= text_field_tag :pin_code, nil, class: "form-control", required: true, autofocus: true
.form-actions
= submit_tag 'Submit', class: 'btn btn-success'
= link_to 'Configure it later', skip_profile_two_factor_auth_path, :method => :patch, class: 'btn btn-cancel' if two_factor_skippable?
......@@ -297,6 +297,7 @@ Rails.application.routes.draw do
resource :two_factor_auth, only: [:new, :create, :destroy] do
member do
post :codes
patch :skip
end
end
end
......
class AddTfaToApplicationSettings < ActiveRecord::Migration
def change
change_table :application_settings do |t|
t.boolean :require_two_factor_authentication, default: false
t.integer :two_factor_grace_period, default: 48
end
end
end
class AddTfaAdditionalFields < ActiveRecord::Migration
def change
change_table :users do |t|
t.datetime :otp_grace_period_started_at, null: true
end
end
end
......@@ -50,6 +50,8 @@ ActiveRecord::Schema.define(version: 20151224123230) do
t.boolean "shared_runners_enabled", default: true, null: false
t.integer "max_artifacts_size", default: 100, null: false
t.string "runners_registration_token"
t.boolean "require_two_factor_authentication", default: false
t.integer "two_factor_grace_period", default: 48
end
create_table "audit_events", force: :cascade do |t|
......@@ -838,6 +840,7 @@ ActiveRecord::Schema.define(version: 20151224123230) do
t.integer "layout", default: 0
t.boolean "hide_project_limit", default: false
t.string "unlock_token"
t.datetime "otp_grace_period_started_at"
end
add_index "users", ["admin"], name: "index_users_on_admin", using: :btree
......
......@@ -28,17 +28,18 @@
- [Using SSH keys](ci/ssh_keys/README.md)
- [User permissions](ci/permissions/README.md)
- [API](ci/api/README.md)
- [Triggering builds through the API](ci/triggers/README.md)
### CI Languages
+ [Testing PHP](ci/languages/php.md)
- [Testing PHP](ci/languages/php.md)
### CI Services
+ [Using MySQL](ci/services/mysql.md)
+ [Using PostgreSQL](ci/services/postgres.md)
+ [Using Redis](ci/services/redis.md)
+ [Using Other Services](ci/docker/using_docker_images.md#how-to-use-other-images-as-services)
- [Using MySQL](ci/services/mysql.md)
- [Using PostgreSQL](ci/services/postgres.md)
- [Using Redis](ci/services/redis.md)
- [Using Other Services](ci/docker/using_docker_images.md#how-to-use-other-images-as-services)
### CI Examples
......
......@@ -90,7 +90,17 @@ GET /users
You can search for users by email or username with: `/users?search=John`
Also see `def search query` in `app/models/user.rb`.
In addition, you can lookup users by username:
```
GET /users?username=:username
```
For example:
```
GET /users?username=jack_smith
```
## Single user
......
......@@ -2,25 +2,26 @@
### User documentation
+ [Quick Start](quick_start/README.md)
+ [Configuring project (.gitlab-ci.yml)](yaml/README.md)
+ [Configuring runner](runners/README.md)
+ [Configuring deployment](deployment/README.md)
+ [Using Docker Images](docker/using_docker_images.md)
+ [Using Docker Build](docker/using_docker_build.md)
+ [Using Variables](variables/README.md)
+ [Using SSH keys](ssh_keys/README.md)
* [Quick Start](quick_start/README.md)
* [Configuring project (.gitlab-ci.yml)](yaml/README.md)
* [Configuring runner](runners/README.md)
* [Configuring deployment](deployment/README.md)
* [Using Docker Images](docker/using_docker_images.md)
* [Using Docker Build](docker/using_docker_build.md)
* [Using Variables](variables/README.md)
* [Using SSH keys](ssh_keys/README.md)
* [Triggering builds through the API](triggers/README.md)
### Languages
+ [Testing PHP](languages/php.md)
* [Testing PHP](languages/php.md)
### Services
+ [Using MySQL](services/mysql.md)
+ [Using PostgreSQL](services/postgres.md)
+ [Using Redis](services/redis.md)
+ [Using Other Services](docker/using_docker_images.md#how-to-use-other-images-as-services)
* [Using MySQL](services/mysql.md)
* [Using PostgreSQL](services/postgres.md)
* [Using Redis](services/redis.md)
* [Using Other Services](docker/using_docker_images.md#how-to-use-other-images-as-services)
### Examples
......@@ -32,5 +33,5 @@
### Administrator documentation
+ [User permissions](permissions/README.md)
+ [API](api/README.md)
* [User permissions](permissions/README.md)
* [API](api/README.md)
# Triggering Builds through the API
_**Note:** This feature was [introduced][ci-229] in GitLab CE 7.14_
Triggers can be used to force a rebuild of a specific branch, tag or commit,
with an API call.
## Add a trigger
You can add a new trigger by going to your project's **Settings > Triggers**.
The **Add trigger** button will create a new token which you can then use to
trigger a rebuild of this particular project.
Once at least one trigger is created, on the **Triggers** page you will find
some descriptive information on how you can
Every new trigger you create, gets assigned a different token which you can
then use inside your scripts or `.gitlab-ci.yml`. You also have a nice
overview of the time the triggers were last used.
![Triggers page overview](img/triggers_page.png)
## Revoke a trigger
You can revoke a trigger any time by going at your project's
**Settings > Triggers** and hitting the **Revoke** button. The action is
irreversible.
## Trigger a build
To trigger a build you need to send a `POST` request to GitLab's API endpoint:
```
POST /projects/:id/trigger/builds
```
The required parameters are the trigger's `token` and the Git `ref` on which
the trigger will be performed. Valid refs are the branch, the tag or the commit
SHA. The `:id` of a project can be found by [querying the API](../api/projects.md)
or by visiting the **Triggers** page which provides self-explanatory examples.
When a rebuild is triggered, the information is exposed in GitLab's UI under
the **Builds** page and the builds are marked as `triggered`.
![Marked rebuilds as triggered on builds page](img/builds_page.png)
---
You can see which trigger caused the rebuild by visiting the single build page.
The token of the trigger is exposed in the UI as you can see from the image
below.
![Marked rebuilds as triggered on a single build page](img/trigger_single_build.png)
---
See the [Examples](#examples) section for more details on how to actually
trigger a rebuild.
## Pass build variables to a trigger
You can pass any number of arbitrary variables in the trigger API call and they
will be available in GitLab CI so that they can be used in your `.gitlab-ci.yml`
file. The parameter is of the form:
```
variables[key]=value
```
This information is also exposed in the UI.
![Build variables in UI](img/trigger_variables.png)
---
See the [Examples](#examples) section below for more details.
## Examples
Using cURL you can trigger a rebuild with minimal effort, for example:
```bash
curl -X POST \
-F token=TOKEN \
-F ref=master \
https://gitlab.example.com/api/v3/projects/9/trigger/builds
```
In this case, the project with ID `9` will get rebuilt on `master` branch.
### Triggering a build within `.gitlab-ci.yml`
You can also benefit by using triggers in your `.gitlab-ci.yml`. Let's say that
you have two projects, A and B, and you want to trigger a rebuild on the `master`
branch of project B whenever a tag on project A is created. This is the job you
need to add in project's A `.gitlab-ci.yml`:
```yaml
build_docs:
stage: deploy
script:
- "curl -X POST -F token=TOKEN -F ref=master https://gitlab.example.com/api/v3/projects/9/trigger/builds"
only:
- tags
```
Now, whenever a new tag is pushed on project A, the build will run and the
`build_docs` job will be executed, triggering a rebuild of project B. The
`stage: deploy` ensures that this job will run only after all jobs with
`stage: test` complete successfully.
_**Note:** If your project is public, passing the token in plain text is
probably not the wiser idea, so you might want to use a
[secure variable](../variables/README.md#user-defined-variables-secure-variables)
for that purpose._
### Making use of trigger variables
Using trigger variables can be proven useful for a variety of reasons.
* Identifiable jobs. Since the variable is exposed in the UI you can know
why the rebuild was triggered if you pass a variable that explains the
purpose.
* Conditional job processing. You can have conditional jobs that run whenever
a certain variable is present.
Consider the following `.gitlab-ci.yml` where we set three
[stages](../yaml/README.md#stages) and the `upload_package` job is run only
when all jobs from the test and build stages pass. When the `UPLOAD_TO_S3`
variable is non-zero, `make upload` is run.
```yaml
stages:
- test
- build
- package
run_tests:
script:
- make test
build_package:
stage: build
script:
- make build
upload_package:
stage: package
script:
- if [ -n "${UPLOAD_TO_S3}" ]; then make upload; fi
```
You can then trigger a rebuild while you pass the `UPLOAD_TO_S3` variable
and the script of the `upload_package` job will run:
```bash
curl -X POST \
-F token=TOKEN \
-F ref=master \
-F "variables[UPLOAD_TO_S3]=true" \
https://gitlab.example.com/api/v3/projects/9/trigger/builds
```
### Using cron to trigger nightly builds
Whether you craft a script or just run cURL directly, you can trigger builds
in conjunction with cron. The example below triggers a build on the `master`
branch of project with ID `9` every night at `00:30`:
```bash
30 0 * * * curl -X POST -F token=TOKEN -F ref=master https://gitlab.example.com/api/v3/projects/9/trigger/builds
```
[ci-229]: https://gitlab.com/gitlab-org/gitlab-ci/merge_requests/229
......@@ -6,3 +6,4 @@
- [Information exclusivity](information_exclusivity.md)
- [Reset your root password](reset_root_password.md)
- [User File Uploads](user_file_uploads.md)
- [Enforce Two-Factor authentication](two_factor_authentication.md)
# Enforce Two-factor Authentication (2FA)
Two-factor Authentication (2FA) provides an additional level of security to your
users' GitLab account. Once enabled, in addition to supplying their username and
password to login, they'll be prompted for a code generated by an application on
their phone.
You can read more about it here:
[Two-factor Authentication (2FA)](doc/profile/two_factor_authentication.md)
## Enabling 2FA
Users on GitLab, can enable it without any admin's intervention. If you want to
enforce everyone to setup 2FA, you can choose from two different ways:
1. Enforce on next login
2. Suggest on next login, but allow a grace period before enforcing.
In the Admin area under **Settings** (`/admin/application_settings`), look for
the "Sign-in Restrictions" area, where you can configure both.
If you want 2FA enforcement to take effect on next login, change the grace
period to `0`
## Disabling 2FA for everyone
There may be some special situations where you want to disable 2FA for everyone
even when forced 2FA is disabled. There is a rake task for that:
```
# use this command if you've installed GitLab with the Omnibus package
sudo gitlab-rake gitlab:two_factor:disable_for_all_users
# if you've installed GitLab from source
sudo -u git -H bundle exec rake gitlab:two_factor:disable_for_all_users RAILS_ENV=production
```
**IMPORTANT: this is a permanent and irreversible action. Users will have to reactivate 2FA from scratch if they want to use it again.**
......@@ -8,11 +8,17 @@ module API
#
# Example Request:
# GET /users
# GET /users?search=Admin
# GET /users?username=root
get do
@users = User.all
@users = @users.active if params[:active].present?
@users = @users.search(params[:search]) if params[:search].present?
@users = paginate @users
if params[:username].present?
@users = User.where(username: params[:username])
else
@users = User.all
@users = @users.active if params[:active].present?
@users = @users.search(params[:search]) if params[:search].present?
@users = paginate @users
end
if current_user.is_admin?
present @users, with: Entities::UserFull
......
......@@ -63,15 +63,15 @@ module Banzai
url = url_for_issue(id, project, only_path: context[:only_path])
title = escape_once("Issue in #{project.external_issue_tracker.title}")
title = "Issue in #{project.external_issue_tracker.title}"
klass = reference_class(:issue)
data = data_attribute(project: project.id, external_issue: id)
text = link_text || match
%(<a href="#{url}" #{data}
title="#{title}"
class="#{klass}">#{text}</a>)
title="#{escape_once(title)}"
class="#{klass}">#{escape_once(text)}</a>)
end
end
......
......@@ -60,7 +60,7 @@ module Banzai
text = link_text || render_colored_label(label)
%(<a href="#{url}" #{data}
class="#{klass}">#{text}</a>)
class="#{klass}">#{escape_once(text)}</a>)
else
match
end
......
......@@ -122,7 +122,7 @@ module Banzai
end
def link_tag(url, data, text)
%(<a href="#{url}" #{data} class="#{link_class}">#{text}</a>)
%(<a href="#{url}" #{data} class="#{link_class}">#{escape_once(text)}</a>)
end
end
end
......
......@@ -98,4 +98,56 @@ feature 'Login', feature: true do
expect(page).to have_content('Invalid login or password.')
end
end
describe 'with required two-factor authentication enabled' do
let(:user) { create(:user) }
before(:each) { stub_application_setting(require_two_factor_authentication: true) }
context 'with grace period defined' do
before(:each) do
stub_application_setting(two_factor_grace_period: 48)
login_with(user)
end
context 'within the grace period' do
it 'redirects to two-factor configuration page' do
expect(current_path).to eq new_profile_two_factor_auth_path
expect(page).to have_content('You must configure Two-Factor Authentication in your account until')
end
it 'two-factor configuration is skippable' do
expect(current_path).to eq new_profile_two_factor_auth_path
click_link 'Configure it later'
expect(current_path).to eq root_path
end
end
context 'after the grace period' do
let(:user) { create(:user, otp_grace_period_started_at: 9999.hours.ago) }
it 'redirects to two-factor configuration page' do
expect(current_path).to eq new_profile_two_factor_auth_path
expect(page).to have_content('You must configure Two-Factor Authentication in your account.')
end
it 'two-factor configuration is not skippable' do
expect(current_path).to eq new_profile_two_factor_auth_path
expect(page).not_to have_link('Configure it later')
end
end
end
context 'without grace pariod defined' do
before(:each) do
stub_application_setting(two_factor_grace_period: 0)
login_with(user)
end
it 'redirects to two-factor configuration page' do
expect(current_path).to eq new_profile_two_factor_auth_path
expect(page).to have_content('You must configure Two-Factor Authentication in your account.')
end
end
end
end
......@@ -27,6 +27,7 @@
# admin_notification_email :string(255)
# shared_runners_enabled :boolean default(TRUE), not null
# max_artifacts_size :integer default(100), not null
# runners_registration_token :string(255)
#
require 'spec_helper'
......
......@@ -27,6 +27,13 @@ describe API::API, api: true do
user['username'] == username
end['username']).to eq(username)
end
it "should return one user" do
get api("/users?username=#{omniauth_user.username}", user)
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(json_response.first['username']).to eq(omniauth_user.username)
end
end
context "when admin" do
......
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