|
| 1 | +// Copyright 2017 Google Inc. All Rights Reserved. |
| 2 | +// |
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | +// you may not use this file except in compliance with the License. |
| 5 | +// You may obtain a copy of the License at |
| 6 | +// |
| 7 | +// http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | +// |
| 9 | +// Unless required by applicable law or agreed to in writing, software |
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | +// See the License for the specific language governing permissions and |
| 13 | +// limitations under the License. |
| 14 | +// |
| 15 | +//////////////////////////////////////////////////////////////////////////////// |
| 16 | +#include "contrib/endpoints/src/api_manager/check_security_rules.h" |
| 17 | +#include <iostream> |
| 18 | +#include <sstream> |
| 19 | +#include "contrib/endpoints/src/api_manager/auth/lib/json_util.h" |
| 20 | +#include "contrib/endpoints/src/api_manager/firebase_rules/firebase_request.h" |
| 21 | +#include "contrib/endpoints/src/api_manager/utils/marshalling.h" |
| 22 | + |
| 23 | +using ::google::api_manager::auth::GetStringValue; |
| 24 | +using ::google::api_manager::firebase_rules::FirebaseRequest; |
| 25 | +using ::google::api_manager::utils::Status; |
| 26 | +const char kFirebaseAudience[] = |
| 27 | + "https://staging-firebaserules.sandbox.googleapis.com/" |
| 28 | + "google.firebase.rules.v1.FirebaseRulesService"; |
| 29 | + |
| 30 | +namespace google { |
| 31 | +namespace api_manager { |
| 32 | +namespace { |
| 33 | + |
| 34 | +const std::string kFailedFirebaseReleaseFetch = |
| 35 | + "Failed to fetch Firebase Release"; |
| 36 | +const std::string kFailedFirebaseTest = "Failed to execute Firebase Test"; |
| 37 | +const std::string kInvalidResponse = |
| 38 | + "Invalid JSON response from Firebase Service"; |
| 39 | +const std::string kV1 = "/v1"; |
| 40 | +const std::string kHttpGetMethod = "GET"; |
| 41 | +const std::string kProjects = "/projects"; |
| 42 | +const std::string kReleases = "/releases"; |
| 43 | +const std::string kRulesetName = "rulesetName"; |
| 44 | +const std::string kContentType = "Content-Type"; |
| 45 | +const std::string kApplication = "application/json"; |
| 46 | + |
| 47 | +std::string GetReleaseName(const context::RequestContext &context) { |
| 48 | + return context.service_context()->service_name() + ":" + |
| 49 | + context.service_context()->service().apis(0).version(); |
| 50 | +} |
| 51 | + |
| 52 | +std::string GetReleaseUrl(const context::RequestContext &context) { |
| 53 | + return context.service_context()->config()->GetFirebaseServer() + kV1 + |
| 54 | + kProjects + "/" + context.service_context()->project_id() + kReleases + |
| 55 | + "/" + GetReleaseName(context); |
| 56 | +} |
| 57 | + |
| 58 | +// An AuthzChecker object is created for every incoming request. It does |
| 59 | +// authorizaiton by calling Firebase Rules service. |
| 60 | +class AuthzChecker : public std::enable_shared_from_this<AuthzChecker> { |
| 61 | + public: |
| 62 | + // Constructor |
| 63 | + AuthzChecker(ApiManagerEnvInterface *env, |
| 64 | + auth::ServiceAccountToken *sa_token); |
| 65 | + |
| 66 | + // Check for Authorization success or failure |
| 67 | + void Check(std::shared_ptr<context::RequestContext> context, |
| 68 | + std::function<void(Status status)> continuation); |
| 69 | + |
| 70 | + private: |
| 71 | + // This method invokes the Firebase TestRuleset API endpoint as well as user |
| 72 | + // defined endpoints provided by the TestRulesetResponse. |
| 73 | + void CallNextRequest(std::function<void(Status status)> continuation); |
| 74 | + |
| 75 | + // Parse the response for GET RELEASE API call |
| 76 | + Status ParseReleaseResponse(const std::string &json_str, |
| 77 | + std::string *ruleset_id); |
| 78 | + |
| 79 | + // Invoke the HTTP call |
| 80 | + void HttpFetch(const std::string &url, const std::string &method, |
| 81 | + const std::string &request_body, |
| 82 | + auth::ServiceAccountToken::JWT_TOKEN_TYPE token_type, |
| 83 | + const std::string &audience, |
| 84 | + std::function<void(Status, std::string &&)> continuation); |
| 85 | + |
| 86 | + std::shared_ptr<AuthzChecker> GetPtr() { return shared_from_this(); } |
| 87 | + |
| 88 | + ApiManagerEnvInterface *env_; |
| 89 | + auth::ServiceAccountToken *sa_token_; |
| 90 | + std::unique_ptr<FirebaseRequest> request_handler_; |
| 91 | +}; |
| 92 | + |
| 93 | +AuthzChecker::AuthzChecker(ApiManagerEnvInterface *env, |
| 94 | + auth::ServiceAccountToken *sa_token) |
| 95 | + : env_(env), sa_token_(sa_token) {} |
| 96 | + |
| 97 | +void AuthzChecker::Check( |
| 98 | + std::shared_ptr<context::RequestContext> context, |
| 99 | + std::function<void(Status status)> final_continuation) { |
| 100 | + // TODO: Check service config to see if "useSecurityRules" is specified. |
| 101 | + // If so, call Firebase Rules service TestRuleset API. |
| 102 | + |
| 103 | + if (!context->service_context()->IsRulesCheckEnabled() || |
| 104 | + context->method() == nullptr || !context->method()->auth()) { |
| 105 | + env_->LogDebug("Skipping Firebase Rules checks since it is disabled."); |
| 106 | + final_continuation(Status::OK); |
| 107 | + return; |
| 108 | + } |
| 109 | + |
| 110 | + // Fetch the Release attributes and get ruleset name. |
| 111 | + auto checker = GetPtr(); |
| 112 | + HttpFetch(GetReleaseUrl(*context), kHttpGetMethod, "", |
| 113 | + auth::ServiceAccountToken::JWT_TOKEN_FOR_FIREBASE, |
| 114 | + kFirebaseAudience, [context, final_continuation, checker]( |
| 115 | + Status status, std::string &&body) { |
| 116 | + std::string ruleset_id; |
| 117 | + if (status.ok()) { |
| 118 | + checker->env_->LogDebug( |
| 119 | + std::string("GetReleasName succeeded with ") + body); |
| 120 | + status = checker->ParseReleaseResponse(body, &ruleset_id); |
| 121 | + } else { |
| 122 | + checker->env_->LogError(std::string("GetReleaseName for ") + |
| 123 | + GetReleaseUrl(*context.get()) + |
| 124 | + " with status " + status.ToString()); |
| 125 | + status = Status(Code::INTERNAL, kFailedFirebaseReleaseFetch); |
| 126 | + } |
| 127 | + |
| 128 | + // If the parsing of the release body is successful, then call the |
| 129 | + // Test Api for firebase rules service. |
| 130 | + if (status.ok()) { |
| 131 | + checker->request_handler_ = std::unique_ptr<FirebaseRequest>( |
| 132 | + new FirebaseRequest(ruleset_id, checker->env_, context)); |
| 133 | + checker->CallNextRequest(final_continuation); |
| 134 | + } else { |
| 135 | + final_continuation(status); |
| 136 | + } |
| 137 | + }); |
| 138 | +} |
| 139 | + |
| 140 | +void AuthzChecker::CallNextRequest( |
| 141 | + std::function<void(Status status)> continuation) { |
| 142 | + if (request_handler_->is_done()) { |
| 143 | + continuation(request_handler_->RequestStatus()); |
| 144 | + return; |
| 145 | + } |
| 146 | + |
| 147 | + auto checker = GetPtr(); |
| 148 | + firebase_rules::HttpRequest http_request = request_handler_->GetHttpRequest(); |
| 149 | + HttpFetch(http_request.url, http_request.method, http_request.body, |
| 150 | + http_request.token_type, http_request.audience, |
| 151 | + [continuation, checker](Status status, std::string &&body) { |
| 152 | + |
| 153 | + checker->env_->LogError(std::string("Response Body = ") + body); |
| 154 | + if (status.ok() && !body.empty()) { |
| 155 | + checker->request_handler_->UpdateResponse(body); |
| 156 | + checker->CallNextRequest(continuation); |
| 157 | + } else { |
| 158 | + checker->env_->LogError( |
| 159 | + std::string("Test API failed with ") + |
| 160 | + (status.ok() ? "Empty Response" : status.ToString())); |
| 161 | + status = Status(Code::INTERNAL, kFailedFirebaseTest); |
| 162 | + continuation(status); |
| 163 | + } |
| 164 | + }); |
| 165 | +} |
| 166 | + |
| 167 | +Status AuthzChecker::ParseReleaseResponse(const std::string &json_str, |
| 168 | + std::string *ruleset_id) { |
| 169 | + grpc_json *json = grpc_json_parse_string_with_len( |
| 170 | + const_cast<char *>(json_str.data()), json_str.length()); |
| 171 | + |
| 172 | + if (!json) { |
| 173 | + return Status(Code::INVALID_ARGUMENT, kInvalidResponse); |
| 174 | + } |
| 175 | + |
| 176 | + Status status = Status::OK; |
| 177 | + const char *id = GetStringValue(json, kRulesetName.c_str()); |
| 178 | + *ruleset_id = (id == nullptr) ? "" : id; |
| 179 | + |
| 180 | + if (ruleset_id->empty()) { |
| 181 | + env_->LogError("Empty ruleset Id received from firebase service"); |
| 182 | + status = Status(Code::INTERNAL, kInvalidResponse); |
| 183 | + } else { |
| 184 | + env_->LogDebug(std::string("Received ruleset Id: ") + *ruleset_id); |
| 185 | + } |
| 186 | + |
| 187 | + grpc_json_destroy(json); |
| 188 | + return status; |
| 189 | +} |
| 190 | + |
| 191 | +void AuthzChecker::HttpFetch( |
| 192 | + const std::string &url, const std::string &method, |
| 193 | + const std::string &request_body, |
| 194 | + auth::ServiceAccountToken::JWT_TOKEN_TYPE token_type, |
| 195 | + const std::string &audience, |
| 196 | + std::function<void(Status, std::string &&)> continuation) { |
| 197 | + env_->LogDebug(std::string("Issue HTTP Request to url :") + url + |
| 198 | + " method : " + method + " body: " + request_body); |
| 199 | + |
| 200 | + std::unique_ptr<HTTPRequest> request(new HTTPRequest([continuation]( |
| 201 | + Status status, std::map<std::string, std::string> &&, |
| 202 | + std::string &&body) { continuation(status, std::move(body)); })); |
| 203 | + |
| 204 | + if (!request) { |
| 205 | + continuation(Status(Code::INTERNAL, "Out of memory"), ""); |
| 206 | + return; |
| 207 | + } |
| 208 | + |
| 209 | + request->set_method(method).set_url(url).set_auth_token( |
| 210 | + sa_token_->GetAuthToken(token_type, audience)); |
| 211 | + |
| 212 | + if (!request_body.empty()) { |
| 213 | + request->set_header(kContentType, kApplication).set_body(request_body); |
| 214 | + } |
| 215 | + |
| 216 | + env_->RunHTTPRequest(std::move(request)); |
| 217 | +} |
| 218 | + |
| 219 | +} // namespace |
| 220 | + |
| 221 | +void CheckSecurityRules(std::shared_ptr<context::RequestContext> context, |
| 222 | + std::function<void(Status status)> continuation) { |
| 223 | + std::shared_ptr<AuthzChecker> checker = std::make_shared<AuthzChecker>( |
| 224 | + context->service_context()->env(), |
| 225 | + context->service_context()->service_account_token()); |
| 226 | + checker->Check(context, continuation); |
| 227 | +} |
| 228 | + |
| 229 | +} // namespace api_manager |
| 230 | +} // namespace google |
0 commit comments