Skip to content
Draft
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
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -247,8 +247,9 @@ Notice how some of the configuration values are wrapped in angle brackets (e.g.

// Defines options for the "Basic" authentication scheme.
"basic": {
// The amount of time before a user's authentication
// token expires.
// The amount of time before a user's authentication token expires.
// The lifetime of a token will be extended by this amount on each
// use as long as the token has not expired.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Use" might be a bit vague to an admin unfamiliar with how we use our tokens. "Request" might help clear up that this is used on every basic HTTP API request.

Suggested change
// use as long as the token has not expired.
// request as long as the token has not expired.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

consider...

- has not expired
+ has not yet expired

Comment on lines +250 to +252
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it's just me, but this wording feels a little inaccurate. The expiration of the token is set to timeout_in_seconds from the point in time of each use, not extended by that amount... right?

To me, it sounds like the expiration will be extended by (by default) an hour each time the token is used. So if I authenticate 4 times in a second, the expiration will be 4 hours away instead of 1 hour away.

Maybe I'm misunderstanding all this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your question indicates the wording isn't clear. Showing some math is likely a better fit. For example:

token_lifetime = <current_time> + <timeout_in_seconds>

The documentation would be updated to align with the idea too.

Thoughts?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think that's much clearer.

"timeout_in_seconds": 3600
},

Expand Down
10 changes: 10 additions & 0 deletions core/include/irods/private/http_api/process_stash.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,16 @@ namespace irods::http::process_stash
/// \since 4.2.12
auto find(const std::string& _key) -> std::optional<boost::any>;

/// Atomically executes operation on value mapped to a specific key.
///
/// This function is thread-safe.
///
/// \param[in] _key The string which maps to the value of interest.
/// \param[in] _visitor The operation to execute on the value mapped to the key.
///
/// \returns A boolean indicating whether the operation was executed.
auto visit(const std::string& _key, const std::function<void(boost::any&)>& _visitor) -> bool;

/// Removes a value from the process stash.
///
/// This function is thread-safe.
Expand Down
130 changes: 72 additions & 58 deletions core/src/common.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -339,81 +339,95 @@ namespace irods::http
boost::trim(bearer_token);
logging::debug("{}: Bearer token: [{}]", __func__, bearer_token);

const auto& config = irods::http::globals::configuration();

bool token_expired = false;
authenticated_client_info client_info_copy;

// Verify the bearer token is known to the server. If not, return an error.
auto mapped_value{irods::http::process_stash::find(bearer_token)};
if (!mapped_value.has_value()) {
const auto& config = irods::http::globals::configuration();

// It's possible that the admin didn't include the OIDC configuration stanza.
// This use-case is allowed, therefore we check for the OIDC configuration before
// attempting to access it. Without this logic, the server would crash.
static const auto oidc_conf_exists{
config.contains(nlohmann::json::json_pointer{"/http_server/authentication/openid_connect"})};
if (oidc_conf_exists) {
nlohmann::json json_res;
const auto& validation_method{irods::http::globals::oidc_configuration()
.at("access_token_validation_method")
.get_ref<const std::string&>()};

// Try parsing token as JWT Access Token
if (validation_method == "local_validation") {
try {
auto token{jwt::decode<jwt::traits::nlohmann_json>(bearer_token)};
auto possible_json_res{openid::validate_using_local_validation(token)};

if (possible_json_res) {
json_res = *possible_json_res;
}
}
// Parsing of the token failed, this is not a JWT access token
catch (const std::exception& e) {
logging::debug("{}: {}", __func__, e.what());
}
}
const auto found_token = irods::http::process_stash::visit(bearer_token, [&](boost::any& _v) {
auto& client_info = boost::any_cast<authenticated_client_info&>(_v);
const auto now = std::chrono::steady_clock::now();

if (now >= client_info.expires_at) {
token_expired = true;
return;
}

// Refresh the lifetime of the bearer token.
static const auto token_lifetime =
config.at(nlohmann::json::json_pointer{"/http_server/authentication/basic/timeout_in_seconds"})
.get<int>();
client_info.expires_at = now + std::chrono::seconds{token_lifetime};

client_info_copy = client_info;
});

if (found_token) {
if (token_expired) {
logging::info("{}: Session for bearer token [{}] has expired.", __func__, bearer_token);
return {.response = fail(status_type::unauthorized)};
}

logging::trace("{}: Client is authenticated.", __func__);
return {.client_info = std::move(client_info_copy)};
}

// It's possible that the admin didn't include the OIDC configuration stanza.
// This use-case is allowed, therefore we check for the OIDC configuration before
// attempting to access it. Without this logic, the server would crash.
static const auto oidc_conf_exists{
config.contains(nlohmann::json::json_pointer{"/http_server/authentication/openid_connect"})};
if (oidc_conf_exists) {
nlohmann::json json_res;
const auto& validation_method{irods::http::globals::oidc_configuration()
.at("access_token_validation_method")
.get_ref<const std::string&>()};

// Try parsing token as JWT Access Token
if (validation_method == "local_validation") {
try {
auto token{jwt::decode<jwt::traits::nlohmann_json>(bearer_token)};
auto possible_json_res{openid::validate_using_local_validation(token)};

// Use introspection endpoint if it exists
static const auto introspection_endpoint_exists{
irods::http::globals::oidc_endpoint_configuration().contains("introspection_endpoint")};
if (introspection_endpoint_exists && validation_method == "introspection") {
auto possible_json_res{openid::validate_using_introspection_endpoint(bearer_token)};
if (possible_json_res) {
json_res = *possible_json_res;
}
}

if (json_res.empty()) {
logging::error("{}: Could not find bearer token matching [{}].", __func__, bearer_token);
return {.response = fail(status_type::unauthorized)};
// Parsing of the token failed, this is not a JWT access token
catch (const std::exception& e) {
logging::debug("{}: {}", __func__, e.what());
}
}

// Do mapping of user to irods user
auto user{map_json_to_user(json_res)};
if (user) {
return {.client_info = {.username = *std::move(user)}};
// Use introspection endpoint if it exists
static const auto introspection_endpoint_exists{
irods::http::globals::oidc_endpoint_configuration().contains("introspection_endpoint")};
if (introspection_endpoint_exists && validation_method == "introspection") {
auto possible_json_res{openid::validate_using_introspection_endpoint(bearer_token)};
if (possible_json_res) {
json_res = *possible_json_res;
}
}

logging::warn("{}: Could not find a matching user.", __func__);
if (json_res.empty()) {
logging::info("{}: Could not find bearer token matching [{}].", __func__, bearer_token);
return {.response = fail(status_type::unauthorized)};
}

logging::debug("{}: No 'openid_connect' stanza found in server configuration.", __func__);
logging::error("{}: Could not find bearer token matching [{}].", __func__, bearer_token);
return {.response = fail(status_type::unauthorized)};
}

auto* client_info{boost::any_cast<authenticated_client_info>(&*mapped_value)};
if (client_info == nullptr) {
logging::error("{}: Could not find bearer token matching [{}].", __func__, bearer_token);
return {.response = fail(status_type::unauthorized)};
}
// Do mapping of user to irods user
auto user{map_json_to_user(json_res)};
if (user) {
return {.client_info = {.username = *std::move(user)}};
}

if (std::chrono::steady_clock::now() >= client_info->expires_at) {
logging::error("{}: Session for bearer token [{}] has expired.", __func__, bearer_token);
logging::warn("{}: Could not find a matching user.", __func__);
return {.response = fail(status_type::unauthorized)};
}

logging::trace("{}: Client is authenticated.", __func__);
return {.client_info = std::move(*client_info)};
logging::debug("{}: No [openid_connect] stanza found in server configuration.", __func__);
logging::info("{}: Could not find bearer token matching [{}].", __func__, bearer_token);
return {.response = fail(status_type::unauthorized)};
} // resolve_client_identity

auto execute_operation(
Expand Down
11 changes: 11 additions & 0 deletions core/src/process_stash.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,17 @@ namespace irods::http::process_stash
return std::nullopt;
} // find

auto visit(const std::string& _key, const std::function<void(boost::any&)>& _visitor) -> bool
{
std::lock_guard write_lock{g_mtx};
if (auto iter = g_stash.find(_key); iter != std::end(g_stash)) {
_visitor(iter->second);
return true;
}

return false;
} // visit

auto erase(const std::string& _key) -> bool
{
std::lock_guard lock{g_mtx};
Expand Down
1 change: 0 additions & 1 deletion endpoints/authentication/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
#include "irods/private/http_api/globals.hpp"
#include "irods/private/http_api/log.hpp"
#include "irods/private/http_api/process_stash.hpp"
#include "irods/private/http_api/openid.hpp"
#include "irods/private/http_api/session.hpp"
#include "irods/private/http_api/transport.hpp"
#include "irods/private/http_api/version.hpp"
Expand Down
Loading