Commit e86e8818 authored by Izaak Alpert's avatar Izaak Alpert Committed by Izaak Alpert

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
parent 9ad5d9a4
......@@ -58,7 +58,43 @@ Return values:
* `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
## 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
......
module API
module APIHelpers
PRIVATE_TOKEN_HEADER = "HTTP_PRIVATE_TOKEN"
PRIVATE_TOKEN_PARAM = :private_token
SUDO_HEADER ="HTTP_SUDO"
SUDO_PARAM = :sudo
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
def user_project
......@@ -95,10 +129,10 @@ module API
def abilities
@abilities ||= begin
abilities = Six.new
abilities << Ability
abilities
end
abilities = Six.new
abilities << Ability
abilities
end
end
end
end
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
\ No newline at end of file
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