Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ext/couchbase
31 changes: 31 additions & 0 deletions ext/couchbase.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -4581,6 +4581,35 @@ cb_Backend_user_upsert(VALUE self, VALUE domain, VALUE user, VALUE timeout)
return Qnil;
}

static VALUE
cb_Backend_change_password(VALUE self, VALUE new_password, VALUE timeout)
{
const auto& cluster = cb_backend_to_cluster(self);

Check_Type(new_password, T_STRING);

try {
couchbase::core::operations::management::change_password_request req{};
cb_extract_timeout(req, timeout);
req.newPassword = cb_string_new(new_password);
auto barrier = std::make_shared<std::promise<couchbase::core::operations::management::change_password_response>>();
auto f = barrier->get_future();
cluster->execute(req, [barrier](couchbase::core::operations::management::change_password_response&& resp) {
barrier->set_value(std::move(resp));
});
if (auto resp = cb_wait_for_future(f); resp.ctx.ec) {
cb_throw_error_code(resp.ctx, "unable to change password");
}

return Qtrue;
} catch (const std::system_error& se) {
rb_exc_raise(cb_map_error_code(se.code(), fmt::format("failed to perform {}: {}", __func__, se.what()), false));
} catch (const ruby_exception& e) {
rb_exc_raise(e.exception_object());
}
return Qnil;
}

static void
cb_extract_group(const couchbase::core::management::rbac::group& entry, VALUE group)
{
Expand Down Expand Up @@ -8097,6 +8126,8 @@ init_backend(VALUE mCouchbase)
rb_define_method(cBackend, "group_drop", VALUE_FUNC(cb_Backend_group_drop), 2);
rb_define_method(cBackend, "group_upsert", VALUE_FUNC(cb_Backend_group_upsert), 2);

rb_define_method(cBackend, "change_password", VALUE_FUNC(cb_Backend_change_password), 2);

rb_define_method(cBackend, "cluster_enable_developer_preview!", VALUE_FUNC(cb_Backend_cluster_enable_developer_preview), 0);

rb_define_method(cBackend, "scope_get_all", VALUE_FUNC(cb_Backend_scope_get_all), 2);
Expand Down
19 changes: 19 additions & 0 deletions lib/couchbase/management/user_manager.rb
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,15 @@ def get_roles(options = GetRolesOptions.new)
end
end

# Changes the password of the currently authenticated user
#
# @param [ChangePasswordOptions] options
#
# @raise [ArgumentError]
def change_password(new_password, options = ChangePasswordOptions.new)
@backend.change_password(new_password, options.timeout)
end

# Gets a group
#
# @param [String] group_name name of the group to get
Expand Down Expand Up @@ -213,6 +222,16 @@ def initialize
end
end

class ChangePasswordOptions
# @return [Integer] the time in milliseconds allowed for the operation to complete
attr_accessor :timeout

# @yieldparam [ChangePasswordOptions] self
def initialize
yield self if block_given?
end
end

class GetRolesOptions
# @return [Integer] the time in milliseconds allowed for the operation to complete
attr_accessor :timeout
Expand Down
91 changes: 91 additions & 0 deletions test/user_manager_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# Copyright 2020-2021 Couchbase, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

require_relative "test_helper"

module Couchbase
class UserManagerTest < Minitest::Test
include TestUtilities

def setup
connect
@test_username = "test_user"
@test_password = "a_password"
@test_user = Management::User.new do |u|
u.username = @test_username
u.password = @test_password
u.roles = [
Management::Role.new do |r|
r.name = "data_reader"
r.bucket = "*"
end,
Management::Role.new do |r|
r.name = "query_select"
r.bucket = "*"
end,
Management::Role.new do |r|
r.name = "data_writer"
r.bucket = "*"
end,
Management::Role.new do |r|
r.name = "query_insert"
r.bucket = "*"
end,
Management::Role.new do |r|
r.name = "query_delete"
r.bucket = "*"
end,
Management::Role.new do |r|
r.name = "query_manage_index"
r.bucket = "*"
end,
]
end
@cluster.users.upsert_user(@test_user)
end

def teardown
connect
@cluster.users.drop_user(@test_username)
disconnect
end

def test_change_password
skip("#{name}: CAVES does not support change_password") if use_caves?

# Connect to the cluster with the test user
orig_options = Cluster::ClusterOptions.new
orig_options.authenticate(@test_username, @test_password)
@cluster = Cluster.connect(@env.connection_string, orig_options)

# Change the test user's password
new_password = "a_new_password"
@cluster.users.change_password(new_password)

# Verify that the connection fails with the old password
assert_raises(Error::AuthenticationFailure) { Cluster.connect(@env.connection_string, orig_options) }

# Verify that the connection succeeds with the new password
new_options = Cluster::ClusterOptions.new
new_options.authenticate(@test_username, new_password)
@cluster = Cluster.connect(@env.connection_string, new_options)

# Change the password back to the original one
@cluster.users.change_password(@test_password)

# Verify that the connection fails with the new password
assert_raises(Error::AuthenticationFailure) { Cluster.connect(@env.connection_string, new_options) }
end
end
end