Commit 39146a39 authored by Lin Jen-Shin's avatar Lin Jen-Shin

Merge remote-tracking branch 'upstream/master' into ce-to-ee-2017-11-03

* upstream/master: (74 commits)
  Resolve ""To do" should be "Todos" on Todos list page"
  Remove including ee module
  Remove Filesystem check metrics that use too much CPU to handle requests
  Set merge_request_diff_id on MR when creating
  Add a column linking an MR to its diff
  Remove useless closeReopenReport specs
  Clarify external artifacts only working when GitLab pages is enabled
  Send SIGSTP before SIGTERM to actually give Sidekiq jobs 30s to finish when the memory killer kicks in
  Remove an exception from the git user default SSH config check
  Geo route whitelisting is too optimistic
  Update .nvmrc to current stable (v9.0.0)
  Update documentation
  Address Douwe's feedback
  Refactor responsive table styles to support nested error block
  Add changelog items
  Update specs for sudo behavior
  Move RSS and incoming email tokens from User Settings > Accounts to User Settings > Access Tokens
  Remove user authentication_token column
  Migrate user private tokens to personal access tokens
  Add sudo API scope
  ...
parents bc2ddbe1 d51ad1ea
7.5
\ No newline at end of file
9.0.0
......@@ -105,8 +105,7 @@ the remaining issues on the GitHub issue tracker.
## I want to contribute!
If you want to contribute to GitLab, but are not sure where to start,
look for [issues with the label `Accepting Merge Requests` and small weight][accepting-mrs-weight].
If you want to contribute to GitLab, [issues with the label `Accepting Merge Requests` and small weight][accepting-mrs-weight] is a great place to start. Issues with a lower weight (1 or 2) are deemed suitable for beginners.
These issues will be of reasonable size and challenge, for anyone to start
contributing to GitLab.
......
......@@ -16,7 +16,7 @@ import CILintEditor from './ci_lint_editor';
import groupsSelect from './groups_select';
/* global Search */
/* global Admin */
/* global NamespaceSelects */
import NamespaceSelect from './namespace_select';
/* global NewCommitForm */
/* global NewBranchForm */
/* global Project */
......@@ -650,7 +650,8 @@ import initGroupAnalytics from './init_group_analytics';
new UsersSelect();
break;
case 'projects':
new NamespaceSelects();
document.querySelectorAll('.js-namespace-select')
.forEach(dropdown => new NamespaceSelect({ dropdown }));
break;
case 'labels':
switch (path[2]) {
......
......@@ -30,7 +30,7 @@ const utils = {
},
isDropDownParts(target) {
if (!target || target.tagName === 'HTML') return false;
if (!target || !target.hasAttribute || target.tagName === 'HTML') return false;
return target.hasAttribute(DATA_TRIGGER) || target.hasAttribute(DATA_DROPDOWN);
},
};
......
......@@ -119,11 +119,9 @@ export default function dropzoneInput(form) {
// removeAllFiles(true) stops uploading files (if any)
// and remove them from dropzone files queue.
$cancelButton.on('click', (e) => {
const target = e.target.closest('.js-main-target-form').querySelector('.div-dropzone');
e.preventDefault();
e.stopPropagation();
Dropzone.forElement(target).removeAllFiles(true);
Dropzone.forElement($formDropzone.get(0)).removeAllFiles(true);
});
// If 'error' event is fired, we store a failed files,
......
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, one-var, vars-on-top, one-var-declaration-per-line, comma-dangle, object-shorthand, no-else-return, prefer-template, quotes, prefer-arrow-callback, no-param-reassign, no-cond-assign, max-len */
/* eslint-disable func-names, space-before-function-paren, no-var, comma-dangle, object-shorthand, no-else-return, prefer-template, quotes, prefer-arrow-callback, max-len */
import Api from './api';
import './lib/utils/url_utility';
(function() {
window.NamespaceSelect = (function() {
function NamespaceSelect(opts) {
this.onSelectItem = this.onSelectItem.bind(this);
var fieldName, showAny;
this.dropdown = opts.dropdown;
showAny = true;
fieldName = 'namespace_id';
if (this.dropdown.attr('data-field-name')) {
fieldName = this.dropdown.data('fieldName');
}
if (this.dropdown.attr('data-show-any')) {
showAny = this.dropdown.data('showAny');
}
this.dropdown.glDropdown({
export default class NamespaceSelect {
constructor(opts) {
const isFilter = opts.dropdown.dataset.isFilter === 'true';
const fieldName = opts.dropdown.dataset.fieldName || 'namespace_id';
$(opts.dropdown).glDropdown({
filterable: true,
selectable: true,
filterRemote: true,
......@@ -32,9 +24,8 @@ import Api from './api';
},
data: function(term, dataCallback) {
return Api.namespaces(term, function(namespaces) {
var anyNamespace;
if (showAny) {
anyNamespace = {
if (isFilter) {
const anyNamespace = {
text: 'Any namespace',
id: null
};
......@@ -52,34 +43,15 @@ import Api from './api';
}
},
renderRow: this.renderRow,
clicked: this.onSelectItem
});
}
NamespaceSelect.prototype.onSelectItem = function(options) {
clicked(options) {
if (!isFilter) {
const { e } = options;
return e.preventDefault();
};
return NamespaceSelect;
})();
window.NamespaceSelects = (function() {
function NamespaceSelects(opts) {
var ref;
if (opts == null) {
opts = {};
e.preventDefault();
}
this.$dropdowns = (ref = opts.$dropdowns) != null ? ref : $('.js-namespace-select');
this.$dropdowns.each(function(i, dropdown) {
var $dropdown;
$dropdown = $(dropdown);
return new window.NamespaceSelect({
dropdown: $dropdown
});
},
url(namespace) {
return gl.utils.mergeUrlParams({ [fieldName]: namespace.id }, window.location.href);
},
});
}
return NamespaceSelects;
})();
}).call(window);
}
<script>
import getActionIcon from '../../../vue_shared/ci_action_icons';
import tooltip from '../../../vue_shared/directives/tooltip';
import icon from '../../../vue_shared/components/icon.vue';
/**
* Renders either a cancel, retry or play icon pointing to the given path.
......@@ -29,17 +29,18 @@
},
},
components: {
icon,
},
directives: {
tooltip,
},
computed: {
actionIconSvg() {
return getActionIcon(this.actionIcon);
},
cssClass() {
return `js-${gl.text.dasherize(this.actionIcon)}`;
const actionIconDash = gl.text.dasherize(this.actionIcon);
return `${actionIconDash} js-icon-${actionIconDash}`;
},
},
};
......@@ -50,14 +51,9 @@
:data-method="actionMethod"
:title="tooltipText"
:href="link"
class="ci-action-icon-container"
data-container="body">
<i
class="ci-action-icon-wrapper"
class="ci-action-icon-container ci-action-icon-wrapper"
:class="cssClass"
v-html="actionIconSvg"
aria-hidden="true"
/>
data-container="body">
<icon :name="actionIcon"/>
</a>
</template>
<script>
import getActionIcon from '../../../vue_shared/ci_action_icons';
import icon from '../../../vue_shared/components/icon.vue';
import tooltip from '../../../vue_shared/directives/tooltip';
/**
......@@ -29,14 +29,12 @@
},
},
directives: {
tooltip,
components: {
icon,
},
computed: {
actionIconSvg() {
return getActionIcon(this.actionIcon);
},
directives: {
tooltip,
},
};
</script>
......@@ -49,7 +47,7 @@
rel="nofollow"
class="ci-action-icon-wrapper js-ci-status-icon"
data-container="body"
v-html="actionIconSvg"
aria-label="Job's action">
<icon :name="actionIcon"/>
</a>
</template>
......@@ -18,7 +18,7 @@
* "group": "success",
* "details_path": "/root/ci-mock/builds/4256",
* "action": {
* "icon": "icon_action_retry",
* "icon": "retry",
* "title": "Retry",
* "path": "/root/ci-mock/builds/4256/retry",
* "method": "post"
......
......@@ -19,7 +19,7 @@
* "group": "success",
* "details_path": "/root/ci-mock/builds/4256",
* "action": {
* "icon": "icon_action_retry",
* "icon": "retry",
* "title": "Retry",
* "path": "/root/ci-mock/builds/4256/retry",
* "method": "post"
......
......@@ -14,7 +14,7 @@
*/
import Flash from '../../flash';
import { borderlessStatusIconEntityMap } from '../../vue_shared/ci_status_icons';
import icon from '../../vue_shared/components/icon.vue';
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import tooltip from '../../vue_shared/directives/tooltip';
......@@ -45,6 +45,7 @@ export default {
components: {
loadingIcon,
icon,
},
updated() {
......@@ -122,8 +123,8 @@ export default {
return `ci-status-icon-${this.stage.status.group}`;
},
svgIcon() {
return borderlessStatusIconEntityMap[this.stage.status.icon];
borderlessIcon() {
return `${this.stage.status.icon}_borderless`;
},
},
};
......@@ -145,9 +146,10 @@ export default {
aria-expanded="false">
<span
v-html="svgIcon"
aria-hidden="true"
:aria-label="stage.title">
<icon
:name="borderlessIcon"/>
</span>
<i
......
......@@ -27,6 +27,8 @@ export default {
'changeFileContent',
]),
initMonaco() {
if (this.shouldHideEditor) return;
if (this.monacoInstance) {
this.monacoInstance.setModel(null);
}
......@@ -94,8 +96,12 @@ export default {
<template>
<div
id="ide"
v-if='!shouldHideEditor'
class="blob-viewer-container blob-editor-container"
>
<div
v-if="shouldHideEditor"
v-html="activeFile.html"
>
</div>
</div>
</template>
import PipelineStage from '../../pipelines/components/stage.vue';
import ciIcon from '../../vue_shared/components/ci_icon.vue';
<<<<<<< HEAD
import { statusIconEntityMap } from '../../vue_shared/ci_status_icons';
import linkedPipelinesMiniList from '../../vue_shared/components/linked_pipelines_mini_list.vue';
=======
import icon from '../../vue_shared/components/icon.vue';
>>>>>>> upstream/master
export default {
name: 'MRWidgetPipeline',
......@@ -11,7 +15,11 @@ export default {
components: {
'pipeline-stage': PipelineStage,
ciIcon,
<<<<<<< HEAD
linkedPipelinesMiniList,
=======
icon,
>>>>>>> upstream/master
},
computed: {
hasPipeline() {
......@@ -22,9 +30,6 @@ export default {
return hasCI && !ciStatus;
},
svg() {
return statusIconEntityMap.icon_status_failed;
},
stageText() {
return this.mr.pipeline.details.stages.length > 1 ? 'stages' : 'stage';
},
......@@ -53,8 +58,10 @@ export default {
<template v-if="hasCIError">
<div class="ci-status-icon ci-status-icon-failed ci-error js-ci-error append-right-10">
<span
v-html="svg"
aria-hidden="true"></span>
aria-hidden="true">
<icon
name="status_failed"/>
</span>
</div>
<div class="media-body">
Could not connect to the CI server. Please check your settings and try again
......
import cancelSVG from 'icons/_icon_action_cancel.svg';
import retrySVG from 'icons/_icon_action_retry.svg';
import playSVG from 'icons/_icon_action_play.svg';
import stopSVG from 'icons/_icon_action_stop.svg';
/**
* For the provided action returns the respective SVG
*
* @param {String} action
* @return {SVG|String}
*/
export default function getActionIcon(action) {
const icons = {
icon_action_cancel: cancelSVG,
icon_action_play: playSVG,
icon_action_retry: retrySVG,
icon_action_stop: stopSVG,
};
return icons[action] || '';
}
import BORDERLESS_CANCELED_SVG from 'icons/_icon_status_canceled_borderless.svg';
import BORDERLESS_CREATED_SVG from 'icons/_icon_status_created_borderless.svg';
import BORDERLESS_FAILED_SVG from 'icons/_icon_status_failed_borderless.svg';
import BORDERLESS_MANUAL_SVG from 'icons/_icon_status_manual_borderless.svg';
import BORDERLESS_PENDING_SVG from 'icons/_icon_status_pending_borderless.svg';
import BORDERLESS_RUNNING_SVG from 'icons/_icon_status_running_borderless.svg';
import BORDERLESS_SKIPPED_SVG from 'icons/_icon_status_skipped_borderless.svg';
import BORDERLESS_SUCCESS_SVG from 'icons/_icon_status_success_borderless.svg';
import BORDERLESS_WARNING_SVG from 'icons/_icon_status_warning_borderless.svg';
import CANCELED_SVG from 'icons/_icon_status_canceled.svg';
import CREATED_SVG from 'icons/_icon_status_created.svg';
import FAILED_SVG from 'icons/_icon_status_failed.svg';
import MANUAL_SVG from 'icons/_icon_status_manual.svg';
import PENDING_SVG from 'icons/_icon_status_pending.svg';
import RUNNING_SVG from 'icons/_icon_status_running.svg';
import SKIPPED_SVG from 'icons/_icon_status_skipped.svg';
import SUCCESS_SVG from 'icons/_icon_status_success.svg';
import WARNING_SVG from 'icons/_icon_status_warning.svg';
export const borderlessStatusIconEntityMap = {
icon_status_canceled: BORDERLESS_CANCELED_SVG,
icon_status_created: BORDERLESS_CREATED_SVG,
icon_status_failed: BORDERLESS_FAILED_SVG,
icon_status_manual: BORDERLESS_MANUAL_SVG,
icon_status_pending: BORDERLESS_PENDING_SVG,
icon_status_running: BORDERLESS_RUNNING_SVG,
icon_status_skipped: BORDERLESS_SKIPPED_SVG,
icon_status_success: BORDERLESS_SUCCESS_SVG,
icon_status_warning: BORDERLESS_WARNING_SVG,
};
export const statusIconEntityMap = {
icon_status_canceled: CANCELED_SVG,
icon_status_created: CREATED_SVG,
icon_status_failed: FAILED_SVG,
icon_status_manual: MANUAL_SVG,
icon_status_pending: PENDING_SVG,
icon_status_running: RUNNING_SVG,
icon_status_skipped: SKIPPED_SVG,
icon_status_success: SUCCESS_SVG,
icon_status_warning: WARNING_SVG,
};
......@@ -43,7 +43,6 @@
computed: {
cssClass() {
const className = this.status.group;
return className ? `ci-status ci-${className}` : 'ci-status';
},
},
......
<script>
import { statusIconEntityMap } from '../ci_status_icons';
import icon from '../../vue_shared/components/icon.vue';
/**
* Renders CI icon based on API response shared between all places where it is used.
......@@ -31,11 +31,11 @@
},
},
computed: {
statusIconSvg() {
return statusIconEntityMap[this.status.icon];
components: {
icon,
},
computed: {
cssClass() {
const status = this.status.group;
return `ci-status-icon ci-status-icon-${status} js-ci-status-icon-${status}`;
......@@ -45,7 +45,8 @@
</script>
<template>
<span
:class="cssClass"
v-html="statusIconSvg">
:class="cssClass">
<icon
:name="status.icon"/>
</span>
</template>
<script>
/* This is a re-usable vue component for rendering a svg sprite
icon
Sample configuration:
<icon
:img-src="userAvatarSrc"
:img-alt="tooltipText"
:tooltip-text="tooltipText"
tooltip-placement="top"
/>
*/
export default {
props: {
name: {
type: String,
required: true,
},
size: {
type: Number,
required: false,
default: 0,
},
cssClasses: {
type: String,
required: false,
default: '',
},
},
computed: {
spriteHref() {
return `${gon.sprite_icons}#${this.name}`;
},
iconSizeClass() {
return this.size ? `s${this.size}` : '';
},
},
};
</script>
<template>
<svg
:class="[iconSizeClass, cssClasses]">
<use
v-bind="{'xlink:href':spriteHref}"/>
</svg>
</template>
......@@ -4,9 +4,12 @@
.cred { color: $common-red; }
.cgreen { color: $common-green; }
.cdark { color: $common-gray-dark; }
<<<<<<< HEAD
.text-secondary {
color: $gl-text-color-secondary;
}
=======
>>>>>>> upstream/master
.underlined-link { text-decoration: underline; }
.hint { font-style: italic; color: $hint-color; }
......
......@@ -333,8 +333,10 @@
svg {
position: relative;
top: 2px;
top: 3px;
margin-right: 3px;
width: 14px;
height: 14px;
}
}
......@@ -348,9 +350,10 @@
svg {
position: relative;
top: 2px;
top: 3px;
margin-right: 3px;
height: 13px;
height: 14px;
width: 14px;
}
a {
......@@ -369,7 +372,7 @@
.build-job {
position: relative;
.fa-arrow-right {
.icon-arrow-right {
position: absolute;
left: 15px;
top: 20px;
......@@ -379,7 +382,7 @@
&.active {
font-weight: $gl-font-weight-bold;
.fa-arrow-right {
.icon-arrow-right {
display: block;
}
}
......@@ -392,8 +395,7 @@
background-color: $row-hover;
}
.fa-refresh {
font-size: 13px;
.icon-retry {
margin-left: 3px;
}
}
......
......@@ -164,8 +164,9 @@
z-index: 300;
}
.ci-action-icon-wrapper {
line-height: 16px;
.ci-action-icon-wrapper svg {
width: 16px;
height: 16px;
}
}
......
......@@ -480,7 +480,7 @@
}
// Action Icons in big pipeline-graph nodes
.ci-action-icon-container .ci-action-icon-wrapper {
.ci-action-icon-container.ci-action-icon-wrapper {
height: 30px;
width: 30px;
background: $white-light;
......@@ -496,8 +496,18 @@
svg {
fill: $gl-text-color-secondary;
position: relative;
left: -1px;
top: -1px;
left: 5px;
top: 2px;
width: 18px;
height: 18px;
}
&.play {
svg {
width: #{$ci-action-icon-size - 8};
height: #{$ci-action-icon-size - 8};
left: 8px;
}
}
&:hover svg {
......@@ -750,17 +760,49 @@ a.linked-pipeline-mini-item {
svg {
fill: $gl-text-color-secondary;
width: $ci-action-icon-size;
height: $ci-action-icon-size;
left: -6px;
width: #{$ci-action-icon-size - 6};
height: #{$ci-action-icon-size - 6};
left: -3px;
position: relative;
top: -3px;
top: -2px;
}
&:hover svg,
&:focus svg {
fill: $gl-text-color;
}
&.icon-action-retry,
&.icon-action-play {
svg {
width: #{$ci-action-icon-size - 6};
height: #{$ci-action-icon-size - 6};
left: 8px;
}
}
svg.icon-action-stop,
svg.icon-action-cancel {
width: 12px;
height: 12px;
top: 1px;
left: -1px;
}
svg.icon-action-play {
width: 11px;
height: 11px;
top: 1px;
left: 1px;
}
svg.icon-action-retry {
width: 16px;
height: 16px;
top: 0;
left: -3px;
}
}
// link to the build
......
......@@ -44,7 +44,7 @@ class Admin::ImpersonationTokensController < Admin::ApplicationController
end
def set_index_vars
@scopes = Gitlab::Auth::API_SCOPES
@scopes = Gitlab::Auth.available_scopes(current_user)
@impersonation_token ||= finder.build
@inactive_impersonation_tokens = finder(state: 'inactive').execute
......
......@@ -11,7 +11,7 @@ class ApplicationController < ActionController::Base
include EnforcesTwoFactorAuthentication
include WithPerformanceBar
before_action :authenticate_user_from_private_token!
before_action :authenticate_user_from_personal_access_token!
before_action :authenticate_user_from_rss_token!
before_action :authenticate_user!
before_action :validate_user_service_ticket!
......@@ -100,13 +100,12 @@ class ApplicationController < ActionController::Base
return try(:authenticated_user)
end
# This filter handles both private tokens and personal access tokens
def authenticate_user_from_private_token!
def authenticate_user_from_personal_access_token!
token = params[:private_token].presence || request.headers['PRIVATE-TOKEN'].presence
return unless token.present?
user = User.find_by_authentication_token(token) || User.find_by_personal_access_token(token)
user = User.find_by_personal_access_token(token)
sessionless_sign_in(user)
end
......
......@@ -30,11 +30,11 @@ class JwtController < ApplicationController
render_unauthorized
end
end
rescue Gitlab::Auth::MissingPersonalTokenError
render_missing_personal_token
rescue Gitlab::Auth::MissingPersonalAccessTokenError
render_missing_personal_access_token
end
def render_missing_personal_token
def render_missing_personal_access_token
render json: {
errors: [
{ code: 'UNAUTHORIZED',
......
......@@ -39,7 +39,7 @@ class Profiles::PersonalAccessTokensController < Profiles::ApplicationController
end
def set_index_vars
@scopes = Gitlab::Auth.available_scopes
@scopes = Gitlab::Auth.available_scopes(current_user)
@inactive_personal_access_tokens = finder(state: 'inactive').execute
@active_personal_access_tokens = finder(state: 'active').execute.order(:expires_at)
......
......@@ -24,16 +24,6 @@ class ProfilesController < Profiles::ApplicationController
end
end
def reset_private_token
Users::UpdateService.new(current_user, user: @user).execute! do |user|
user.reset_authentication_token!
end
flash[:notice] = "Private token was successfully reset"
redirect_to profile_account_path
end
def reset_incoming_email_token
Users::UpdateService.new(current_user, user: @user).execute! do |user|
user.reset_incoming_email_token!
......@@ -41,7 +31,7 @@ class ProfilesController < Profiles::ApplicationController
flash[:notice] = "Incoming email token was successfully reset"
redirect_to profile_account_path
redirect_to profile_personal_access_tokens_path
end
def reset_rss_token
......@@ -51,7 +41,7 @@ class ProfilesController < Profiles::ApplicationController
flash[:notice] = "RSS token was successfully reset"
redirect_to profile_account_path
redirect_to profile_personal_access_tokens_path
end
def audit_log
......
......@@ -53,8 +53,8 @@ class Projects::GitHttpClientController < Projects::ApplicationController
send_challenges
render plain: "HTTP Basic: Access denied\n", status: 401
rescue Gitlab::Auth::MissingPersonalTokenError
render_missing_personal_token
rescue Gitlab::Auth::MissingPersonalAccessTokenError
render_missing_personal_access_token
end
def basic_auth_provided?
......@@ -78,7 +78,7 @@ class Projects::GitHttpClientController < Projects::ApplicationController
@project, @wiki, @redirected_path = Gitlab::RepoPath.parse("#{params[:namespace_id]}/#{params[:project_id]}")
end
def render_missing_personal_token
def render_missing_personal_access_token
render plain: "HTTP Basic: Access denied\n" \
"You must use a personal access token with 'api' scope for Git over HTTP.\n" \
"You can generate one at #{profile_personal_access_tokens_url}",
......
......@@ -63,34 +63,34 @@ module CiStatusHelper
def ci_icon_for_status(status)
if detailed_status?(status)
return custom_icon(status.icon)
return sprite_icon(status.icon)
end
icon_name =
case status
when 'success'
'icon_status_success'
'status_success'
when 'success_with_warnings'
'icon_status_warning'
'status_warning'
when 'failed'
'icon_status_failed'
'status_failed'
when 'pending'
'icon_status_pending'
'status_pending'
when 'running'
'icon_status_running'
'status_running'
when 'play'
'icon_play'
'play'
when 'created'
'icon_status_created'
'status_created'
when 'skipped'
'icon_status_skipped'
'status_skipped'
when 'manual'
'icon_status_manual'
'status_manual'
else
'icon_status_canceled'
'status_canceled'
end
custom_icon(icon_name)
sprite_icon(icon_name, size: 16)
end
def pipeline_status_cache_key(pipeline_status)
......
# Placeholder class for model that is implemented in EE
# It will reserve (ee#3853) '&' as a reference prefix, but the table does not exists in CE
class Epic < ActiveRecord::Base
<<<<<<< HEAD
prepend EE::Epic
=======
>>>>>>> upstream/master
# TODO: this will be implemented as part of #3853
def to_reference
end
......
......@@ -15,11 +15,14 @@ class Issue < ActiveRecord::Base
include RelativePositioning
include CreatedAtFilterable
include TimeTrackable
<<<<<<< HEAD
WEIGHT_RANGE = 1..9
WEIGHT_ALL = 'Everything'.freeze
WEIGHT_ANY = 'Any Weight'.freeze
WEIGHT_NONE = 'No Weight'.freeze
=======
>>>>>>> upstream/master
DueDateStruct = Struct.new(:title, :name).freeze
NoDueDate = DueDateStruct.new('No Due Date', '0').freeze
......
......@@ -48,6 +48,10 @@ class MergeRequestDiff < ActiveRecord::Base
# Collect information about commits and diff from repository
# and save it to the database as serialized data
def save_git_content
MergeRequest
.where('id = ? AND COALESCE(latest_merge_request_diff_id, 0) < ?', self.merge_request_id, self.id)
.update_all(latest_merge_request_diff_id: self.id)
ensure_commit_shas
save_commits
save_diffs
......
......@@ -2,5 +2,13 @@ class OauthAccessToken < Doorkeeper::AccessToken
belongs_to :resource_owner, class_name: 'User'
belongs_to :application, class_name: 'Doorkeeper::Application'
alias_method :user, :resource_owner
alias_attribute :user, :resource_owner
def scopes=(value)
if value.is_a?(Array)
super(Doorkeeper::OAuth::Scopes.from_array(value).to_s)
else
super
end
end
end
......@@ -24,8 +24,8 @@ class User < ActiveRecord::Base
ignore_column :external_email
ignore_column :email_provider
ignore_column :authentication_token
add_authentication_token_field :authentication_token
add_authentication_token_field :incoming_email_token
add_authentication_token_field :rss_token
......@@ -166,7 +166,7 @@ class User < ActiveRecord::Base
before_validation :sanitize_attrs
before_validation :set_notification_email, if: :email_changed?
before_validation :set_public_email, if: :public_email_changed?
before_save :ensure_authentication_token, :ensure_incoming_email_token
before_save :ensure_incoming_email_token
before_save :ensure_user_rights_and_limits, if: :external_changed?
before_save :skip_reconfirmation!, if: ->(user) { user.email_changed? && user.read_only_attribute?(:email) }
before_save :check_for_verified_email, if: ->(user) { user.email_changed? && !user.new_record? }
......@@ -188,8 +188,6 @@ class User < ActiveRecord::Base
# Note: When adding an option, it MUST go on the end of the array.
enum project_view: [:readme, :activity, :files]
alias_attribute :private_token, :authentication_token
delegate :path, to: :namespace, allow_nil: true, prefix: true
accepts_nested_attributes_for :namespace
......
......@@ -39,11 +39,8 @@ class AccessTokenValidationService
token_scopes = token.scopes.map(&:to_sym)
required_scopes.any? do |scope|
if scope.respond_to?(:sufficient?)
scope = API::Scope.new(scope) unless scope.is_a?(API::Scope)
scope.sufficient?(token_scopes, request)
else
API::Scope.new(scope).sufficient?(token_scopes, request)
end
end
end
end
......
class IssuableBaseService < BaseService
<<<<<<< HEAD
prepend ::EE::IssuableBaseService
=======
>>>>>>> upstream/master
private
def filter_params(issuable)
......
......@@ -49,7 +49,10 @@ module MergeRequests
create_branch_change_note(merge_request, 'target',
merge_request.previous_changes['target_branch'].first,
merge_request.target_branch)
<<<<<<< HEAD
reset_approvals(merge_request)
=======
>>>>>>> upstream/master
end
if merge_request.previous_changes.include?('assignee_id')
......
......@@ -6,8 +6,7 @@ class MetricsService
Gitlab::HealthChecks::Redis::RedisCheck,
Gitlab::HealthChecks::Redis::CacheCheck,
Gitlab::HealthChecks::Redis::QueuesCheck,
Gitlab::HealthChecks::Redis::SharedStateCheck,
Gitlab::HealthChecks::FsShardsCheck
Gitlab::HealthChecks::Redis::SharedStateCheck
].freeze
def prometheus_metrics_text
......
......@@ -24,7 +24,7 @@
%td
= truncate(hook_log.url, length: 50)
%td.light
#{number_with_precision(hook_log.execution_duration, precision: 2)} ms
#{number_with_precision(hook_log.execution_duration, precision: 2)} sec
%td.light
= time_ago_with_tooltip(hook_log.created_at)
%td
......
......@@ -14,7 +14,7 @@
= hidden_field_tag :namespace_id, params[:namespace_id]
- namespace = Namespace.find(params[:namespace_id])
- toggle_text = "#{namespace.kind}: #{namespace.full_path}"
= dropdown_toggle(toggle_text, { toggle: 'dropdown' }, { toggle_class: 'js-namespace-select large' })
= dropdown_toggle(toggle_text, { toggle: 'dropdown', is_filter: 'true' }, { toggle_class: 'js-namespace-select large' })
.dropdown-menu.dropdown-select.dropdown-menu-align-right
= dropdown_title('Namespaces')
= dropdown_filter("Search for Namespace")
......
......@@ -117,7 +117,7 @@
= f.label :new_namespace_id, "Namespace", class: 'control-label'
.col-sm-10
.dropdown
= dropdown_toggle('Search for Namespace', { toggle: 'dropdown', field_name: 'new_namespace_id', show_any: 'false' }, { toggle_class: 'js-namespace-select large' })
= dropdown_toggle('Search for Namespace', { toggle: 'dropdown', field_name: 'new_namespace_id' }, { toggle_class: 'js-namespace-select large' })
.dropdown-menu.dropdown-select
= dropdown_title('Namespaces')
= dropdown_filter("Search for Namespace")
......
......@@ -5,9 +5,9 @@
- if link && status.has_details?
= link_to status.details_path, class: css_classes, title: title do
= custom_icon(status.icon)
= sprite_icon(status.icon)
= status.text
- else
%span{ class: css_classes, title: title }
= custom_icon(status.icon)
= sprite_icon(status.icon)
= status.text
......@@ -7,13 +7,13 @@
- if status.has_details?
= link_to status.details_path, class: 'mini-pipeline-graph-dropdown-item', data: { toggle: 'tooltip', title: tooltip, container: 'body' } do
%span{ class: klass }= custom_icon(status.icon)
%span{ class: klass }= sprite_icon(status.icon)
%span.ci-build-text= subject.name
- else
.menu-item.mini-pipeline-graph-dropdown-item{ data: { toggle: 'tooltip', title: tooltip, container: 'body' } }
%span{ class: klass }= custom_icon(status.icon)
%span{ class: klass }= sprite_icon(status.icon)
%span.ci-build-text= subject.name
- if status.has_action?
= link_to status.action_path, class: 'ci-action-icon-wrapper js-ci-action-icon', method: status.action_method, data: { toggle: 'tooltip', title: status.action_title, container: 'body' } do
= custom_icon(status.action_icon)
= link_to status.action_path, class: "ci-action-icon-wrapper js-ci-action-icon", method: status.action_method, data: { toggle: 'tooltip', title: status.action_title, container: 'body' } do
= sprite_icon(status.action_icon, css_class: "icon-action-#{status.action_icon}")
......@@ -8,7 +8,7 @@
%li.todos-pending{ class: active_when(params[:state].blank? || params[:state] == 'pending') }>
= link_to todos_filter_path(state: 'pending') do
%span
To do
Todos
%span.badge
= number_with_delimiter(todos_pending_count)
%li.todos-done{ class: active_when(params[:state] == 'done') }>
......
- name = label.parameterize
- attribute = name.underscore
.reset-action
%p.cgray
= label_tag name, label, class: "label-light"
= text_field_tag name, current_user.send(attribute), class: 'form-control', readonly: true, onclick: 'this.select()'
%p.help-block
= help_text
.prepend-top-default
= link_to button_label, [:reset, attribute, :profile], method: :put, data: { confirm: 'Are you sure?' }, class: 'btn btn-default private-token'
......@@ -6,22 +6,6 @@
.alert.alert-info
Some options are unavailable for LDAP accounts
.row.prepend-top-default
.col-lg-4.profile-settings-sidebar
%h4.prepend-top-0
Private Tokens
%p
Keep these tokens secret, anyone with access to them can interact with
GitLab as if they were you.
.col-lg-8.private-tokens-reset
= render partial: 'reset_token', locals: { label: 'Private token', button_label: 'Reset private token', help_text: 'Your private token is used to access the API and Atom feeds without username/password authentication.' }
= render partial: 'reset_token', locals: { label: 'RSS token', button_label: 'Reset RSS token', help_text: 'Your RSS token is used to create urls for personalized RSS feeds.' }
- if incoming_email_token_enabled?
= render partial: 'reset_token', locals: { label: 'Incoming email token', button_label: 'Reset incoming email token', help_text: 'Your incoming email token is used to create new issues by email, and is included in your project-specific email addresses.' }
%hr
.row.prepend-top-default
.col-lg-4.profile-settings-sidebar
%h4.prepend-top-0
......
......@@ -30,3 +30,40 @@
= render "shared/personal_access_tokens_form", path: profile_personal_access_tokens_path, impersonation: false, token: @personal_access_token, scopes: @scopes
= render "shared/personal_access_tokens_table", impersonation: false, active_tokens: @active_personal_access_tokens, inactive_tokens: @inactive_personal_access_tokens
%hr
.row.prepend-top-default
.col-lg-4.profile-settings-sidebar
%h4.prepend-top-0
RSS token
%p
Your RSS token is used to authenticate you when your RSS reader loads a personalized RSS feed, and is included in your personal RSS feed URLs.
%p
It cannot be used to access any other data.
.col-lg-8.rss-token-reset
= label_tag :rss_token, 'RSS token', class: "label-light"
= text_field_tag :rss_token, current_user.rss_token, class: 'form-control', readonly: true, onclick: 'this.select()'
%p.help-block
Keep this token secret. Anyone who gets ahold of it can read activity and issue RSS feeds as if they were you.
You should
= link_to 'reset it', [:reset, :rss_token, :profile], method: :put, data: { confirm: 'Are you sure? Any RSS URLs currently in use will stop working.' }
if that ever happens.
- if incoming_email_token_enabled?
%hr
.row.prepend-top-default
.col-lg-4.profile-settings-sidebar
%h4.prepend-top-0
Incoming email token
%p
Your incoming email token is used to authenticate you when you create a new issue by email, and is included in your personal project-specific email addresses.
%p
It cannot be used to access any other data.
.col-lg-8.incoming-email-token-reset
= label_tag :incoming_email_token, 'Incoming email token', class: "label-light"
= text_field_tag :incoming_email_token, current_user.incoming_email_token, class: 'form-control', readonly: true, onclick: 'this.select()'
%p.help-block
Keep this token secret. Anyone who gets ahold of it can create issues as if they were you.
You should
= link_to 'reset it', [:reset, :incoming_email_token, :profile], method: :put, data: { confirm: 'Are you sure? Any issue email addresses currently in use will stop working.' }
if that ever happens.
......@@ -24,7 +24,7 @@
%td
= truncate(hook_log.url, length: 50)
%td.light
#{number_with_precision(hook_log.execution_duration, precision: 2)} ms
#{number_with_precision(hook_log.execution_duration, precision: 2)} sec
%td.light
= time_ago_with_tooltip(hook_log.created_at)
%td
......
......@@ -91,7 +91,7 @@
- builds.select{|build| build.status == build_status}.each do |build|
.build-job{ class: sidebar_build_class(build, @build), data: { stage: build.stage } }
= link_to project_job_path(@project, build) do
= icon('arrow-right')
= sprite_icon('arrow-right', size:16, css_class: 'icon-arrow-right')
%span{ class: "ci-status-icon-#{build.status}" }
= ci_icon_for_status(build.status)
%span
......@@ -100,4 +100,5 @@
- else
= build.id
- if build.retried?
%i.fa.fa-refresh.has-tooltip{ data: { container: 'body', placement: 'bottom' }, title: 'Job was retried' }
%span.has-tooltip{ data: { container: 'body', placement: 'bottom' }, title: 'Job was retried' }
= sprite_icon('retry', size:16, css_class: 'icon-retry')
......@@ -7,7 +7,7 @@
.stage-container.dropdown{ class: klass }
%button.mini-pipeline-graph-dropdown-toggle.has-tooltip.js-builds-dropdown-button{ class: "ci-status-icon-#{detailed_status.group}", type: 'button', data: { toggle: 'dropdown', title: "#{stage.name}: #{detailed_status.label}", placement: 'top', "stage-endpoint" => stage_project_pipeline_path(pipeline.project, pipeline, stage: stage.name) } }
= custom_icon(icon_status)
= sprite_icon(icon_status)
= icon('caret-down')
%ul.dropdown-menu.mini-pipeline-graph-dropdown-menu.js-builds-dropdown-container
......
......@@ -11,7 +11,7 @@
= hook_log.trigger.singularize.titleize
%p
%strong Elapsed time:
#{number_with_precision(hook_log.execution_duration, precision: 2)} ms
#{number_with_precision(hook_log.execution_duration, precision: 2)} sec
%p
%strong Request time:
= time_ago_with_tooltip(hook_log.created_at)
......
---
title: Tighten up whitelisting of certain Geo routes
merge_request: 15082
author:
type: fixed
---
title: Add a latest_merge_request_diff_id column to merge_requests
merge_request: 15035
author:
type: performance
---
title: Todos spelled correctly on Todos list page
merge_request: 15015
author:
type: changed
---
title: Fix webhooks recent deliveries
merge_request: 15146
author: Alexander Randa (@randaalex)
type: fixed
---
title: Add sudo scope for OAuth and Personal Access Tokens to be used by admins to
impersonate other users on the API
merge_request:
author:
type: added
---
title: Convert private tokens to Personal Access Tokens with sudo scope
merge_request:
author:
type: security
---
title: Remove private tokens from web interface and API
merge_request:
author:
type: security
---
title: Remove Session API now that private tokens are removed from user API endpoints
merge_request:
author:
type: removed
---
title: Fix cancel button not working while uploading on the new issue page
merge_request: 15137
author:
type: fixed
---
title: Remove Filesystem check metrics that use too much CPU to handle requests
merge_request:
author:
type: performance
---
title: Make NamespaceSelect change URL when filtering
merge_request: 14888
author:
type: fixed
......@@ -58,9 +58,10 @@ en:
expired: "The access token expired"
unknown: "The access token is invalid"
scopes:
api: Access your API
read_user: Read user information
api: Access the authenticated user's API
read_user: Read the authenticated user's personal information
openid: Authenticate using OpenID Connect
sudo: Perform API actions as any user in the system (if the authenticated user is an admin)
flash:
applications:
......
......@@ -6,7 +6,6 @@ resource :profile, only: [:show, :update] do
get :audit_log
get :applications, to: 'oauth/applications#index'
put :reset_private_token
put :reset_incoming_email_token
put :reset_rss_token
put :update_username
......
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class MigrateUserAuthenticationTokenToPersonalAccessToken < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
# Set this constant to true if this migration requires downtime.
DOWNTIME = false
# disable_ddl_transaction!
TOKEN_NAME = 'Private Token'.freeze
def up
execute <<~SQL
INSERT INTO personal_access_tokens (user_id, token, name, created_at, updated_at, scopes)
SELECT id, authentication_token, '#{TOKEN_NAME}', NOW(), NOW(), '#{%w[api].to_yaml}'
FROM users
WHERE authentication_token IS NOT NULL
AND admin = FALSE
AND NOT EXISTS (
SELECT true
FROM personal_access_tokens
WHERE user_id = users.id
AND token = users.authentication_token
)
SQL
# Admins also need the `sudo` scope
execute <<~SQL
INSERT INTO personal_access_tokens (user_id, token, name, created_at, updated_at, scopes)
SELECT id, authentication_token, '#{TOKEN_NAME}', NOW(), NOW(), '#{%w[api sudo].to_yaml}'
FROM users
WHERE authentication_token IS NOT NULL
AND admin = TRUE
AND NOT EXISTS (
SELECT true
FROM personal_access_tokens
WHERE user_id = users.id
AND token = users.authentication_token
)
SQL
end
def down
if Gitlab::Database.postgresql?
execute <<~SQL
UPDATE users
SET authentication_token = pats.token
FROM (
SELECT user_id, token
FROM personal_access_tokens
WHERE name = '#{TOKEN_NAME}'
) AS pats
WHERE id = pats.user_id
SQL
else
execute <<~SQL
UPDATE users
INNER JOIN personal_access_tokens AS pats
ON users.id = pats.user_id
SET authentication_token = pats.token
WHERE pats.name = '#{TOKEN_NAME}'
SQL
end
execute <<~SQL
DELETE FROM personal_access_tokens
WHERE name = '#{TOKEN_NAME}'
AND EXISTS (
SELECT true
FROM users
WHERE id = personal_access_tokens.user_id
AND authentication_token = personal_access_tokens.token
)
SQL
end
end
class AddLatestMergeRequestDiffIdToMergeRequests < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_column :merge_requests, :latest_merge_request_diff_id, :integer
add_concurrent_index :merge_requests, :latest_merge_request_diff_id
add_concurrent_foreign_key :merge_requests, :merge_request_diffs,
column: :latest_merge_request_diff_id,
on_delete: :nullify
end
def down
remove_foreign_key :merge_requests, column: :latest_merge_request_diff_id
if index_exists?(:merge_requests, :latest_merge_request_diff_id)
remove_concurrent_index :merge_requests, :latest_merge_request_diff_id
end
remove_column :merge_requests, :latest_merge_request_diff_id
end
end
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class RemoveUserAuthenticationToken < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
remove_column :users, :authentication_token
end
def down
add_column :users, :authentication_token, :string
add_concurrent_index :users, :authentication_token, unique: true
end
end
class PopulateMergeRequestsLatestMergeRequestDiffId < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
BATCH_SIZE = 1_000
class MergeRequest < ActiveRecord::Base
self.table_name = 'merge_requests'
include ::EachBatch
end
disable_ddl_transaction!
def up
update = '
latest_merge_request_diff_id = (
SELECT MAX(id)
FROM merge_request_diffs
WHERE merge_requests.id = merge_request_diffs.merge_request_id
)'.squish
MergeRequest.where(latest_merge_request_diff_id: nil).each_batch(of: BATCH_SIZE) do |relation|
relation.update_all(update)
end
end
end
......@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20171017145932) do
ActiveRecord::Schema.define(version: 20171026082505) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
......@@ -1239,6 +1239,7 @@ ActiveRecord::Schema.define(version: 20171017145932) do
t.boolean "ref_fetched"
t.string "merge_jid"
t.boolean "discussion_locked"
t.integer "latest_merge_request_diff_id"
end
add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree
......@@ -1247,6 +1248,7 @@ ActiveRecord::Schema.define(version: 20171017145932) do
add_index "merge_requests", ["deleted_at"], name: "index_merge_requests_on_deleted_at", using: :btree
add_index "merge_requests", ["description"], name: "index_merge_requests_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"}
add_index "merge_requests", ["head_pipeline_id"], name: "index_merge_requests_on_head_pipeline_id", using: :btree
add_index "merge_requests", ["latest_merge_request_diff_id"], name: "index_merge_requests_on_latest_merge_request_diff_id", using: :btree
add_index "merge_requests", ["milestone_id"], name: "index_merge_requests_on_milestone_id", using: :btree
add_index "merge_requests", ["source_branch"], name: "index_merge_requests_on_source_branch", using: :btree
add_index "merge_requests", ["source_project_id", "source_branch"], name: "index_merge_requests_on_source_project_id_and_source_branch", using: :btree
......@@ -2066,7 +2068,6 @@ ActiveRecord::Schema.define(version: 20171017145932) do
t.string "skype", default: "", null: false
t.string "linkedin", default: "", null: false
t.string "twitter", default: "", null: false
t.string "authentication_token"
t.string "bio"
t.integer "failed_attempts", default: 0
t.datetime "locked_at"
......@@ -2124,7 +2125,6 @@ ActiveRecord::Schema.define(version: 20171017145932) do
end
add_index "users", ["admin"], name: "index_users_on_admin", using: :btree
add_index "users", ["authentication_token"], name: "index_users_on_authentication_token", unique: true, using: :btree
add_index "users", ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true, using: :btree
add_index "users", ["created_at"], name: "index_users_on_created_at", using: :btree
add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree
......@@ -2279,6 +2279,7 @@ ActiveRecord::Schema.define(version: 20171017145932) do
add_foreign_key "merge_request_metrics", "ci_pipelines", column: "pipeline_id", on_delete: :cascade
add_foreign_key "merge_request_metrics", "merge_requests", on_delete: :cascade
add_foreign_key "merge_requests", "ci_pipelines", column: "head_pipeline_id", name: "fk_fd82eae0b9", on_delete: :nullify
add_foreign_key "merge_requests", "merge_request_diffs", column: "latest_merge_request_diff_id", name: "fk_06067f5644", on_delete: :nullify
add_foreign_key "merge_requests", "projects", column: "target_project_id", name: "fk_a6963e8447", on_delete: :cascade
add_foreign_key "merge_requests_closing_issues", "issues", on_delete: :cascade
add_foreign_key "merge_requests_closing_issues", "merge_requests", on_delete: :cascade
......
......@@ -28,7 +28,7 @@ The MemoryKiller is controlled using environment variables.
delayed shutdown is triggered. The default value for Omnibus packages is set
[in the omnibus-gitlab
repository](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/files/gitlab-cookbooks/gitlab/attributes/default.rb).
- `SIDEKIQ_MEMORY_KILLER_GRACE_TIME`: defaults 900 seconds (15 minutes). When
- `SIDEKIQ_MEMORY_KILLER_GRACE_TIME`: defaults to 900 seconds (15 minutes). When
a shutdown is triggered, the Sidekiq process will keep working normally for
another 15 minutes.
- `SIDEKIQ_MEMORY_KILLER_SHUTDOWN_WAIT`: defaults to 30 seconds. When the grace
......@@ -36,5 +36,3 @@ The MemoryKiller is controlled using environment variables.
Existing jobs get 30 seconds to finish. After that, the MemoryKiller tells
Sidekiq to shut down, and an external supervision mechanism (e.g. Runit) must
restart Sidekiq.
- `SIDEKIQ_MEMORY_KILLER_SHUTDOWN_SIGNAL`: defaults to `SIGKILL`. The name of
the final signal sent to the Sidekiq process when we want it to shut down.
......@@ -141,7 +141,7 @@ separate Rails process to debug the issue:
1. Log in to your GitLab account.
1. Copy the URL that is causing problems (e.g. https://gitlab.com/ABC).
1. Obtain the private token for your user (Profile Settings -> Account).
1. Create a Personal Access Token for your user (Profile Settings -> Access Tokens).
1. Bring up the GitLab Rails console. For omnibus users, run:
```
......
......@@ -51,7 +51,6 @@ following locations:
- [Repository Files](repository_files.md)
- [Runners](runners.md)
- [Services](services.md)
- [Session](session.md)
- [Settings](settings.md)
- [Sidekiq metrics](sidekiq_metrics.md)
- [System Hooks](system_hooks.md)
......@@ -87,27 +86,10 @@ API requests should be prefixed with `api` and the API version. The API version
is defined in [`lib/api.rb`][lib-api-url]. For example, the root of the v4 API
is at `/api/v4`.
For endpoints that require [authentication](#authentication), you need to pass
a `private_token` parameter via query string or header. If passed as a header,
the header name must be `PRIVATE-TOKEN` (uppercase and with a dash instead of
an underscore).
Example of a valid API request:
```
GET /projects?private_token=9koXpg98eAheJpvBs5tK
```
Example of a valid API request using cURL and authentication via header:
Example of a valid API request using cURL:
```shell
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects"
```
Example of a valid API request using cURL and authentication via a query string:
```shell
curl "https://gitlab.example.com/api/v4/projects?private_token=9koXpg98eAheJpvBs5tK"
curl "https://gitlab.example.com/api/v4/projects"
```
The API uses JSON to serialize data. You don't need to specify `.json` at the
......@@ -115,15 +97,20 @@ end of an API URL.
## Authentication
Most API requests require authentication via a session cookie or token. For
Most API requests require authentication, or will only return public data when
authentication is not provided. For
those cases where it is not required, this will be mentioned in the documentation
for each individual endpoint. For example, the [`/projects/:id` endpoint](projects.md).
There are three types of access tokens available:
There are three ways to authenticate with the GitLab API:
1. [OAuth2 tokens](#oauth2-tokens)
1. [Private tokens](#private-tokens)
1. [Personal access tokens](#personal-access-tokens)
1. [Session cookie](#session-cookie)
For admins who want to authenticate with the API as a specific user, or who want to build applications or scripts that do so, two options are available:
1. [Impersonation tokens](#impersonation-tokens)
2. [Sudo](#sudo)
If authentication information is invalid or omitted, an error message will be
returned with status code `401`:
......@@ -134,74 +121,84 @@ returned with status code `401`:
}
```
### Session cookie
### OAuth2 tokens
When signing in to GitLab as an ordinary user, a `_gitlab_session` cookie is
set. The API will use this cookie for authentication if it is present, but using
the API to generate a new session cookie is currently not supported.
You can use an [OAuth2 token](oauth2.md) to authenticate with the API by passing it in either the
`access_token` parameter or the `Authorization` header.
### OAuth2 tokens
Example of using the OAuth2 token in a parameter:
You can use an OAuth 2 token to authenticate with the API by passing it either in the
`access_token` parameter or in the `Authorization` header.
```shell
curl https://gitlab.example.com/api/v4/projects?access_token=OAUTH-TOKEN
```
Example of using the OAuth2 token in the header:
Example of using the OAuth2 token in a header:
```shell
curl --header "Authorization: Bearer OAUTH-TOKEN" https://gitlab.example.com/api/v4/projects
```
Read more about [GitLab as an OAuth2 client](oauth2.md).
Read more about [GitLab as an OAuth2 provider](oauth2.md).
### Private tokens
### Personal access tokens
Private tokens provide full access to the GitLab API. Anyone with access to
them can interact with GitLab as if they were you. You can find or reset your
private token in your account page (`/profile/account`).
You can use a [personal access token][pat] to authenticate with the API by passing it in either the
`private_token` parameter or the `Private-Token` header.
For examples of usage, [read the basic usage section](#basic-usage).
Example of using the personal access token in a parameter:
### Personal access tokens
```shell
curl https://gitlab.example.com/api/v4/projects?private_token=9koXpg98eAheJpvBs5tK
```
Example of using the personal access token in a header:
Instead of using your private token which grants full access to your account,
personal access tokens could be a better fit because of their granular
permissions.
```shell
curl --header "Private-Token: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects
```
Once you have your token, pass it to the API using either the `private_token`
parameter or the `PRIVATE-TOKEN` header. For examples of usage,
[read the basic usage section](#basic-usage).
Read more about [personal access tokens][pat].
### Session cookie
When signing in to the main GitLab application, a `_gitlab_session` cookie is
set. The API will use this cookie for authentication if it is present, but using
the API to generate a new session cookie is currently not supported.
[Read more about personal access tokens.][pat]
The primary user of this authentication method is the web frontend of GitLab itself,
which can use the API as the authenticated user to get a list of their projects,
for example, without needing to explicitly pass an access token.
### Impersonation tokens
> [Introduced][ce-9099] in GitLab 9.0. Needs admin permissions.
Impersonation tokens are a type of [personal access token][pat]
that can only be created by an admin for a specific user.
that can only be created by an admin for a specific user. They are a great fit
if you want to build applications or scripts that authenticate with the API as a specific user.
They are a better alternative to using the user's password/private token
or using the [Sudo](#sudo) feature which also requires the admin's password
or private token, since the password/token can change over time. Impersonation
tokens are a great fit if you want to build applications or tools which
authenticate with the API as a specific user.
They are an alternative to directly using the user's password or one of their
personal access tokens, and to using the [Sudo](#sudo) feature, since the user's (or admin's, in the case of Sudo)
password/token may not be known or may change over time.
For more information, refer to the
[users API](users.md#retrieve-user-impersonation-tokens) docs.
For examples of usage, [read the basic usage section](#basic-usage).
Impersonation tokens are used exactly like regular personal access tokens, and can be passed in either the
`private_token` parameter or the `Private-Token` header.
### Sudo
> Needs admin permissions.
All API requests support performing an API call as if you were another user,
provided your private token is from an administrator account. You need to pass
the `sudo` parameter either via query string or a header with an ID/username of
provided you are authenticated as an administrator with an OAuth or Personal Access Token that has the `sudo` scope.
You need to pass the `sudo` parameter either via query string or a header with an ID/username of
the user you want to perform the operation as. If passed as a header, the
header name must be `SUDO` (uppercase).
header name must be `Sudo`.
If a non administrative `private_token` is provided, then an error message will
If a non administrative access token is provided, an error message will
be returned with status code `403`:
```json
......@@ -210,12 +207,23 @@ be returned with status code `403`:
}
```
If an access token without the `sudo` scope is provided, an error message will
be returned with status code `403`:
```json
{
"error": "insufficient_scope",
"error_description": "The request requires higher privileges than provided by the access token.",
"scope": "sudo"
}
```
If the sudo user ID or username cannot be found, an error message will be
returned with status code `404`:
```json
{
"message": "404 Not Found: No user id or username for: <id/username>"
"message": "404 User with ID or username '123' Not Found"
}
```
......@@ -229,7 +237,7 @@ GET /projects?private_token=9koXpg98eAheJpvBs5tK&sudo=username
```
```shell
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --header "SUDO: username" "https://gitlab.example.com/api/v4/projects"
curl --header "Private-Token: 9koXpg98eAheJpvBs5tK" --header "Sudo: username" "https://gitlab.example.com/api/v4/projects"
```
Example of a valid API call and a request using cURL with sudo request,
......@@ -240,7 +248,7 @@ GET /projects?private_token=9koXpg98eAheJpvBs5tK&sudo=23
```
```shell
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --header "SUDO: 23" "https://gitlab.example.com/api/v4/projects"
curl --header "Private-Token: 9koXpg98eAheJpvBs5tK" --header "Sudo: 23" "https://gitlab.example.com/api/v4/projects"
```
## Status codes
......
# Session API
>**Deprecation notice:**
Starting in GitLab 8.11, this feature has been **disabled** for users with
[two-factor authentication][2fa] turned on. These users can access the API
using [personal access tokens] instead.
You can login with both GitLab and LDAP credentials in order to obtain the
private token.
```
POST /session
```
| Attribute | Type | Required | Description |
| ---------- | ------- | -------- | -------- |
| `login` | string | yes | The username of the user|
| `email` | string | yes if login is not provided | The email of the user |
| `password` | string | yes | The password of the user |
```bash
curl --request POST "https://gitlab.example.com/api/v4/session?login=john_smith&password=strongpassw0rd"
```
Example response:
```json
{
"name": "John Smith",
"username": "john_smith",
"id": 32,
"state": "active",
"avatar_url": null,
"created_at": "2015-01-29T21:07:19.440Z",
"is_admin": true,
"bio": null,
"skype": "",
"linkedin": "",
"twitter": "",
"website_url": "",
"email": "john@example.com",
"theme_id": 1,
"color_scheme_id": 1,
"projects_limit": 10,
"current_sign_in_at": "2015-07-07T07:10:58.392Z",
"identities": [],
"can_create_group": true,
"can_create_project": true,
"two_factor_enabled": false,
"private_token": "9koXpg98eAheJpvBs5tK"
}
```
[2fa]: ../user/profile/account/two_factor_authentication.md
[personal access tokens]: ../user/profile/personal_access_tokens.md
......@@ -415,8 +415,7 @@ GET /user
"can_create_group": true,
"can_create_project": true,
"two_factor_enabled": true,
"external": false,
"private_token": "dd34asd13as"
"external": false
}
```
......
......@@ -31,12 +31,12 @@ There are three methods to enable the use of `docker build` and `docker run` dur
The simplest approach is to install GitLab Runner in `shell` execution mode.
GitLab Runner then executes job scripts as the `gitlab-runner` user.
1. Install [GitLab Runner](https://gitlab.com/gitlab-org/gitlab-ci-multi-runner/#installation).
1. Install [GitLab Runner](https://gitlab.com/gitlab-org/gitlab-runner/#installation).
1. During GitLab Runner installation select `shell` as method of executing job scripts or use command:
```bash
sudo gitlab-ci-multi-runner register -n \
sudo gitlab-runner register -n \
--url https://gitlab.com/ \
--registration-token REGISTRATION_TOKEN \
--executor shell \
......@@ -93,7 +93,7 @@ In order to do that, follow the steps:
mode:
```bash
sudo gitlab-ci-multi-runner register -n \
sudo gitlab-runner register -n \
--url https://gitlab.com/ \
--registration-token REGISTRATION_TOKEN \
--executor docker \
......@@ -178,7 +178,7 @@ In order to do that, follow the steps:
1. Register GitLab Runner from the command line to use `docker` and share `/var/run/docker.sock`:
```bash
sudo gitlab-ci-multi-runner register -n \
sudo gitlab-runner register -n \
--url https://gitlab.com/ \
--registration-token REGISTRATION_TOKEN \
--executor docker \
......
......@@ -501,8 +501,8 @@ First start with creating a file named `build_script`:
```bash
cat <<EOF > build_script
git clone https://gitlab.com/gitlab-org/gitlab-ci-multi-runner.git /builds/gitlab-org/gitlab-ci-multi-runner
cd /builds/gitlab-org/gitlab-ci-multi-runner
git clone https://gitlab.com/gitlab-org/gitlab-runner.git /builds/gitlab-org/gitlab-runner
cd /builds/gitlab-org/gitlab-runner
make
EOF
```
......
......@@ -267,10 +267,10 @@ terminal execute:
```bash
# Check using docker executor
gitlab-ci-multi-runner exec docker test:app
gitlab-runner exec docker test:app
# Check using shell executor
gitlab-ci-multi-runner exec shell test:app
gitlab-runner exec shell test:app
```
## Example project
......
......@@ -64,7 +64,7 @@ To build this project you also need to have [GitLab Runner](https://docs.gitlab.
You can use public runners available on `gitlab.com`, but you can register your own:
```
gitlab-ci-multi-runner register \
gitlab-runner register \
--non-interactive \
--url "https://gitlab.com/" \
--registration-token "PROJECT_REGISTRATION_TOKEN" \
......
......@@ -61,7 +61,7 @@ correctly with your CI jobs:
1. First, make sure you have used [relative URLs](#configuring-the-gitmodules-file)
for the submodules located in the same GitLab server.
1. Next, if you are using `gitlab-ci-multi-runner` v1.10+, you can set the
1. Next, if you are using `gitlab-runner` v1.10+, you can set the
`GIT_SUBMODULE_STRATEGY` variable to either `normal` or `recursive` to tell
the runner to fetch your submodules before the job:
```yaml
......@@ -71,7 +71,7 @@ correctly with your CI jobs:
See the [`.gitlab-ci.yml` reference](yaml/README.md#git-submodule-strategy)
for more details about `GIT_SUBMODULE_STRATEGY`.
1. If you are using an older version of `gitlab-ci-multi-runner`, then use
1. If you are using an older version of `gitlab-runner`, then use
`git submodule sync/update` in `before_script`:
```yaml
......
......@@ -459,11 +459,11 @@ Rendered example:
### cURL commands
- Use `https://gitlab.example.com/api/v4/` as an endpoint.
- Wherever needed use this private token: `9koXpg98eAheJpvBs5tK`.
- Wherever needed use this personal access token: `9koXpg98eAheJpvBs5tK`.
- Always put the request first. `GET` is the default so you don't have to
include it.
- Use double quotes to the URL when it includes additional parameters.
- Prefer to use examples using the private token and don't pass data of
- Prefer to use examples using the personal access token and don't pass data of
username and password.
| Methods | Description |
......
......@@ -60,6 +60,35 @@ writing one](testing_levels.md#consider-not-writing-a-system-test)!
- It's ok to look for DOM elements but don't abuse it since it makes the tests
more brittle
#### Debugging Capybara
Sometimes you may need to debug Capybara tests by observing browser behavior.
You can pause Capybara and view the website on the browser by using the
`live_debug` method in your spec. The current page will be automatically opened
in your default browser.
You may need to sign in first (the current user's credentials are displayed in
the terminal).
To resume the test run, press any key.
For example:
```
$ bin/rspec spec/features/auto_deploy_spec.rb:34
Running via Spring preloader in process 8999
Run options: include {:locations=>{"./spec/features/auto_deploy_spec.rb"=>[34]}}
Current example is paused for live debugging
The current user credentials are: user2 / 12345678
Press any key to resume the execution of the example!
Back to the example!
.
Finished in 34.51 seconds (files took 0.76702 seconds to load)
1 example, 0 failures
```
### `let` variables
GitLab's RSpec suite has made extensive use of `let` variables to reduce
......
......@@ -126,7 +126,7 @@ always in-sync with the codebase.
[GitLab Workhorse]: https://gitlab.com/gitlab-org/gitlab-workhorse
[Gitaly]: https://gitlab.com/gitlab-org/gitaly
[GitLab Pages]: https://gitlab.com/gitlab-org/gitlab-pages
[GitLab Runner]: https://gitlab.com/gitlab-org/gitlab-ci-multi-runner
[GitLab Runner]: https://gitlab.com/gitlab-org/gitlab-runner
[GitLab Omnibus]: https://gitlab.com/gitlab-org/omnibus-gitlab
[GitLab QA]: https://gitlab.com/gitlab-org/gitlab-qa
[part of GitLab Rails]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/qa
......
......@@ -186,7 +186,7 @@ Runner.
We recommend using a separate machine for each GitLab Runner, if you plan to
use the CI features.
[security reasons]: https://gitlab.com/gitlab-org/gitlab-ci-multi-runner/blob/master/docs/security/index.md
[security reasons]: https://gitlab.com/gitlab-org/gitlab-runner/blob/master/docs/security/index.md
## Supported web browsers
......
......@@ -149,18 +149,3 @@ cp config/secrets.yml.bak config/secrets.yml
sudo /etc/init.d/gitlab start
```
## Clear authentication tokens for all users. Important! Data loss!
Clear authentication tokens for all users in the GitLab database. This
task is useful if your users' authentication tokens might have been exposed in
any way. All the existing tokens will become invalid, and new tokens are
automatically generated upon sign-in or user modification.
```
# omnibus-gitlab
sudo gitlab-rake gitlab:users:clear_all_authentication_tokens
# installation from source
bundle exec rake gitlab:users:clear_all_authentication_tokens RAILS_ENV=production
```
......@@ -517,7 +517,7 @@ Feature.get(:auto_devops_banner_disabled).enable
Or through the HTTP API with an admin access token:
```sh
curl --data "value=true" --header "PRIVATE-TOKEN: private_token" https://gitlab.example.com/api/v4/features/auto_devops_banner_disabled
curl --data "value=true" --header "PRIVATE-TOKEN: personal_access_token" https://gitlab.example.com/api/v4/features/auto_devops_banner_disabled
```
[ce-37115]: https://gitlab.com/gitlab-org/gitlab-ce/issues/37115
......
......@@ -55,10 +55,10 @@ The curriculum is composed of GitLab videos, screencasts, presentations, project
#### 1.5. Migrating from other Source Control
1. [Migrating from BitBucket/Stash](https://docs.gitlab.com/ee/workflow/importing/import_projects_from_bitbucket.html)
1. [Migrating from GitHub](https://docs.gitlab.com/ee/workflow/importing/import_projects_from_github.html)
1. [Migrating from SVN](https://docs.gitlab.com/ee/workflow/importing/migrating_from_svn.html)
1. [Migrating from Fogbugz](https://docs.gitlab.com/ee/workflow/importing/import_projects_from_fogbugz.html)
1. [Migrating from BitBucket/Stash](https://docs.gitlab.com/ee/user/project/import/bitbucket.html)
1. [Migrating from GitHub](https://docs.gitlab.com/ee/user/project/import/github.html)
1. [Migrating from SVN](https://docs.gitlab.com/ee/user/project/import/svn.html)
1. [Migrating from Fogbugz](https://docs.gitlab.com/ee/user/project/import/fogbugz.html)
#### 1.6. GitLab Inc.
......@@ -80,13 +80,13 @@ The curriculum is composed of GitLab videos, screencasts, presentations, project
- Being part of our Great Community and Contributing to GitLab
1. [Getting Started with the GitLab Development Kit (GDK)](https://about.gitlab.com/2016/06/08/getting-started-with-gitlab-development-kit/)
1. [Contributing Technical Articles to the GitLab Blog](https://about.gitlab.com/2016/01/26/call-for-writers/)
1. [GitLab Training Workshops](https://about.gitlab.com/training)
1. [GitLab Training Workshops](https://docs.gitlab.com/ce/university/training/end-user/)
1. [GitLab Professional Services](https://about.gitlab.com/services/)
#### 1.8 GitLab Training Material
1. [Git and GitLab Terminology](glossary/README.md)
1. [Git and GitLab Workshop - Slides](https://docs.google.com/presentation/d/1JzTYD8ij9slejV2-TO-NzjCvlvj6mVn9BORePXNJoMI/edit?usp=drive_web)
1. [Git and GitLab Revision](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/university/training/end-user)
---
......
......@@ -460,7 +460,7 @@ A route table contains rules (called routes) that determine where network traffi
### Runners
Actual build machines/containers that [run and execute tests](https://gitlab.com/gitlab-org/gitlab-ci-multi-runner) you have specified to be run on GitLab CI.
Actual build machines/containers that [run and execute tests](https://gitlab.com/gitlab-org/gitlab-runner) you have specified to be run on GitLab CI.
### Sidekiq
......
......@@ -53,8 +53,8 @@ git log --since=1.month.ago --until=3.weeks.ago
```
cd ~/workspace
git clone git@gitlab.com:gitlab-org/gitlab-ci-multi-runner.git
cd gitlab-ci-multi-runner
git clone git@gitlab.com:gitlab-org/gitlab-runner.git
cd gitlab-runner
git log --author="Travis"
git log --since=1.month.ago --until=3.weeks.ago
git log --since=1.month.ago --until=1.day.ago --author="Travis"
......
......@@ -52,7 +52,7 @@ You can edit your account settings by navigating from the up-right corner menu b
From there, you can:
- Update your personal information
- Manage [private tokens](../../api/README.md#private-tokens), email tokens, [2FA](account/two_factor_authentication.md)
- Manage [2FA](account/two_factor_authentication.md)
- Change your username and [delete your account](account/delete_account.md)
- Manage applications that can
[use GitLab as an OAuth provider](../../integration/oauth_provider.md#introduction-to-oauth)
......
......@@ -2,17 +2,15 @@
> [Introduced][ce-3749] in GitLab 8.8.
Personal access tokens are useful if you need access to the [GitLab API][api].
Instead of using your private token which grants full access to your account,
personal access tokens could be a better fit because of their
[granular permissions](#limiting-scopes-of-a-personal-access-token).
Personal access tokens are the preferred way for third party applications and scripts to
authenticate with the [GitLab API][api], if using [OAuth2](../../api/oauth2.md) is not practical.
You can also use them to authenticate against Git over HTTP. They are the only
accepted method of authentication when you have
[Two-Factor Authentication (2FA)][2fa] enabled.
Once you have your token, [pass it to the API][usage] using either the
`private_token` parameter or the `PRIVATE-TOKEN` header.
`private_token` parameter or the `Private-Token` header.
The expiration of personal access tokens happens on the date you define,
at midnight UTC.
......@@ -49,12 +47,14 @@ the following table.
|`read_user` | Allows access to the read-only endpoints under `/users`. Essentially, any of the `GET` requests in the [Users API][users] are allowed ([introduced][ce-5951] in GitLab 8.15). |
| `api` | Grants complete access to the API (read/write) ([introduced][ce-5951] in GitLab 8.15). Required for accessing Git repositories over HTTP when 2FA is enabled. |
| `read_registry` | Allows to read [container registry] images if a project is private and authorization is required ([introduced][ce-11845] in GitLab 9.3). |
| `sudo` | Allows performing API actions as any user in the system (if the authenticated user is an admin) ([introduced][ce-14838] in GitLab 10.2). |
[2fa]: ../account/two_factor_authentication.md
[api]: ../../api/README.md
[ce-3749]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3749
[ce-5951]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5951
[ce-11845]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11845
[ce-14838]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14838
[container registry]: ../project/container_registry.md
[users]: ../../api/users.md
[usage]: ../../api/README.md#basic-usage
[usage]: ../../api/README.md#personal-access-tokens
......@@ -52,7 +52,8 @@ directly in the job artifacts browser without the need to download them.
>**Note:**
With [GitLab 10.1][ce-14399], HTML files in a public project can be previewed
directly in a new tab without the need to download them.
directly in a new tab without the need to download them when
[GitLab Pages](../../../administration/pages/index.md) is enabled
After a job finishes, if you visit the job's specific page, there are three
buttons. You can download the artifacts archive or browse its contents, whereas
......@@ -69,7 +70,8 @@ browse inside them.
Below you can see how browsing looks like. In this case we have browsed inside
the archive and at this point there is one directory, a couple files, and
one HTML file that you can view directly online (opens in a new tab).
one HTML file that you can view directly online when
[GitLab Pages](../../../administration/pages/index.md) is enabled (opens in a new tab).
![Job artifacts browser](img/job_artifacts_browser.png)
......
......@@ -157,7 +157,6 @@ module API
mount ::API::Runner
mount ::API::Runners
mount ::API::Services
mount ::API::Session
mount ::API::Settings
mount ::API::SidekiqMetrics
mount ::API::Snippets
......
......@@ -44,73 +44,51 @@ module API
# Helper Methods for Grape Endpoint
module HelperMethods
<<<<<<< HEAD
def find_current_user
user =
find_user_from_private_token ||
find_user_from_oauth_token ||
find_user_from_warden ||
find_user_by_job_token
=======
def find_current_user!
user = find_user_from_access_token || find_user_from_warden
return unless user
>>>>>>> upstream/master
return nil unless user
raise UnauthorizedError unless Gitlab::UserAccess.new(user).allowed? && user.can?(:access_api)
forbidden!('User is blocked') unless Gitlab::UserAccess.new(user).allowed? && user.can?(:access_api)
user
end
def private_token
params[PRIVATE_TOKEN_PARAM] || env[PRIVATE_TOKEN_HEADER]
end
private
def find_user_from_private_token
token_string = private_token.to_s
return nil unless token_string.present?
def access_token
return @access_token if defined?(@access_token)
user =
find_user_by_authentication_token(token_string) ||
find_user_by_personal_access_token(token_string)
raise UnauthorizedError unless user
user
@access_token = find_oauth_access_token || find_personal_access_token
end
# Invokes the doorkeeper guard.
#
# If token is presented and valid, then it sets @current_user.
#
# If the token does not have sufficient scopes to cover the requred scopes,
# then it raises InsufficientScopeError.
#
# If the token is expired, then it raises ExpiredError.
#
# If the token is revoked, then it raises RevokedError.
#
# If the token is not found (nil), then it returns nil
#
# Arguments:
#
# scopes: (optional) scopes required for this guard.
# Defaults to empty array.
#
def find_user_from_oauth_token
access_token = find_oauth_access_token
def validate_access_token!(scopes: [])
return unless access_token
find_user_by_access_token(access_token)
case AccessTokenValidationService.new(access_token, request: request).validate(scopes: scopes)
when AccessTokenValidationService::INSUFFICIENT_SCOPE
raise InsufficientScopeError.new(scopes)
when AccessTokenValidationService::EXPIRED
raise ExpiredError
when AccessTokenValidationService::REVOKED
raise RevokedError
end
def find_user_by_authentication_token(token_string)
User.find_by_authentication_token(token_string)
end
def find_user_by_personal_access_token(token_string)
access_token = PersonalAccessToken.find_by_token(token_string)
private
def find_user_from_access_token
return unless access_token
find_user_by_access_token(access_token)
validate_access_token!
access_token.user || raise(UnauthorizedError)
end
# Check the Rails session for valid authentication details
......@@ -144,34 +122,26 @@ module API
end
def find_oauth_access_token
return @oauth_access_token if defined?(@oauth_access_token)
token = Doorkeeper::OAuth::Token.from_request(doorkeeper_request, *Doorkeeper.configuration.access_token_methods)
return @oauth_access_token = nil unless token
return unless token
@oauth_access_token = OauthAccessToken.by_token(token)
raise UnauthorizedError unless @oauth_access_token
# Expiration, revocation and scopes are verified in `find_user_by_access_token`
access_token = OauthAccessToken.by_token(token)
raise UnauthorizedError unless access_token
@oauth_access_token.revoke_previous_refresh_token!
@oauth_access_token
access_token.revoke_previous_refresh_token!
access_token
end
def find_user_by_access_token(access_token)
scopes = scopes_registered_for_endpoint
def find_personal_access_token
token = (params[PRIVATE_TOKEN_PARAM] || env[PRIVATE_TOKEN_HEADER]).to_s
return unless token.present?
case AccessTokenValidationService.new(access_token, request: request).validate(scopes: scopes)
when AccessTokenValidationService::INSUFFICIENT_SCOPE
raise InsufficientScopeError.new(scopes)
when AccessTokenValidationService::EXPIRED
raise ExpiredError
# Expiration, revocation and scopes are verified in `find_user_by_access_token`
access_token = PersonalAccessToken.find_by(token: token)
raise UnauthorizedError unless access_token
when AccessTokenValidationService::REVOKED
raise RevokedError
when AccessTokenValidationService::VALID
access_token.user
end
access_token
end
def doorkeeper_request
......@@ -255,7 +225,7 @@ module API
class InsufficientScopeError < StandardError
attr_reader :scopes
def initialize(scopes)
@scopes = scopes
@scopes = scopes.map { |s| s.try(:name) || s }
end
end
end
......
......@@ -60,10 +60,6 @@ module API
expose :admin?, as: :is_admin
end
class UserWithPrivateDetails < UserWithAdmin
expose :private_token
end
class Email < Grape::Entity
expose :id, :email
end
......
......@@ -43,6 +43,8 @@ module API
sudo!
validate_access_token!(scopes: scopes_registered_for_endpoint) unless sudo?
@current_user
end
......@@ -427,7 +429,7 @@ module API
return @initial_current_user if defined?(@initial_current_user)
begin
@initial_current_user = Gitlab::Auth::UniqueIpsLimiter.limit_user! { find_current_user }
@initial_current_user = Gitlab::Auth::UniqueIpsLimiter.limit_user! { find_current_user! }
rescue APIGuard::UnauthorizedError
unauthorized!
end
......@@ -435,24 +437,23 @@ module API
def sudo!
return unless sudo_identifier
return unless initial_current_user
unauthorized! unless initial_current_user
unless initial_current_user.admin?
forbidden!('Must be admin to use sudo')
end
# Only private tokens should be used for the SUDO feature
unless private_token == initial_current_user.private_token
forbidden!('Private token must be specified in order to use sudo')
unless access_token
forbidden!('Must be authenticated using an OAuth or Personal Access Token to use sudo')
end
validate_access_token!(scopes: [:sudo])
sudoed_user = find_user(sudo_identifier)
not_found!("User with ID or username '#{sudo_identifier}'") unless sudoed_user
if sudoed_user
@current_user = sudoed_user
else
not_found!("No user id or username for: #{sudo_identifier}")
end
end
def sudo_identifier
......
module API
class Session < Grape::API
desc 'Login to get token' do
success Entities::UserWithPrivateDetails
end
params do
optional :login, type: String, desc: 'The username'
optional :email, type: String, desc: 'The email of the user'
requires :password, type: String, desc: 'The password of the user'
at_least_one_of :login, :email
end
post "/session" do
user = Gitlab::Auth.find_with_user_password(params[:email] || params[:login], params[:password])
return unauthorized! unless user
return render_api_error!('401 Unauthorized. You have 2FA enabled. Please use a personal access token to access the API', 401) if user.two_factor_enabled?
present user, with: Entities::UserWithPrivateDetails
end
end
end
......@@ -513,9 +513,7 @@ module API
end
get do
entity =
if sudo?
Entities::UserWithPrivateDetails
elsif current_user.admin?
if current_user.admin?
Entities::UserWithAdmin
else
Entities::UserPublic
......
module Gitlab
module Auth
MissingPersonalTokenError = Class.new(StandardError)
MissingPersonalAccessTokenError = Class.new(StandardError)
REGISTRY_SCOPES = [:read_registry].freeze
# Scopes used for GitLab API access
API_SCOPES = [:api, :read_user].freeze
API_SCOPES = [:api, :read_user, :sudo].freeze
# Scopes used for OpenID Connect
OPENID_SCOPES = [:openid].freeze
......@@ -39,7 +39,7 @@ module Gitlab
# If sign-in is disabled and LDAP is not configured, recommend a
# personal access token on failed auth attempts
raise Gitlab::Auth::MissingPersonalTokenError
raise Gitlab::Auth::MissingPersonalAccessTokenError
end
def find_with_user_password(login, password)
......@@ -107,7 +107,7 @@ module Gitlab
user = find_with_user_password(login, password)
return unless user
raise Gitlab::Auth::MissingPersonalTokenError if user.two_factor_enabled?
raise Gitlab::Auth::MissingPersonalAccessTokenError if user.two_factor_enabled?
Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, full_authentication_abilities)
end
......@@ -129,7 +129,7 @@ module Gitlab
token = PersonalAccessTokensFinder.new(state: 'active').find_by(token: password)
if token && valid_scoped_token?(token, available_scopes)
Gitlab::Auth::Result.new(token.user, nil, :personal_token, abilities_for_scope(token.scopes))
Gitlab::Auth::Result.new(token.user, nil, :personal_access_token, abilities_for_scope(token.scopes))
end
end
......@@ -227,8 +227,10 @@ module Gitlab
[]
end
def available_scopes
API_SCOPES + registry_scopes
def available_scopes(current_user = nil)
scopes = API_SCOPES + registry_scopes
scopes.delete(:sudo) if current_user && !current_user.admin?
scopes
end
# Other available scopes
......
......@@ -8,7 +8,7 @@ module Gitlab
end
def action_icon
'icon_action_cancel'
'cancel'
end
def action_path
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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