Commit 4de8a3da authored by Douwe Maan's avatar Douwe Maan

Merge branch 'jxterry-private-profile' into 'master'

Add an option to have a private profile on GitLab

See merge request gitlab-org/gitlab-ce!20387
parents adc327d3 99011a61
...@@ -99,7 +99,8 @@ class ProfilesController < Profiles::ApplicationController ...@@ -99,7 +99,8 @@ class ProfilesController < Profiles::ApplicationController
:username, :username,
:website_url, :website_url,
:organization, :organization,
:preferred_language :preferred_language,
:private_profile
) )
end end
end end
...@@ -13,6 +13,8 @@ class UsersController < ApplicationController ...@@ -13,6 +13,8 @@ class UsersController < ApplicationController
skip_before_action :authenticate_user! skip_before_action :authenticate_user!
before_action :user, except: [:exists] before_action :user, except: [:exists]
before_action :authorize_read_user_profile!,
only: [:calendar, :calendar_activities, :groups, :projects, :contributed_projects, :snippets]
def show def show
respond_to do |format| respond_to do |format|
...@@ -148,4 +150,8 @@ class UsersController < ApplicationController ...@@ -148,4 +150,8 @@ class UsersController < ApplicationController
def build_canonical_path(user) def build_canonical_path(user)
url_for(safe_params.merge(username: user.to_param)) url_for(safe_params.merge(username: user.to_param))
end end
def authorize_read_user_profile!
access_denied! unless can?(current_user, :read_user_profile, user)
end
end end
class PersonalProjectsFinder < UnionFinder class PersonalProjectsFinder < UnionFinder
include Gitlab::Allowable
def initialize(user, params = {}) def initialize(user, params = {})
@user = user @user = user
@params = params @params = params
...@@ -14,6 +16,8 @@ class PersonalProjectsFinder < UnionFinder ...@@ -14,6 +16,8 @@ class PersonalProjectsFinder < UnionFinder
# #
# Returns an ActiveRecord::Relation. # Returns an ActiveRecord::Relation.
def execute(current_user = nil) def execute(current_user = nil)
return Project.none unless can?(current_user, :read_user_profile, @user)
segments = all_projects(current_user) segments = all_projects(current_user)
find_union(segments, Project).includes(:namespace).order_updated_desc find_union(segments, Project).includes(:namespace).order_updated_desc
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
class UserRecentEventsFinder class UserRecentEventsFinder
prepend FinderWithCrossProjectAccess prepend FinderWithCrossProjectAccess
include FinderMethods include FinderMethods
include Gitlab::Allowable
requires_cross_project_access requires_cross_project_access
...@@ -21,6 +22,8 @@ class UserRecentEventsFinder ...@@ -21,6 +22,8 @@ class UserRecentEventsFinder
end end
def execute def execute
return Event.none unless can?(current_user, :read_user_profile, target_user)
recent_events(params[:offset] || 0) recent_events(params[:offset] || 0)
.joins(:project) .joins(:project)
.with_associations .with_associations
......
...@@ -42,7 +42,13 @@ module UsersHelper ...@@ -42,7 +42,13 @@ module UsersHelper
private private
def get_profile_tabs def get_profile_tabs
[:activity, :groups, :contributed, :projects, :snippets] tabs = []
if can?(current_user, :read_user_profile, @user)
tabs += [:activity, :groups, :contributed, :projects, :snippets]
end
tabs
end end
def get_current_user_menu_items def get_current_user_menu_items
......
...@@ -5,6 +5,9 @@ class UserPolicy < BasePolicy ...@@ -5,6 +5,9 @@ class UserPolicy < BasePolicy
desc "This is the ghost user" desc "This is the ghost user"
condition(:subject_ghost, scope: :subject, score: 0) { @subject.ghost? } condition(:subject_ghost, scope: :subject, score: 0) { @subject.ghost? }
desc "The profile is private"
condition(:private_profile, scope: :subject, score: 0) { @subject.private_profile? }
rule { ~restricted_public_level }.enable :read_user rule { ~restricted_public_level }.enable :read_user
rule { ~anonymous }.enable :read_user rule { ~anonymous }.enable :read_user
...@@ -12,4 +15,7 @@ class UserPolicy < BasePolicy ...@@ -12,4 +15,7 @@ class UserPolicy < BasePolicy
enable :destroy_user enable :destroy_user
enable :update_user enable :update_user
end end
rule { default }.enable :read_user_profile
rule { private_profile & ~(user_is_self | admin) }.prevent :read_user_profile
end end
...@@ -64,7 +64,8 @@ module Users ...@@ -64,7 +64,8 @@ module Users
:theme_id, :theme_id,
:twitter, :twitter,
:username, :username,
:website_url :website_url,
:private_profile
] ]
end end
......
...@@ -69,6 +69,12 @@ ...@@ -69,6 +69,12 @@
= f.text_field :location = f.text_field :location
= f.text_field :organization = f.text_field :organization
= f.text_area :bio, rows: 4, maxlength: 250, help: 'Tell us about yourself in fewer than 250 characters.' = f.text_area :bio, rows: 4, maxlength: 250, help: 'Tell us about yourself in fewer than 250 characters.'
%hr
%h5 Private profile
- private_profile_label = capture do
Don't display activity-related personal information on your profile
= link_to icon('question-circle'), help_page_path('user/profile/index.md', anchor: 'private-profile')
= f.check_box :private_profile, label: private_profile_label
.prepend-top-default.append-bottom-default .prepend-top-default.append-bottom-default
= f.submit 'Update profile settings', class: 'btn btn-success' = f.submit 'Update profile settings', class: 'btn btn-success'
= link_to 'Cancel', user_path(current_user), class: 'btn btn-cancel' = link_to 'Cancel', user_path(current_user), class: 'btn btn-cancel'
......
...@@ -23,8 +23,9 @@ ...@@ -23,8 +23,9 @@
= link_to new_abuse_report_path(user_id: @user.id, ref_url: request.referrer), class: 'btn', = link_to new_abuse_report_path(user_id: @user.id, ref_url: request.referrer), class: 'btn',
title: 'Report abuse', data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do title: 'Report abuse', data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do
= icon('exclamation-circle') = icon('exclamation-circle')
= link_to user_path(@user, rss_url_options), class: 'btn btn-default has-tooltip', title: 'Subscribe', 'aria-label': 'Subscribe' do - if can?(current_user, :read_user_profile, @user)
= icon('rss') = link_to user_path(@user, rss_url_options), class: 'btn btn-default has-tooltip', title: 'Subscribe', 'aria-label': 'Subscribe' do
= icon('rss')
- if current_user && current_user.admin? - if current_user && current_user.admin?
= link_to [:admin, @user], class: 'btn btn-default', title: 'View user in admin area', = link_to [:admin, @user], class: 'btn btn-default', title: 'View user in admin area',
data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
...@@ -40,10 +41,12 @@ ...@@ -40,10 +41,12 @@
= @user.name = @user.name
.cover-desc.member-date .cover-desc.member-date
%span.middle-dot-divider %p
@#{@user.username} %span.middle-dot-divider
%span.middle-dot-divider @#{@user.username}
Member since #{@user.created_at.to_date.to_s(:long)} - if can?(current_user, :read_user_profile, @user)
%span.middle-dot-divider
Member since #{@user.created_at.to_date.to_s(:long)}
.cover-desc .cover-desc
- unless @user.public_email.blank? - unless @user.public_email.blank?
...@@ -78,30 +81,31 @@ ...@@ -78,30 +81,31 @@
%p.profile-user-bio %p.profile-user-bio
= @user.bio = @user.bio
.scrolling-tabs-container - unless profile_tabs.empty?
.fade-left= icon('angle-left') .scrolling-tabs-container
.fade-right= icon('angle-right') .fade-left= icon('angle-left')
%ul.nav-links.user-profile-nav.scrolling-tabs.nav.nav-tabs .fade-right= icon('angle-right')
- if profile_tab?(:activity) %ul.nav-links.user-profile-nav.scrolling-tabs.nav.nav-tabs
%li.js-activity-tab - if profile_tab?(:activity)
= link_to user_path, data: { target: 'div#activity', action: 'activity', toggle: 'tab' } do %li.js-activity-tab
Activity = link_to user_path, data: { target: 'div#activity', action: 'activity', toggle: 'tab' } do
- if profile_tab?(:groups) Activity
%li.js-groups-tab - if profile_tab?(:groups)
= link_to user_groups_path, data: { target: 'div#groups', action: 'groups', toggle: 'tab', endpoint: user_groups_path(format: :json) } do %li.js-groups-tab
Groups = link_to user_groups_path, data: { target: 'div#groups', action: 'groups', toggle: 'tab', endpoint: user_groups_path(format: :json) } do
- if profile_tab?(:contributed) Groups
%li.js-contributed-tab - if profile_tab?(:contributed)
= link_to user_contributed_projects_path, data: { target: 'div#contributed', action: 'contributed', toggle: 'tab', endpoint: user_contributed_projects_path(format: :json) } do %li.js-contributed-tab
Contributed projects = link_to user_contributed_projects_path, data: { target: 'div#contributed', action: 'contributed', toggle: 'tab', endpoint: user_contributed_projects_path(format: :json) } do
- if profile_tab?(:projects) Contributed projects
%li.js-projects-tab - if profile_tab?(:projects)
= link_to user_projects_path, data: { target: 'div#projects', action: 'projects', toggle: 'tab', endpoint: user_projects_path(format: :json) } do %li.js-projects-tab
Personal projects = link_to user_projects_path, data: { target: 'div#projects', action: 'projects', toggle: 'tab', endpoint: user_projects_path(format: :json) } do
- if profile_tab?(:snippets) Personal projects
%li.js-snippets-tab - if profile_tab?(:snippets)
= link_to user_snippets_path, data: { target: 'div#snippets', action: 'snippets', toggle: 'tab', endpoint: user_snippets_path(format: :json) } do %li.js-snippets-tab
Snippets = link_to user_snippets_path, data: { target: 'div#snippets', action: 'snippets', toggle: 'tab', endpoint: user_snippets_path(format: :json) } do
Snippets
%div{ class: container_class } %div{ class: container_class }
.tab-content .tab-content
...@@ -137,3 +141,13 @@ ...@@ -137,3 +141,13 @@
.loading-status .loading-status
= spinner = spinner
- if profile_tabs.empty?
.row
.col-12
.svg-content
= image_tag 'illustrations/profile_private_mode.svg'
.col-12.text-center
.text-content
%h4
This user has a private profile
---
title: Add an option to have a private profile on GitLab.
merge_request: 20387
author: jxterry
type: added
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class AddPrivateProfileToUsers < ActiveRecord::Migration
DOWNTIME = false
def change
add_column :users, :private_profile, :boolean
end
end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20180704204006) do ActiveRecord::Schema.define(version: 20180722103201) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -2124,6 +2124,7 @@ ActiveRecord::Schema.define(version: 20180704204006) do ...@@ -2124,6 +2124,7 @@ ActiveRecord::Schema.define(version: 20180704204006) do
t.integer "theme_id", limit: 2 t.integer "theme_id", limit: 2
t.integer "accepted_term_id" t.integer "accepted_term_id"
t.string "feed_token" t.string "feed_token"
t.boolean "private_profile"
end end
add_index "users", ["admin"], name: "index_users_on_admin", using: :btree add_index "users", ["admin"], name: "index_users_on_admin", using: :btree
......
...@@ -105,7 +105,8 @@ GET /users ...@@ -105,7 +105,8 @@ GET /users
"can_create_group": true, "can_create_group": true,
"can_create_project": true, "can_create_project": true,
"two_factor_enabled": true, "two_factor_enabled": true,
"external": false "external": false,
"private_profile": false
}, },
{ {
"id": 2, "id": 2,
...@@ -135,7 +136,8 @@ GET /users ...@@ -135,7 +136,8 @@ GET /users
"can_create_group": true, "can_create_group": true,
"can_create_project": true, "can_create_project": true,
"two_factor_enabled": true, "two_factor_enabled": true,
"external": false "external": false,
"private_profile": false
} }
] ]
``` ```
...@@ -248,7 +250,8 @@ Parameters: ...@@ -248,7 +250,8 @@ Parameters:
"can_create_group": true, "can_create_group": true,
"can_create_project": true, "can_create_project": true,
"two_factor_enabled": true, "two_factor_enabled": true,
"external": false "external": false,
"private_profile": false
} }
``` ```
...@@ -288,6 +291,7 @@ Parameters: ...@@ -288,6 +291,7 @@ Parameters:
- `skip_confirmation` (optional) - Skip confirmation - true or false (default) - `skip_confirmation` (optional) - Skip confirmation - true or false (default)
- `external` (optional) - Flags the user as external - true or false(default) - `external` (optional) - Flags the user as external - true or false(default)
- `avatar` (optional) - Image file for user's avatar - `avatar` (optional) - Image file for user's avatar
- `private_profile (optional) - User's profile is private - true or false
## User modification ## User modification
...@@ -318,6 +322,7 @@ Parameters: ...@@ -318,6 +322,7 @@ Parameters:
- `skip_reconfirmation` (optional) - Skip reconfirmation - true or false (default) - `skip_reconfirmation` (optional) - Skip reconfirmation - true or false (default)
- `external` (optional) - Flags the user as external - true or false(default) - `external` (optional) - Flags the user as external - true or false(default)
- `avatar` (optional) - Image file for user's avatar - `avatar` (optional) - Image file for user's avatar
- `private_profile (optional) - User's profile is private - true or false
On password update, user will be forced to change it upon next login. On password update, user will be forced to change it upon next login.
Note, at the moment this method does only return a `404` error, Note, at the moment this method does only return a `404` error,
...@@ -382,7 +387,8 @@ GET /user ...@@ -382,7 +387,8 @@ GET /user
"can_create_group": true, "can_create_group": true,
"can_create_project": true, "can_create_project": true,
"two_factor_enabled": true, "two_factor_enabled": true,
"external": false "external": false,
"private_profile": false
} }
``` ```
...@@ -429,7 +435,8 @@ GET /user ...@@ -429,7 +435,8 @@ GET /user
"can_create_group": true, "can_create_group": true,
"can_create_project": true, "can_create_project": true,
"two_factor_enabled": true, "two_factor_enabled": true,
"external": false "external": false,
"private_profile": false
} }
``` ```
......
...@@ -68,6 +68,28 @@ Alternatively, you can follow [this detailed procedure from the GitLab Team Hand ...@@ -68,6 +68,28 @@ Alternatively, you can follow [this detailed procedure from the GitLab Team Hand
which also covers the case where you have projects hosted with which also covers the case where you have projects hosted with
[GitLab Pages](../project/pages/index.md). [GitLab Pages](../project/pages/index.md).
## Private profile
The following information will be hidden from the user profile page (https://gitlab.example.com/username) if this feature is enabled:
- Atom feed
- Date when account is created
- Activity tab
- Groups tab
- Contributed projects tab
- Personal projects tab
- Snippets tab
To enable private profile:
1. Navigate to your personal [profile settings](#profile-settings).
1. Check the "Private profile" option.
1. Hit **Update profile settings**.
NOTE: **Note:**
You and GitLab admins can see your the abovementioned information on your profile even if it is private.
## Troubleshooting ## Troubleshooting
### Why do I keep getting signed out? ### Why do I keep getting signed out?
......
...@@ -30,7 +30,7 @@ module API ...@@ -30,7 +30,7 @@ module API
end end
class User < UserBasic class User < UserBasic
expose :created_at expose :created_at, if: ->(user, opts) { Ability.allowed?(opts[:current_user], :read_user_profile, user) }
expose :bio, :location, :skype, :linkedin, :twitter, :website_url, :organization expose :bio, :location, :skype, :linkedin, :twitter, :website_url, :organization
end end
...@@ -55,6 +55,7 @@ module API ...@@ -55,6 +55,7 @@ module API
expose :can_create_project?, as: :can_create_project expose :can_create_project?, as: :can_create_project
expose :two_factor_enabled?, as: :two_factor_enabled expose :two_factor_enabled?, as: :two_factor_enabled
expose :external expose :external
expose :private_profile
end end
class UserWithAdmin < UserPublic class UserWithAdmin < UserPublic
......
...@@ -12,7 +12,7 @@ module API ...@@ -12,7 +12,7 @@ module API
key = Key.find(params[:id]) key = Key.find(params[:id])
present key, with: Entities::SSHKeyWithUser present key, with: Entities::SSHKeyWithUser, current_user: current_user
end end
end end
end end
......
...@@ -42,6 +42,7 @@ module API ...@@ -42,6 +42,7 @@ module API
optional :can_create_group, type: Boolean, desc: 'Flag indicating the user can create groups' optional :can_create_group, type: Boolean, desc: 'Flag indicating the user can create groups'
optional :external, type: Boolean, desc: 'Flag indicating the user is an external user' optional :external, type: Boolean, desc: 'Flag indicating the user is an external user'
optional :avatar, type: File, desc: 'Avatar image for user' optional :avatar, type: File, desc: 'Avatar image for user'
optional :private_profile, type: Boolean, desc: 'Flag indicating the user has a private profile'
optional :min_access_level, type: Integer, values: Gitlab::Access.all_values, desc: 'Limit by minimum access level of authenticated user' optional :min_access_level, type: Integer, values: Gitlab::Access.all_values, desc: 'Limit by minimum access level of authenticated user'
all_or_none_of :extern_uid, :provider all_or_none_of :extern_uid, :provider
end end
...@@ -97,7 +98,7 @@ module API ...@@ -97,7 +98,7 @@ module API
entity = current_user&.admin? ? Entities::UserWithAdmin : Entities::UserBasic entity = current_user&.admin? ? Entities::UserWithAdmin : Entities::UserBasic
users = users.preload(:identities, :u2f_registrations) if entity == Entities::UserWithAdmin users = users.preload(:identities, :u2f_registrations) if entity == Entities::UserWithAdmin
users, options = with_custom_attributes(users, with: entity) users, options = with_custom_attributes(users, { with: entity, current_user: current_user })
present paginate(users), options present paginate(users), options
end end
...@@ -114,7 +115,7 @@ module API ...@@ -114,7 +115,7 @@ module API
user = User.find_by(id: params[:id]) user = User.find_by(id: params[:id])
not_found!('User') unless user && can?(current_user, :read_user, user) not_found!('User') unless user && can?(current_user, :read_user, user)
opts = current_user&.admin? ? { with: Entities::UserWithAdmin } : { with: Entities::User } opts = { with: current_user&.admin? ? Entities::UserWithAdmin : Entities::User, current_user: current_user }
user, opts = with_custom_attributes(user, opts) user, opts = with_custom_attributes(user, opts)
present user, opts present user, opts
...@@ -140,7 +141,7 @@ module API ...@@ -140,7 +141,7 @@ module API
user = ::Users::CreateService.new(current_user, params).execute(skip_authorization: true) user = ::Users::CreateService.new(current_user, params).execute(skip_authorization: true)
if user.persisted? if user.persisted?
present user, with: Entities::UserPublic present user, with: Entities::UserPublic, current_user: current_user
else else
conflict!('Email has already been taken') if User conflict!('Email has already been taken') if User
.where(email: user.email) .where(email: user.email)
...@@ -199,7 +200,7 @@ module API ...@@ -199,7 +200,7 @@ module API
result = ::Users::UpdateService.new(current_user, user_params.except(:extern_uid, :provider).merge(user: user)).execute result = ::Users::UpdateService.new(current_user, user_params.except(:extern_uid, :provider).merge(user: user)).execute
if result[:status] == :success if result[:status] == :success
present user, with: Entities::UserPublic present user, with: Entities::UserPublic, current_user: current_user
else else
render_validation_error!(user) render_validation_error!(user)
end end
...@@ -546,7 +547,7 @@ module API ...@@ -546,7 +547,7 @@ module API
Entities::UserPublic Entities::UserPublic
end end
present current_user, with: entity present current_user, with: entity, current_user: current_user
end end
end end
......
...@@ -2,6 +2,8 @@ require 'spec_helper' ...@@ -2,6 +2,8 @@ require 'spec_helper'
describe UsersController do describe UsersController do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:private_user) { create(:user, private_profile: true) }
let(:public_user) { create(:user) }
describe 'GET #show' do describe 'GET #show' do
context 'with rendered views' do context 'with rendered views' do
...@@ -98,16 +100,47 @@ describe UsersController do ...@@ -98,16 +100,47 @@ describe UsersController do
expect(assigns(:events)).to be_empty expect(assigns(:events)).to be_empty
end end
it 'hides events if the user has a private profile' do
Gitlab::DataBuilder::Push.build_sample(project, private_user)
get :show, username: private_user.username, format: :json
expect(assigns(:events)).to be_empty
end
end end
end end
describe 'GET #calendar' do describe 'GET #calendar' do
it 'renders calendar' do context 'for user' do
sign_in(user) let(:project) { create(:project) }
before do
sign_in(user)
project.add_developer(user)
end
context 'with public profile' do
it 'renders calendar' do
push_data = Gitlab::DataBuilder::Push.build_sample(project, public_user)
EventCreateService.new.push(project, public_user, push_data)
get :calendar, username: public_user.username, format: :json
get :calendar, username: user.username, format: :json expect(response).to have_gitlab_http_status(200)
end
end
context 'with private profile' do
it 'does not render calendar' do
push_data = Gitlab::DataBuilder::Push.build_sample(project, private_user)
EventCreateService.new.push(project, private_user, push_data)
expect(response).to have_gitlab_http_status(200) get :calendar, username: private_user.username, format: :json
expect(response).to have_gitlab_http_status(:not_found)
end
end
end end
context 'forked project' do context 'forked project' do
...@@ -150,9 +183,26 @@ describe UsersController do ...@@ -150,9 +183,26 @@ describe UsersController do
expect(assigns(:calendar_date)).to eq(Date.parse('2014-07-31')) expect(assigns(:calendar_date)).to eq(Date.parse('2014-07-31'))
end end
it 'renders calendar_activities' do context 'for user' do
get :calendar_activities, username: user.username context 'with public profile' do
expect(response).to render_template('calendar_activities') it 'renders calendar_activities' do
push_data = Gitlab::DataBuilder::Push.build_sample(project, public_user)
EventCreateService.new.push(project, public_user, push_data)
get :calendar_activities, username: public_user.username
expect(assigns[:events]).not_to be_empty
end
end
context 'with private profile' do
it 'does not render calendar_activities' do
push_data = Gitlab::DataBuilder::Push.build_sample(project, private_user)
EventCreateService.new.push(project, private_user, push_data)
get :calendar_activities, username: private_user.username
expect(response).to have_gitlab_http_status(:not_found)
end
end
end end
end end
......
...@@ -3,15 +3,53 @@ require 'spec_helper' ...@@ -3,15 +3,53 @@ require 'spec_helper'
describe 'User page' do describe 'User page' do
let(:user) { create(:user) } let(:user) { create(:user) }
it 'shows all the tabs' do context 'with public profile' do
visit(user_path(user)) it 'shows all the tabs' do
visit(user_path(user))
page.within '.nav-links' do
expect(page).to have_link('Activity') page.within '.nav-links' do
expect(page).to have_link('Groups') expect(page).to have_link('Activity')
expect(page).to have_link('Contributed projects') expect(page).to have_link('Groups')
expect(page).to have_link('Personal projects') expect(page).to have_link('Contributed projects')
expect(page).to have_link('Snippets') expect(page).to have_link('Personal projects')
expect(page).to have_link('Snippets')
end
end
it 'does not show private profile message' do
visit(user_path(user))
expect(page).not_to have_content("This user has a private profile")
end
end
context 'with private profile' do
let(:user) { create(:user, private_profile: true) }
it 'shows no tab' do
visit(user_path(user))
expect(page).to have_css("div.profile-header")
expect(page).not_to have_css("ul.nav-links")
end
it 'shows private profile message' do
visit(user_path(user))
expect(page).to have_content("This user has a private profile")
end
it 'shows own tabs' do
sign_in(user)
visit(user_path(user))
page.within '.nav-links' do
expect(page).to have_link('Activity')
expect(page).to have_link('Groups')
expect(page).to have_link('Contributed projects')
expect(page).to have_link('Personal projects')
expect(page).to have_link('Snippets')
end
end end
end end
......
...@@ -29,11 +29,22 @@ describe UserRecentEventsFinder do ...@@ -29,11 +29,22 @@ describe UserRecentEventsFinder do
public_project.add_developer(current_user) public_project.add_developer(current_user)
end end
it 'returns all the events' do context 'when profile is public' do
expect(finder.execute).to include(private_event, internal_event, public_event) it 'returns all the events' do
expect(finder.execute).to include(private_event, internal_event, public_event)
end
end
context 'when profile is private' do
it 'returns no event' do
allow(Ability).to receive(:allowed?).and_call_original
allow(Ability).to receive(:allowed?).with(current_user, :read_user_profile, project_owner).and_return(false)
expect(finder.execute).to be_empty
end
end end
it 'does not include the events if the user cannot read cross project' do it 'does not include the events if the user cannot read cross project' do
expect(Ability).to receive(:allowed?).and_call_original
expect(Ability).to receive(:allowed?).with(current_user, :read_cross_project) { false } expect(Ability).to receive(:allowed?).with(current_user, :read_cross_project) { false }
expect(finder.execute).to be_empty expect(finder.execute).to be_empty
end end
......
...@@ -25,8 +25,20 @@ describe UsersHelper do ...@@ -25,8 +25,20 @@ describe UsersHelper do
allow(helper).to receive(:can?).and_return(true) allow(helper).to receive(:can?).and_return(true)
end end
it 'includes all the expected tabs' do context 'with public profile' do
expect(tabs).to include(:activity, :groups, :contributed, :projects, :snippets) it 'includes all the expected tabs' do
expect(tabs).to include(:activity, :groups, :contributed, :projects, :snippets)
end
end
context 'with private profile' do
before do
allow(helper).to receive(:can?).with(user, :read_user_profile, nil).and_return(false)
end
it 'is empty' do
expect(tabs).to be_empty
end
end end
end end
......
...@@ -11,6 +11,7 @@ describe API::Users do ...@@ -11,6 +11,7 @@ describe API::Users do
let(:ldap_blocked_user) { create(:omniauth_user, provider: 'ldapmain', state: 'ldap_blocked') } let(:ldap_blocked_user) { create(:omniauth_user, provider: 'ldapmain', state: 'ldap_blocked') }
let(:not_existing_user_id) { (User.maximum('id') || 0 ) + 10 } let(:not_existing_user_id) { (User.maximum('id') || 0 ) + 10 }
let(:not_existing_pat_id) { (PersonalAccessToken.maximum('id') || 0 ) + 10 } let(:not_existing_pat_id) { (PersonalAccessToken.maximum('id') || 0 ) + 10 }
let(:private_user) { create(:user, private_profile: true) }
describe 'GET /users' do describe 'GET /users' do
context "when unauthenticated" do context "when unauthenticated" do
...@@ -254,6 +255,13 @@ describe API::Users do ...@@ -254,6 +255,13 @@ describe API::Users do
expect(response).to match_response_schema('public_api/v4/user/admin') expect(response).to match_response_schema('public_api/v4/user/admin')
expect(json_response['is_admin']).to be(false) expect(json_response['is_admin']).to be(false)
end end
it "includes the `created_at` field for private users" do
get api("/users/#{private_user.id}", admin)
expect(response).to match_response_schema('public_api/v4/user/admin')
expect(json_response.keys).to include 'created_at'
end
end end
context 'for an anonymous user' do context 'for an anonymous user' do
...@@ -272,6 +280,20 @@ describe API::Users do ...@@ -272,6 +280,20 @@ describe API::Users do
expect(response).to have_gitlab_http_status(404) expect(response).to have_gitlab_http_status(404)
end end
it "returns the `created_at` field for public users" do
get api("/users/#{user.id}")
expect(response).to match_response_schema('public_api/v4/user/basic')
expect(json_response.keys).to include 'created_at'
end
it "does not return the `created_at` field for private users" do
get api("/users/#{private_user.id}")
expect(response).to match_response_schema('public_api/v4/user/basic')
expect(json_response.keys).not_to include 'created_at'
end
end end
it "returns a 404 error if user id not found" do it "returns a 404 error if user id not found" do
...@@ -374,6 +396,18 @@ describe API::Users do ...@@ -374,6 +396,18 @@ describe API::Users do
expect(new_user.recently_sent_password_reset?).to eq(true) expect(new_user.recently_sent_password_reset?).to eq(true)
end end
it "creates user with private profile" do
post api('/users', admin), attributes_for(:user, private_profile: true)
expect(response).to have_gitlab_http_status(201)
user_id = json_response['id']
new_user = User.find(user_id)
expect(new_user).not_to eq(nil)
expect(new_user.private_profile?).to eq(true)
end
it "does not create user with invalid email" do it "does not create user with invalid email" do
post api('/users', admin), post api('/users', admin),
email: 'invalid email', email: 'invalid email',
...@@ -583,6 +617,13 @@ describe API::Users do ...@@ -583,6 +617,13 @@ describe API::Users do
expect(user.reload.external?).to be_truthy expect(user.reload.external?).to be_truthy
end end
it "updates private profile" do
put api("/users/#{user.id}", admin), { private_profile: true }
expect(response).to have_gitlab_http_status(200)
expect(user.reload.private_profile).to eq(true)
end
it "does not update admin status" do it "does not update admin status" do
put api("/users/#{admin_user.id}", admin), { can_create_group: false } put api("/users/#{admin_user.id}", admin), { can_create_group: false }
......
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