| #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 |