mirror of
https://github.com/wahyd4/gitlabhq.git
synced 2026-08-10 21:26:07 +10:00
Allow creating Personal Access Tokens through the website Related to #2979 - Allow a user to create personal access tokens, and use them to authenticate - Refactor `API::Helpers` into `API::Helpers::Core` and `API::Helpers::Authentication` # Tasks - [ ] #2979 (!3749) - Personal Access Tokens - [x] Basic Implementation - [x] Add UI to add "Personal Access Tokens" - [x] Reload `lib/api` on every request - [x] Respect these tokens for API requests - [x] Just a param or a header too? - [x] Allow revoking tokens - [x] Expire tokens - [x] Left bar should have a "PAT" icon - [x] Scopes? - [x] Copy to Clipboard - [x] Show active/inactive tokens separately - [x] No need to check for expired/revoked in the appropriate places - [x] Why does regular ApplicationController check for private token? - [x] Support non-API requests - [x] Revert (or work on) `lib/api` eager loading - [x] Create MR - [x] Refactoring - [x] Fix tests - [x] Write more tests - [x] Add screenshots to MR - [x] Add description of query performance to MR - [x] Limit the number of queries in the `personal_access_tokens` page - [x] Wait for CI to pass - [x] Fix merge issues in schema.rb - [x] Assign MR to endboss - [x] Wait for feedback - [x] Fix feedback - [x] Wait for CI to pass - [x] Assign to @rspeicher - [x] Fix @rspeicher's comments - [x] Wait for CI to pass - [x] Assign back to @rspeicher - [x] Write documentation and ping @axil - [x] Wait for Axil to respond - [x] Assign to endboss - [x] Address Douwe's feedback - [x] Use the `private_token` or `authentication_token` param instead of `personal_access_token` - [x] Ditto for the header - [x] Assign to endboss - [x] Make sure CI is green - [x] Address Douwe's feedback - [x] Don't go through the `authenticate_user_from_private_token!` method, if a private token is supplied (or combine them) - [x] In `authenticate_user_from_personal_access_token!` don't hit DB if `token_string` is `nil` - [x] Use `current_user.personal_access_tokens.build` in the controller - [x] Remove the "We aren't using `personal_access_token` as the root param" comment - [x] `No need for = "...", we can just have the Inactive ... #{...} on the next line` in the view - [x] Render dates in a (more) human format - [x] CSS issue with table - [x] Don't show the tokens in the UI indefinitely - [x] How to implement scopes? Add-on to current impl? Doorkeeper? - [x] Wait for @DouweM's comments about scopes - [x] Address @DouweM's second review - [x] Try not using `native['innerHTML']` - [x] use contexts for all "when ..." - [x] Ensure consistency (styling) with other pages for "You don't have any tokens" message - [x] "Actions" table column doesn't need a label - [x] %td can be moved outside of the if/else statement - [x] The header title should be "Profile Settings" - [x] Can this be a `before_create`, so we don't need to use `generate`? - [x] If it couldn't be revoked, will we show an error? - [x] If it couldn't be saved, will we show an error? - [x] Merge master - [x] Update CHANGELOG entry - [x] Add tests for form errors? - [x] Post screenshots - [x] Tag @jschatz1 for review - [x] Wait for [build](https://gitlab.com/gitlab-org/gitlab-ce/commit/0dff6fd/builds) to pass - [x] Respond to @jschatz1's comments - [x] Hardcoded colors should be variables - [x] Should not be allowed to chose a date in the past - [x] Use the same table as in the Applications tab - [x] button should say "Create Personal Access Token" - [x] Float the revoke to the right on the `a` - [x] Change revocation message. "Are you sure you want to revoke this certificate? This action cannot be undone." - [x] Date stays selected and looks selected even though date is set as "never". - [x] ~~hover on the calendar button shifts~~ (not caused by this MR - happens on `milestones#new` as well) - [x] Don't use the panel for the created token - [x] Use a normal flash for "Your new personal access token has been created" - [x] Show the input (with the token) below it full width. - [x] Put the "Make sure you save it - you won't be able to access it again." message near the input - [x] Have the input highlight all on single click - [x] Update screenshots - [x] Merge master in + conflicts - [x] Assign to @jschatz1 again - [x] Respond to @jschatz1's comments - [x] No button for clipboard, only link - [x] text-danger - [x] highlight fade on that area where the token was created - [x] Make sure [build](https://gitlab.com/gitlab-org/gitlab-ce/commit/d754d99179f1ffe846fcc1d8e858163b39efc5dc/builds) is green - [x] Assign to @jschatz1 - [x] Wait for [build](https://gitlab.com/gitlab-org/gitlab-ce/commit/faa0e3f7580bc38d4d12916b4589c64d6c2678a7/builds) to pass - [x] Respond to @DouweM's feedback - [x] move the redirect_to out of the if/else - [x] certificate -> token - [x] datepicker back to text field - [x] combine the get_user_from_private_token and get_user_from_personal_access_token methods in ApplicationController - [x] combine the get_user_from_private_token and get_user_from_personal_access_token methods in `lib/api/helpers` - [x] don't need the new constants - [x] Wait for [build](https://gitlab.com/gitlab-org/gitlab-ce/commit/9d7cda3ddce52baad9618466a5d00319b333be57/builds) to pass - [ ] Wait for merge # Screenshots     See merge request !3749
216 lines
7.0 KiB
Ruby
216 lines
7.0 KiB
Ruby
require 'spec_helper'
|
|
|
|
describe API::Helpers, api: true do
|
|
|
|
include API::Helpers
|
|
include ApiHelpers
|
|
|
|
let(:user) { create(:user) }
|
|
let(:admin) { create(:admin) }
|
|
let(:key) { create(:key, user: user) }
|
|
|
|
let(:params) { {} }
|
|
let(:env) { {} }
|
|
|
|
def set_env(token_usr, identifier)
|
|
clear_env
|
|
clear_param
|
|
env[API::Helpers::PRIVATE_TOKEN_HEADER] = token_usr.private_token
|
|
env[API::Helpers::SUDO_HEADER] = identifier
|
|
end
|
|
|
|
def set_param(token_usr, identifier)
|
|
clear_env
|
|
clear_param
|
|
params[API::Helpers::PRIVATE_TOKEN_PARAM] = token_usr.private_token
|
|
params[API::Helpers::SUDO_PARAM] = identifier
|
|
end
|
|
|
|
def clear_env
|
|
env.delete(API::Helpers::PRIVATE_TOKEN_HEADER)
|
|
env.delete(API::Helpers::SUDO_HEADER)
|
|
end
|
|
|
|
def clear_param
|
|
params.delete(API::Helpers::PRIVATE_TOKEN_PARAM)
|
|
params.delete(API::Helpers::SUDO_PARAM)
|
|
end
|
|
|
|
def error!(message, status)
|
|
raise Exception
|
|
end
|
|
|
|
describe ".current_user" do
|
|
describe "when authenticating using a user's private token" do
|
|
it "should return nil for an invalid token" do
|
|
env[API::Helpers::PRIVATE_TOKEN_HEADER] = 'invalid token'
|
|
allow_any_instance_of(self.class).to receive(:doorkeeper_guard){ false }
|
|
expect(current_user).to be_nil
|
|
end
|
|
|
|
it "should return nil for a user without access" do
|
|
env[API::Helpers::PRIVATE_TOKEN_HEADER] = user.private_token
|
|
allow(Gitlab::UserAccess).to receive(:allowed?).and_return(false)
|
|
expect(current_user).to be_nil
|
|
end
|
|
|
|
it "should leave user as is when sudo not specified" do
|
|
env[API::Helpers::PRIVATE_TOKEN_HEADER] = user.private_token
|
|
expect(current_user).to eq(user)
|
|
clear_env
|
|
params[API::Helpers::PRIVATE_TOKEN_PARAM] = user.private_token
|
|
expect(current_user).to eq(user)
|
|
end
|
|
end
|
|
|
|
describe "when authenticating using a user's personal access tokens" do
|
|
let(:personal_access_token) { create(:personal_access_token, user: user) }
|
|
|
|
it "should return nil for an invalid token" do
|
|
env[API::Helpers::PRIVATE_TOKEN_HEADER] = 'invalid token'
|
|
allow_any_instance_of(self.class).to receive(:doorkeeper_guard){ false }
|
|
expect(current_user).to be_nil
|
|
end
|
|
|
|
it "should return nil for a user without access" do
|
|
env[API::Helpers::PRIVATE_TOKEN_HEADER] = personal_access_token.token
|
|
allow(Gitlab::UserAccess).to receive(:allowed?).and_return(false)
|
|
expect(current_user).to be_nil
|
|
end
|
|
|
|
it "should leave user as is when sudo not specified" do
|
|
env[API::Helpers::PRIVATE_TOKEN_HEADER] = personal_access_token.token
|
|
expect(current_user).to eq(user)
|
|
clear_env
|
|
params[API::Helpers::PRIVATE_TOKEN_PARAM] = personal_access_token.token
|
|
expect(current_user).to eq(user)
|
|
end
|
|
|
|
it 'does not allow revoked tokens' do
|
|
personal_access_token.revoke!
|
|
env[API::Helpers::PRIVATE_TOKEN_HEADER] = personal_access_token.token
|
|
allow_any_instance_of(self.class).to receive(:doorkeeper_guard){ false }
|
|
expect(current_user).to be_nil
|
|
end
|
|
|
|
it 'does not allow expired tokens' do
|
|
personal_access_token.update_attributes!(expires_at: 1.day.ago)
|
|
env[API::Helpers::PRIVATE_TOKEN_HEADER] = personal_access_token.token
|
|
allow_any_instance_of(self.class).to receive(:doorkeeper_guard){ false }
|
|
expect(current_user).to be_nil
|
|
end
|
|
end
|
|
|
|
it "should change current user to sudo when admin" do
|
|
set_env(admin, user.id)
|
|
expect(current_user).to eq(user)
|
|
set_param(admin, user.id)
|
|
expect(current_user).to eq(user)
|
|
set_env(admin, user.username)
|
|
expect(current_user).to eq(user)
|
|
set_param(admin, user.username)
|
|
expect(current_user).to eq(user)
|
|
end
|
|
|
|
it "should throw an error when the current user is not an admin and attempting to sudo" do
|
|
set_env(user, admin.id)
|
|
expect { current_user }.to raise_error(Exception)
|
|
set_param(user, admin.id)
|
|
expect { current_user }.to raise_error(Exception)
|
|
set_env(user, admin.username)
|
|
expect { current_user }.to raise_error(Exception)
|
|
set_param(user, admin.username)
|
|
expect { current_user }.to raise_error(Exception)
|
|
end
|
|
|
|
it "should throw an error when the user cannot be found for a given id" do
|
|
id = user.id + admin.id
|
|
expect(user.id).not_to eq(id)
|
|
expect(admin.id).not_to eq(id)
|
|
set_env(admin, id)
|
|
expect { current_user }.to raise_error(Exception)
|
|
|
|
set_param(admin, id)
|
|
expect { current_user }.to raise_error(Exception)
|
|
end
|
|
|
|
it "should throw an error when the user cannot be found for a given username" do
|
|
username = "#{user.username}#{admin.username}"
|
|
expect(user.username).not_to eq(username)
|
|
expect(admin.username).not_to eq(username)
|
|
set_env(admin, username)
|
|
expect { current_user }.to raise_error(Exception)
|
|
|
|
set_param(admin, username)
|
|
expect { current_user }.to raise_error(Exception)
|
|
end
|
|
|
|
it "should handle sudo's to oneself" do
|
|
set_env(admin, admin.id)
|
|
expect(current_user).to eq(admin)
|
|
set_param(admin, admin.id)
|
|
expect(current_user).to eq(admin)
|
|
set_env(admin, admin.username)
|
|
expect(current_user).to eq(admin)
|
|
set_param(admin, admin.username)
|
|
expect(current_user).to eq(admin)
|
|
end
|
|
|
|
it "should handle multiple sudo's to oneself" do
|
|
set_env(admin, user.id)
|
|
expect(current_user).to eq(user)
|
|
expect(current_user).to eq(user)
|
|
set_env(admin, user.username)
|
|
expect(current_user).to eq(user)
|
|
expect(current_user).to eq(user)
|
|
|
|
set_param(admin, user.id)
|
|
expect(current_user).to eq(user)
|
|
expect(current_user).to eq(user)
|
|
set_param(admin, user.username)
|
|
expect(current_user).to eq(user)
|
|
expect(current_user).to eq(user)
|
|
end
|
|
|
|
it "should handle multiple sudo's to oneself using string ids" do
|
|
set_env(admin, user.id.to_s)
|
|
expect(current_user).to eq(user)
|
|
expect(current_user).to eq(user)
|
|
|
|
set_param(admin, user.id.to_s)
|
|
expect(current_user).to eq(user)
|
|
expect(current_user).to eq(user)
|
|
end
|
|
end
|
|
|
|
describe '.sudo_identifier' do
|
|
it "should return integers when input is an int" do
|
|
set_env(admin, '123')
|
|
expect(sudo_identifier).to eq(123)
|
|
set_env(admin, '0001234567890')
|
|
expect(sudo_identifier).to eq(1234567890)
|
|
|
|
set_param(admin, '123')
|
|
expect(sudo_identifier).to eq(123)
|
|
set_param(admin, '0001234567890')
|
|
expect(sudo_identifier).to eq(1234567890)
|
|
end
|
|
|
|
it "should return string when input is an is not an int" do
|
|
set_env(admin, '12.30')
|
|
expect(sudo_identifier).to eq("12.30")
|
|
set_env(admin, 'hello')
|
|
expect(sudo_identifier).to eq('hello')
|
|
set_env(admin, ' 123')
|
|
expect(sudo_identifier).to eq(' 123')
|
|
|
|
set_param(admin, '12.30')
|
|
expect(sudo_identifier).to eq("12.30")
|
|
set_param(admin, 'hello')
|
|
expect(sudo_identifier).to eq('hello')
|
|
set_param(admin, ' 123')
|
|
expect(sudo_identifier).to eq(' 123')
|
|
end
|
|
end
|
|
end
|