Commit 2b474dc2 authored by Tiago Botelho's avatar Tiago Botelho

refactors finder and correlated code

parent 9f2e4742
class Admin::ImpersonationTokensController < Admin::ApplicationController class Admin::ImpersonationTokensController < Admin::ApplicationController
before_action :user before_action :user, :finder
def index def index
set_index_vars set_index_vars
end end
def create def create
# We never want to non-impersonate a user @impersonation_token = finder.execute.build(impersonation_token_params)
@impersonation_token = user.personal_access_tokens.build(impersonation_token_params.merge(impersonation: true))
if @impersonation_token.save if @impersonation_token.save
flash[:impersonation_token] = @impersonation_token.token flash[:impersonation_token] = @impersonation_token.token
...@@ -19,7 +18,7 @@ class Admin::ImpersonationTokensController < Admin::ApplicationController ...@@ -19,7 +18,7 @@ class Admin::ImpersonationTokensController < Admin::ApplicationController
end end
def revoke def revoke
@impersonation_token = user.personal_access_tokens.impersonation.find(params[:id]) @impersonation_token = finder.execute(id: params[:id])
if @impersonation_token.revoke! if @impersonation_token.revoke!
flash[:notice] = "Revoked impersonation token #{@impersonation_token.name}!" flash[:notice] = "Revoked impersonation token #{@impersonation_token.name}!"
...@@ -36,14 +35,21 @@ class Admin::ImpersonationTokensController < Admin::ApplicationController ...@@ -36,14 +35,21 @@ class Admin::ImpersonationTokensController < Admin::ApplicationController
@user ||= User.find_by!(username: params[:user_id]) @user ||= User.find_by!(username: params[:user_id])
end end
def finder
@finder ||= PersonalAccessTokensFinder.new(user: user, impersonation: true)
end
def impersonation_token_params def impersonation_token_params
params.require(:personal_access_token).permit(:name, :expires_at, :impersonation, scopes: []) params.require(:personal_access_token).permit(:name, :expires_at, :impersonation, scopes: [])
end end
def set_index_vars def set_index_vars
@impersonation_token ||= user.personal_access_tokens.build finder.params[:state] = 'active'
@impersonation_token ||= finder.execute.build
@scopes = Gitlab::Auth::SCOPES @scopes = Gitlab::Auth::SCOPES
@active_impersonation_tokens = user.personal_access_tokens.impersonation.active.order(:expires_at) finder.params[:order] = :expires_at
@inactive_impersonation_tokens = user.personal_access_tokens.impersonation.inactive @active_impersonation_tokens = finder.execute
finder.params[:state] = 'inactive'
@inactive_impersonation_tokens = finder.execute
end end
end end
class Profiles::PersonalAccessTokensController < Profiles::ApplicationController class Profiles::PersonalAccessTokensController < Profiles::ApplicationController
before_action :finder
def index def index
set_index_vars set_index_vars
end end
def create def create
@personal_access_token = current_user.personal_access_tokens.build(personal_access_token_params) @personal_access_token = finder.execute.build(personal_access_token_params)
if @personal_access_token.save if @personal_access_token.save
flash[:personal_access_token] = @personal_access_token.token flash[:personal_access_token] = @personal_access_token.token
...@@ -16,7 +18,7 @@ class Profiles::PersonalAccessTokensController < Profiles::ApplicationController ...@@ -16,7 +18,7 @@ class Profiles::PersonalAccessTokensController < Profiles::ApplicationController
end end
def revoke def revoke
@personal_access_token = current_user.personal_access_tokens.find(params[:id]) @personal_access_token = finder.execute(id: params[:id])
if @personal_access_token.revoke! if @personal_access_token.revoke!
flash[:notice] = "Revoked personal access token #{@personal_access_token.name}!" flash[:notice] = "Revoked personal access token #{@personal_access_token.name}!"
...@@ -29,14 +31,21 @@ class Profiles::PersonalAccessTokensController < Profiles::ApplicationController ...@@ -29,14 +31,21 @@ class Profiles::PersonalAccessTokensController < Profiles::ApplicationController
private private
def finder
@finder ||= PersonalAccessTokensFinder.new(user: current_user, impersonation: false)
end
def personal_access_token_params def personal_access_token_params
params.require(:personal_access_token).permit(:name, :expires_at, scopes: []) params.require(:personal_access_token).permit(:name, :expires_at, scopes: [])
end end
def set_index_vars def set_index_vars
@personal_access_token ||= current_user.personal_access_tokens.build finder.params[:state] = 'active'
@personal_access_token ||= finder.execute.build
@scopes = Gitlab::Auth::SCOPES @scopes = Gitlab::Auth::SCOPES
@active_personal_access_tokens = current_user.personal_access_tokens.active.order(:expires_at) finder.params[:order] = :expires_at
@inactive_personal_access_tokens = current_user.personal_access_tokens.inactive @active_personal_access_tokens = finder.execute
finder.params[:state] = 'inactive'
@inactive_personal_access_tokens = finder.execute
end end
end end
class PersonalAccessTokensFinder class PersonalAccessTokensFinder
def initialize(user, params = {}) attr_accessor :params
@user = user
def initialize(params = {})
@params = params @params = params
end end
def execute def execute(token: nil, id: nil)
pat_id = token_id? tokens = by_impersonation
personal_access_tokens = @user.personal_access_tokens
personal_access_tokens = personal_access_tokens.impersonation if impersonation?
return find_token_by_id(personal_access_tokens, pat_id) if pat_id return tokens.find_by_token(token) if token
return tokens.find_by_id(id) if id
case state? tokens = by_state(tokens)
when 'active' tokens.order(@params[:order]) if @params[:order]
personal_access_tokens.active
when 'inactive' tokens
personal_access_tokens.inactive
else
personal_access_tokens
end
end end
private private
def state? def personal_access_tokens
@params[:state].presence @params[:user] ? @params[:user].personal_access_tokens : PersonalAccessToken.all
end end
def impersonation? def by_impersonation
@params[:impersonation].presence case @params[:impersonation]
when true
personal_access_tokens.with_impersonation
when false
personal_access_tokens.without_impersonation
else
personal_access_tokens
end end
def token_id?
@params[:personal_access_token_id].presence
end end
def find_token_by_id(personal_access_tokens, pat_id) def by_state(tokens)
personal_access_tokens.find_by(id: pat_id) case @params[:state]
when 'active'
tokens.active
when 'inactive'
tokens.inactive
else
tokens
end
end end
end end
...@@ -9,11 +9,10 @@ class PersonalAccessToken < ActiveRecord::Base ...@@ -9,11 +9,10 @@ class PersonalAccessToken < ActiveRecord::Base
before_save :ensure_token before_save :ensure_token
default_scope { where(impersonation: false) } scope :active, -> { where("revoked = false AND (expires_at >= NOW() OR expires_at IS NULL)") }
scope :active, -> { where(revoked: false).where("expires_at >= NOW() OR expires_at IS NULL") }
scope :inactive, -> { where("revoked = true OR expires_at < NOW()") } scope :inactive, -> { where("revoked = true OR expires_at < NOW()") }
scope :impersonation, -> { unscoped.where(impersonation: true) } scope :with_impersonation, -> { where(impersonation: true) }
scope :with_impersonation_tokens, -> { unscoped } scope :without_impersonation, -> { where(impersonation: false) }
def revoke! def revoke!
self.revoked = true self.revoked = true
......
...@@ -86,19 +86,6 @@ ...@@ -86,19 +86,6 @@
:javascript :javascript
var $dateField = $('#personal_access_token_expires_at');
var date = $dateField.val();
new Pikaday({
field: $dateField.get(0),
theme: 'gitlab-theme',
format: 'yyyy-mm-dd',
minDate: new Date(),
onSelect: function(dateText) {
$dateField.val(dateFormat(new Date(dateText), 'yyyy-mm-dd'));
}
});
$("#created-personal-access-token").click(function() { $("#created-personal-access-token").click(function() {
this.select(); this.select();
}); });
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
new Pikaday({ new Pikaday({
field: $dateField.get(0), field: $dateField.get(0),
theme: 'gitlab-theme', theme: 'gitlab-theme',
format: 'YYYY-MM-DD', format: 'yyyy-mm-dd',
minDate: new Date(), minDate: new Date(),
onSelect: function(dateText) { onSelect: function(dateText) {
$dateField.val(dateFormat(new Date(dateText), 'yyyy-mm-dd')); $dateField.val(dateFormat(new Date(dateText), 'yyyy-mm-dd'));
......
...@@ -2,11 +2,19 @@ ...@@ -2,11 +2,19 @@
## List ## List
This function takes pagination parameters `page` and `per_page` to restrict the list of personal access tokens.
``` ```
GET /personal_access_tokens GET /personal_access_tokens
``` ```
An example: Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `state` | string | no | filter tokens based on state (all, active, inactive) |
Example response:
```json ```json
[ [
{ {
...@@ -20,20 +28,6 @@ An example: ...@@ -20,20 +28,6 @@ An example:
] ]
``` ```
In addition, you can filter tokens based on state: `all`, `active` and `inactive`
```
GET /personal_access_tokens?state=all
```
```
GET /personal_access_tokens?state=active
```
```
GET /personal_access_tokens?state=inactive
```
## Show ## Show
``` ```
......
...@@ -831,18 +831,21 @@ Example response: ...@@ -831,18 +831,21 @@ Example response:
## Retrieve user personal access tokens ## Retrieve user personal access tokens
It retrieves every personal access token of the user. Note that only administrators can do this. It retrieves every personal access token of the user. Note that only administrators can do this.
This function takes pagination parameters `page` and `per_page` to restrict the list of personal access tokens.
``` ```
GET /users/:user_id/personal_access_tokens GET /users/:id/personal_access_tokens
``` ```
Parameters: Parameters:
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- | | --------- | ---- | -------- | ----------- |
| `user_id` | integer | yes | The ID of the user | | `id` | integer | yes | The ID of the user |
| `state` | string | no | filter tokens based on state (all, active, inactive) |
| `impersonation` | boolean | no | The impersonation flag of the personal access token |
An example: Example response:
```json ```json
[ [
{ {
...@@ -852,45 +855,25 @@ An example: ...@@ -852,45 +855,25 @@ An example:
"expires_at": "2017-01-04", "expires_at": "2017-01-04",
"scopes": ['api'], "scopes": ['api'],
"active": true, "active": true,
"impersonation": false, "impersonation": true,
"token": "9koXpg98eAheJpvBs5tK" "token": "9koXpg98eAheJpvBs5tK"
} }
] ]
``` ```
In addition, you can filter tokens based on state: `all`, `active` and `inactive`
```
GET /users/:user_id/personal_access_tokens?state=all
```
```
GET /users/:user_id/personal_access_tokens?state=active
```
```
GET /users/:user_id/personal_access_tokens?state=inactive
```
Finally, you can filter based on impersonation: `true` or `false`.
```
GET /users/:user_id/personal_access_tokens?impersonation=true
```
## Show a user personal access token ## Show a user personal access token
It shows a user's personal access token. Note that only administrators can do this. It shows a user's personal access token. Note that only administrators can do this.
``` ```
GET /users/:user_id/personal_access_tokens/:personal_access_token_id GET /users/:id/personal_access_tokens/:personal_access_token_id
``` ```
Parameters: Parameters:
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- | | --------- | ---- | -------- | ----------- |
| `user_id` | integer | yes | The ID of the user | | `id` | integer | yes | The ID of the user |
| `personal_access_token_id` | integer | yes | The ID of the personal access token | | `personal_access_token_id` | integer | yes | The ID of the personal access token |
## Create a personal access token ## Create a personal access token
...@@ -901,14 +884,14 @@ both API calls and Git reads and writes. The user will not see these tokens in h ...@@ -901,14 +884,14 @@ both API calls and Git reads and writes. The user will not see these tokens in h
settings page. settings page.
``` ```
POST /users/:user_id/personal_access_tokens POST /users/:id/personal_access_tokens
``` ```
Parameters: Parameters:
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- | | --------- | ---- | -------- | ----------- |
| `user_id` | integer | yes | The ID of the user | | `id` | integer | yes | The ID of the user |
| `name` | string | yes | The name of the personal access token | | `name` | string | yes | The name of the personal access token |
| `expires_at` | date | no | The expiration date of the personal access token | | `expires_at` | date | no | The expiration date of the personal access token |
| `scopes` | array | no | The array of scopes of the personal access token | | `scopes` | array | no | The array of scopes of the personal access token |
...@@ -919,12 +902,12 @@ Parameters: ...@@ -919,12 +902,12 @@ Parameters:
It revokes a personal access token. Note that only administrators can revoke impersonation tokens. It revokes a personal access token. Note that only administrators can revoke impersonation tokens.
``` ```
DELETE /users/:user_id/personal_access_tokens/:personal_access_token_id DELETE /users/:id/personal_access_tokens/:personal_access_token_id
``` ```
Parameters: Parameters:
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- | | --------- | ---- | -------- | ----------- |
| `user_id` | integer | yes | The ID of the user | | `id` | integer | yes | The ID of the user |
| `personal_access_token_id` | integer | yes | The ID of the personal access token | | `personal_access_token_id` | integer | yes | The ID of the personal access token |
module API module API
class PersonalAccessTokens < Grape::API class PersonalAccessTokens < Grape::API
before { authenticate! } include PaginationParams
before do
authenticate!
@finder = PersonalAccessTokensFinder.new(user: current_user, impersonation: false)
end
resource :personal_access_tokens do resource :personal_access_tokens do
desc 'Retrieve personal access tokens' do desc 'Retrieve personal access tokens' do
...@@ -9,8 +14,12 @@ module API ...@@ -9,8 +14,12 @@ module API
end end
params do params do
optional :state, type: String, default: 'all', values: %w[all active inactive], desc: 'Filters (all|active|inactive) personal_access_tokens' optional :state, type: String, default: 'all', values: %w[all active inactive], desc: 'Filters (all|active|inactive) personal_access_tokens'
use :pagination
end
get do
@finder.params.merge!(declared_params(include_missing: false))
present paginate(@finder.execute), with: Entities::PersonalAccessToken
end end
get { present PersonalAccessTokensFinder.new(current_user, params).execute, with: Entities::PersonalAccessToken }
desc 'Retrieve personal access token' do desc 'Retrieve personal access token' do
detail 'This feature was introduced in GitLab 9.0' detail 'This feature was introduced in GitLab 9.0'
...@@ -20,7 +29,7 @@ module API ...@@ -20,7 +29,7 @@ module API
requires :personal_access_token_id, type: Integer, desc: 'The ID of the personal access token' requires :personal_access_token_id, type: Integer, desc: 'The ID of the personal access token'
end end
get ':personal_access_token_id' do get ':personal_access_token_id' do
personal_access_token = PersonalAccessTokensFinder.new(current_user, declared_params(include_missing: false)).execute personal_access_token = @finder.execute(id: declared_params[:personal_access_token_id])
not_found!('Personal Access Token') unless personal_access_token not_found!('Personal Access Token') unless personal_access_token
present personal_access_token, with: Entities::PersonalAccessToken present personal_access_token, with: Entities::PersonalAccessToken
...@@ -36,7 +45,7 @@ module API ...@@ -36,7 +45,7 @@ module API
optional :scopes, type: Array, desc: 'The array of scopes of the personal access token' optional :scopes, type: Array, desc: 'The array of scopes of the personal access token'
end end
post do post do
personal_access_token = current_user.personal_access_tokens.build(declared_params(include_missing: false)) personal_access_token = @finder.execute.build(declared_params(include_missing: false))
if personal_access_token.save if personal_access_token.save
present personal_access_token, with: Entities::PersonalAccessTokenWithToken present personal_access_token, with: Entities::PersonalAccessTokenWithToken
...@@ -52,12 +61,10 @@ module API ...@@ -52,12 +61,10 @@ module API
requires :personal_access_token_id, type: Integer, desc: 'The ID of the personal access token' requires :personal_access_token_id, type: Integer, desc: 'The ID of the personal access token'
end end
delete ':personal_access_token_id' do delete ':personal_access_token_id' do
personal_access_token = PersonalAccessTokensFinder.new(current_user, declared_params(include_missing: false)).execute personal_access_token = @finder.execute(id: declared_params[:personal_access_token_id])
not_found!('Personal Access Token') unless personal_access_token not_found!('Personal Access Token') unless personal_access_token
personal_access_token.revoke! personal_access_token.revoke!
no_content!
end end
end end
end end
......
...@@ -373,7 +373,11 @@ module API ...@@ -373,7 +373,11 @@ module API
end end
segment ':id' do segment ':id' do
resource :personal_access_tokens do resource :personal_access_tokens do
before { authenticated_as_admin! } before do
authenticated_as_admin!
user = find_user(params)
@finder = PersonalAccessTokensFinder.new(user: user)
end
desc 'Retrieve personal access tokens. Available only for admins.' do desc 'Retrieve personal access tokens. Available only for admins.' do
detail 'This feature was introduced in GitLab 9.0' detail 'This feature was introduced in GitLab 9.0'
...@@ -381,11 +385,12 @@ module API ...@@ -381,11 +385,12 @@ module API
end end
params do params do
optional :state, type: String, default: 'all', values: %w[all active inactive], desc: 'Filters (all|active|inactive) personal_access_tokens' optional :state, type: String, default: 'all', values: %w[all active inactive], desc: 'Filters (all|active|inactive) personal_access_tokens'
optional :impersonation, type: Boolean, default: false, desc: 'Filters only impersonation personal_access_tokens' optional :impersonation, type: Boolean, desc: 'Filters only impersonation personal_access_tokens'
use :pagination
end end
get do get do
user = find_user(params) @finder.params.merge!(declared_params(include_missing: false))
present PersonalAccessTokensFinder.new(user, params).execute, with: Entities::ImpersonationToken present paginate(@finder.execute), with: Entities::ImpersonationToken
end end
desc 'Create a personal access token. Available only for admins.' do desc 'Create a personal access token. Available only for admins.' do
...@@ -396,11 +401,10 @@ module API ...@@ -396,11 +401,10 @@ module API
requires :name, type: String, desc: 'The name of the personal access token' requires :name, type: String, desc: 'The name of the personal access token'
optional :expires_at, type: Date, desc: 'The expiration date in the format YEAR-MONTH-DAY of the personal access token' optional :expires_at, type: Date, desc: 'The expiration date in the format YEAR-MONTH-DAY of the personal access token'
optional :scopes, type: Array, desc: 'The array of scopes of the personal access token' optional :scopes, type: Array, desc: 'The array of scopes of the personal access token'
optional :impersonation, type: Boolean, default: false, desc: 'The impersonation flag of the personal access token' optional :impersonation, type: Boolean, desc: 'The impersonation flag of the personal access token'
end end
post do post do
user = find_user(params) personal_access_token = @finder.execute.build(declared_params(include_missing: false))
personal_access_token = PersonalAccessTokensFinder.new(user).execute.build(declared_params(include_missing: false))
if personal_access_token.save if personal_access_token.save
present personal_access_token, with: Entities::ImpersonationToken present personal_access_token, with: Entities::ImpersonationToken
...@@ -415,12 +419,9 @@ module API ...@@ -415,12 +419,9 @@ module API
end end
params do params do
requires :personal_access_token_id, type: Integer, desc: 'The ID of the personal access token' requires :personal_access_token_id, type: Integer, desc: 'The ID of the personal access token'
optional :impersonation, type: Boolean, default: false, desc: 'The impersonation flag of the personal access token'
end end
get ':personal_access_token_id' do get ':personal_access_token_id' do
user = find_user(params) personal_access_token = @finder.execute(id: declared_params[:personal_access_token_id])
personal_access_token = PersonalAccessTokensFinder.new(user, declared_params(include_missing: false)).execute
not_found!('Personal Access Token') unless personal_access_token not_found!('Personal Access Token') unless personal_access_token
present personal_access_token, with: Entities::ImpersonationToken present personal_access_token, with: Entities::ImpersonationToken
...@@ -431,17 +432,12 @@ module API ...@@ -431,17 +432,12 @@ module API
end end
params do params do
requires :personal_access_token_id, type: Integer, desc: 'The ID of the personal access token' requires :personal_access_token_id, type: Integer, desc: 'The ID of the personal access token'
optional :impersonation, type: Boolean, default: false, desc: 'The impersonation flag of the personal access token'
end end
delete ':personal_access_token_id' do delete ':personal_access_token_id' do
user = find_user(params) personal_access_token = @finder.execute(id: declared_params[:personal_access_token_id])
personal_access_token = PersonalAccessTokensFinder.new(user, declared_params(include_missing: false)).execute
not_found!('Personal Access Token') unless personal_access_token not_found!('Personal Access Token') unless personal_access_token
personal_access_token.revoke! personal_access_token.revoke!
no_content!
end end
end end
end end
......
...@@ -105,7 +105,7 @@ module Gitlab ...@@ -105,7 +105,7 @@ module Gitlab
def personal_access_token_check(password) def personal_access_token_check(password)
return unless password.present? return unless password.present?
token = PersonalAccessToken.with_impersonation_tokens.active.find_by_token(password) token = PersonalAccessTokensFinder.new(state: 'active').execute(token: password)
if token && valid_api_token?(token) if token && valid_api_token?(token)
Gitlab::Auth::Result.new(token.user, nil, :personal_token, full_authentication_abilities) Gitlab::Auth::Result.new(token.user, nil, :personal_token, full_authentication_abilities)
......
...@@ -4,24 +4,14 @@ describe 'Admin > Users > Impersonation Tokens', feature: true, js: true do ...@@ -4,24 +4,14 @@ describe 'Admin > Users > Impersonation Tokens', feature: true, js: true do
let(:admin) { create(:admin) } let(:admin) { create(:admin) }
let!(:user) { create(:user) } let!(:user) { create(:user) }
def active_personal_access_tokens def active_impersonation_tokens
find(".table.active-impersonation-tokens") find(".table.active-impersonation-tokens")
end end
def inactive_personal_access_tokens def inactive_impersonation_tokens
find(".table.inactive-impersonation-tokens") find(".table.inactive-impersonation-tokens")
end end
def created_personal_access_token
find("#created-impersonation-token").value
end
def disallow_personal_access_token_saves!
allow_any_instance_of(PersonalAccessToken).to receive(:save).and_return(false)
errors = ActiveModel::Errors.new(PersonalAccessToken.new).tap { |e| e.add(:name, "cannot be nil") }
allow_any_instance_of(PersonalAccessToken).to receive(:errors).and_return(errors)
end
before { login_as(admin) } before { login_as(admin) }
describe "token creation" do describe "token creation" do
...@@ -40,44 +30,43 @@ describe 'Admin > Users > Impersonation Tokens', feature: true, js: true do ...@@ -40,44 +30,43 @@ describe 'Admin > Users > Impersonation Tokens', feature: true, js: true do
check "api" check "api"
check "read_user" check "read_user"
expect { click_on "Create Impersonation Token" }.to change { PersonalAccessToken.impersonation.count } expect { click_on "Create Impersonation Token" }.to change { PersonalAccessToken.with_impersonation.count }
expect(active_personal_access_tokens).to have_text(name) expect(active_impersonation_tokens).to have_text(name)
expect(active_personal_access_tokens).to have_text('In') expect(active_impersonation_tokens).to have_text('In')
expect(active_personal_access_tokens).to have_text('api') expect(active_impersonation_tokens).to have_text('api')
expect(active_personal_access_tokens).to have_text('read_user') expect(active_impersonation_tokens).to have_text('read_user')
end end
end end
describe "inactive tokens" do describe 'active tokens' do
let!(:personal_access_token) { create(:impersonation_personal_access_token, user: user) } let!(:impersonation_token) { create(:impersonation_personal_access_token, user: user) }
let!(:personal_access_token) { create(:personal_access_token, user: user) }
it "allows revocation of an active impersonation token" do it 'only shows impersonation tokens' do
visit admin_user_impersonation_tokens_path(user_id: user.username) visit admin_user_impersonation_tokens_path(user_id: user.username)
click_on "Revoke" expect(active_impersonation_tokens).to have_text(impersonation_token.name)
expect(active_impersonation_tokens).not_to have_text(personal_access_token.name)
expect(inactive_personal_access_tokens).to have_text(personal_access_token.name) end
end end
it "moves expired tokens to the 'inactive' section" do describe "inactive tokens" do
personal_access_token.update(expires_at: 5.days.ago) let!(:impersonation_token) { create(:impersonation_personal_access_token, user: user) }
it "allows revocation of an active impersonation token" do
visit admin_user_impersonation_tokens_path(user_id: user.username) visit admin_user_impersonation_tokens_path(user_id: user.username)
expect(inactive_personal_access_tokens).to have_text(personal_access_token.name) click_on "Revoke"
expect(inactive_impersonation_tokens).to have_text(impersonation_token.name)
end end
context "when revocation fails" do it "moves expired tokens to the 'inactive' section" do
before { disallow_personal_access_token_saves! } impersonation_token.update(expires_at: 5.days.ago)
it "displays an error message" do
visit admin_user_impersonation_tokens_path(user_id: user.username) visit admin_user_impersonation_tokens_path(user_id: user.username)
click_on "Revoke" expect(inactive_impersonation_tokens).to have_text(impersonation_token.name)
expect(active_personal_access_tokens).to have_text(personal_access_token.name)
expect(page).to have_content("Could not revoke")
end
end end
end end
end end
require 'spec_helper'
describe PersonalAccessTokensFinder do
describe '#execute' do
let(:user) { create(:user) }
let!(:active_personal_access_token) { create(:personal_access_token, user: user) }
let!(:expired_personal_access_token) { create(:expired_personal_access_token, user: user) }
let!(:revoked_personal_access_token) { create(:revoked_personal_access_token, user: user) }
let!(:active_impersonation_token) { create(:impersonation_personal_access_token, user: user, impersonation: true) }
let!(:expired_impersonation_token) { create(:expired_personal_access_token, user: user, impersonation: true) }
let!(:revoked_impersonation_token) { create(:revoked_personal_access_token, user: user, impersonation: true) }
subject { finder.execute }
describe 'without user' do
let(:finder) { described_class.new }
it do
is_expected.to contain_exactly(active_personal_access_token, active_impersonation_token,
revoked_personal_access_token, expired_personal_access_token,
revoked_impersonation_token, expired_impersonation_token)
end
describe 'without impersonation' do
before { finder.params.merge!(impersonation: false) }
it { is_expected.to contain_exactly(active_personal_access_token, revoked_personal_access_token, expired_personal_access_token) }
describe 'with active state' do
before { finder.params.merge!(state: 'active') }
it { is_expected.to contain_exactly(active_personal_access_token) }
end
describe 'with inactive state' do
before { finder.params.merge!(state: 'inactive') }
it { is_expected.to contain_exactly(revoked_personal_access_token, expired_personal_access_token) }
end
end
describe 'with impersonation' do
before { finder.params.merge!(impersonation: true) }
it { is_expected.to contain_exactly(active_impersonation_token, revoked_impersonation_token, expired_impersonation_token) }
describe 'with active state' do
before { finder.params.merge!(state: 'active') }
it { is_expected.to contain_exactly(active_impersonation_token) }
end
describe 'with inactive state' do
before { finder.params.merge!(state: 'inactive') }
it { is_expected.to contain_exactly(revoked_impersonation_token, expired_impersonation_token) }
end
end
describe 'with active state' do
before { finder.params.merge!(state: 'active') }
it { is_expected.to contain_exactly(active_personal_access_token, active_impersonation_token) }
end
describe 'with inactive state' do
before { finder.params.merge!(state: 'inactive') }
it do
is_expected.to contain_exactly(expired_personal_access_token, revoked_personal_access_token,
expired_impersonation_token, revoked_impersonation_token)
end
end
describe 'with id' do
subject { finder.execute(id: active_personal_access_token.id) }
it { is_expected.to eq(active_personal_access_token) }
describe 'with impersonation' do
before { finder.params.merge!(impersonation: true) }
it { is_expected.to be_nil }
end
end
describe 'with token' do
subject { finder.execute(token: active_personal_access_token.token) }
it { is_expected.to eq(active_personal_access_token) }
describe 'with impersonation' do
before { finder.params.merge!(impersonation: true) }
it { is_expected.to be_nil }
end
end
end
describe 'with user' do
let(:user2) { create(:user) }
let(:finder) { described_class.new(user: user) }
let!(:other_user_active_personal_access_token) { create(:personal_access_token, user: user2) }
let!(:other_user_expired_personal_access_token) { create(:expired_personal_access_token, user: user2) }
let!(:other_user_revoked_personal_access_token) { create(:revoked_personal_access_token, user: user2) }
let!(:other_user_active_impersonation_token) { create(:impersonation_personal_access_token, user: user2, impersonation: true) }
let!(:other_user_expired_impersonation_token) { create(:expired_personal_access_token, user: user2, impersonation: true) }
let!(:other_user_revoked_impersonation_token) { create(:revoked_personal_access_token, user: user2, impersonation: true) }
it do
is_expected.to contain_exactly(active_personal_access_token, active_impersonation_token,
revoked_personal_access_token, expired_personal_access_token,
revoked_impersonation_token, expired_impersonation_token)
end
describe 'without impersonation' do
before { finder.params.merge!(impersonation: false) }
it { is_expected.to contain_exactly(active_personal_access_token, revoked_personal_access_token, expired_personal_access_token) }
describe 'with active state' do
before { finder.params.merge!(state: 'active') }
it { is_expected.to contain_exactly(active_personal_access_token) }
end
describe 'with inactive state' do
before { finder.params.merge!(state: 'inactive') }
it { is_expected.to contain_exactly(revoked_personal_access_token, expired_personal_access_token) }
end
end
describe 'with impersonation' do
before { finder.params.merge!(impersonation: true) }
it { is_expected.to contain_exactly(active_impersonation_token, revoked_impersonation_token, expired_impersonation_token) }
describe 'with active state' do
before { finder.params.merge!(state: 'active') }
it { is_expected.to contain_exactly(active_impersonation_token) }
end
describe 'with inactive state' do
before { finder.params.merge!(state: 'inactive') }
it { is_expected.to contain_exactly(revoked_impersonation_token, expired_impersonation_token) }
end
end
describe 'with active state' do
before { finder.params.merge!(state: 'active') }
it { is_expected.to contain_exactly(active_personal_access_token, active_impersonation_token) }
end
describe 'with inactive state' do
before { finder.params.merge!(state: 'inactive') }
it do
is_expected.to contain_exactly(expired_personal_access_token, revoked_personal_access_token,
expired_impersonation_token, revoked_impersonation_token)
end
end
describe 'with id' do
subject { finder.execute(id: active_personal_access_token.id) }
it { is_expected.to eq(active_personal_access_token) }
describe 'with impersonation' do
before { finder.params.merge!(impersonation: true) }
it { is_expected.to be_nil }
end
end
describe 'with token' do
subject { finder.execute(token: active_personal_access_token.token) }
it { is_expected.to eq(active_personal_access_token) }
describe 'with impersonation' do
before { finder.params.merge!(impersonation: true) }
it { is_expected.to be_nil }
end
end
end
end
end
...@@ -118,10 +118,10 @@ describe Gitlab::Auth, lib: true do ...@@ -118,10 +118,10 @@ describe Gitlab::Auth, lib: true do
end end
it 'succeeds if it is an impersonation token' do it 'succeeds if it is an impersonation token' do
personal_access_token = create(:personal_access_token, impersonation: true, scopes: []) impersonation_token = create(:personal_access_token, impersonation: true, scopes: ['api'])
expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: '') expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: '')
expect(gl_auth.find_for_git_client('', personal_access_token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(personal_access_token.user, nil, :personal_token, full_authentication_abilities)) expect(gl_auth.find_for_git_client('', impersonation_token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(impersonation_token.user, nil, :personal_token, full_authentication_abilities))
end end
it 'fails for personal access tokens with other scopes' do it 'fails for personal access tokens with other scopes' do
...@@ -131,6 +131,13 @@ describe Gitlab::Auth, lib: true do ...@@ -131,6 +131,13 @@ describe Gitlab::Auth, lib: true do
expect(gl_auth.find_for_git_client('', personal_access_token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(nil, nil)) expect(gl_auth.find_for_git_client('', personal_access_token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(nil, nil))
end end
it 'fails for impersonation token with other scopes' do
impersonation_token = create(:personal_access_token, scopes: ['read_user'])
expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: '')
expect(gl_auth.find_for_git_client('', impersonation_token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(nil, nil))
end
it 'fails if password is nil' do it 'fails if password is nil' do
expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: '') expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: '')
expect(gl_auth.find_for_git_client('', nil, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(nil, nil)) expect(gl_auth.find_for_git_client('', nil, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(nil, nil))
......
...@@ -16,6 +16,7 @@ describe PersonalAccessToken, models: true do ...@@ -16,6 +16,7 @@ describe PersonalAccessToken, models: true do
expect(invalid_personal_access_token.token).not_to be_nil expect(invalid_personal_access_token.token).not_to be_nil
end end
end end
describe ".active?" do describe ".active?" do
let(:active_personal_access_token) { build(:personal_access_token) } let(:active_personal_access_token) { build(:personal_access_token) }
let(:revoked_personal_access_token) { build(:revoked_personal_access_token) } let(:revoked_personal_access_token) { build(:revoked_personal_access_token) }
......
...@@ -5,8 +5,10 @@ describe API::PersonalAccessTokens, api: true do ...@@ -5,8 +5,10 @@ describe API::PersonalAccessTokens, api: true do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:not_found_token) { (PersonalAccessToken.maximum('id') || 0) + 10 } let(:not_found_token) { (PersonalAccessToken.maximum('id') || 0) + 10 }
let(:finder) { PersonalAccessTokensFinder.new(user: user, impersonation: false) }
describe "GET /personal_access_tokens" do describe "GET /personal_access_tokens" do
let!(:active_impersonation_token) { create(:impersonation_personal_access_token, user: user) }
let!(:active_personal_access_token) { create(:personal_access_token, user: user) } let!(:active_personal_access_token) { create(:personal_access_token, user: user) }
let!(:revoked_personal_access_token) { create(:revoked_personal_access_token, user: user) } let!(:revoked_personal_access_token) { create(:revoked_personal_access_token, user: user) }
let!(:expired_personal_access_token) { create(:expired_personal_access_token, user: user) } let!(:expired_personal_access_token) { create(:expired_personal_access_token, user: user) }
...@@ -16,7 +18,7 @@ describe API::PersonalAccessTokens, api: true do ...@@ -16,7 +18,7 @@ describe API::PersonalAccessTokens, api: true do
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(json_response).to be_an Array expect(json_response).to be_an Array
expect(json_response.size).to eq(user.personal_access_tokens.count) expect(json_response.size).to eq(finder.execute.count)
json_personal_access_token = json_response.detect do |personal_access_token| json_personal_access_token = json_response.detect do |personal_access_token|
personal_access_token['id'] == active_personal_access_token.id personal_access_token['id'] == active_personal_access_token.id
...@@ -27,18 +29,24 @@ describe API::PersonalAccessTokens, api: true do ...@@ -27,18 +29,24 @@ describe API::PersonalAccessTokens, api: true do
end end
it 'returns an array of active personal access tokens if active is set to true' do it 'returns an array of active personal access tokens if active is set to true' do
finder.params[:state] = 'active'
get api("/personal_access_tokens?state=active", user) get api("/personal_access_tokens?state=active", user)
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(json_response).to be_an Array expect(json_response).to be_an Array
expect(json_response.size).to eq(finder.execute.count)
expect(json_response).to all(include('active' => true)) expect(json_response).to all(include('active' => true))
end end
it 'returns an array of inactive personal access tokens if active is set to false' do it 'returns an array of inactive personal access tokens if active is set to false' do
finder.params[:state] = 'inactive'
get api("/personal_access_tokens?state=inactive", user) get api("/personal_access_tokens?state=inactive", user)
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(json_response).to be_an Array expect(json_response).to be_an Array
expect(json_response.size).to eq(finder.execute.count)
expect(json_response).to all(include('active' => false)) expect(json_response).to all(include('active' => false))
end end
end end
...@@ -46,7 +54,7 @@ describe API::PersonalAccessTokens, api: true do ...@@ -46,7 +54,7 @@ describe API::PersonalAccessTokens, api: true do
describe 'POST /personal_access_tokens' do describe 'POST /personal_access_tokens' do
let(:name) { 'my new pat' } let(:name) { 'my new pat' }
let(:expires_at) { '2016-12-28' } let(:expires_at) { '2016-12-28' }
let(:scopes) { ['api', 'read_user'] } let(:scopes) { %w(api read_user) }
it 'returns validation error if personal access token miss some attributes' do it 'returns validation error if personal access token miss some attributes' do
post api("/personal_access_tokens", user) post api("/personal_access_tokens", user)
...@@ -73,7 +81,8 @@ describe API::PersonalAccessTokens, api: true do ...@@ -73,7 +81,8 @@ describe API::PersonalAccessTokens, api: true do
expect(json_response['active']).to eq(false) expect(json_response['active']).to eq(false)
expect(json_response['revoked']).to eq(false) expect(json_response['revoked']).to eq(false)
expect(json_response['token']).to be_present expect(json_response['token']).to be_present
expect(PersonalAccessToken.find(personal_access_token_id)).not_to be_nil expect(json_response['impersonation']).not_to be_present
expect(finder.execute(id: personal_access_token_id)).not_to be_nil
end end
end end
......
...@@ -12,6 +12,7 @@ describe API::Users, api: true do ...@@ -12,6 +12,7 @@ describe API::Users, api: true do
let(:ldap_blocked_user) { create(:omniauth_user, provider: 'ldapmain', state: 'ldap_blocked') } let(:ldap_blocked_user) { create(:omniauth_user, provider: 'ldapmain', state: 'ldap_blocked') }
let(:not_existing_user_id) { (User.maximum('id') || 0 ) + 10 } let(:not_existing_user_id) { (User.maximum('id') || 0 ) + 10 }
let(:not_existing_pat_id) { (PersonalAccessToken.maximum('id') || 0 ) + 10 } let(:not_existing_pat_id) { (PersonalAccessToken.maximum('id') || 0 ) + 10 }
let(:finder) { PersonalAccessTokensFinder.new(user: user) }
describe "GET /users" do describe "GET /users" do
context "when unauthenticated" do context "when unauthenticated" do
...@@ -1178,41 +1179,60 @@ describe API::Users, api: true do ...@@ -1178,41 +1179,60 @@ describe API::Users, api: true do
expect(json_response['message']).to eq('403 Forbidden') expect(json_response['message']).to eq('403 Forbidden')
end end
it 'returns an array of non impersonated personal access tokens' do it 'returns an array of all impersonated and non-impersonated tokens' do
get api("/users/#{user.id}/personal_access_tokens", admin) get api("/users/#{user.id}/personal_access_tokens", admin)
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array expect(json_response).to be_an Array
expect(json_response.size).to eq(user.personal_access_tokens.count) expect(json_response.size).to eq(finder.execute.count)
end
it 'returns an array of non impersonated personal access tokens' do
finder.params[:impersonation] = false
get api("/users/#{user.id}/personal_access_tokens?impersonation=false", admin)
expect(response).to have_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.size).to eq(finder.execute.count)
expect(json_response.detect do |personal_access_token| expect(json_response.detect do |personal_access_token|
personal_access_token['id'] == active_personal_access_token.id personal_access_token['id'] == active_personal_access_token.id
end['token']).to eq(active_personal_access_token.token) end['token']).to eq(active_personal_access_token.token)
end end
it 'returns an array of active personal access tokens if active is set to true' do it 'returns an array of active personal access tokens if active is set to true' do
get api("/users/#{user.id}/personal_access_tokens?state=active", admin) finder.params.merge!(state: 'active', impersonation: false)
get api("/users/#{user.id}/personal_access_tokens?state=active&impersonation=false", admin)
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array expect(json_response).to be_an Array
expect(json_response.size).to eq(user.personal_access_tokens.active.count) expect(json_response.size).to eq(finder.execute.count)
expect(json_response).to all(include('active' => true)) expect(json_response).to all(include('active' => true))
end end
it 'returns an array of inactive personal access tokens if active is set to false' do it 'returns an array of inactive personal access tokens if active is set to false' do
get api("/users/#{user.id}/personal_access_tokens?state=inactive", admin) finder.params.merge!(state: 'inactive', impersonation: false)
get api("/users/#{user.id}/personal_access_tokens?impersonation=false&state=inactive", admin)
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(json_response).to be_an Array expect(json_response).to be_an Array
expect(json_response.size).to eq(user.personal_access_tokens.inactive.count) expect(json_response.size).to eq(finder.execute.count)
expect(json_response).to all(include('active' => false)) expect(json_response).to all(include('active' => false))
end end
it 'returns an array of impersonation personal access tokens if impersonation is set to true' do it 'returns an array of impersonation personal access tokens if impersonation is set to true' do
finder.params[:impersonation] = true
get api("/users/#{user.id}/personal_access_tokens?impersonation=true", admin) get api("/users/#{user.id}/personal_access_tokens?impersonation=true", admin)
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array expect(json_response).to be_an Array
expect(json_response.size).to eq(user.personal_access_tokens.impersonation.count) expect(json_response.size).to eq(finder.execute.count)
expect(json_response).to all(include('impersonation' => true)) expect(json_response).to all(include('impersonation' => true))
end end
end end
...@@ -1220,7 +1240,7 @@ describe API::Users, api: true do ...@@ -1220,7 +1240,7 @@ describe API::Users, api: true do
describe 'POST /users/:id/personal_access_tokens' do describe 'POST /users/:id/personal_access_tokens' do
let(:name) { 'my new pat' } let(:name) { 'my new pat' }
let(:expires_at) { '2016-12-28' } let(:expires_at) { '2016-12-28' }
let(:scopes) { ['api', 'read_user'] } let(:scopes) { %w(api read_user) }
let(:impersonation) { true } let(:impersonation) { true }
it 'returns validation error if personal access token misses some attributes' do it 'returns validation error if personal access token misses some attributes' do
...@@ -1265,7 +1285,7 @@ describe API::Users, api: true do ...@@ -1265,7 +1285,7 @@ describe API::Users, api: true do
expect(json_response['revoked']).to be_falsey expect(json_response['revoked']).to be_falsey
expect(json_response['token']).to be_present expect(json_response['token']).to be_present
expect(json_response['impersonation']).to eq(impersonation) expect(json_response['impersonation']).to eq(impersonation)
expect(PersonalAccessToken.with_impersonation_tokens.find(json_response['id'])).not_to be_nil expect(finder.execute(id: json_response['id'])).not_to be_nil
end end
end end
...@@ -1301,19 +1321,6 @@ describe API::Users, api: true do ...@@ -1301,19 +1321,6 @@ describe API::Users, api: true do
expect(json_response['token']).to be_present expect(json_response['token']).to be_present
expect(json_response['impersonation']).to be_falsey expect(json_response['impersonation']).to be_falsey
end end
it 'does not return an impersonation token without the specified field' do
get api("/users/#{user.id}/personal_access_tokens/#{impersonation_token.id}", admin)
expect(response).to have_http_status(404)
end
it 'returns an impersonation token' do
get api("/users/#{user.id}/personal_access_tokens/#{impersonation_token.id}?impersonation=true", admin)
expect(response).to have_http_status(200)
expect(json_response['impersonation']).to be_truthy
end
end end
describe 'DELETE /users/:id/personal_access_tokens/:personal_access_token_id' do describe 'DELETE /users/:id/personal_access_tokens/:personal_access_token_id' do
...@@ -1348,21 +1355,5 @@ describe API::Users, api: true do ...@@ -1348,21 +1355,5 @@ describe API::Users, api: true do
expect(personal_access_token.revoked).to be_falsey expect(personal_access_token.revoked).to be_falsey
expect(personal_access_token.reload.revoked).to be_truthy expect(personal_access_token.reload.revoked).to be_truthy
end end
it 'does not find impersonated token without specified field' do
delete api("/users/#{user.id}/personal_access_tokens/#{impersonation_token.id}", admin)
expect(response).to have_http_status(404)
expect(impersonation_token.revoked).to be_falsey
expect(impersonation_token.reload.revoked).to be_falsey
end
it 'revokes an impersonation token' do
delete api("/users/#{user.id}/personal_access_tokens/#{impersonation_token.id}?impersonation=true", admin)
expect(response).to have_http_status(204)
expect(impersonation_token.revoked).to be_falsey
expect(impersonation_token.reload.revoked).to be_truthy
end
end end
end end
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