API: admin users can sudo commands as other users
-Specifying a header of SUDO or adding a :sudo with either user id, or username of the user will set the current_user to be that user if your identifying private_token/PRIVATE_TOKEN is an administrator token
This commit is contained in:
parent
9ad5d9a4c6
commit
e86e881832
|
@ -58,7 +58,43 @@ Return values:
|
||||||
* `409 Conflict` - A conflicting resource already exists, e.g. creating a project with a name that already exists
|
* `409 Conflict` - A conflicting resource already exists, e.g. creating a project with a name that already exists
|
||||||
* `500 Server Error` - While handling the request something went wrong on the server side
|
* `500 Server Error` - While handling the request something went wrong on the server side
|
||||||
|
|
||||||
|
## Sudo
|
||||||
|
All API requests support performing an api call as if you were another user, if your private token is for an administration account. You need to pass `sudo` parameter by url or header with an id or username of the user you want to perform the operation as. If passed as header, the header name must be "SUDO" (capitals).
|
||||||
|
|
||||||
|
If a non administrative `private_token` is provided then an error message will be returned with status code 403:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"message": "403 Forbidden: Must be admin to use sudo"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If the sudo user id or username cannot be found then an error message will be returned with status code 404:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"message": "404 Not Found: No user id or username for: <id/username>"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Example of a valid API with sudo request:
|
||||||
|
|
||||||
|
```
|
||||||
|
GET http://example.com/api/v3/projects?private_token=QVy1PB7sTxfy4pqfZM1U&sudo=username
|
||||||
|
```
|
||||||
|
```
|
||||||
|
GET http://example.com/api/v3/projects?private_token=QVy1PB7sTxfy4pqfZM1U&sudo=23
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Example for a valid API request with sudo using curl and authentication via header:
|
||||||
|
|
||||||
|
```
|
||||||
|
curl --header "PRIVATE-TOKEN: QVy1PB7sTxfy4pqfZM1U" --header "SUDO: username" "http://example.com/api/v3/projects"
|
||||||
|
```
|
||||||
|
```
|
||||||
|
curl --header "PRIVATE-TOKEN: QVy1PB7sTxfy4pqfZM1U" --header "SUDO: 23" "http://example.com/api/v3/projects"
|
||||||
|
```
|
||||||
|
|
||||||
#### Pagination
|
#### Pagination
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,41 @@
|
||||||
module API
|
module API
|
||||||
module APIHelpers
|
module APIHelpers
|
||||||
|
PRIVATE_TOKEN_HEADER = "HTTP_PRIVATE_TOKEN"
|
||||||
|
PRIVATE_TOKEN_PARAM = :private_token
|
||||||
|
SUDO_HEADER ="HTTP_SUDO"
|
||||||
|
SUDO_PARAM = :sudo
|
||||||
|
|
||||||
def current_user
|
def current_user
|
||||||
@current_user ||= User.find_by_authentication_token(params[:private_token] || env["HTTP_PRIVATE_TOKEN"])
|
@current_user ||= User.find_by_authentication_token(params[PRIVATE_TOKEN_PARAM] || env[PRIVATE_TOKEN_HEADER])
|
||||||
|
identifier = sudo_identifier()
|
||||||
|
# If the sudo is the current user do nothing
|
||||||
|
if (identifier && !(@current_user.id == identifier || @current_user.username == identifier))
|
||||||
|
render_api_error!('403 Forbidden: Must be admin to use sudo', 403) unless @current_user.is_admin?
|
||||||
|
begin
|
||||||
|
|
||||||
|
if (identifier.is_a?(Integer))
|
||||||
|
user = User.find_by_id(identifier)
|
||||||
|
else
|
||||||
|
user = User.find_by_username(identifier)
|
||||||
|
end
|
||||||
|
if user.nil?
|
||||||
|
not_found!("No user id or username for: #{identifier}")
|
||||||
|
end
|
||||||
|
@current_user = user
|
||||||
|
rescue => ex
|
||||||
|
not_found!("No user id or username for: #{identifier}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
@current_user
|
||||||
|
end
|
||||||
|
|
||||||
|
def sudo_identifier()
|
||||||
|
identifier = params[SUDO_PARAM] == nil ? env[SUDO_HEADER] : params[SUDO_PARAM]
|
||||||
|
if (!!(identifier =~ /^[0-9]+$/))
|
||||||
|
identifier.to_i
|
||||||
|
else
|
||||||
|
identifier
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def user_project
|
def user_project
|
||||||
|
@ -95,10 +129,10 @@ module API
|
||||||
|
|
||||||
def abilities
|
def abilities
|
||||||
@abilities ||= begin
|
@abilities ||= begin
|
||||||
abilities = Six.new
|
abilities = Six.new
|
||||||
abilities << Ability
|
abilities << Ability
|
||||||
abilities
|
abilities
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,159 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe Gitlab::API do
|
||||||
|
include Gitlab::APIHelpers
|
||||||
|
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[Gitlab::APIHelpers::PRIVATE_TOKEN_HEADER] = token_usr.private_token
|
||||||
|
env[Gitlab::APIHelpers::SUDO_HEADER] = identifier
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
def set_param(token_usr, identifier)
|
||||||
|
clear_env
|
||||||
|
clear_param
|
||||||
|
params[Gitlab::APIHelpers::PRIVATE_TOKEN_PARAM] = token_usr.private_token
|
||||||
|
params[Gitlab::APIHelpers::SUDO_PARAM] = identifier
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
def clear_env
|
||||||
|
env.delete(Gitlab::APIHelpers::PRIVATE_TOKEN_HEADER)
|
||||||
|
env.delete(Gitlab::APIHelpers::SUDO_HEADER)
|
||||||
|
end
|
||||||
|
|
||||||
|
def clear_param
|
||||||
|
params.delete(Gitlab::APIHelpers::PRIVATE_TOKEN_PARAM)
|
||||||
|
params.delete(Gitlab::APIHelpers::SUDO_PARAM)
|
||||||
|
end
|
||||||
|
|
||||||
|
def error!(message, status)
|
||||||
|
raise Exception
|
||||||
|
end
|
||||||
|
|
||||||
|
describe ".current_user" do
|
||||||
|
it "should leave user as is when sudo not specified" do
|
||||||
|
env[Gitlab::APIHelpers::PRIVATE_TOKEN_HEADER] = user.private_token
|
||||||
|
current_user.should == user
|
||||||
|
clear_env
|
||||||
|
params[Gitlab::APIHelpers::PRIVATE_TOKEN_PARAM] = user.private_token
|
||||||
|
current_user.should == user
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should change current user to sudo when admin" do
|
||||||
|
set_env(admin, user.id)
|
||||||
|
current_user.should == user
|
||||||
|
set_param(admin, user.id)
|
||||||
|
current_user.should == user
|
||||||
|
set_env(admin, user.username)
|
||||||
|
current_user.should == user
|
||||||
|
set_param(admin, user.username)
|
||||||
|
current_user.should == 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
|
||||||
|
set_param(user, admin.id)
|
||||||
|
expect { current_user }.to raise_error
|
||||||
|
set_env(user, admin.username)
|
||||||
|
expect { current_user }.to raise_error
|
||||||
|
set_param(user, admin.username)
|
||||||
|
expect { current_user }.to raise_error
|
||||||
|
end
|
||||||
|
it "should throw an error when the user cannot be found for a given id" do
|
||||||
|
id = user.id + admin.id
|
||||||
|
user.id.should_not == id
|
||||||
|
admin.id.should_not == id
|
||||||
|
set_env(admin, id)
|
||||||
|
expect { current_user }.to raise_error
|
||||||
|
|
||||||
|
set_param(admin, id)
|
||||||
|
expect { current_user }.to raise_error
|
||||||
|
end
|
||||||
|
it "should throw an error when the user cannot be found for a given username" do
|
||||||
|
username = "#{user.username}#{admin.username}"
|
||||||
|
user.username.should_not == username
|
||||||
|
admin.username.should_not == username
|
||||||
|
set_env(admin, username)
|
||||||
|
expect { current_user }.to raise_error
|
||||||
|
|
||||||
|
set_param(admin, username)
|
||||||
|
expect { current_user }.to raise_error
|
||||||
|
end
|
||||||
|
it "should handle sudo's to oneself" do
|
||||||
|
set_env(admin, admin.id)
|
||||||
|
current_user.should == admin
|
||||||
|
set_param(admin, admin.id)
|
||||||
|
current_user.should == admin
|
||||||
|
set_env(admin, admin.username)
|
||||||
|
current_user.should == admin
|
||||||
|
set_param(admin, admin.username)
|
||||||
|
current_user.should == admin
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should handle multiple sudo's to oneself" do
|
||||||
|
set_env(admin, user.id)
|
||||||
|
current_user.should == user
|
||||||
|
current_user.should == user
|
||||||
|
set_env(admin, user.username)
|
||||||
|
current_user.should == user
|
||||||
|
current_user.should == user
|
||||||
|
|
||||||
|
set_param(admin, user.id)
|
||||||
|
current_user.should == user
|
||||||
|
current_user.should == user
|
||||||
|
set_param(admin, user.username)
|
||||||
|
current_user.should == user
|
||||||
|
current_user.should == user
|
||||||
|
end
|
||||||
|
it "should handle multiple sudo's to oneself using string ids" do
|
||||||
|
set_env(admin, user.id.to_s)
|
||||||
|
current_user.should == user
|
||||||
|
current_user.should == user
|
||||||
|
|
||||||
|
set_param(admin, user.id.to_s)
|
||||||
|
current_user.should == user
|
||||||
|
current_user.should == user
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '.sudo_identifier' do
|
||||||
|
it "should return integers when input is an int" do
|
||||||
|
set_env(admin, '123')
|
||||||
|
sudo_identifier.should == 123
|
||||||
|
set_env(admin, '0001234567890')
|
||||||
|
sudo_identifier.should == 1234567890
|
||||||
|
|
||||||
|
set_param(admin, '123')
|
||||||
|
sudo_identifier.should == 123
|
||||||
|
set_param(admin, '0001234567890')
|
||||||
|
sudo_identifier.should == 1234567890
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should return string when input is an is not an int" do
|
||||||
|
set_env(admin, '12.30')
|
||||||
|
sudo_identifier.should == "12.30"
|
||||||
|
set_env(admin, 'hello')
|
||||||
|
sudo_identifier.should == 'hello'
|
||||||
|
set_env(admin, ' 123')
|
||||||
|
sudo_identifier.should == ' 123'
|
||||||
|
|
||||||
|
set_param(admin, '12.30')
|
||||||
|
sudo_identifier.should == "12.30"
|
||||||
|
set_param(admin, 'hello')
|
||||||
|
sudo_identifier.should == 'hello'
|
||||||
|
set_param(admin, ' 123')
|
||||||
|
sudo_identifier.should == ' 123'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue