| #include "oauth_utils.h" |
| |
| #include <exception> |
| #include <optional> |
| #include <string> |
| #include <string_view> |
| |
| #include "absl/log/log.h" |
| #include "absl/strings/match.h" |
| #include "absl/strings/str_cat.h" |
| #include "redfish_v1.pb.h" |
| #include "zatar/x509_certificate.h" |
| #include <nlohmann/json.hpp> |
| #include "jwt-cpp/jwt.h" |
| #include "jwt-cpp/traits/nlohmann-json/defaults.h" |
| #include "config_parser.h" |
| #include "g3/grpc_headers.h" |
| |
| namespace milotic::authz { |
| |
| std::string GenerateOAuthSubject(const PeerSpiffeIdentity& peer_identity) { |
| std::string subject; |
| |
| absl::StrAppend(&subject, "URI:", peer_identity.spiffe_id); |
| if (peer_identity.fqdn) { |
| absl::StrAppend(&subject, ", ", "DNS:", *peer_identity.fqdn); |
| } |
| |
| return subject; |
| } |
| |
| std::optional<std::string> GetOAuthTokenFromRequest( |
| const ::redfish::v1::Request& request) { |
| // Both field name of the Authorization header and the token type are case |
| // insensitive |
| // References |
| // https://www.rfc-editor.org/rfc/rfc7230#section-3.2 |
| // https://www.rfc-editor.org/rfc/rfc6749#section-5.1 |
| for (const auto& [key, value] : request.headers()) { |
| constexpr std::string_view kBearer = "Bearer "; |
| if (absl::EqualsIgnoreCase(key, "Authorization") && |
| absl::StartsWithIgnoreCase(value, kBearer)) { |
| return value.substr(kBearer.size()); |
| } |
| } |
| return std::nullopt; |
| } |
| |
| grpc::Status VerifyAndExtractRoleFromToken(const std::string& token, |
| const std::string& public_key, |
| const std::string& expected_subject, |
| const std::string& expected_audience, |
| const std::string& expected_issuer, |
| std::string& redfish_role) { |
| try { |
| auto verifier = jwt::verify() |
| .allow_algorithm(jwt::algorithm::rs256(public_key)) |
| .with_subject(expected_subject) |
| .with_audience(expected_audience) |
| .with_issuer(expected_issuer); |
| auto decoded = jwt::decode(token); |
| verifier.verify(decoded); |
| |
| nlohmann::json scope = decoded.get_payload_claim("scope").to_json(); |
| const std::string* scope_str = scope.get_ptr<const std::string*>(); |
| if (scope_str == nullptr) { |
| return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, |
| "scope is not a string"); |
| } |
| constexpr std::string_view kRedfishRole = "Redfish.Role."; |
| if (!absl::StartsWith(*scope_str, kRedfishRole)) { |
| return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, |
| "scope doesn't start with \"Redfish.Role.\""); |
| } |
| redfish_role = scope_str->substr(kRedfishRole.size()); |
| return grpc::Status::OK; |
| } catch (const std::exception& e) { |
| // TODO(nanzhou) enumerate all the possible expceptions in jwt-cpp |
| LOG(WARNING) << "Token verification failed!"; |
| LOG(WARNING) << "Token is " << token; |
| LOG(WARNING) << "Public key is " << public_key; |
| LOG(WARNING) << "Expected subject is " << expected_subject |
| << "; Expected audience is " << expected_audience |
| << "; Expected issuer is " << expected_issuer; |
| return grpc::Status(grpc::StatusCode::INTERNAL, e.what()); |
| } |
| } |
| |
| } // namespace milotic::authz |