Commit e86e8818327059279247db3a451994c6a62ab161

Authored by Izaak Alpert
Committed by Izaak Alpert
1 parent 9ad5d9a4

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
doc/api/README.md
@@ -58,7 +58,43 @@ Return values: @@ -58,7 +58,43 @@ Return values:
58 * `409 Conflict` - A conflicting resource already exists, e.g. creating a project with a name that already exists 58 * `409 Conflict` - A conflicting resource already exists, e.g. creating a project with a name that already exists
59 * `500 Server Error` - While handling the request something went wrong on the server side 59 * `500 Server Error` - While handling the request something went wrong on the server side
60 60
  61 +## Sudo
  62 +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).
61 63
  64 +If a non administrative `private_token` is provided then an error message will be returned with status code 403:
  65 +
  66 +```json
  67 +{
  68 + "message": "403 Forbidden: Must be admin to use sudo"
  69 +}
  70 +```
  71 +
  72 +If the sudo user id or username cannot be found then an error message will be returned with status code 404:
  73 +
  74 +```json
  75 +{
  76 + "message": "404 Not Found: No user id or username for: <id/username>"
  77 +}
  78 +```
  79 +
  80 +Example of a valid API with sudo request:
  81 +
  82 +```
  83 +GET http://example.com/api/v3/projects?private_token=QVy1PB7sTxfy4pqfZM1U&sudo=username
  84 +```
  85 +```
  86 +GET http://example.com/api/v3/projects?private_token=QVy1PB7sTxfy4pqfZM1U&sudo=23
  87 +```
  88 +
  89 +
  90 +Example for a valid API request with sudo using curl and authentication via header:
  91 +
  92 +```
  93 +curl --header "PRIVATE-TOKEN: QVy1PB7sTxfy4pqfZM1U" --header "SUDO: username" "http://example.com/api/v3/projects"
  94 +```
  95 +```
  96 +curl --header "PRIVATE-TOKEN: QVy1PB7sTxfy4pqfZM1U" --header "SUDO: 23" "http://example.com/api/v3/projects"
  97 +```
62 98
63 #### Pagination 99 #### Pagination
64 100
lib/api/helpers.rb
1 module API 1 module API
2 module APIHelpers 2 module APIHelpers
  3 + PRIVATE_TOKEN_HEADER = "HTTP_PRIVATE_TOKEN"
  4 + PRIVATE_TOKEN_PARAM = :private_token
  5 + SUDO_HEADER ="HTTP_SUDO"
  6 + SUDO_PARAM = :sudo
  7 +
3 def current_user 8 def current_user
4 - @current_user ||= User.find_by_authentication_token(params[:private_token] || env["HTTP_PRIVATE_TOKEN"]) 9 + @current_user ||= User.find_by_authentication_token(params[PRIVATE_TOKEN_PARAM] || env[PRIVATE_TOKEN_HEADER])
  10 + identifier = sudo_identifier()
  11 + # If the sudo is the current user do nothing
  12 + if (identifier && !(@current_user.id == identifier || @current_user.username == identifier))
  13 + render_api_error!('403 Forbidden: Must be admin to use sudo', 403) unless @current_user.is_admin?
  14 + begin
  15 +
  16 + if (identifier.is_a?(Integer))
  17 + user = User.find_by_id(identifier)
  18 + else
  19 + user = User.find_by_username(identifier)
  20 + end
  21 + if user.nil?
  22 + not_found!("No user id or username for: #{identifier}")
  23 + end
  24 + @current_user = user
  25 + rescue => ex
  26 + not_found!("No user id or username for: #{identifier}")
  27 + end
  28 + end
  29 + @current_user
  30 + end
  31 +
  32 + def sudo_identifier()
  33 + identifier = params[SUDO_PARAM] == nil ? env[SUDO_HEADER] : params[SUDO_PARAM]
  34 + if (!!(identifier =~ /^[0-9]+$/))
  35 + identifier.to_i
  36 + else
  37 + identifier
  38 + end
5 end 39 end
6 40
7 def user_project 41 def user_project
@@ -95,10 +129,10 @@ module API @@ -95,10 +129,10 @@ module API
95 129
96 def abilities 130 def abilities
97 @abilities ||= begin 131 @abilities ||= begin
98 - abilities = Six.new  
99 - abilities << Ability  
100 - abilities  
101 - end 132 + abilities = Six.new
  133 + abilities << Ability
  134 + abilities
  135 + end
102 end 136 end
103 end 137 end
104 end 138 end
spec/requests/api/api_helpers_spec.rb 0 → 100644
@@ -0,0 +1,159 @@ @@ -0,0 +1,159 @@
  1 +require 'spec_helper'
  2 +
  3 +describe Gitlab::API do
  4 + include Gitlab::APIHelpers
  5 + include ApiHelpers
  6 + let(:user) { create(:user) }
  7 + let(:admin) { create(:admin) }
  8 + let(:key) { create(:key, user: user) }
  9 +
  10 + let(:params) { {} }
  11 + let(:env) { {} }
  12 +
  13 + def set_env(token_usr, identifier)
  14 + clear_env
  15 + clear_param
  16 + env[Gitlab::APIHelpers::PRIVATE_TOKEN_HEADER] = token_usr.private_token
  17 + env[Gitlab::APIHelpers::SUDO_HEADER] = identifier
  18 + end
  19 +
  20 +
  21 + def set_param(token_usr, identifier)
  22 + clear_env
  23 + clear_param
  24 + params[Gitlab::APIHelpers::PRIVATE_TOKEN_PARAM] = token_usr.private_token
  25 + params[Gitlab::APIHelpers::SUDO_PARAM] = identifier
  26 + end
  27 +
  28 +
  29 + def clear_env
  30 + env.delete(Gitlab::APIHelpers::PRIVATE_TOKEN_HEADER)
  31 + env.delete(Gitlab::APIHelpers::SUDO_HEADER)
  32 + end
  33 +
  34 + def clear_param
  35 + params.delete(Gitlab::APIHelpers::PRIVATE_TOKEN_PARAM)
  36 + params.delete(Gitlab::APIHelpers::SUDO_PARAM)
  37 + end
  38 +
  39 + def error!(message, status)
  40 + raise Exception
  41 + end
  42 +
  43 + describe ".current_user" do
  44 + it "should leave user as is when sudo not specified" do
  45 + env[Gitlab::APIHelpers::PRIVATE_TOKEN_HEADER] = user.private_token
  46 + current_user.should == user
  47 + clear_env
  48 + params[Gitlab::APIHelpers::PRIVATE_TOKEN_PARAM] = user.private_token
  49 + current_user.should == user
  50 + end
  51 +
  52 + it "should change current user to sudo when admin" do
  53 + set_env(admin, user.id)
  54 + current_user.should == user
  55 + set_param(admin, user.id)
  56 + current_user.should == user
  57 + set_env(admin, user.username)
  58 + current_user.should == user
  59 + set_param(admin, user.username)
  60 + current_user.should == user
  61 + end
  62 +
  63 + it "should throw an error when the current user is not an admin and attempting to sudo" do
  64 + set_env(user, admin.id)
  65 + expect { current_user }.to raise_error
  66 + set_param(user, admin.id)
  67 + expect { current_user }.to raise_error
  68 + set_env(user, admin.username)
  69 + expect { current_user }.to raise_error
  70 + set_param(user, admin.username)
  71 + expect { current_user }.to raise_error
  72 + end
  73 + it "should throw an error when the user cannot be found for a given id" do
  74 + id = user.id + admin.id
  75 + user.id.should_not == id
  76 + admin.id.should_not == id
  77 + set_env(admin, id)
  78 + expect { current_user }.to raise_error
  79 +
  80 + set_param(admin, id)
  81 + expect { current_user }.to raise_error
  82 + end
  83 + it "should throw an error when the user cannot be found for a given username" do
  84 + username = "#{user.username}#{admin.username}"
  85 + user.username.should_not == username
  86 + admin.username.should_not == username
  87 + set_env(admin, username)
  88 + expect { current_user }.to raise_error
  89 +
  90 + set_param(admin, username)
  91 + expect { current_user }.to raise_error
  92 + end
  93 + it "should handle sudo's to oneself" do
  94 + set_env(admin, admin.id)
  95 + current_user.should == admin
  96 + set_param(admin, admin.id)
  97 + current_user.should == admin
  98 + set_env(admin, admin.username)
  99 + current_user.should == admin
  100 + set_param(admin, admin.username)
  101 + current_user.should == admin
  102 + end
  103 +
  104 + it "should handle multiple sudo's to oneself" do
  105 + set_env(admin, user.id)
  106 + current_user.should == user
  107 + current_user.should == user
  108 + set_env(admin, user.username)
  109 + current_user.should == user
  110 + current_user.should == user
  111 +
  112 + set_param(admin, user.id)
  113 + current_user.should == user
  114 + current_user.should == user
  115 + set_param(admin, user.username)
  116 + current_user.should == user
  117 + current_user.should == user
  118 + end
  119 + it "should handle multiple sudo's to oneself using string ids" do
  120 + set_env(admin, user.id.to_s)
  121 + current_user.should == user
  122 + current_user.should == user
  123 +
  124 + set_param(admin, user.id.to_s)
  125 + current_user.should == user
  126 + current_user.should == user
  127 + end
  128 + end
  129 +
  130 + describe '.sudo_identifier' do
  131 + it "should return integers when input is an int" do
  132 + set_env(admin, '123')
  133 + sudo_identifier.should == 123
  134 + set_env(admin, '0001234567890')
  135 + sudo_identifier.should == 1234567890
  136 +
  137 + set_param(admin, '123')
  138 + sudo_identifier.should == 123
  139 + set_param(admin, '0001234567890')
  140 + sudo_identifier.should == 1234567890
  141 + end
  142 +
  143 + it "should return string when input is an is not an int" do
  144 + set_env(admin, '12.30')
  145 + sudo_identifier.should == "12.30"
  146 + set_env(admin, 'hello')
  147 + sudo_identifier.should == 'hello'
  148 + set_env(admin, ' 123')
  149 + sudo_identifier.should == ' 123'
  150 +
  151 + set_param(admin, '12.30')
  152 + sudo_identifier.should == "12.30"
  153 + set_param(admin, 'hello')
  154 + sudo_identifier.should == 'hello'
  155 + set_param(admin, ' 123')
  156 + sudo_identifier.should == ' 123'
  157 + end
  158 + end
  159 +end
0 \ No newline at end of file 160 \ No newline at end of file