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
12 changes: 11 additions & 1 deletion contrib/endpoints/src/api_manager/auth/service_account_token.cc
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,20 @@ Status ServiceAccountToken::SetClientAuthSecret(const std::string& secret) {
void ServiceAccountToken::SetAudience(JWT_TOKEN_TYPE type,
const std::string& audience) {
GOOGLE_CHECK(type >= 0 && type < JWT_TOKEN_TYPE_MAX);
jwt_tokens_[type].set_audience(audience);
if (jwt_tokens_[type].audience() != audience) {
jwt_tokens_[type].set_token("", 0);
jwt_tokens_[type].set_audience(audience);
}
}

const std::string& ServiceAccountToken::GetAuthToken(JWT_TOKEN_TYPE type) {
return GetAuthToken(type, jwt_tokens_[type].audience());
}

const std::string& ServiceAccountToken::GetAuthToken(
JWT_TOKEN_TYPE type, const std::string& audience) {
SetAudience(type, audience);

// Uses authentication secret if available.
if (!client_auth_secret_.empty()) {
GOOGLE_CHECK(type >= 0 && type < JWT_TOKEN_TYPE_MAX);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,13 @@ class ServiceAccountToken {
// Otherwise, use the access token fetched from metadata server.
const std::string& GetAuthToken(JWT_TOKEN_TYPE type);

// Gets the auth token to access Google services. This method accepts an
// audience parameter to set when generating JWT token.
// If client auth secret is specified, use it to calcualte JWT token.
// Otherwise, use the access token fetched from metadata server.
const std::string& GetAuthToken(JWT_TOKEN_TYPE type,
const std::string& audience);

private:
// Stores base token info. Used for both OAuth and JWT tokens.
class TokenInfo {
Expand Down
20 changes: 13 additions & 7 deletions contrib/endpoints/src/api_manager/check_security_rules.cc
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
using ::google::api_manager::auth::GetStringValue;
using ::google::api_manager::firebase_rules::FirebaseRequest;
using ::google::api_manager::utils::Status;
const char kFirebaseAudience[] =
"https://staging-firebaserules.sandbox.googleapis.com/"
"google.firebase.rules.v1.FirebaseRulesService";

namespace google {
namespace api_manager {
Expand Down Expand Up @@ -77,6 +80,7 @@ class AuthzChecker : public std::enable_shared_from_this<AuthzChecker> {
void HttpFetch(const std::string &url, const std::string &method,
const std::string &request_body,
auth::ServiceAccountToken::JWT_TOKEN_TYPE token_type,
const std::string &audience,
std::function<void(Status, std::string &&)> continuation);

std::shared_ptr<AuthzChecker> GetPtr() { return shared_from_this(); }
Expand Down Expand Up @@ -107,8 +111,8 @@ void AuthzChecker::Check(
auto checker = GetPtr();
HttpFetch(GetReleaseUrl(*context), kHttpGetMethod, "",
auth::ServiceAccountToken::JWT_TOKEN_FOR_FIREBASE,
[context, final_continuation, checker](Status status,
std::string &&body) {
kFirebaseAudience, [context, final_continuation, checker](
Status status, std::string &&body) {
std::string ruleset_id;
if (status.ok()) {
checker->env_->LogDebug(
Expand Down Expand Up @@ -143,16 +147,17 @@ void AuthzChecker::CallNextRequest(
auto checker = GetPtr();
firebase_rules::HttpRequest http_request = request_handler_->GetHttpRequest();
HttpFetch(http_request.url, http_request.method, http_request.body,
http_request.token_type,
http_request.token_type, http_request.audience,
[continuation, checker](Status status, std::string &&body) {

checker->env_->LogError(std::string("Response Body = ") + body);
if (status.ok()) {
if (status.ok() && !body.empty()) {
checker->request_handler_->UpdateResponse(body);
checker->CallNextRequest(continuation);
} else {
checker->env_->LogError(std::string("Test API failed with ") +
status.ToString());
checker->env_->LogError(
std::string("Test API failed with ") +
(status.ok() ? "Empty Response" : status.ToString()));
status = Status(Code::INTERNAL, kFailedFirebaseTest);
continuation(status);
}
Expand Down Expand Up @@ -187,6 +192,7 @@ void AuthzChecker::HttpFetch(
const std::string &url, const std::string &method,
const std::string &request_body,
auth::ServiceAccountToken::JWT_TOKEN_TYPE token_type,
const std::string &audience,
std::function<void(Status, std::string &&)> continuation) {
env_->LogDebug(std::string("Issue HTTP Request to url :") + url +
" method : " + method + " body: " + request_body);
Expand All @@ -201,7 +207,7 @@ void AuthzChecker::HttpFetch(
}

request->set_method(method).set_url(url).set_auth_token(
sa_token_->GetAuthToken(token_type));
sa_token_->GetAuthToken(token_type, audience));

if (!request_body.empty()) {
request->set_header(kContentType, kApplication).set_body(request_body);
Expand Down
76 changes: 46 additions & 30 deletions contrib/endpoints/src/api_manager/check_security_rules_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ using ::google::protobuf::RepeatedPtrField;
// Tuple with arg<0> = function name
// arg<1> = url, arg<2> = method, arg<3> = body.
using FuncTuple =
std::tuple<std::string, std::string, std::string, std::string>;
std::tuple<std::string, std::string, std::string, std::string, std::string>;
using ::google::api_manager::proto::TestRulesetResponse;
using FunctionCall = TestRulesetResponse::TestResult::FunctionCall;

Expand Down Expand Up @@ -157,14 +157,16 @@ static const char kDummyBody[] = R"(
"key" : "value"
})";

static const char kDummyAudience[] = "test-audience";

const char kFirstRequest[] =
R"({"testSuite":{"testCases":[{"expectation":"ALLOW","request":{"method":"get","path":"/ListShelves","auth":{"token":{"email":"limin-429@appspot.gserviceaccount.com","email_verified":true,"azp":"limin-429@appspot.gserviceaccount.com","aud":"https://myfirebaseapp.appspot.com","sub":"113424383671131376652","iat":1486575396,"iss":"https://accounts.google.com","exp":1486578996}}}}]}})";

const char kSecondRequest[] =
R"({"testSuite":{"testCases":[{"expectation":"ALLOW","request":{"auth":{"token":{"email":"limin-429@appspot.gserviceaccount.com","azp":"limin-429@appspot.gserviceaccount.com","aud":"https://myfirebaseapp.appspot.com","sub":"113424383671131376652","iss":"https://accounts.google.com","email_verified":true,"iat":1486575396,"exp":1486578996}},"method":"get","path":"/ListShelves"},"functionMocks":[{"function":"f1","args":[{"exactValue":"http://url1"},{"exactValue":"POST"},{"exactValue":{"key":"value"}}],"result":{"value":{"key":"value"}}}]}]}})";
R"({"testSuite":{"testCases":[{"expectation":"ALLOW","request":{"auth":{"token":{"email":"limin-429@appspot.gserviceaccount.com","azp":"limin-429@appspot.gserviceaccount.com","aud":"https://myfirebaseapp.appspot.com","sub":"113424383671131376652","iss":"https://accounts.google.com","email_verified":true,"iat":1486575396,"exp":1486578996}},"method":"get","path":"/ListShelves"},"functionMocks":[{"function":"f1","args":[{"exactValue":"http://url1"},{"exactValue":"POST"},{"exactValue":{"key":"value"}},{"exactValue":"test-audience"}],"result":{"value":{"key":"value"}}}]}]}})";

const char kThirdRequest[] =
R"({"testSuite":{"testCases":[{"expectation":"ALLOW","request":{"method":"get","path":"/ListShelves","auth":{"token":{"email":"limin-429@appspot.gserviceaccount.com","iat":1486575396,"azp":"limin-429@appspot.gserviceaccount.com","exp":1486578996,"email_verified":true,"sub":"113424383671131376652","aud":"https://myfirebaseapp.appspot.com","iss":"https://accounts.google.com"}}},"functionMocks":[{"function":"f2","args":[{"exactValue":"http://url2"},{"exactValue":"GET"},{"exactValue":{"key":"value"}}],"result":{"value":{"key":"value"}}},{"function":"f3","args":[{"exactValue":"https://url3"},{"exactValue":"GET"},{"exactValue":{"key":"value"}}],"result":{"value":{"key":"value"}}},{"function":"f1","args":[{"exactValue":"http://url1"},{"exactValue":"POST"},{"exactValue":{"key":"value"}}],"result":{"value":{"key":"value"}}}]}]}})";
R"({"testSuite":{"testCases":[{"expectation":"ALLOW","request":{"method":"get","path":"/ListShelves","auth":{"token":{"email":"limin-429@appspot.gserviceaccount.com","iat":1486575396,"azp":"limin-429@appspot.gserviceaccount.com","exp":1486578996,"email_verified":true,"sub":"113424383671131376652","aud":"https://myfirebaseapp.appspot.com","iss":"https://accounts.google.com"}}},"functionMocks":[{"function":"f2","args":[{"exactValue":"http://url2"},{"exactValue":"GET"},{"exactValue":{"key":"value"}},{"exactValue":"test-audience"}],"result":{"value":{"key":"value"}}},{"function":"f3","args":[{"exactValue":"https://url3"},{"exactValue":"GET"},{"exactValue":{"key":"value"}},{"exactValue":"test-audience"}],"result":{"value":{"key":"value"}}},{"function":"f1","args":[{"exactValue":"http://url1"},{"exactValue":"POST"},{"exactValue":{"key":"value"}},{"exactValue":"test-audience"}],"result":{"value":{"key":"value"}}}]}]}})";

::google::protobuf::Value ToValue(const std::string &arg) {
::google::protobuf::Value value;
Expand Down Expand Up @@ -197,7 +199,8 @@ MATCHER_P3(HTTPRequestMatches, url, method, body, "") {
}

FunctionCall BuildCall(const std::string &name, const std::string &url,
const std::string &method, const std::string &body) {
const std::string &method, const std::string &body,
const std::string &audience) {
FunctionCall func_call;
func_call.set_function(name);

Expand All @@ -213,6 +216,10 @@ FunctionCall BuildCall(const std::string &name, const std::string &url,
*(func_call.add_args()) = ToValue(body);
}

if (!audience.empty()) {
*(func_call.add_args()) = ToValue(audience);
}

return func_call;
}

Expand Down Expand Up @@ -386,6 +393,10 @@ class CheckSecurityRulesTest : public ::testing::Test {
Status status = utils::JsonToProto(std::get<3>(http), &body);
*(func->add_args()) = body;
}

if (!std::get<4>(http).empty()) {
func->add_args()->set_string_value(std::get<4>(http));
}
}

std::string json_str;
Expand Down Expand Up @@ -526,26 +537,30 @@ class CheckSecurityRulesFunctions : public CheckSecurityRulesTest,
InSequence s;

ExpectCall(release_url_, "GET", "", kRelease);
ExpectCall(
ruleset_test_url_, "POST", kFirstRequest,
BuildTestRulesetResponse(
false, {std::make_tuple("f1", "http://url1", "POST", kDummyBody)}));
ExpectCall(ruleset_test_url_, "POST", kFirstRequest,
BuildTestRulesetResponse(
false, {std::make_tuple("f1", "http://url1", "POST",
kDummyBody, kDummyAudience)}));

ExpectCall("http://url1", "POST", kDummyBody, kDummyBody);
ExpectCall(
ruleset_test_url_, "POST", kSecondRequest,
BuildTestRulesetResponse(
false, {std::make_tuple("f2", "http://url2", "GET", kDummyBody),
std::make_tuple("f3", "https://url3", "GET", kDummyBody),
std::make_tuple("f1", "http://url1", "POST", kDummyBody)}));
ExpectCall(ruleset_test_url_, "POST", kSecondRequest,
BuildTestRulesetResponse(
false, {std::make_tuple("f2", "http://url2", "GET",
kDummyBody, kDummyAudience),
std::make_tuple("f3", "https://url3", "GET",
kDummyBody, kDummyAudience),
std::make_tuple("f1", "http://url1", "POST",
kDummyBody, kDummyAudience)}));
ExpectCall("http://url2", "GET", kDummyBody, kDummyBody);
ExpectCall("https://url3", "GET", kDummyBody, kDummyBody);
ExpectCall(ruleset_test_url_, "POST", kThirdRequest,
BuildTestRulesetResponse(
GetParam(),
{std::make_tuple("f2", "http://url2", "GET", kDummyBody),
std::make_tuple("f3", "https://url3", "GET", kDummyBody),
std::make_tuple("f1", "http://url1", "POST", kDummyBody)}));
GetParam(), {std::make_tuple("f2", "http://url2", "GET",
kDummyBody, kDummyAudience),
std::make_tuple("f3", "https://url3", "GET",
kDummyBody, kDummyAudience),
std::make_tuple("f1", "http://url1", "POST",
kDummyBody, kDummyAudience)}));
}
};

Expand Down Expand Up @@ -611,18 +626,19 @@ TEST_P(CheckSecurityRulesBadFunctions, CheckBadFunctionArguments) {
});
}

INSTANTIATE_TEST_CASE_P(CheckSecurityRulesBadFunctionArguments,
CheckSecurityRulesBadFunctions,
::testing::Values(
// Empty function name
std::make_tuple("", "http://url1", "POST",
kDummyBody),
// Argument count less than 2
std::make_tuple("f1", "", "", ""),
// The url is not set
std::make_tuple("f1", "", "POST", kDummyBody),
// The url is not a http or https protocol
std::make_tuple("f1", "ftp://url1", "BODY", "")));
INSTANTIATE_TEST_CASE_P(
CheckSecurityRulesBadFunctionArguments, CheckSecurityRulesBadFunctions,
::testing::Values(
// Empty function name
std::make_tuple("", "http://url1", "POST", kDummyBody, kDummyAudience),
// Argument count less than 3
std::make_tuple("f1", "http://url1", "", "", kDummyAudience),
// The url is not set
std::make_tuple("f1", "", "POST", kDummyBody, kDummyAudience),
// The url is not a http or https protocol
std::make_tuple("f1", "ftp://url1", "POST", kDummyBody, kDummyAudience),
// The audience is not present
std::make_tuple("f1", "http://url1", "GET", kDummyBody, "")));
}
} // namespace api_manager
} // namespace google
15 changes: 0 additions & 15 deletions contrib/endpoints/src/api_manager/context/service_context.cc
Original file line number Diff line number Diff line change
Expand Up @@ -73,21 +73,6 @@ ServiceContext::ServiceContext(std::unique_ptr<ApiManagerEnvInterface> env,
->service_control_config()
.intermediate_report_min_interval();
}

service_account_token_.SetAudience(
auth::ServiceAccountToken::JWT_TOKEN_FOR_FIREBASE, kFirebaseAudience);

if (config_->server_config() &&
!config_->server_config()
->api_check_security_rules_config()
.authorization_service_audience()
.empty()) {
service_account_token_.SetAudience(
auth::ServiceAccountToken::JWT_TOKEN_FOR_AUTHORIZATION_SERVICE,
config_->server_config()
->api_check_security_rules_config()
.authorization_service_audience());
}
}

MethodCallInfo ServiceContext::GetMethodCallInfo(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ const std::string kFirebaseDeleteMethod = "delete";
const std::string kFirebaseUpdateMethod = "update";
const std::string kV1 = "/v1";
const std::string kTestQuery = ":test?alt=json";
const char kFirebaseAudience[] =
"https://staging-firebaserules.sandbox.googleapis.com/"
"google.firebase.rules.v1.FirebaseRulesService";

void SetProtoValue(const std::string &key,
const ::google::protobuf::Value &value,
Expand Down Expand Up @@ -94,6 +97,8 @@ FirebaseRequest::FirebaseRequest(
firebase_http_request_.method = kHttpPostMethod;
firebase_http_request_.token_type =
auth::ServiceAccountToken::JWT_TOKEN_FOR_FIREBASE;
firebase_http_request_.audience = kFirebaseAudience;

external_http_request_.token_type =
auth::ServiceAccountToken::JWT_TOKEN_FOR_AUTHORIZATION_SERVICE;

Expand Down Expand Up @@ -305,6 +310,8 @@ Status FirebaseRequest::SetNextRequest() {
auto call = *func_call_iter_;
external_http_request_.url = call.args(0).string_value();
external_http_request_.method = call.args(1).string_value();
external_http_request_.audience =
call.args(call.args_size() - 1).string_value();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

"audience" is an optional field here. For datastore, we provide a default audience if not specified.

How do you handle the case that "audience" field is provided by in the rules for datastore? Do you overwrite the audience field with the default datastore audience?

How do you handle the case that "audience" is not provided? What is the default audience?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It is not optional right now since there is no default behavior implemented right now. There is no default audience being specified at all.

std::string body;
status =
utils::ProtoToJson(call.args(2), &body, utils::JsonOptions::DEFAULT);
Expand Down Expand Up @@ -334,9 +341,9 @@ Status FirebaseRequest::CheckFuncCallArgs(const FunctionCall &func) {

// We only support functions that call with three argument: HTTP URL, HTTP
// method and body. The body can be empty
if (func.args_size() < 2 || func.args_size() > 3) {
if (func.args_size() < 3 || func.args_size() > 4) {
std::ostringstream os;
os << func.function() << " Require 2 or 3 arguments. But has "
os << func.function() << " Require 3 or 4 arguments. But has "
<< func.args_size();
return Status(Code::INVALID_ARGUMENT, os.str());
}
Expand All @@ -348,6 +355,14 @@ Status FirebaseRequest::CheckFuncCallArgs(const FunctionCall &func) {
std::string(func.function() + " Arguments 1 and 2 should be strings"));
}

if (func.args(func.args_size() - 1).kind_case() !=
google::protobuf::Value::kStringValue) {
return Status(
Code::INVALID_ARGUMENT,
std::string(func.function() + "The last argument should be a string"
"that specifies audience"));
}

if (!utils::IsHttpRequest(func.args(0).string_value())) {
return Status(
Code::INVALID_ARGUMENT,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ struct HttpRequest {
std::string method;
std::string body;
auth::ServiceAccountToken::JWT_TOKEN_TYPE token_type;
std::string audience;
};

// A FirebaseRequest object understands the various http requests that need
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,6 @@ message ApiAuthenticationConfig {
message ApiCheckSecurityRulesConfig {
// Firebase server to use.
string firebase_server = 1;
string authorization_service_audience = 2;
}

message Experimental {
Expand Down