summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortiagonbotelho <tiagonbotelho@hotmail.com>2016-11-21 12:59:37 +0000
committertiagonbotelho <tiagonbotelho@hotmail.com>2016-12-07 14:42:51 +0000
commit3ed96afc47c481db4f8c0a6581602abaee920808 (patch)
tree9f840b076417839018586c99d0555fefd4b714cd
parent8b379465a5be48c8062379a3dea8e58110c52d87 (diff)
downloadgitlab-ce-24537-reenable-private-token-with-sudo.tar.gz
adds impersonator variable and makes sudo usage overall more clear24537-reenable-private-token-with-sudo
-rw-r--r--changelogs/unreleased/24537-reenable-private-token-with-sudo.yml5
-rw-r--r--doc/api/users.md51
-rw-r--r--lib/api/entities.rb7
-rw-r--r--lib/api/helpers.rb13
-rw-r--r--lib/api/session.rb4
-rw-r--r--lib/api/users.rb16
-rw-r--r--spec/fixtures/api/schemas/user/login.json37
-rw-r--r--spec/fixtures/api/schemas/user/public.json79
-rw-r--r--spec/requests/api/api_helpers_spec.rb199
-rw-r--r--spec/requests/api/users_spec.rb79
10 files changed, 390 insertions, 100 deletions
diff --git a/changelogs/unreleased/24537-reenable-private-token-with-sudo.yml b/changelogs/unreleased/24537-reenable-private-token-with-sudo.yml
new file mode 100644
index 00000000000..9fbbaeb914d
--- /dev/null
+++ b/changelogs/unreleased/24537-reenable-private-token-with-sudo.yml
@@ -0,0 +1,5 @@
+---
+title: Reenables /user API request to return private-token if user is admin and request
+ is made with sudo
+merge_request: 7615
+author:
diff --git a/doc/api/users.md b/doc/api/users.md
index 52a6b691610..28b6c7bd491 100644
--- a/doc/api/users.md
+++ b/doc/api/users.md
@@ -291,7 +291,9 @@ Parameters:
- `id` (required) - The ID of the user
-## Current user
+## User
+
+### For normal users
Gets currently authenticated user.
@@ -335,6 +337,53 @@ GET /user
}
```
+### For admins
+
+Parameters:
+
+- `sudo` (required) - the ID of a user
+
+```
+GET /user
+```
+
+```json
+{
+ "id": 1,
+ "username": "john_smith",
+ "email": "john@example.com",
+ "name": "John Smith",
+ "state": "active",
+ "avatar_url": "http://localhost:3000/uploads/user/avatar/1/index.jpg",
+ "web_url": "http://localhost:3000/john_smith",
+ "created_at": "2012-05-23T08:00:58Z",
+ "is_admin": false,
+ "bio": null,
+ "location": null,
+ "skype": "",
+ "linkedin": "",
+ "twitter": "",
+ "website_url": "",
+ "organization": "",
+ "last_sign_in_at": "2012-06-01T11:41:01Z",
+ "confirmed_at": "2012-05-23T09:05:22Z",
+ "theme_id": 1,
+ "color_scheme_id": 2,
+ "projects_limit": 100,
+ "current_sign_in_at": "2012-06-02T06:36:55Z",
+ "identities": [
+ {"provider": "github", "extern_uid": "2435223452345"},
+ {"provider": "bitbucket", "extern_uid": "john_smith"},
+ {"provider": "google_oauth2", "extern_uid": "8776128412476123468721346"}
+ ],
+ "can_create_group": true,
+ "can_create_project": true,
+ "two_factor_enabled": true,
+ "external": false,
+ "private_token": "dd34asd13as"
+}
+```
+
## List SSH keys
Get a list of currently authenticated user's SSH keys.
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 1dd191161ef..006d5f9f44e 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -22,7 +22,7 @@ module API
expose :provider, :extern_uid
end
- class UserFull < User
+ class UserPublic < User
expose :last_sign_in_at
expose :confirmed_at
expose :email
@@ -32,10 +32,9 @@ module API
expose :can_create_project?, as: :can_create_project
expose :two_factor_enabled?, as: :two_factor_enabled
expose :external
- expose :private_token, if: lambda { |user, options| user.is_admin? && options[:sudo_identifier] }
end
- class UserLogin < UserFull
+ class UserWithPrivateToken < UserPublic
expose :private_token
end
@@ -290,7 +289,7 @@ module API
end
class SSHKeyWithUser < SSHKey
- expose :user, using: Entities::UserFull
+ expose :user, using: Entities::UserPublic
end
class Note < Grape::Entity
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 7f94ede7940..6a47efc74f0 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -44,11 +44,14 @@ module API
return nil
end
- identifier = sudo_identifier()
+ identifier = sudo_identifier
- # If the sudo is the current user do nothing
- if identifier && !(@current_user.id == identifier || @current_user.username == identifier)
+ if identifier
+ # We check for private_token because we cannot allow PAT to be used
forbidden!('Must be admin to use sudo') unless @current_user.is_admin?
+ forbidden!('Private token must be specified in order to use sudo') unless private_token_used?
+
+ @impersonator = @current_user
@current_user = User.by_username_or_id(identifier)
not_found!("No user id or username for: #{identifier}") if @current_user.nil?
end
@@ -399,6 +402,10 @@ module API
links.join(', ')
end
+ def private_token_used?
+ private_token == @current_user.private_token
+ end
+
def secret_token
Gitlab::Shell.secret_token
end
diff --git a/lib/api/session.rb b/lib/api/session.rb
index d09400b81f5..002ffd1d154 100644
--- a/lib/api/session.rb
+++ b/lib/api/session.rb
@@ -1,7 +1,7 @@
module API
class Session < Grape::API
desc 'Login to get token' do
- success Entities::UserLogin
+ success Entities::UserWithPrivateToken
end
params do
optional :login, type: String, desc: 'The username'
@@ -14,7 +14,7 @@ module API
return unauthorized! unless user
return render_api_error!('401 Unauthorized. You have 2FA enabled. Please use a personal access token to access the API', 401) if user.two_factor_enabled?
- present user, with: Entities::UserLogin
+ present user, with: Entities::UserWithPrivateToken
end
end
end
diff --git a/lib/api/users.rb b/lib/api/users.rb
index b3870e0c7c8..1dab799dd61 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -51,7 +51,7 @@ module API
users = users.external if params[:external] && current_user.is_admin?
end
- entity = current_user.is_admin? ? Entities::UserFull : Entities::UserBasic
+ entity = current_user.is_admin? ? Entities::UserPublic : Entities::UserBasic
present paginate(users), with: entity
end
@@ -66,7 +66,7 @@ module API
not_found!('User') unless user
if current_user && current_user.is_admin?
- present user, with: Entities::UserFull
+ present user, with: Entities::UserPublic
elsif can?(current_user, :read_user, user)
present user, with: Entities::User
else
@@ -75,7 +75,7 @@ module API
end
desc 'Create a user. Available only for admins.' do
- success Entities::UserFull
+ success Entities::UserPublic
end
params do
requires :email, type: String, desc: 'The email of the user'
@@ -99,7 +99,7 @@ module API
end
if user.save
- present user, with: Entities::UserFull
+ present user, with: Entities::UserPublic
else
conflict!('Email has already been taken') if User.
where(email: user.email).
@@ -114,7 +114,7 @@ module API
end
desc 'Update a user. Available only for admins.' do
- success Entities::UserFull
+ success Entities::UserPublic
end
params do
requires :id, type: Integer, desc: 'The ID of the user'
@@ -161,7 +161,7 @@ module API
user_params.delete(:provider)
if user.update_attributes(user_params)
- present user, with: Entities::UserFull
+ present user, with: Entities::UserPublic
else
render_validation_error!(user)
end
@@ -350,10 +350,10 @@ module API
resource :user do
desc 'Get the currently authenticated user' do
- success Entities::UserFull
+ success Entities::UserPublic
end
get do
- present current_user, with: Entities::UserFull, sudo_identifier: sudo_identifier
+ present current_user, with: @impersonator ? Entities::UserWithPrivateToken : Entities::UserPublic
end
desc "Get the currently authenticated user's SSH keys" do
diff --git a/spec/fixtures/api/schemas/user/login.json b/spec/fixtures/api/schemas/user/login.json
new file mode 100644
index 00000000000..e6c1d9c9d84
--- /dev/null
+++ b/spec/fixtures/api/schemas/user/login.json
@@ -0,0 +1,37 @@
+{
+ "type": "object",
+ "required": [
+ "id",
+ "username",
+ "email",
+ "name",
+ "state",
+ "avatar_url",
+ "web_url",
+ "created_at",
+ "is_admin",
+ "bio",
+ "location",
+ "skype",
+ "linkedin",
+ "twitter",
+ "website_url",
+ "organization",
+ "last_sign_in_at",
+ "confirmed_at",
+ "theme_id",
+ "color_scheme_id",
+ "projects_limit",
+ "current_sign_in_at",
+ "identities",
+ "can_create_group",
+ "can_create_project",
+ "two_factor_enabled",
+ "external",
+ "private_token"
+ ],
+ "properties": {
+ "$ref": "full.json",
+ "private_token": { "type": "string" }
+ }
+}
diff --git a/spec/fixtures/api/schemas/user/public.json b/spec/fixtures/api/schemas/user/public.json
new file mode 100644
index 00000000000..dbd5d32e89c
--- /dev/null
+++ b/spec/fixtures/api/schemas/user/public.json
@@ -0,0 +1,79 @@
+{
+ "type": "object",
+ "required": [
+ "id",
+ "username",
+ "email",
+ "name",
+ "state",
+ "avatar_url",
+ "web_url",
+ "created_at",
+ "is_admin",
+ "bio",
+ "location",
+ "skype",
+ "linkedin",
+ "twitter",
+ "website_url",
+ "organization",
+ "last_sign_in_at",
+ "confirmed_at",
+ "theme_id",
+ "color_scheme_id",
+ "projects_limit",
+ "current_sign_in_at",
+ "identities",
+ "can_create_group",
+ "can_create_project",
+ "two_factor_enabled",
+ "external"
+ ],
+ "properties": {
+ "id": { "type": "integer" },
+ "username": { "type": "string" },
+ "email": {
+ "type": "string",
+ "pattern": "^[^@]+@[^@]+$"
+ },
+ "name": { "type": "string" },
+ "state": {
+ "type": "string",
+ "enum": ["active", "blocked"]
+ },
+ "avatar_url": { "type": "string" },
+ "web_url": { "type": "string" },
+ "created_at": { "type": "date" },
+ "is_admin": { "type": "boolean" },
+ "bio": { "type": ["string", "null"] },
+ "location": { "type": ["string", "null"] },
+ "skype": { "type": "string" },
+ "linkedin": { "type": "string" },
+ "twitter": { "type": "string "},
+ "website_url": { "type": "string" },
+ "organization": { "type": ["string", "null"] },
+ "last_sign_in_at": { "type": "date" },
+ "confirmed_at": { "type": ["date", "null"] },
+ "theme_id": { "type": "integer" },
+ "color_scheme_id": { "type": "integer" },
+ "projects_limit": { "type": "integer" },
+ "current_sign_in_at": { "type": "date" },
+ "identities": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "provider": {
+ "type": "string",
+ "enum": ["github", "bitbucket", "google_oauth2"]
+ },
+ "extern_uid": { "type": ["number", "string"] }
+ }
+ }
+ },
+ "can_create_group": { "type": "boolean" },
+ "can_create_project": { "type": "boolean" },
+ "two_factor_enabled": { "type": "boolean" },
+ "external": { "type": "boolean" }
+ }
+}
diff --git a/spec/requests/api/api_helpers_spec.rb b/spec/requests/api/api_helpers_spec.rb
index 36517ad0f8c..3f34309f419 100644
--- a/spec/requests/api/api_helpers_spec.rb
+++ b/spec/requests/api/api_helpers_spec.rb
@@ -153,85 +153,144 @@ describe API::Helpers, api: true do
end
end
- it "changes 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
+ context 'sudo usage' do
+ context 'with admin' do
+ context 'with header' do
+ context 'with id' do
+ it 'changes current_user to sudo' do
+ set_env(admin, user.id)
- it "throws 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
+ expect(current_user).to eq(user)
+ end
- it "throws 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)
+ it 'handles sudo to oneself' do
+ set_env(admin, admin.id)
- set_param(admin, id)
- expect { current_user }.to raise_error(Exception)
- end
+ expect(current_user).to eq(admin)
+ end
- it "throws 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)
+ it 'throws an error when user cannot be found' do
+ id = user.id + admin.id
+ expect(user.id).not_to eq(id)
+ expect(admin.id).not_to eq(id)
- set_param(admin, username)
- expect { current_user }.to raise_error(Exception)
- end
+ set_env(admin, id)
- it "handles 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
+ expect { current_user }.to raise_error(Exception)
+ end
+ end
- it "handles 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
+ context 'with username' do
+ it 'changes current_user to sudo' do
+ set_env(admin, user.username)
+
+ expect(current_user).to eq(user)
+ end
+
+ it 'handles sudo to oneself' do
+ set_env(admin, admin.username)
+
+ expect(current_user).to eq(admin)
+ end
+
+ it "throws 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)
+ end
+ end
+ end
+
+ context 'with param' do
+ context 'with id' do
+ it 'changes current_user to sudo' do
+ set_param(admin, user.id)
+
+ expect(current_user).to eq(user)
+ end
+
+ it 'handles sudo to oneself' do
+ set_param(admin, admin.id)
+
+ expect(current_user).to eq(admin)
+ end
+
+ it 'handles sudo to oneself using string' do
+ set_env(admin, user.id.to_s)
+
+ expect(current_user).to eq(user)
+ end
+
+ it 'throws an error when user cannot be found' do
+ id = user.id + admin.id
+ expect(user.id).not_to eq(id)
+ expect(admin.id).not_to eq(id)
- it "handles 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, id)
- set_param(admin, user.id.to_s)
- expect(current_user).to eq(user)
- expect(current_user).to eq(user)
+ expect { current_user }.to raise_error(Exception)
+ end
+ end
+
+ context 'with username' do
+ it 'changes current_user to sudo' do
+ set_param(admin, user.username)
+
+ expect(current_user).to eq(user)
+ end
+
+ it 'handles sudo to oneself' do
+ set_param(admin, admin.username)
+
+ expect(current_user).to eq(admin)
+ end
+
+ it "throws 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_param(admin, username)
+
+ expect { current_user }.to raise_error(Exception)
+ end
+ end
+ end
+ end
+
+ context 'with regular user' do
+ context 'with env' do
+ it 'changes current_user to sudo when admin and user id' do
+ set_env(user, admin.id)
+
+ expect { current_user }.to raise_error(Exception)
+ end
+
+ it 'changes current_user to sudo when admin and user username' do
+ set_env(user, admin.username)
+
+ expect { current_user }.to raise_error(Exception)
+ end
+ end
+
+ context 'with params' do
+ it 'changes current_user to sudo when admin and user id' do
+ set_param(user, admin.id)
+
+ expect { current_user }.to raise_error(Exception)
+ end
+
+ it 'changes current_user to sudo when admin and user username' do
+ set_param(user, admin.username)
+
+ expect { current_user }.to raise_error(Exception)
+ end
+ end
+ end
end
end
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index f82f52e7399..c37dbfa0a33 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -651,20 +651,75 @@ describe API::Users, api: true do
end
describe "GET /user" do
- it "returns current user" do
- get api("/user", user)
- expect(response).to have_http_status(200)
- expect(json_response['email']).to eq(user.email)
- expect(json_response['is_admin']).to eq(user.is_admin?)
- expect(json_response['can_create_project']).to eq(user.can_create_project?)
- expect(json_response['can_create_group']).to eq(user.can_create_group?)
- expect(json_response['projects_limit']).to eq(user.projects_limit)
- expect(json_response['private_token']).to be_blank
+ let(:personal_access_token) { create(:personal_access_token, user: user) }
+ let(:private_token) { user.private_token }
+
+ context 'with regular user' do
+ context 'with personal access token' do
+ it 'returns 403 without private token when sudo is defined' do
+ get api("/user?private_token=#{personal_access_token.token}&sudo=#{user.id}")
+
+ expect(response).to have_http_status(403)
+ end
+ end
+
+ context 'with private token' do
+ it 'returns 403 without private token when sudo defined' do
+ get api("/user?private_token=#{private_token}&sudo=#{user.id}")
+
+ expect(response).to have_http_status(403)
+ end
+ end
+
+ it 'returns current user without private token when sudo not defined' do
+ get api("/user", user)
+
+ expect(response).to have_http_status(200)
+ expect(response).to match_response_schema('user/public')
+ end
end
- it "returns 401 error if user is unauthenticated" do
- get api("/user")
- expect(response).to have_http_status(401)
+ context 'with admin' do
+ let(:user) { create(:admin) }
+
+ context 'with personal access token' do
+ it 'returns 403 without private token when sudo defined' do
+ get api("/user?private_token=#{personal_access_token.token}&sudo=#{user.id}")
+
+ expect(response).to have_http_status(403)
+ end
+
+ it 'returns current user without private token when sudo not defined' do
+ get api("/user?private_token=#{personal_access_token.token}")
+
+ expect(response).to have_http_status(200)
+ expect(response).to match_response_schema('user/public')
+ end
+ end
+
+ context 'with private token' do
+ it 'returns current user with private token when sudo defined' do
+ get api("/user?private_token=#{private_token}&sudo=#{user.id}")
+
+ expect(response).to have_http_status(200)
+ expect(response).to match_response_schema('user/login')
+ end
+
+ it 'returns current user without private token when sudo not defined' do
+ get api("/user?private_token=#{private_token}")
+
+ expect(response).to have_http_status(200)
+ expect(response).to match_response_schema('user/public')
+ end
+ end
+ end
+
+ context 'with unauthenticated user' do
+ it "returns 401 error if user is unauthenticated" do
+ get api("/user")
+
+ expect(response).to have_http_status(401)
+ end
end
end