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 9.0.0
\ No newline at end of file
...@@ -105,8 +105,7 @@ the remaining issues on the GitHub issue tracker. ...@@ -105,8 +105,7 @@ the remaining issues on the GitHub issue tracker.
## I want to contribute! ## I want to contribute!
If you want to contribute to GitLab, but are not sure where to start, 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.
look for [issues with the label `Accepting Merge Requests` and small weight][accepting-mrs-weight].
These issues will be of reasonable size and challenge, for anyone to start These issues will be of reasonable size and challenge, for anyone to start
contributing to GitLab. contributing to GitLab.
......
...@@ -16,7 +16,7 @@ import CILintEditor from './ci_lint_editor'; ...@@ -16,7 +16,7 @@ import CILintEditor from './ci_lint_editor';
import groupsSelect from './groups_select'; import groupsSelect from './groups_select';
/* global Search */ /* global Search */
/* global Admin */ /* global Admin */
/* global NamespaceSelects */ import NamespaceSelect from './namespace_select';
/* global NewCommitForm */ /* global NewCommitForm */
/* global NewBranchForm */ /* global NewBranchForm */
/* global Project */ /* global Project */
...@@ -650,7 +650,8 @@ import initGroupAnalytics from './init_group_analytics'; ...@@ -650,7 +650,8 @@ import initGroupAnalytics from './init_group_analytics';
new UsersSelect(); new UsersSelect();
break; break;
case 'projects': case 'projects':
new NamespaceSelects(); document.querySelectorAll('.js-namespace-select')
.forEach(dropdown => new NamespaceSelect({ dropdown }));
break; break;
case 'labels': case 'labels':
switch (path[2]) { switch (path[2]) {
......
...@@ -30,7 +30,7 @@ const utils = { ...@@ -30,7 +30,7 @@ const utils = {
}, },
isDropDownParts(target) { 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); return target.hasAttribute(DATA_TRIGGER) || target.hasAttribute(DATA_DROPDOWN);
}, },
}; };
......
...@@ -119,11 +119,9 @@ export default function dropzoneInput(form) { ...@@ -119,11 +119,9 @@ export default function dropzoneInput(form) {
// removeAllFiles(true) stops uploading files (if any) // removeAllFiles(true) stops uploading files (if any)
// and remove them from dropzone files queue. // and remove them from dropzone files queue.
$cancelButton.on('click', (e) => { $cancelButton.on('click', (e) => {
const target = e.target.closest('.js-main-target-form').querySelector('.div-dropzone');
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
Dropzone.forElement(target).removeAllFiles(true); Dropzone.forElement($formDropzone.get(0)).removeAllFiles(true);
}); });
// If 'error' event is fired, we store a failed files, // 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 Api from './api';
import './lib/utils/url_utility';
(function() { export default class NamespaceSelect {
window.NamespaceSelect = (function() { constructor(opts) {
function NamespaceSelect(opts) { const isFilter = opts.dropdown.dataset.isFilter === 'true';
this.onSelectItem = this.onSelectItem.bind(this); const fieldName = opts.dropdown.dataset.fieldName || 'namespace_id';
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({
filterable: true,
selectable: true,
filterRemote: true,
search: {
fields: ['path']
},
fieldName: fieldName,
toggleLabel: function(selected) {
if (selected.id == null) {
return selected.text;
} else {
return selected.kind + ": " + selected.full_path;
}
},
data: function(term, dataCallback) {
return Api.namespaces(term, function(namespaces) {
var anyNamespace;
if (showAny) {
anyNamespace = {
text: 'Any namespace',
id: null
};
namespaces.unshift(anyNamespace);
namespaces.splice(1, 0, 'divider');
}
return dataCallback(namespaces);
});
},
text: function(namespace) {
if (namespace.id == null) {
return namespace.text;
} else {
return namespace.kind + ": " + namespace.full_path;
}
},
renderRow: this.renderRow,
clicked: this.onSelectItem
});
}
NamespaceSelect.prototype.onSelectItem = function(options) {
const { e } = options;
return e.preventDefault();
};
return NamespaceSelect; $(opts.dropdown).glDropdown({
})(); filterable: true,
selectable: true,
window.NamespaceSelects = (function() { filterRemote: true,
function NamespaceSelects(opts) { search: {
var ref; fields: ['path']
if (opts == null) { },
opts = {}; fieldName: fieldName,
} toggleLabel: function(selected) {
this.$dropdowns = (ref = opts.$dropdowns) != null ? ref : $('.js-namespace-select'); if (selected.id == null) {
this.$dropdowns.each(function(i, dropdown) { return selected.text;
var $dropdown; } else {
$dropdown = $(dropdown); return selected.kind + ": " + selected.full_path;
return new window.NamespaceSelect({ }
dropdown: $dropdown },
data: function(term, dataCallback) {
return Api.namespaces(term, function(namespaces) {
if (isFilter) {
const anyNamespace = {
text: 'Any namespace',
id: null
};
namespaces.unshift(anyNamespace);
namespaces.splice(1, 0, 'divider');
}
return dataCallback(namespaces);
}); });
}); },
} text: function(namespace) {
if (namespace.id == null) {
return NamespaceSelects; return namespace.text;
})(); } else {
}).call(window); return namespace.kind + ": " + namespace.full_path;
}
},
renderRow: this.renderRow,
clicked(options) {
if (!isFilter) {
const { e } = options;
e.preventDefault();
}
},
url(namespace) {
return gl.utils.mergeUrlParams({ [fieldName]: namespace.id }, window.location.href);
},
});
}
}
<script> <script>
import getActionIcon from '../../../vue_shared/ci_action_icons';
import tooltip from '../../../vue_shared/directives/tooltip'; 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. * Renders either a cancel, retry or play icon pointing to the given path.
...@@ -29,17 +29,18 @@ ...@@ -29,17 +29,18 @@
}, },
}, },
components: {
icon,
},
directives: { directives: {
tooltip, tooltip,
}, },
computed: { computed: {
actionIconSvg() {
return getActionIcon(this.actionIcon);
},
cssClass() { cssClass() {
return `js-${gl.text.dasherize(this.actionIcon)}`; const actionIconDash = gl.text.dasherize(this.actionIcon);
return `${actionIconDash} js-icon-${actionIconDash}`;
}, },
}, },
}; };
...@@ -50,14 +51,9 @@ ...@@ -50,14 +51,9 @@
:data-method="actionMethod" :data-method="actionMethod"
:title="tooltipText" :title="tooltipText"
:href="link" :href="link"
class="ci-action-icon-container" class="ci-action-icon-container ci-action-icon-wrapper"
:class="cssClass"
data-container="body"> data-container="body">
<icon :name="actionIcon"/>
<i
class="ci-action-icon-wrapper"
:class="cssClass"
v-html="actionIconSvg"
aria-hidden="true"
/>
</a> </a>
</template> </template>
<script> <script>
import getActionIcon from '../../../vue_shared/ci_action_icons'; import icon from '../../../vue_shared/components/icon.vue';
import tooltip from '../../../vue_shared/directives/tooltip'; import tooltip from '../../../vue_shared/directives/tooltip';
/** /**
...@@ -29,14 +29,12 @@ ...@@ -29,14 +29,12 @@
}, },
}, },
directives: { components: {
tooltip, icon,
}, },
computed: { directives: {
actionIconSvg() { tooltip,
return getActionIcon(this.actionIcon);
},
}, },
}; };
</script> </script>
...@@ -49,7 +47,7 @@ ...@@ -49,7 +47,7 @@
rel="nofollow" rel="nofollow"
class="ci-action-icon-wrapper js-ci-status-icon" class="ci-action-icon-wrapper js-ci-status-icon"
data-container="body" data-container="body"
v-html="actionIconSvg"
aria-label="Job's action"> aria-label="Job's action">
<icon :name="actionIcon"/>
</a> </a>
</template> </template>
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
* "group": "success", * "group": "success",
* "details_path": "/root/ci-mock/builds/4256", * "details_path": "/root/ci-mock/builds/4256",
* "action": { * "action": {
* "icon": "icon_action_retry", * "icon": "retry",
* "title": "Retry", * "title": "Retry",
* "path": "/root/ci-mock/builds/4256/retry", * "path": "/root/ci-mock/builds/4256/retry",
* "method": "post" * "method": "post"
......
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
* "group": "success", * "group": "success",
* "details_path": "/root/ci-mock/builds/4256", * "details_path": "/root/ci-mock/builds/4256",
* "action": { * "action": {
* "icon": "icon_action_retry", * "icon": "retry",
* "title": "Retry", * "title": "Retry",
* "path": "/root/ci-mock/builds/4256/retry", * "path": "/root/ci-mock/builds/4256/retry",
* "method": "post" * "method": "post"
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
*/ */
import Flash from '../../flash'; 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 loadingIcon from '../../vue_shared/components/loading_icon.vue';
import tooltip from '../../vue_shared/directives/tooltip'; import tooltip from '../../vue_shared/directives/tooltip';
...@@ -45,6 +45,7 @@ export default { ...@@ -45,6 +45,7 @@ export default {
components: { components: {
loadingIcon, loadingIcon,
icon,
}, },
updated() { updated() {
...@@ -122,8 +123,8 @@ export default { ...@@ -122,8 +123,8 @@ export default {
return `ci-status-icon-${this.stage.status.group}`; return `ci-status-icon-${this.stage.status.group}`;
}, },
svgIcon() { borderlessIcon() {
return borderlessStatusIconEntityMap[this.stage.status.icon]; return `${this.stage.status.icon}_borderless`;
}, },
}, },
}; };
...@@ -145,9 +146,10 @@ export default { ...@@ -145,9 +146,10 @@ export default {
aria-expanded="false"> aria-expanded="false">
<span <span
v-html="svgIcon"
aria-hidden="true" aria-hidden="true"
:aria-label="stage.title"> :aria-label="stage.title">
<icon
:name="borderlessIcon"/>
</span> </span>
<i <i
......
...@@ -27,6 +27,8 @@ export default { ...@@ -27,6 +27,8 @@ export default {
'changeFileContent', 'changeFileContent',
]), ]),
initMonaco() { initMonaco() {
if (this.shouldHideEditor) return;
if (this.monacoInstance) { if (this.monacoInstance) {
this.monacoInstance.setModel(null); this.monacoInstance.setModel(null);
} }
...@@ -94,8 +96,12 @@ export default { ...@@ -94,8 +96,12 @@ export default {
<template> <template>
<div <div
id="ide" id="ide"
v-if='!shouldHideEditor'
class="blob-viewer-container blob-editor-container" class="blob-viewer-container blob-editor-container"
> >
<div
v-if="shouldHideEditor"
v-html="activeFile.html"
>
</div>
</div> </div>
</template> </template>
import PipelineStage from '../../pipelines/components/stage.vue'; import PipelineStage from '../../pipelines/components/stage.vue';
import ciIcon from '../../vue_shared/components/ci_icon.vue'; import ciIcon from '../../vue_shared/components/ci_icon.vue';
<<<<<<< HEAD
import { statusIconEntityMap } from '../../vue_shared/ci_status_icons'; import { statusIconEntityMap } from '../../vue_shared/ci_status_icons';
import linkedPipelinesMiniList from '../../vue_shared/components/linked_pipelines_mini_list.vue'; import linkedPipelinesMiniList from '../../vue_shared/components/linked_pipelines_mini_list.vue';
=======
import icon from '../../vue_shared/components/icon.vue';
>>>>>>> upstream/master
export default { export default {
name: 'MRWidgetPipeline', name: 'MRWidgetPipeline',
...@@ -11,7 +15,11 @@ export default { ...@@ -11,7 +15,11 @@ export default {
components: { components: {
'pipeline-stage': PipelineStage, 'pipeline-stage': PipelineStage,
ciIcon, ciIcon,
<<<<<<< HEAD
linkedPipelinesMiniList, linkedPipelinesMiniList,
=======
icon,
>>>>>>> upstream/master
}, },
computed: { computed: {
hasPipeline() { hasPipeline() {
...@@ -22,9 +30,6 @@ export default { ...@@ -22,9 +30,6 @@ export default {
return hasCI && !ciStatus; return hasCI && !ciStatus;
}, },
svg() {
return statusIconEntityMap.icon_status_failed;
},
stageText() { stageText() {
return this.mr.pipeline.details.stages.length > 1 ? 'stages' : 'stage'; return this.mr.pipeline.details.stages.length > 1 ? 'stages' : 'stage';
}, },
...@@ -53,8 +58,10 @@ export default { ...@@ -53,8 +58,10 @@ export default {
<template v-if="hasCIError"> <template v-if="hasCIError">
<div class="ci-status-icon ci-status-icon-failed ci-error js-ci-error append-right-10"> <div class="ci-status-icon ci-status-icon-failed ci-error js-ci-error append-right-10">
<span <span
v-html="svg" aria-hidden="true">
aria-hidden="true"></span> <icon
name="status_failed"/>
</span>
</div> </div>
<div class="media-body"> <div class="media-body">
Could not connect to the CI server. Please check your settings and try again 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 @@ ...@@ -43,7 +43,6 @@
computed: { computed: {
cssClass() { cssClass() {
const className = this.status.group; const className = this.status.group;
return className ? `ci-status ci-${className}` : 'ci-status'; return className ? `ci-status ci-${className}` : 'ci-status';
}, },
}, },
......
<script> <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. * Renders CI icon based on API response shared between all places where it is used.
...@@ -31,11 +31,11 @@ ...@@ -31,11 +31,11 @@
}, },
}, },
computed: { components: {
statusIconSvg() { icon,
return statusIconEntityMap[this.status.icon]; },
},
computed: {
cssClass() { cssClass() {
const status = this.status.group; const status = this.status.group;
return `ci-status-icon ci-status-icon-${status} js-ci-status-icon-${status}`; return `ci-status-icon ci-status-icon-${status} js-ci-status-icon-${status}`;
...@@ -45,7 +45,8 @@ ...@@ -45,7 +45,8 @@
</script> </script>
<template> <template>
<span <span
:class="cssClass" :class="cssClass">
v-html="statusIconSvg"> <icon
:name="status.icon"/>
</span> </span>
</template> </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 @@ ...@@ -4,9 +4,12 @@
.cred { color: $common-red; } .cred { color: $common-red; }
.cgreen { color: $common-green; } .cgreen { color: $common-green; }
.cdark { color: $common-gray-dark; } .cdark { color: $common-gray-dark; }
<<<<<<< HEAD
.text-secondary { .text-secondary {
color: $gl-text-color-secondary; color: $gl-text-color-secondary;
} }
=======
>>>>>>> upstream/master
.underlined-link { text-decoration: underline; } .underlined-link { text-decoration: underline; }
.hint { font-style: italic; color: $hint-color; } .hint { font-style: italic; color: $hint-color; }
......
...@@ -333,8 +333,10 @@ ...@@ -333,8 +333,10 @@
svg { svg {
position: relative; position: relative;
top: 2px; top: 3px;
margin-right: 3px; margin-right: 3px;
width: 14px;
height: 14px;
} }
} }
...@@ -348,9 +350,10 @@ ...@@ -348,9 +350,10 @@
svg { svg {
position: relative; position: relative;
top: 2px; top: 3px;
margin-right: 3px; margin-right: 3px;
height: 13px; height: 14px;
width: 14px;
} }
a { a {
...@@ -369,7 +372,7 @@ ...@@ -369,7 +372,7 @@
.build-job { .build-job {
position: relative; position: relative;
.fa-arrow-right { .icon-arrow-right {
position: absolute; position: absolute;
left: 15px; left: 15px;
top: 20px; top: 20px;
...@@ -379,7 +382,7 @@ ...@@ -379,7 +382,7 @@
&.active { &.active {
font-weight: $gl-font-weight-bold; font-weight: $gl-font-weight-bold;
.fa-arrow-right { .icon-arrow-right {
display: block; display: block;
} }
} }
...@@ -392,8 +395,7 @@ ...@@ -392,8 +395,7 @@
background-color: $row-hover; background-color: $row-hover;
} }
.fa-refresh { .icon-retry {
font-size: 13px;
margin-left: 3px; margin-left: 3px;
} }
} }
......
...@@ -164,8 +164,9 @@ ...@@ -164,8 +164,9 @@
z-index: 300; z-index: 300;
} }
.ci-action-icon-wrapper { .ci-action-icon-wrapper svg {
line-height: 16px; width: 16px;
height: 16px;
} }
} }
......
...@@ -480,7 +480,7 @@ ...@@ -480,7 +480,7 @@
} }
// Action Icons in big pipeline-graph nodes // 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; height: 30px;
width: 30px; width: 30px;
background: $white-light; background: $white-light;
...@@ -496,8 +496,18 @@ ...@@ -496,8 +496,18 @@
svg { svg {
fill: $gl-text-color-secondary; fill: $gl-text-color-secondary;
position: relative; position: relative;
left: -1px; left: 5px;
top: -1px; top: 2px;
width: 18px;
height: 18px;
}
&.play {
svg {
width: #{$ci-action-icon-size - 8};
height: #{$ci-action-icon-size - 8};
left: 8px;
}
} }
&:hover svg { &:hover svg {
...@@ -750,17 +760,49 @@ a.linked-pipeline-mini-item { ...@@ -750,17 +760,49 @@ a.linked-pipeline-mini-item {
svg { svg {
fill: $gl-text-color-secondary; fill: $gl-text-color-secondary;
width: $ci-action-icon-size; width: #{$ci-action-icon-size - 6};
height: $ci-action-icon-size; height: #{$ci-action-icon-size - 6};
left: -6px; left: -3px;
position: relative; position: relative;
top: -3px; top: -2px;
} }
&:hover svg, &:hover svg,
&:focus svg { &:focus svg {
fill: $gl-text-color; 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 // link to the build
......
...@@ -44,7 +44,7 @@ class Admin::ImpersonationTokensController < Admin::ApplicationController ...@@ -44,7 +44,7 @@ class Admin::ImpersonationTokensController < Admin::ApplicationController
end end
def set_index_vars def set_index_vars
@scopes = Gitlab::Auth::API_SCOPES @scopes = Gitlab::Auth.available_scopes(current_user)
@impersonation_token ||= finder.build @impersonation_token ||= finder.build
@inactive_impersonation_tokens = finder(state: 'inactive').execute @inactive_impersonation_tokens = finder(state: 'inactive').execute
......
...@@ -11,7 +11,7 @@ class ApplicationController < ActionController::Base ...@@ -11,7 +11,7 @@ class ApplicationController < ActionController::Base
include EnforcesTwoFactorAuthentication include EnforcesTwoFactorAuthentication
include WithPerformanceBar 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_from_rss_token!
before_action :authenticate_user! before_action :authenticate_user!
before_action :validate_user_service_ticket! before_action :validate_user_service_ticket!
...@@ -100,13 +100,12 @@ class ApplicationController < ActionController::Base ...@@ -100,13 +100,12 @@ class ApplicationController < ActionController::Base
return try(:authenticated_user) return try(:authenticated_user)
end end
# This filter handles both private tokens and personal access tokens def authenticate_user_from_personal_access_token!
def authenticate_user_from_private_token!
token = params[:private_token].presence || request.headers['PRIVATE-TOKEN'].presence token = params[:private_token].presence || request.headers['PRIVATE-TOKEN'].presence
return unless token.present? 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) sessionless_sign_in(user)
end end
......
...@@ -30,11 +30,11 @@ class JwtController < ApplicationController ...@@ -30,11 +30,11 @@ class JwtController < ApplicationController
render_unauthorized render_unauthorized
end end
end end
rescue Gitlab::Auth::MissingPersonalTokenError rescue Gitlab::Auth::MissingPersonalAccessTokenError
render_missing_personal_token render_missing_personal_access_token
end end
def render_missing_personal_token def render_missing_personal_access_token
render json: { render json: {
errors: [ errors: [
{ code: 'UNAUTHORIZED', { code: 'UNAUTHORIZED',
......
...@@ -39,7 +39,7 @@ class Profiles::PersonalAccessTokensController < Profiles::ApplicationController ...@@ -39,7 +39,7 @@ class Profiles::PersonalAccessTokensController < Profiles::ApplicationController
end end
def set_index_vars def set_index_vars
@scopes = Gitlab::Auth.available_scopes @scopes = Gitlab::Auth.available_scopes(current_user)
@inactive_personal_access_tokens = finder(state: 'inactive').execute @inactive_personal_access_tokens = finder(state: 'inactive').execute
@active_personal_access_tokens = finder(state: 'active').execute.order(:expires_at) @active_personal_access_tokens = finder(state: 'active').execute.order(:expires_at)
......
...@@ -24,16 +24,6 @@ class ProfilesController < Profiles::ApplicationController ...@@ -24,16 +24,6 @@ class ProfilesController < Profiles::ApplicationController
end end
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 def reset_incoming_email_token
Users::UpdateService.new(current_user, user: @user).execute! do |user| Users::UpdateService.new(current_user, user: @user).execute! do |user|
user.reset_incoming_email_token! user.reset_incoming_email_token!
...@@ -41,7 +31,7 @@ class ProfilesController < Profiles::ApplicationController ...@@ -41,7 +31,7 @@ class ProfilesController < Profiles::ApplicationController
flash[:notice] = "Incoming email token was successfully reset" flash[:notice] = "Incoming email token was successfully reset"
redirect_to profile_account_path redirect_to profile_personal_access_tokens_path
end end
def reset_rss_token def reset_rss_token
...@@ -51,7 +41,7 @@ class ProfilesController < Profiles::ApplicationController ...@@ -51,7 +41,7 @@ class ProfilesController < Profiles::ApplicationController
flash[:notice] = "RSS token was successfully reset" flash[:notice] = "RSS token was successfully reset"
redirect_to profile_account_path redirect_to profile_personal_access_tokens_path
end end
def audit_log def audit_log
......
...@@ -53,8 +53,8 @@ class Projects::GitHttpClientController < Projects::ApplicationController ...@@ -53,8 +53,8 @@ class Projects::GitHttpClientController < Projects::ApplicationController
send_challenges send_challenges
render plain: "HTTP Basic: Access denied\n", status: 401 render plain: "HTTP Basic: Access denied\n", status: 401
rescue Gitlab::Auth::MissingPersonalTokenError rescue Gitlab::Auth::MissingPersonalAccessTokenError
render_missing_personal_token render_missing_personal_access_token
end end
def basic_auth_provided? def basic_auth_provided?
...@@ -78,7 +78,7 @@ class Projects::GitHttpClientController < Projects::ApplicationController ...@@ -78,7 +78,7 @@ class Projects::GitHttpClientController < Projects::ApplicationController
@project, @wiki, @redirected_path = Gitlab::RepoPath.parse("#{params[:namespace_id]}/#{params[:project_id]}") @project, @wiki, @redirected_path = Gitlab::RepoPath.parse("#{params[:namespace_id]}/#{params[:project_id]}")
end end
def render_missing_personal_token def render_missing_personal_access_token
render plain: "HTTP Basic: Access denied\n" \ render plain: "HTTP Basic: Access denied\n" \
"You must use a personal access token with 'api' scope for Git over HTTP.\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}", "You can generate one at #{profile_personal_access_tokens_url}",
......
...@@ -63,34 +63,34 @@ module CiStatusHelper ...@@ -63,34 +63,34 @@ module CiStatusHelper
def ci_icon_for_status(status) def ci_icon_for_status(status)
if detailed_status?(status) if detailed_status?(status)
return custom_icon(status.icon) return sprite_icon(status.icon)
end end
icon_name = icon_name =
case status case status
when 'success' when 'success'
'icon_status_success' 'status_success'
when 'success_with_warnings' when 'success_with_warnings'
'icon_status_warning' 'status_warning'
when 'failed' when 'failed'
'icon_status_failed' 'status_failed'
when 'pending' when 'pending'
'icon_status_pending' 'status_pending'
when 'running' when 'running'
'icon_status_running' 'status_running'
when 'play' when 'play'
'icon_play' 'play'
when 'created' when 'created'
'icon_status_created' 'status_created'
when 'skipped' when 'skipped'
'icon_status_skipped' 'status_skipped'
when 'manual' when 'manual'
'icon_status_manual' 'status_manual'
else else
'icon_status_canceled' 'status_canceled'
end end
custom_icon(icon_name) sprite_icon(icon_name, size: 16)
end end
def pipeline_status_cache_key(pipeline_status) def pipeline_status_cache_key(pipeline_status)
......
# Placeholder class for model that is implemented in EE # 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 # It will reserve (ee#3853) '&' as a reference prefix, but the table does not exists in CE
class Epic < ActiveRecord::Base class Epic < ActiveRecord::Base
<<<<<<< HEAD
prepend EE::Epic prepend EE::Epic
=======
>>>>>>> upstream/master
# TODO: this will be implemented as part of #3853 # TODO: this will be implemented as part of #3853
def to_reference def to_reference
end end
......
...@@ -15,11 +15,14 @@ class Issue < ActiveRecord::Base ...@@ -15,11 +15,14 @@ class Issue < ActiveRecord::Base
include RelativePositioning include RelativePositioning
include CreatedAtFilterable include CreatedAtFilterable
include TimeTrackable include TimeTrackable
<<<<<<< HEAD
WEIGHT_RANGE = 1..9 WEIGHT_RANGE = 1..9
WEIGHT_ALL = 'Everything'.freeze WEIGHT_ALL = 'Everything'.freeze
WEIGHT_ANY = 'Any Weight'.freeze WEIGHT_ANY = 'Any Weight'.freeze
WEIGHT_NONE = 'No Weight'.freeze WEIGHT_NONE = 'No Weight'.freeze
=======
>>>>>>> upstream/master
DueDateStruct = Struct.new(:title, :name).freeze DueDateStruct = Struct.new(:title, :name).freeze
NoDueDate = DueDateStruct.new('No Due Date', '0').freeze NoDueDate = DueDateStruct.new('No Due Date', '0').freeze
......
...@@ -48,6 +48,10 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -48,6 +48,10 @@ class MergeRequestDiff < ActiveRecord::Base
# Collect information about commits and diff from repository # Collect information about commits and diff from repository
# and save it to the database as serialized data # and save it to the database as serialized data
def save_git_content 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 ensure_commit_shas
save_commits save_commits
save_diffs save_diffs
......
...@@ -2,5 +2,13 @@ class OauthAccessToken < Doorkeeper::AccessToken ...@@ -2,5 +2,13 @@ class OauthAccessToken < Doorkeeper::AccessToken
belongs_to :resource_owner, class_name: 'User' belongs_to :resource_owner, class_name: 'User'
belongs_to :application, class_name: 'Doorkeeper::Application' 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 end
...@@ -24,8 +24,8 @@ class User < ActiveRecord::Base ...@@ -24,8 +24,8 @@ class User < ActiveRecord::Base
ignore_column :external_email ignore_column :external_email
ignore_column :email_provider 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 :incoming_email_token
add_authentication_token_field :rss_token add_authentication_token_field :rss_token
...@@ -166,7 +166,7 @@ class User < ActiveRecord::Base ...@@ -166,7 +166,7 @@ class User < ActiveRecord::Base
before_validation :sanitize_attrs before_validation :sanitize_attrs
before_validation :set_notification_email, if: :email_changed? before_validation :set_notification_email, if: :email_changed?
before_validation :set_public_email, if: :public_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 :ensure_user_rights_and_limits, if: :external_changed?
before_save :skip_reconfirmation!, if: ->(user) { user.email_changed? && user.read_only_attribute?(:email) } 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? } before_save :check_for_verified_email, if: ->(user) { user.email_changed? && !user.new_record? }
...@@ -188,8 +188,6 @@ class User < ActiveRecord::Base ...@@ -188,8 +188,6 @@ class User < ActiveRecord::Base
# Note: When adding an option, it MUST go on the end of the array. # Note: When adding an option, it MUST go on the end of the array.
enum project_view: [:readme, :activity, :files] enum project_view: [:readme, :activity, :files]
alias_attribute :private_token, :authentication_token
delegate :path, to: :namespace, allow_nil: true, prefix: true delegate :path, to: :namespace, allow_nil: true, prefix: true
accepts_nested_attributes_for :namespace accepts_nested_attributes_for :namespace
......
...@@ -39,11 +39,8 @@ class AccessTokenValidationService ...@@ -39,11 +39,8 @@ class AccessTokenValidationService
token_scopes = token.scopes.map(&:to_sym) token_scopes = token.scopes.map(&:to_sym)
required_scopes.any? do |scope| 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) scope.sufficient?(token_scopes, request)
else
API::Scope.new(scope).sufficient?(token_scopes, request)
end
end end
end end
end end
......
class IssuableBaseService < BaseService class IssuableBaseService < BaseService
<<<<<<< HEAD
prepend ::EE::IssuableBaseService prepend ::EE::IssuableBaseService
=======
>>>>>>> upstream/master
private private
def filter_params(issuable) def filter_params(issuable)
......
...@@ -49,7 +49,10 @@ module MergeRequests ...@@ -49,7 +49,10 @@ module MergeRequests
create_branch_change_note(merge_request, 'target', create_branch_change_note(merge_request, 'target',
merge_request.previous_changes['target_branch'].first, merge_request.previous_changes['target_branch'].first,
merge_request.target_branch) merge_request.target_branch)
<<<<<<< HEAD
reset_approvals(merge_request) reset_approvals(merge_request)
=======
>>>>>>> upstream/master
end end
if merge_request.previous_changes.include?('assignee_id') if merge_request.previous_changes.include?('assignee_id')
......
...@@ -6,8 +6,7 @@ class MetricsService ...@@ -6,8 +6,7 @@ class MetricsService
Gitlab::HealthChecks::Redis::RedisCheck, Gitlab::HealthChecks::Redis::RedisCheck,
Gitlab::HealthChecks::Redis::CacheCheck, Gitlab::HealthChecks::Redis::CacheCheck,
Gitlab::HealthChecks::Redis::QueuesCheck, Gitlab::HealthChecks::Redis::QueuesCheck,
Gitlab::HealthChecks::Redis::SharedStateCheck, Gitlab::HealthChecks::Redis::SharedStateCheck
Gitlab::HealthChecks::FsShardsCheck
].freeze ].freeze
def prometheus_metrics_text def prometheus_metrics_text
......
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
%td %td
= truncate(hook_log.url, length: 50) = truncate(hook_log.url, length: 50)
%td.light %td.light
#{number_with_precision(hook_log.execution_duration, precision: 2)} ms #{number_with_precision(hook_log.execution_duration, precision: 2)} sec
%td.light %td.light
= time_ago_with_tooltip(hook_log.created_at) = time_ago_with_tooltip(hook_log.created_at)
%td %td
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
= hidden_field_tag :namespace_id, params[:namespace_id] = hidden_field_tag :namespace_id, params[:namespace_id]
- namespace = Namespace.find(params[:namespace_id]) - namespace = Namespace.find(params[:namespace_id])
- toggle_text = "#{namespace.kind}: #{namespace.full_path}" - 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-menu.dropdown-select.dropdown-menu-align-right
= dropdown_title('Namespaces') = dropdown_title('Namespaces')
= dropdown_filter("Search for Namespace") = dropdown_filter("Search for Namespace")
......
...@@ -117,7 +117,7 @@ ...@@ -117,7 +117,7 @@
= f.label :new_namespace_id, "Namespace", class: 'control-label' = f.label :new_namespace_id, "Namespace", class: 'control-label'
.col-sm-10 .col-sm-10
.dropdown .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-menu.dropdown-select
= dropdown_title('Namespaces') = dropdown_title('Namespaces')
= dropdown_filter("Search for Namespace") = dropdown_filter("Search for Namespace")
......
...@@ -5,9 +5,9 @@ ...@@ -5,9 +5,9 @@
- if link && status.has_details? - if link && status.has_details?
= link_to status.details_path, class: css_classes, title: title do = link_to status.details_path, class: css_classes, title: title do
= custom_icon(status.icon) = sprite_icon(status.icon)
= status.text = status.text
- else - else
%span{ class: css_classes, title: title } %span{ class: css_classes, title: title }
= custom_icon(status.icon) = sprite_icon(status.icon)
= status.text = status.text
...@@ -7,13 +7,13 @@ ...@@ -7,13 +7,13 @@
- if status.has_details? - if status.has_details?
= link_to status.details_path, class: 'mini-pipeline-graph-dropdown-item', data: { toggle: 'tooltip', title: tooltip, container: 'body' } do = 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 %span.ci-build-text= subject.name
- else - else
.menu-item.mini-pipeline-graph-dropdown-item{ data: { toggle: 'tooltip', title: tooltip, container: 'body' } } .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 %span.ci-build-text= subject.name
- if status.has_action? - 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 = 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) = sprite_icon(status.action_icon, css_class: "icon-action-#{status.action_icon}")
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
%li.todos-pending{ class: active_when(params[:state].blank? || params[:state] == 'pending') }> %li.todos-pending{ class: active_when(params[:state].blank? || params[:state] == 'pending') }>
= link_to todos_filter_path(state: 'pending') do = link_to todos_filter_path(state: 'pending') do
%span %span
To do Todos
%span.badge %span.badge
= number_with_delimiter(todos_pending_count) = number_with_delimiter(todos_pending_count)
%li.todos-done{ class: active_when(params[:state] == 'done') }> %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 @@ ...@@ -6,22 +6,6 @@
.alert.alert-info .alert.alert-info
Some options are unavailable for LDAP accounts 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 .row.prepend-top-default
.col-lg-4.profile-settings-sidebar .col-lg-4.profile-settings-sidebar
%h4.prepend-top-0 %h4.prepend-top-0
......
...@@ -30,3 +30,40 @@ ...@@ -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_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 = 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 @@ ...@@ -24,7 +24,7 @@
%td %td
= truncate(hook_log.url, length: 50) = truncate(hook_log.url, length: 50)
%td.light %td.light
#{number_with_precision(hook_log.execution_duration, precision: 2)} ms #{number_with_precision(hook_log.execution_duration, precision: 2)} sec
%td.light %td.light
= time_ago_with_tooltip(hook_log.created_at) = time_ago_with_tooltip(hook_log.created_at)
%td %td
......
...@@ -91,7 +91,7 @@ ...@@ -91,7 +91,7 @@
- builds.select{|build| build.status == build_status}.each do |build| - builds.select{|build| build.status == build_status}.each do |build|
.build-job{ class: sidebar_build_class(build, @build), data: { stage: build.stage } } .build-job{ class: sidebar_build_class(build, @build), data: { stage: build.stage } }
= link_to project_job_path(@project, build) do = 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}" } %span{ class: "ci-status-icon-#{build.status}" }
= ci_icon_for_status(build.status) = ci_icon_for_status(build.status)
%span %span
...@@ -100,4 +100,5 @@ ...@@ -100,4 +100,5 @@
- else - else
= build.id = build.id
- if build.retried? - 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 @@ ...@@ -7,7 +7,7 @@
.stage-container.dropdown{ class: klass } .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) } } %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') = icon('caret-down')
%ul.dropdown-menu.mini-pipeline-graph-dropdown-menu.js-builds-dropdown-container %ul.dropdown-menu.mini-pipeline-graph-dropdown-menu.js-builds-dropdown-container
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
= hook_log.trigger.singularize.titleize = hook_log.trigger.singularize.titleize
%p %p
%strong Elapsed time: %strong Elapsed time:
#{number_with_precision(hook_log.execution_duration, precision: 2)} ms #{number_with_precision(hook_log.execution_duration, precision: 2)} sec
%p %p
%strong Request time: %strong Request time:
= time_ago_with_tooltip(hook_log.created_at) = 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: ...@@ -58,9 +58,10 @@ en:
expired: "The access token expired" expired: "The access token expired"
unknown: "The access token is invalid" unknown: "The access token is invalid"
scopes: scopes:
api: Access your API api: Access the authenticated user's API
read_user: Read user information read_user: Read the authenticated user's personal information
openid: Authenticate using OpenID Connect openid: Authenticate using OpenID Connect
sudo: Perform API actions as any user in the system (if the authenticated user is an admin)
flash: flash:
applications: applications:
......
...@@ -6,7 +6,6 @@ resource :profile, only: [:show, :update] do ...@@ -6,7 +6,6 @@ resource :profile, only: [:show, :update] do
get :audit_log get :audit_log
get :applications, to: 'oauth/applications#index' get :applications, to: 'oauth/applications#index'
put :reset_private_token
put :reset_incoming_email_token put :reset_incoming_email_token
put :reset_rss_token put :reset_rss_token
put :update_username 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 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # 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 # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -1239,6 +1239,7 @@ ActiveRecord::Schema.define(version: 20171017145932) do ...@@ -1239,6 +1239,7 @@ ActiveRecord::Schema.define(version: 20171017145932) do
t.boolean "ref_fetched" t.boolean "ref_fetched"
t.string "merge_jid" t.string "merge_jid"
t.boolean "discussion_locked" t.boolean "discussion_locked"
t.integer "latest_merge_request_diff_id"
end end
add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree 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 ...@@ -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", ["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", ["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", ["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", ["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_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 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 ...@@ -2066,7 +2068,6 @@ ActiveRecord::Schema.define(version: 20171017145932) do
t.string "skype", default: "", null: false t.string "skype", default: "", null: false
t.string "linkedin", default: "", null: false t.string "linkedin", default: "", null: false
t.string "twitter", default: "", null: false t.string "twitter", default: "", null: false
t.string "authentication_token"
t.string "bio" t.string "bio"
t.integer "failed_attempts", default: 0 t.integer "failed_attempts", default: 0
t.datetime "locked_at" t.datetime "locked_at"
...@@ -2124,7 +2125,6 @@ ActiveRecord::Schema.define(version: 20171017145932) do ...@@ -2124,7 +2125,6 @@ ActiveRecord::Schema.define(version: 20171017145932) do
end end
add_index "users", ["admin"], name: "index_users_on_admin", using: :btree 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", ["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", ["created_at"], name: "index_users_on_created_at", using: :btree
add_index "users", ["email"], name: "index_users_on_email", unique: true, 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 ...@@ -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", "ci_pipelines", column: "pipeline_id", on_delete: :cascade
add_foreign_key "merge_request_metrics", "merge_requests", 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", "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", "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", "issues", on_delete: :cascade
add_foreign_key "merge_requests_closing_issues", "merge_requests", 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. ...@@ -28,7 +28,7 @@ The MemoryKiller is controlled using environment variables.
delayed shutdown is triggered. The default value for Omnibus packages is set delayed shutdown is triggered. The default value for Omnibus packages is set
[in the omnibus-gitlab [in the omnibus-gitlab
repository](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/files/gitlab-cookbooks/gitlab/attributes/default.rb). 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 a shutdown is triggered, the Sidekiq process will keep working normally for
another 15 minutes. another 15 minutes.
- `SIDEKIQ_MEMORY_KILLER_SHUTDOWN_WAIT`: defaults to 30 seconds. When the grace - `SIDEKIQ_MEMORY_KILLER_SHUTDOWN_WAIT`: defaults to 30 seconds. When the grace
...@@ -36,5 +36,3 @@ The MemoryKiller is controlled using environment variables. ...@@ -36,5 +36,3 @@ The MemoryKiller is controlled using environment variables.
Existing jobs get 30 seconds to finish. After that, the MemoryKiller tells 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 Sidekiq to shut down, and an external supervision mechanism (e.g. Runit) must
restart Sidekiq. 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: ...@@ -141,7 +141,7 @@ separate Rails process to debug the issue:
1. Log in to your GitLab account. 1. Log in to your GitLab account.
1. Copy the URL that is causing problems (e.g. https://gitlab.com/ABC). 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: 1. Bring up the GitLab Rails console. For omnibus users, run:
``` ```
......
...@@ -51,7 +51,6 @@ following locations: ...@@ -51,7 +51,6 @@ following locations:
- [Repository Files](repository_files.md) - [Repository Files](repository_files.md)
- [Runners](runners.md) - [Runners](runners.md)
- [Services](services.md) - [Services](services.md)
- [Session](session.md)
- [Settings](settings.md) - [Settings](settings.md)
- [Sidekiq metrics](sidekiq_metrics.md) - [Sidekiq metrics](sidekiq_metrics.md)
- [System Hooks](system_hooks.md) - [System Hooks](system_hooks.md)
...@@ -87,27 +86,10 @@ API requests should be prefixed with `api` and the API version. The API version ...@@ -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 defined in [`lib/api.rb`][lib-api-url]. For example, the root of the v4 API
is at `/api/v4`. is at `/api/v4`.
For endpoints that require [authentication](#authentication), you need to pass Example of a valid API request using cURL:
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:
```shell ```shell
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects" curl "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"
``` ```
The API uses JSON to serialize data. You don't need to specify `.json` at the 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. ...@@ -115,15 +97,20 @@ end of an API URL.
## Authentication ## 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 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). 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. [OAuth2 tokens](#oauth2-tokens)
1. [Private tokens](#private-tokens)
1. [Personal access tokens](#personal-access-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 If authentication information is invalid or omitted, an error message will be
returned with status code `401`: returned with status code `401`:
...@@ -134,74 +121,84 @@ 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 You can use an [OAuth2 token](oauth2.md) to authenticate with the API by passing it in either the
set. The API will use this cookie for authentication if it is present, but using `access_token` parameter or the `Authorization` header.
the API to generate a new session cookie is currently not supported.
### 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 ```shell
`access_token` parameter or in the `Authorization` header. 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 ```shell
curl --header "Authorization: Bearer OAUTH-TOKEN" https://gitlab.example.com/api/v4/projects 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 You can use a [personal access token][pat] to authenticate with the API by passing it in either the
them can interact with GitLab as if they were you. You can find or reset your `private_token` parameter or the `Private-Token` header.
private token in your account page (`/profile/account`).
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, ```shell
personal access tokens could be a better fit because of their granular curl --header "Private-Token: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects
permissions. ```
Once you have your token, pass it to the API using either the `private_token` Read more about [personal access tokens][pat].
parameter or the `PRIVATE-TOKEN` header. For examples of usage,
[read the basic usage section](#basic-usage). ### 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 ### Impersonation tokens
> [Introduced][ce-9099] in GitLab 9.0. Needs admin permissions. > [Introduced][ce-9099] in GitLab 9.0. Needs admin permissions.
Impersonation tokens are a type of [personal access token][pat] 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 They are an alternative to directly using the user's password or one of their
or using the [Sudo](#sudo) feature which also requires the admin's password personal access tokens, and to using the [Sudo](#sudo) feature, since the user's (or admin's, in the case of Sudo)
or private token, since the password/token can change over time. Impersonation password/token may not be known or may change over time.
tokens are a great fit if you want to build applications or tools which
authenticate with the API as a specific user.
For more information, refer to the For more information, refer to the
[users API](users.md#retrieve-user-impersonation-tokens) docs. [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 ### Sudo
> Needs admin permissions. > Needs admin permissions.
All API requests support performing an API call as if you were another user, 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 provided you are authenticated as an administrator with an OAuth or Personal Access Token that has the `sudo` scope.
the `sudo` parameter either via query string or a header with an ID/username of
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 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`: be returned with status code `403`:
```json ```json
...@@ -210,12 +207,23 @@ be returned with status code `403`: ...@@ -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 If the sudo user ID or username cannot be found, an error message will be
returned with status code `404`: returned with status code `404`:
```json ```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 ...@@ -229,7 +237,7 @@ GET /projects?private_token=9koXpg98eAheJpvBs5tK&sudo=username
``` ```
```shell ```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, 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 ...@@ -240,7 +248,7 @@ GET /projects?private_token=9koXpg98eAheJpvBs5tK&sudo=23
``` ```
```shell ```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 ## 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 ...@@ -415,8 +415,7 @@ GET /user
"can_create_group": true, "can_create_group": true,
"can_create_project": true, "can_create_project": true,
"two_factor_enabled": true, "two_factor_enabled": true,
"external": false, "external": false
"private_token": "dd34asd13as"
} }
``` ```
......
...@@ -31,12 +31,12 @@ There are three methods to enable the use of `docker build` and `docker run` dur ...@@ -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. The simplest approach is to install GitLab Runner in `shell` execution mode.
GitLab Runner then executes job scripts as the `gitlab-runner` user. 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: 1. During GitLab Runner installation select `shell` as method of executing job scripts or use command:
```bash ```bash
sudo gitlab-ci-multi-runner register -n \ sudo gitlab-runner register -n \
--url https://gitlab.com/ \ --url https://gitlab.com/ \
--registration-token REGISTRATION_TOKEN \ --registration-token REGISTRATION_TOKEN \
--executor shell \ --executor shell \
...@@ -93,7 +93,7 @@ In order to do that, follow the steps: ...@@ -93,7 +93,7 @@ In order to do that, follow the steps:
mode: mode:
```bash ```bash
sudo gitlab-ci-multi-runner register -n \ sudo gitlab-runner register -n \
--url https://gitlab.com/ \ --url https://gitlab.com/ \
--registration-token REGISTRATION_TOKEN \ --registration-token REGISTRATION_TOKEN \
--executor docker \ --executor docker \
...@@ -178,7 +178,7 @@ In order to do that, follow the steps: ...@@ -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`: 1. Register GitLab Runner from the command line to use `docker` and share `/var/run/docker.sock`:
```bash ```bash
sudo gitlab-ci-multi-runner register -n \ sudo gitlab-runner register -n \
--url https://gitlab.com/ \ --url https://gitlab.com/ \
--registration-token REGISTRATION_TOKEN \ --registration-token REGISTRATION_TOKEN \
--executor docker \ --executor docker \
......
...@@ -501,8 +501,8 @@ First start with creating a file named `build_script`: ...@@ -501,8 +501,8 @@ First start with creating a file named `build_script`:
```bash ```bash
cat <<EOF > build_script cat <<EOF > build_script
git clone https://gitlab.com/gitlab-org/gitlab-ci-multi-runner.git /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-ci-multi-runner cd /builds/gitlab-org/gitlab-runner
make make
EOF EOF
``` ```
......
...@@ -267,10 +267,10 @@ terminal execute: ...@@ -267,10 +267,10 @@ terminal execute:
```bash ```bash
# Check using docker executor # Check using docker executor
gitlab-ci-multi-runner exec docker test:app gitlab-runner exec docker test:app
# Check using shell executor # Check using shell executor
gitlab-ci-multi-runner exec shell test:app gitlab-runner exec shell test:app
``` ```
## Example project ## Example project
......
...@@ -64,7 +64,7 @@ To build this project you also need to have [GitLab Runner](https://docs.gitlab. ...@@ -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: 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 \ --non-interactive \
--url "https://gitlab.com/" \ --url "https://gitlab.com/" \
--registration-token "PROJECT_REGISTRATION_TOKEN" \ --registration-token "PROJECT_REGISTRATION_TOKEN" \
......
...@@ -61,7 +61,7 @@ correctly with your CI jobs: ...@@ -61,7 +61,7 @@ correctly with your CI jobs:
1. First, make sure you have used [relative URLs](#configuring-the-gitmodules-file) 1. First, make sure you have used [relative URLs](#configuring-the-gitmodules-file)
for the submodules located in the same GitLab server. 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 `GIT_SUBMODULE_STRATEGY` variable to either `normal` or `recursive` to tell
the runner to fetch your submodules before the job: the runner to fetch your submodules before the job:
```yaml ```yaml
...@@ -71,7 +71,7 @@ correctly with your CI jobs: ...@@ -71,7 +71,7 @@ correctly with your CI jobs:
See the [`.gitlab-ci.yml` reference](yaml/README.md#git-submodule-strategy) See the [`.gitlab-ci.yml` reference](yaml/README.md#git-submodule-strategy)
for more details about `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`: `git submodule sync/update` in `before_script`:
```yaml ```yaml
......
...@@ -459,11 +459,11 @@ Rendered example: ...@@ -459,11 +459,11 @@ Rendered example:
### cURL commands ### cURL commands
- Use `https://gitlab.example.com/api/v4/` as an endpoint. - 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 - Always put the request first. `GET` is the default so you don't have to
include it. include it.
- Use double quotes to the URL when it includes additional parameters. - 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. username and password.
| Methods | Description | | Methods | Description |
......
...@@ -60,6 +60,35 @@ writing one](testing_levels.md#consider-not-writing-a-system-test)! ...@@ -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 - It's ok to look for DOM elements but don't abuse it since it makes the tests
more brittle 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 ### `let` variables
GitLab's RSpec suite has made extensive use of `let` variables to reduce GitLab's RSpec suite has made extensive use of `let` variables to reduce
......
...@@ -126,7 +126,7 @@ always in-sync with the codebase. ...@@ -126,7 +126,7 @@ always in-sync with the codebase.
[GitLab Workhorse]: https://gitlab.com/gitlab-org/gitlab-workhorse [GitLab Workhorse]: https://gitlab.com/gitlab-org/gitlab-workhorse
[Gitaly]: https://gitlab.com/gitlab-org/gitaly [Gitaly]: https://gitlab.com/gitlab-org/gitaly
[GitLab Pages]: https://gitlab.com/gitlab-org/gitlab-pages [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 Omnibus]: https://gitlab.com/gitlab-org/omnibus-gitlab
[GitLab QA]: https://gitlab.com/gitlab-org/gitlab-qa [GitLab QA]: https://gitlab.com/gitlab-org/gitlab-qa
[part of GitLab Rails]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/qa [part of GitLab Rails]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/qa
......
...@@ -186,7 +186,7 @@ Runner. ...@@ -186,7 +186,7 @@ Runner.
We recommend using a separate machine for each GitLab Runner, if you plan to We recommend using a separate machine for each GitLab Runner, if you plan to
use the CI features. 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 ## Supported web browsers
......
...@@ -149,18 +149,3 @@ cp config/secrets.yml.bak config/secrets.yml ...@@ -149,18 +149,3 @@ cp config/secrets.yml.bak config/secrets.yml
sudo /etc/init.d/gitlab start 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 ...@@ -517,7 +517,7 @@ Feature.get(:auto_devops_banner_disabled).enable
Or through the HTTP API with an admin access token: Or through the HTTP API with an admin access token:
```sh ```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 [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 ...@@ -55,10 +55,10 @@ The curriculum is composed of GitLab videos, screencasts, presentations, project
#### 1.5. Migrating from other Source Control #### 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 BitBucket/Stash](https://docs.gitlab.com/ee/user/project/import/bitbucket.html)
1. [Migrating from GitHub](https://docs.gitlab.com/ee/workflow/importing/import_projects_from_github.html) 1. [Migrating from GitHub](https://docs.gitlab.com/ee/user/project/import/github.html)
1. [Migrating from SVN](https://docs.gitlab.com/ee/workflow/importing/migrating_from_svn.html) 1. [Migrating from SVN](https://docs.gitlab.com/ee/user/project/import/svn.html)
1. [Migrating from Fogbugz](https://docs.gitlab.com/ee/workflow/importing/import_projects_from_fogbugz.html) 1. [Migrating from Fogbugz](https://docs.gitlab.com/ee/user/project/import/fogbugz.html)
#### 1.6. GitLab Inc. #### 1.6. GitLab Inc.
...@@ -80,13 +80,13 @@ The curriculum is composed of GitLab videos, screencasts, presentations, project ...@@ -80,13 +80,13 @@ The curriculum is composed of GitLab videos, screencasts, presentations, project
- Being part of our Great Community and Contributing to GitLab - 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. [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. [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.8 GitLab Training Material
1. [Git and GitLab Terminology](glossary/README.md) 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 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 ...@@ -460,7 +460,7 @@ A route table contains rules (called routes) that determine where network traffi
### Runners ### 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 ### Sidekiq
......
...@@ -53,8 +53,8 @@ git log --since=1.month.ago --until=3.weeks.ago ...@@ -53,8 +53,8 @@ git log --since=1.month.ago --until=3.weeks.ago
``` ```
cd ~/workspace cd ~/workspace
git clone git@gitlab.com:gitlab-org/gitlab-ci-multi-runner.git git clone git@gitlab.com:gitlab-org/gitlab-runner.git
cd gitlab-ci-multi-runner cd gitlab-runner
git log --author="Travis" git log --author="Travis"
git log --since=1.month.ago --until=3.weeks.ago git log --since=1.month.ago --until=3.weeks.ago
git log --since=1.month.ago --until=1.day.ago --author="Travis" 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 ...@@ -52,7 +52,7 @@ You can edit your account settings by navigating from the up-right corner menu b
From there, you can: From there, you can:
- Update your personal information - 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) - Change your username and [delete your account](account/delete_account.md)
- Manage applications that can - Manage applications that can
[use GitLab as an OAuth provider](../../integration/oauth_provider.md#introduction-to-oauth) [use GitLab as an OAuth provider](../../integration/oauth_provider.md#introduction-to-oauth)
......
...@@ -2,17 +2,15 @@ ...@@ -2,17 +2,15 @@
> [Introduced][ce-3749] in GitLab 8.8. > [Introduced][ce-3749] in GitLab 8.8.
Personal access tokens are useful if you need access to the [GitLab API][api]. Personal access tokens are the preferred way for third party applications and scripts to
Instead of using your private token which grants full access to your account, authenticate with the [GitLab API][api], if using [OAuth2](../../api/oauth2.md) is not practical.
personal access tokens could be a better fit because of their
[granular permissions](#limiting-scopes-of-a-personal-access-token).
You can also use them to authenticate against Git over HTTP. They are the only You can also use them to authenticate against Git over HTTP. They are the only
accepted method of authentication when you have accepted method of authentication when you have
[Two-Factor Authentication (2FA)][2fa] enabled. [Two-Factor Authentication (2FA)][2fa] enabled.
Once you have your token, [pass it to the API][usage] using either the 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, The expiration of personal access tokens happens on the date you define,
at midnight UTC. at midnight UTC.
...@@ -49,12 +47,14 @@ the following table. ...@@ -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). | |`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. | | `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). | | `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 [2fa]: ../account/two_factor_authentication.md
[api]: ../../api/README.md [api]: ../../api/README.md
[ce-3749]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3749 [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-5951]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5951
[ce-11845]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11845 [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 [container registry]: ../project/container_registry.md
[users]: ../../api/users.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. ...@@ -52,7 +52,8 @@ directly in the job artifacts browser without the need to download them.
>**Note:** >**Note:**
With [GitLab 10.1][ce-14399], HTML files in a public project can be previewed 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 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 buttons. You can download the artifacts archive or browse its contents, whereas
...@@ -69,7 +70,8 @@ browse inside them. ...@@ -69,7 +70,8 @@ browse inside them.
Below you can see how browsing looks like. In this case we have browsed inside 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 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) ![Job artifacts browser](img/job_artifacts_browser.png)
......
...@@ -157,7 +157,6 @@ module API ...@@ -157,7 +157,6 @@ module API
mount ::API::Runner mount ::API::Runner
mount ::API::Runners mount ::API::Runners
mount ::API::Services mount ::API::Services
mount ::API::Session
mount ::API::Settings mount ::API::Settings
mount ::API::SidekiqMetrics mount ::API::SidekiqMetrics
mount ::API::Snippets mount ::API::Snippets
......
...@@ -44,73 +44,51 @@ module API ...@@ -44,73 +44,51 @@ module API
# Helper Methods for Grape Endpoint # Helper Methods for Grape Endpoint
module HelperMethods module HelperMethods
<<<<<<< HEAD
def find_current_user def find_current_user
user = user =
find_user_from_private_token || find_user_from_private_token ||
find_user_from_oauth_token || find_user_from_oauth_token ||
find_user_from_warden || find_user_from_warden ||
find_user_by_job_token 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 forbidden!('User is blocked') unless Gitlab::UserAccess.new(user).allowed? && user.can?(:access_api)
raise UnauthorizedError unless Gitlab::UserAccess.new(user).allowed? && user.can?(:access_api)
user user
end end
def private_token def access_token
params[PRIVATE_TOKEN_PARAM] || env[PRIVATE_TOKEN_HEADER] return @access_token if defined?(@access_token)
end
private
def find_user_from_private_token
token_string = private_token.to_s
return nil unless token_string.present?
user = @access_token = find_oauth_access_token || find_personal_access_token
find_user_by_authentication_token(token_string) ||
find_user_by_personal_access_token(token_string)
raise UnauthorizedError unless user
user
end end
# Invokes the doorkeeper guard. def validate_access_token!(scopes: [])
#
# 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
return unless access_token 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
end end
def find_user_by_authentication_token(token_string) private
User.find_by_authentication_token(token_string)
end
def find_user_by_personal_access_token(token_string) def find_user_from_access_token
access_token = PersonalAccessToken.find_by_token(token_string)
return unless access_token return unless access_token
find_user_by_access_token(access_token) validate_access_token!
access_token.user || raise(UnauthorizedError)
end end
# Check the Rails session for valid authentication details # Check the Rails session for valid authentication details
...@@ -144,34 +122,26 @@ module API ...@@ -144,34 +122,26 @@ module API
end end
def find_oauth_access_token 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) 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) # Expiration, revocation and scopes are verified in `find_user_by_access_token`
raise UnauthorizedError unless @oauth_access_token access_token = OauthAccessToken.by_token(token)
raise UnauthorizedError unless access_token
@oauth_access_token.revoke_previous_refresh_token! access_token.revoke_previous_refresh_token!
@oauth_access_token access_token
end end
def find_user_by_access_token(access_token) def find_personal_access_token
scopes = scopes_registered_for_endpoint 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) # Expiration, revocation and scopes are verified in `find_user_by_access_token`
when AccessTokenValidationService::INSUFFICIENT_SCOPE access_token = PersonalAccessToken.find_by(token: token)
raise InsufficientScopeError.new(scopes) raise UnauthorizedError unless access_token
when AccessTokenValidationService::EXPIRED
raise ExpiredError
when AccessTokenValidationService::REVOKED access_token
raise RevokedError
when AccessTokenValidationService::VALID
access_token.user
end
end end
def doorkeeper_request def doorkeeper_request
...@@ -255,7 +225,7 @@ module API ...@@ -255,7 +225,7 @@ module API
class InsufficientScopeError < StandardError class InsufficientScopeError < StandardError
attr_reader :scopes attr_reader :scopes
def initialize(scopes) def initialize(scopes)
@scopes = scopes @scopes = scopes.map { |s| s.try(:name) || s }
end end
end end
end end
......
...@@ -60,10 +60,6 @@ module API ...@@ -60,10 +60,6 @@ module API
expose :admin?, as: :is_admin expose :admin?, as: :is_admin
end end
class UserWithPrivateDetails < UserWithAdmin
expose :private_token
end
class Email < Grape::Entity class Email < Grape::Entity
expose :id, :email expose :id, :email
end end
......
...@@ -43,6 +43,8 @@ module API ...@@ -43,6 +43,8 @@ module API
sudo! sudo!
validate_access_token!(scopes: scopes_registered_for_endpoint) unless sudo?
@current_user @current_user
end end
...@@ -427,7 +429,7 @@ module API ...@@ -427,7 +429,7 @@ module API
return @initial_current_user if defined?(@initial_current_user) return @initial_current_user if defined?(@initial_current_user)
begin 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 rescue APIGuard::UnauthorizedError
unauthorized! unauthorized!
end end
...@@ -435,24 +437,23 @@ module API ...@@ -435,24 +437,23 @@ module API
def sudo! def sudo!
return unless sudo_identifier return unless sudo_identifier
return unless initial_current_user
unauthorized! unless initial_current_user
unless initial_current_user.admin? unless initial_current_user.admin?
forbidden!('Must be admin to use sudo') forbidden!('Must be admin to use sudo')
end end
# Only private tokens should be used for the SUDO feature unless access_token
unless private_token == initial_current_user.private_token forbidden!('Must be authenticated using an OAuth or Personal Access Token to use sudo')
forbidden!('Private token must be specified in order to use sudo')
end end
validate_access_token!(scopes: [:sudo])
sudoed_user = find_user(sudo_identifier) 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
@current_user = sudoed_user
else
not_found!("No user id or username for: #{sudo_identifier}")
end
end end
def sudo_identifier 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 ...@@ -513,9 +513,7 @@ module API
end end
get do get do
entity = entity =
if sudo? if current_user.admin?
Entities::UserWithPrivateDetails
elsif current_user.admin?
Entities::UserWithAdmin Entities::UserWithAdmin
else else
Entities::UserPublic Entities::UserPublic
......
module Gitlab module Gitlab
module Auth module Auth
MissingPersonalTokenError = Class.new(StandardError) MissingPersonalAccessTokenError = Class.new(StandardError)
REGISTRY_SCOPES = [:read_registry].freeze REGISTRY_SCOPES = [:read_registry].freeze
# Scopes used for GitLab API access # Scopes used for GitLab API access
API_SCOPES = [:api, :read_user].freeze API_SCOPES = [:api, :read_user, :sudo].freeze
# Scopes used for OpenID Connect # Scopes used for OpenID Connect
OPENID_SCOPES = [:openid].freeze OPENID_SCOPES = [:openid].freeze
...@@ -39,7 +39,7 @@ module Gitlab ...@@ -39,7 +39,7 @@ module Gitlab
# If sign-in is disabled and LDAP is not configured, recommend a # If sign-in is disabled and LDAP is not configured, recommend a
# personal access token on failed auth attempts # personal access token on failed auth attempts
raise Gitlab::Auth::MissingPersonalTokenError raise Gitlab::Auth::MissingPersonalAccessTokenError
end end
def find_with_user_password(login, password) def find_with_user_password(login, password)
...@@ -107,7 +107,7 @@ module Gitlab ...@@ -107,7 +107,7 @@ module Gitlab
user = find_with_user_password(login, password) user = find_with_user_password(login, password)
return unless user 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) Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, full_authentication_abilities)
end end
...@@ -129,7 +129,7 @@ module Gitlab ...@@ -129,7 +129,7 @@ module Gitlab
token = PersonalAccessTokensFinder.new(state: 'active').find_by(token: password) token = PersonalAccessTokensFinder.new(state: 'active').find_by(token: password)
if token && valid_scoped_token?(token, available_scopes) 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
end end
...@@ -227,8 +227,10 @@ module Gitlab ...@@ -227,8 +227,10 @@ module Gitlab
[] []
end end
def available_scopes def available_scopes(current_user = nil)
API_SCOPES + registry_scopes scopes = API_SCOPES + registry_scopes
scopes.delete(:sudo) if current_user && !current_user.admin?
scopes
end end
# Other available scopes # Other available scopes
......
...@@ -8,7 +8,7 @@ module Gitlab ...@@ -8,7 +8,7 @@ module Gitlab
end end
def action_icon def action_icon
'icon_action_cancel' 'cancel'
end end
def action_path 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