Commit fa17712c authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Merge pull request #3525 from karlhungus/api_sudo

API: admin users can sudo commands as other users
parents 9ad5d9a4 fdc23a93
......@@ -190,6 +190,14 @@ class User < ActiveRecord::Base
def search query
where("name LIKE :query OR email LIKE :query OR username LIKE :query", query: "%#{query}%")
end
def by_username_or_id(name_or_id)
if (name_or_id.is_a?(Integer))
User.find_by_id(name_or_id)
else
User.find_by_username(name_or_id)
end
end
end
#
......
......@@ -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
@current_user = User.by_username_or_id(identifier)
rescue => ex
not_found!("No user id or username for: #{identifier}")
end
not_found!("No user id or username for: #{identifier}") if current_user.nil?
end
@current_user
end
def sudo_identifier()
identifier ||= params[SUDO_PARAM] ||= env[SUDO_HEADER]
# Regex for integers
if (!!(identifier =~ /^[0-9]+$/))
identifier.to_i
else
identifier
end
end
def user_project
......
......@@ -208,4 +208,14 @@ describe User do
user.can_create_group.should == false
end
end
describe 'by_username_or_id' do
let(:user1){create(:user, username: 'foo')}
it "should get the correct user" do
User.by_username_or_id(user1.id).should == user1
User.by_username_or_id('foo').should == user1
User.by_username_or_id(-1).should be_nil
User.by_username_or_id('bar').should be_nil
end
end
end
require 'spec_helper'
describe API do
include API::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[API::APIHelpers::PRIVATE_TOKEN_HEADER] = token_usr.private_token
env[API::APIHelpers::SUDO_HEADER] = identifier
end
def set_param(token_usr, identifier)
clear_env
clear_param
params[API::APIHelpers::PRIVATE_TOKEN_PARAM] = token_usr.private_token
params[API::APIHelpers::SUDO_PARAM] = identifier
end
def clear_env
env.delete(API::APIHelpers::PRIVATE_TOKEN_HEADER)
env.delete(API::APIHelpers::SUDO_HEADER)
end
def clear_param
params.delete(API::APIHelpers::PRIVATE_TOKEN_PARAM)
params.delete(API::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[API::APIHelpers::PRIVATE_TOKEN_HEADER] = user.private_token
current_user.should == user
clear_env
params[API::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