Skip to content

Commit 16951ee

Browse files
committed
Adjust error handling and add unit tests
1 parent 7059bae commit 16951ee

File tree

9 files changed

+299
-19
lines changed

9 files changed

+299
-19
lines changed

app/actions/user_create.rb

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,8 @@ class Error < StandardError
44
end
55

66
def create(message:)
7-
begin
8-
shadow_user = User.create_uaa_shadow_user(message.username, message.origin) if message.username && message.origin
9-
rescue CF::UAA::TargetError => e
10-
raise e unless e.info['error'] == 'scim_resource_already_exists'
11-
12-
existing_guid = e.info['user_id']
13-
end
14-
15-
user_guid = existing_guid || shadow_user['id'] || message.guid
7+
shadow_user = User.create_uaa_shadow_user(message.username, message.origin) if message.username && message.origin
8+
user_guid = shadow_user ? shadow_user['id'] : message.guid
169

1710
user = User.create(guid: user_guid)
1811
User.db.transaction do

app/controllers/v3/users_controller.rb

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,12 @@ def show
3939

4040
def create
4141
message = UserCreateMessage.new(hashed_params[:body])
42-
unauthorized! unless permission_queryer.can_write_globally? || org_managers_can_create_users?(message:)
42+
unauthorized! unless permission_queryer.can_write_globally? || org_managers_can_create_users?
4343
unprocessable!(message.errors.full_messages) unless message.valid?
4444

45+
# prevent org_managers from creating users by guid
46+
unauthorized! if !permission_queryer.can_write_globally? && !(!message.guid && org_managers_can_create_users?)
47+
4548
user = UserCreate.new.create(message:)
4649

4750
render status: :created, json: Presenters::V3::UserPresenter.new(user, uaa_users: User.uaa_users_info([user.guid]))
@@ -94,7 +97,7 @@ def user_not_found!
9497
resource_not_found!(:user)
9598
end
9699

97-
def org_managers_can_create_users?(message:)
98-
VCAP::CloudController::Config.config.get(:allow_user_creation_by_org_manager) && permission_queryer.is_org_manager? && !message.guid
100+
def org_managers_can_create_users?
101+
VCAP::CloudController::Config.config.get(:allow_user_creation_by_org_manager) && permission_queryer.is_org_manager?
99102
end
100103
end

app/messages/user_create_message.rb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ module VCAP::CloudController
44
class UserCreateMessage < MetadataBaseMessage
55
register_allowed_keys %i[guid origin username]
66

7-
87
class UserCreateValidator < ActiveModel::Validator
98
def validate(record)
109
if record.guid

lib/cloud_controller/dependency_locator.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ def uaa_username_lookup_client
289289
end
290290

291291
def uaa_shadow_user_creation_client
292-
client = config.get(:uaa, :clients)&.find { |client| client['name'] == 'cloud_controller_shadow_user_creation' }
292+
client = config.get(:uaa, :clients)&.find { |client_config| client_config['name'] == 'cloud_controller_shadow_user_creation' }
293293

294294
return unless client
295295

lib/cloud_controller/uaa/uaa_client.rb

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,12 @@ def origins_for_username(username)
9797
end
9898

9999
def create_shadow_user(username, origin)
100-
with_cache_retry { scim.add(:user, { username: username, origin: origin, emails: [{ primary: true, value: username}]}) }
101-
rescue CF::UAA::BadTarget => e
100+
with_cache_retry { scim.add(:user, { username: username, origin: origin, emails: [{ primary: true, value: username }] }) }
101+
rescue CF::UAA::TargetError => e
102+
raise e unless e.info['error'] == 'scim_resource_already_exists'
103+
104+
{ 'id' => e.info['user_id'] }
105+
rescue CF::UAA::UAAError => e
102106
logger.error("UAA request for creating a user failed: #{e.inspect}")
103107
raise UaaUnavailable
104108
end

spec/request/users_spec.rb

Lines changed: 154 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -720,7 +720,7 @@
720720
let(:uaa_client_id) { 'cc_routing' }
721721

722722
before do
723-
allow(uaa_client).to receive_messages(users_for_ids: {}, get_clients: [{ client_id: uaa_client_id }])
723+
allow(uaa_client).to receive_messages(users_for_ids: {})
724724
end
725725

726726
let(:api_call) { ->(user_headers) { post '/v3/users', params.to_json, user_headers } }
@@ -814,6 +814,159 @@
814814
end
815815
end
816816
end
817+
818+
context 'when "allow_user_creation_by_org_manager" is enabled' do
819+
before do
820+
TestConfig.override(allow_user_creation_by_org_manager: true)
821+
allow(CloudController::DependencyLocator.instance).to receive(:uaa_shadow_user_creation_client).and_return(uaa_client)
822+
end
823+
824+
describe 'when creating a user by guid' do
825+
before do
826+
allow(uaa_client).to receive(:users_for_ids).and_return({})
827+
end
828+
829+
let(:api_call) { ->(user_headers) { post '/v3/users', params.to_json, user_headers } }
830+
831+
let(:user_json) do
832+
{
833+
guid: params[:guid],
834+
created_at: iso8601,
835+
updated_at: iso8601,
836+
username: nil,
837+
presentation_name: params[:guid],
838+
origin: nil,
839+
metadata: {
840+
labels: {},
841+
annotations: {}
842+
},
843+
links: {
844+
self: { href: %r{#{Regexp.escape(link_prefix)}/v3/users/#{params[:guid]}} }
845+
}
846+
}
847+
end
848+
849+
let(:expected_codes_and_responses) do
850+
h = Hash.new(
851+
code: 403
852+
)
853+
h['admin'] = {
854+
code: 201,
855+
response_object: user_json
856+
}
857+
h
858+
end
859+
860+
it_behaves_like 'permissions for single object endpoint', ALL_PERMISSIONS
861+
end
862+
863+
describe 'when creating a user by username and origin' do
864+
let(:params) do
865+
{
866+
username: 'some-user',
867+
origin: 'idp.local'
868+
}
869+
end
870+
let(:user_guid) { 'new-user-guid' }
871+
872+
let(:api_call) { ->(user_headers) { post '/v3/users', params.to_json, user_headers } }
873+
874+
let(:user_json) do
875+
{
876+
guid: user_guid,
877+
created_at: iso8601,
878+
updated_at: iso8601,
879+
username: params[:username],
880+
presentation_name: params[:username],
881+
origin: params[:origin],
882+
metadata: {
883+
labels: {},
884+
annotations: {}
885+
},
886+
links: {
887+
self: { href: %r{#{Regexp.escape(link_prefix)}/v3/users/#{user_guid}} }
888+
}
889+
}
890+
end
891+
892+
let(:expected_codes_and_responses) do
893+
h = Hash.new(
894+
code: 403
895+
)
896+
h['admin'] = {
897+
code: 201,
898+
response_object: user_json
899+
}
900+
h['org_manager'] = {
901+
code: 201,
902+
response_object: user_json
903+
}
904+
h
905+
end
906+
907+
before do
908+
allow(uaa_client).to receive(:users_for_ids).with(['new-user-guid']).and_return(
909+
{
910+
user_guid => {
911+
'username' => 'some-user',
912+
'origin' => 'idp.local'
913+
}
914+
}
915+
)
916+
allow(uaa_client).to receive(:create_shadow_user).and_return({ 'id' => user_guid })
917+
end
918+
919+
it_behaves_like 'permissions for single object endpoint', ALL_PERMISSIONS
920+
end
921+
922+
context 'when parameters are invalid' do
923+
let(:params) do
924+
{
925+
guid: user_guid,
926+
username: 'some-user',
927+
origin: 'idp.local'
928+
}
929+
end
930+
let(:user_guid) { 'new-user-guid' }
931+
932+
let(:api_call) { ->(user_headers) { post '/v3/users', params.to_json, user_headers } }
933+
934+
let(:user_json) do
935+
{
936+
guid: user_guid,
937+
created_at: iso8601,
938+
updated_at: iso8601,
939+
username: params[:username],
940+
presentation_name: params[:username],
941+
origin: params[:origin],
942+
metadata: {
943+
labels: {},
944+
annotations: {}
945+
},
946+
links: {
947+
self: { href: %r{#{Regexp.escape(link_prefix)}/v3/users/#{user_guid}} }
948+
}
949+
}
950+
end
951+
952+
let(:expected_codes_and_responses) do
953+
h = Hash.new(
954+
code: 403
955+
)
956+
h['admin'] = {
957+
code: 422,
958+
response_object: user_json
959+
}
960+
h['org_manager'] = {
961+
code: 422,
962+
response_object: user_json
963+
}
964+
h
965+
end
966+
967+
it_behaves_like 'permissions for single object endpoint', ALL_PERMISSIONS
968+
end
969+
end
817970
end
818971

819972
describe 'PATCH /v3/users/:guid' do

spec/unit/actions/user_create_spec.rb

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,16 @@ module VCAP::CloudController
5555
end
5656

5757
describe 'creating users' do
58-
context 'when creating a UAA user' do
58+
before do
59+
allow(User).to receive(:create_uaa_shadow_user).and_return({ 'id' => guid })
60+
end
61+
62+
context 'when creating a UAA user by guid' do
5963
let(:message) do
6064
UserCreateMessage.new({ guid:, metadata: })
6165
end
6266

63-
it 'creates a user' do
67+
it 'creates a user in ccdb' do
6468
created_user = nil
6569
expect do
6670
created_user = subject.create(message:)
@@ -73,6 +77,46 @@ module VCAP::CloudController
7377
)
7478
expect(created_user).to have_annotations({ key_name: 'anno', value: 'tations' })
7579
end
80+
81+
it 'does not create a shadow user in uaa' do
82+
subject.create(message:)
83+
expect(User).not_to have_received(:create_uaa_shadow_user)
84+
end
85+
end
86+
87+
context 'when creating a UAA user by username and origin' do
88+
let(:message) do
89+
UserCreateMessage.new({ username:, origin:, metadata: })
90+
end
91+
92+
it 'creates a user in ccdb' do
93+
created_user = nil
94+
expect do
95+
created_user = subject.create(message:)
96+
end.to change(User, :count).by(1)
97+
98+
expect(created_user.guid).to eq guid
99+
expect(created_user).to have_labels(
100+
{ prefix: nil, key_name: 'release', value: 'stable' },
101+
{ prefix: 'seriouseats.com', key_name: 'potato', value: 'mashed' }
102+
)
103+
expect(created_user).to have_annotations({ key_name: 'anno', value: 'tations' })
104+
end
105+
106+
it 'creates a shadow user in uaa' do
107+
subject.create(message:)
108+
expect(User).to have_received(:create_uaa_shadow_user).with(username, origin)
109+
end
110+
111+
context 'when an UaaUnavailable error is raised' do
112+
before do
113+
allow(User).to receive(:create_uaa_shadow_user).and_raise(UaaUnavailable)
114+
end
115+
116+
it 'raises the error' do
117+
expect { subject.create(message:) }.to raise_error(UaaUnavailable)
118+
end
119+
end
76120
end
77121

78122
context 'when creating a UAA client' do

spec/unit/lib/uaa/uaa_client_spec.rb

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,61 @@ module VCAP::CloudController
469469
end
470470
end
471471

472+
describe '#create_shadow_user' do
473+
context 'when user does not exist in uaa' do
474+
before do
475+
scim = instance_double(CF::UAA::Scim)
476+
allow(scim).to receive(:add).and_return({ 'id' => '7b5fe025-fe6b-4f82-af6e-2534523233a6', 'username' => '[email protected]', 'origin' => 'idp.local' })
477+
allow(uaa_client).to receive_messages(scim:)
478+
end
479+
480+
it 'creates the user and returns the id' do
481+
shadow_user = uaa_client.create_shadow_user('[email protected]', 'idp.local')
482+
expect(shadow_user['id']).to eq('7b5fe025-fe6b-4f82-af6e-2534523233a6')
483+
expect(shadow_user['username']).to eq('[email protected]')
484+
expect(shadow_user['origin']).to eq('idp.local')
485+
end
486+
end
487+
488+
context 'when user already exists in uaa' do
489+
before do
490+
scim = instance_double(CF::UAA::Scim)
491+
allow(scim).to receive(:add).and_raise(
492+
CF::UAA::TargetError.new({
493+
'user_id' => 'ea2824df-b2b5-4444-a18f-b4c7b227a184',
494+
'error_description' => 'Username already in use: [email protected]',
495+
'verified' => true,
496+
'active' => true,
497+
'error' => 'scim_resource_already_exists',
498+
'message' => 'Username already in use: [email protected]'
499+
})
500+
)
501+
allow(uaa_client).to receive_messages(scim:)
502+
end
503+
504+
it 'catches the exception and returns the id' do
505+
shadow_user = uaa_client.create_shadow_user('[email protected]', 'idp.local')
506+
expect(shadow_user['id']).to eq('ea2824df-b2b5-4444-a18f-b4c7b227a184')
507+
end
508+
end
509+
510+
context 'when something goes wrong communicating with uaa' do
511+
let(:uaa_error) { CF::UAA::UAAError.new('some error') }
512+
let(:mock_logger) { double(:steno_logger, error: nil) }
513+
514+
before do
515+
scim = instance_double(CF::UAA::Scim)
516+
allow(scim).to receive(:add).and_raise(uaa_error)
517+
allow(uaa_client).to receive_messages(scim: scim, logger: mock_logger)
518+
end
519+
520+
it 'logs the error and raises UaaUnavailable' do
521+
expect { uaa_client.create_shadow_user('[email protected]', 'idp.local') }.to raise_error(UaaUnavailable)
522+
expect(mock_logger).to have_received(:error).with("UAA request for creating a user failed: #{uaa_error.inspect}")
523+
end
524+
end
525+
end
526+
472527
describe '#id_for_username' do
473528
let(:username) { '[email protected]' }
474529

0 commit comments

Comments
 (0)