Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
2 changes: 2 additions & 0 deletions contrib/endpoints/src/api_manager/auth.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ struct UserInfo {
// Authorized party of the incoming JWT.
// See http://openid.net/specs/openid-connect-core-1_0.html#IDToken
std::string authorized_party;
// String of claims
char *claims;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

do we need this one?

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.

Yes. We do. The UserInfo is where the decoded JWT claims are stored so it can be used in the later part of the work flow. This actually is the same flow as it is used for JWT audience and issuer fields.

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.

Changed it to std::string

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Can we make it std::string?

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.

Done.


// Returns audiences as a comma separated strings.
std::string AudiencesAsString() const {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -699,12 +699,15 @@ grpc_jwt_verifier_status JwtValidatorImpl::FillUserInfoAndSetExp(

// Optional field.
const grpc_json *grpc_json = grpc_jwt_claims_json(claims_);

char *json_str = grpc_json_dump_to_string(const_cast<::grpc_json *>(grpc_json), 0);
user_info->claims = json_str;

const char *email = GetStringValue(grpc_json, "email");
user_info->email = email == nullptr ? "" : email;
const char *authorized_party = GetStringValue(grpc_json, "azp");
user_info->authorized_party =
authorized_party == nullptr ? "" : authorized_party;

exp_ = system_clock::from_time_t(grpc_jwt_claims_expires_at(claims_).tv_sec);

return GRPC_JWT_VERIFIER_OK;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ class ServiceAccountToken {
enum JWT_TOKEN_TYPE {
JWT_TOKEN_FOR_SERVICE_CONTROL = 0,
JWT_TOKEN_FOR_CLOUD_TRACING,
JWT_TOKEN_FOR_FIREBASE,
JWT_TOKEN_TYPE_MAX,
};
// Set audience. Only calcualtes JWT token with specified audience.
Expand Down
4 changes: 4 additions & 0 deletions contrib/endpoints/src/api_manager/check_auth.cc
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,9 @@ void AuthChecker::LookupJwtCache() {
if (cache_hit) {
CheckAudience(true);
} else {
env_->LogInfo("Before Parse JWT");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remember remove these logs before submit.

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.

Done

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.

Done

ParseJwt();
env_->LogInfo("After Parse JWT");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

remove logging?

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.

Done

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.

Looks like the merge put them back in. I will delete them before the next commit.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remove these logs before submit.

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.

Done

}
}

Expand Down Expand Up @@ -243,6 +245,8 @@ void AuthChecker::CheckAudience(bool cache_hit) {
context_->set_auth_audience(audience);
context_->set_auth_authorized_party(user_info_.authorized_party);

context_->set_auth_claims(user_info_.claims);

// Remove http/s header and trailing '/' for issuer.
std::string issuer = utils::GetUrlContent(user_info_.issuer);
if (!context_->method()->isIssuerAllowed(issuer)) {
Expand Down
275 changes: 227 additions & 48 deletions contrib/endpoints/src/api_manager/check_security_rules.cc
Original file line number Diff line number Diff line change
Expand Up @@ -14,92 +14,271 @@
//
////////////////////////////////////////////////////////////////////////////////
#include "contrib/endpoints/src/api_manager/check_security_rules.h"
#include "contrib/endpoints/src/api_manager/auth/lib/json_util.h"
#include <sstream>
#include <iostream>

#include <string>

#include "contrib/endpoints/include/api_manager/api_manager.h"
#include "contrib/endpoints/include/api_manager/request.h"

using ::google::api_manager::auth::GetProperty;
using ::google::api_manager::auth::GetStringValue;
using ::google::api_manager::utils::Status;
using ::google::protobuf::util::error::Code;

namespace google {
namespace api_manager {

namespace {

const char kFirebaseServerStaging[] =

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The whole class should be an anonymous namespace. You should not remove it.

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.

done.

"https://staging-firebaserules.sandbox.googleapis.com/";

// An AuthzChecker object is created for every incoming request. It does
// authorizaiton by calling Firebase Rules service.
class AuthzChecker : public std::enable_shared_from_this<AuthzChecker> {
public:
AuthzChecker(std::shared_ptr<context::RequestContext> context,
std::function<void(Status status)> continuation);
const char kFirebaseService[] =

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

where are these two constants used?

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.

I will delete them once i have done more testing.

"/google.firebase.rules.v1.FirebaseRulesService";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This comment applies to the class. Why do you remove it?

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.

Added it back. Initially I had code that was creating the object in the service context but this did not work due to circular dependences. I removed it as a part of that change and forgot to add it back.

const char kFailedFirebaseReleaseFetch[] = "Failed to fetch Firebase Release";
const char kFailedFirebaseTest[] = "Failed to execute Firebase Test";
const char kInvalidResponse[] = "Invalid JSON response from Firebase Service";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

If AuthzChecker class is used only in check_seucurity_rules.cc/h, we prefer to put the class definition in .cc file. This way, any place that "include" check_security_rules.h will only include CheckSecurityRules() function definition (not AuthzChecker class).

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.

Done.

AuthzChecker::AuthzChecker(ApiManagerEnvInterface *env,
auth::ServiceAccountToken *sa_token)
: env_(env),
sa_token_(sa_token) {}

void AuthzChecker::Check(
std::shared_ptr<context::RequestContext> context,
std::function<void(Status status)> final_continuation) {
// TODO: Check service config to see if "useSecurityRules" is specified.
// If so, call Firebase Rules service TestRuleset API.

void Check();
if (!context->service_context()->RequireAuth() ||
context->method() == nullptr || !context->method()->auth()) {
env_->LogDebug(
std::string("Autherization and JWT validation was not performed")
+ " skipping authorization");
final_continuation(Status::OK);
return;
}

private:
// Helper function to send a http GET request.
void HttpFetch(const std::string &url, const std::string &request_body,
std::function<void(Status, std::string &&)> continuation);
// Fetch the Release attributes.
auto pChecker = GetPtr();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Google c++ style guide, not to use camel case in variable name

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.

Hmm. This code is again similar to check_auth.cc below. Let me know if you want me to fix it in both places.

void AuthChecker::DiscoverJwksUri(const std::string &url) {
auto pChecker = GetPtr();
HttpFetch(url, [pChecker](S

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.

Done

HttpFetch(GetReleaseUrl(context), std::string("GET"), std::string(""),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

no need for std::string("GET"), just "GET", compiler will convert it

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.

done.

[context, final_continuation, pChecker] (Status status,
std::string &&body) {
std::string ruleset_id;
if (status.ok()) {
pChecker->env_->LogDebug(
std::string("GetReleasName succeeded with ") + body);
std::tie(status, ruleset_id) = pChecker->ParseReleaseResponse(&body);
} else {
pChecker->env_->LogError(std::string("GetReleaseName for ")
+ pChecker->GetReleaseUrl(context)
+ " with status " + status.ToString());
status = Status(Code::INTERNAL, kFailedFirebaseReleaseFetch);
}

// Get Auth token for accessing Firebase Rules service.
const std::string &GetAuthToken();
// If the parsing of the release body is successful, then call the
// Test Api for firebase rules service.
if (status.ok()) {
pChecker->CheckAuth(ruleset_id, context, final_continuation);
} else {
final_continuation(status);
}
});
}

// Request context.
std::shared_ptr<context::RequestContext> context_;
void AuthzChecker::CheckAuth(
std::string ruleset_id,
std::shared_ptr<context::RequestContext> context,
std::function<void(Status status)> continuation) {
auto pChecker = GetPtr();

// Pointer to access ESP running environment.
ApiManagerEnvInterface *env_;
HttpFetch(std::string(kFirebaseServerStaging) + "v1/" + ruleset_id +

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

use a separate function for URL

":test?alt=json",
std::string("POST"), BuildTestRequestBody(context),
[context, continuation, pChecker, ruleset_id]
(Status status, std::string &&body) {

// The final continuation function.
std::function<void(Status status)> on_done_;
};
if (status.ok()) {
pChecker->env_->LogDebug(
std::string("Test API succeeded with ") + body);
status = pChecker->ParseTestResponse(context, &body);
} else {
pChecker->env_->LogError(std::string("Test API failed with ")
+ status.ToString());
status = Status(Code::INTERNAL, kFailedFirebaseTest);
}

AuthzChecker::AuthzChecker(std::shared_ptr<context::RequestContext> context,
std::function<void(Status status)> continuation)
: context_(context),
env_(context_->service_context()->env()),
on_done_(continuation) {}
continuation(status);
});
}

void AuthzChecker::Check() {
// TODO: Check service config to see if "useSecurityRules" is specified.
// If so, call Firebase Rules service TestRuleset API.
std::pair<Status, std::string> AuthzChecker::ParseReleaseResponse(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

can we pass out ruleset_id in the argument. e.g.
Status Parse(..., string* ruleset_id)

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.

Done.

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.

Done

std::string *json_str) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

pass in const std::string& if you are not modifying json_str

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.

Done

grpc_json *json = grpc_json_parse_string_with_len(
const_cast<char *>(json_str->data()), json_str->length());

if (!json) {
return std::make_pair(Status(Code::INVALID_ARGUMENT, kInvalidResponse),
"");
}

const char *ruleset_id = GetStringValue(json, "rulesetName");
std::pair<Status, std::string> result = std::make_pair(Status::OK,
ruleset_id);
if (ruleset_id == nullptr) {
env_->LogError("Empty ruleset Id received from firebase service");
result =std::make_pair(Status(Code::INTERNAL, kInvalidResponse), "");
} else {
env_->LogInfo(std::string("Received ruleset Id: ") + ruleset_id);
}

grpc_json_destroy(json);
return result;
}

Status AuthzChecker::ParseTestResponse(
std::shared_ptr<context::RequestContext> context,
std::string *json_str) {
grpc_json *json = grpc_json_parse_string_with_len(
const_cast<char *>(json_str->data()), json_str->length());


if (!json) {
return Status(Code::INVALID_ARGUMENT,
"Invalid JSON response from Firebase Service");
}

Status status = Status::OK;
Status invalid = Status(Code::INTERNAL, kInvalidResponse);

const grpc_json *testResults = GetProperty(json, "testResults");
if (testResults == nullptr) {
env_->LogError("TestResults are null");
status = invalid;
} else {
const char *result = GetStringValue(testResults->child, "state");
if (result == nullptr) {
env_->LogInfo("Result state is empty");
status = invalid;
} else if (std::string(result) != "SUCCESS") {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

"SUCCESS" as constant, and make the constant as std::string.

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.

I have declared a const char[] kTestSuccess with the rest of the cont strings. Let me know if this is not what you had in mind.

status = Status(Code::PERMISSION_DENIED,
std::string("Unauthorized ")
+ context->request()->GetRequestHTTPMethod()
+ " access to resource " + context->request()->GetRequestPath(),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Does response have detail info we can pass back to help users?

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.

No. Unfortunatley, the Test API that we use only provided with a response that looks like this:

{
"testResults": [
{
"state": "SUCCESS"
}
]
}

or

{
"testResults": [
{
"state": "FAILURE"
}
]
}

The response itself is a HTTP 200 OK message.

Status::AUTH);
}
}

grpc_json_destroy(json);
return status;
}

const std::string &AuthzChecker::GetAuthToken() {
// TODO: Get Auth token for accessing Firebase Rules service.
static std::string empty;
return empty;
std::string AuthzChecker::GetOperation(std::string httpMethod) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

need to be a class member function?

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.

Made it static function.

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.

Made it static.

if (httpMethod == "POST") {
return std::string("create");
}

if (httpMethod == "GET" || httpMethod == "HEAD" || httpMethod == "OPTIONS") {
return std::string("get");
}

if (httpMethod == "DELETE") {
return std::string("delete");
}

return std::string("update");
}

std::string AuthzChecker::BuildTestRequestBody(
std::shared_ptr<context::RequestContext> context) {

std::shared_ptr<std::ostringstream> ss(new std::ostringstream);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

not need to use new. just allocate it from stack.

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.

Done.

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.

Done


int openCount = 0;
*(ss.get()) << "{"
<< Stringify("test_cases") + ": "
<< "[ {";
++openCount;
AddToBody("service_name", context->service_context()->service_name(), false, ss);
AddToBody("resource_path", context->request()->GetRequestPath(),
false, ss);
AddToBody("operation", GetOperation(context->request()->GetRequestHTTPMethod()),
false, ss);
AddToBody("expectation", "ALLOW", false, ss);
AddToBody("variables", ss);
++openCount;
AddToBody("request", ss);
++openCount;
AddToBody("auth", ss);
++openCount;

*(ss.get()) << Stringify("token") + ": " << context->auth_claims();

while(openCount-- > 0) {
*(ss.get()) << "} ";
}

*(ss.get()) << "] }";
return ss->str();
}

void AuthzChecker::AddToBody(const std::string &key,
std::shared_ptr<std::ostringstream> ss) {
*(ss.get()) << Stringify(key.c_str()) + ": " + "{";
}
void AuthzChecker::AddToBody(const std::string &key, const std::string &value,
bool end, std::shared_ptr<std::ostringstream> ss) {
*(ss.get()) << Stringify(key.c_str()) + ": " + Stringify(value.c_str());
if (!end) {
*(ss.get()) << ", ";
}
}

const std::string AuthzChecker::GetReleaseName(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

no need to be a class member function

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.

Same reason as above. There is no meaning to this method outside the context of the class.

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.

Changed this to static

std::shared_ptr<context::RequestContext> request_context) {
return request_context->service_context()->service_name() + ":"
+ request_context->service_context()->service().apis(0).version();
}

const std::string AuthzChecker::GetReleaseUrl(
std::shared_ptr<context::RequestContext> request_context) {
return std::string(kFirebaseServerStaging) + "v1/projects/"
+ request_context->service_context()->project_id() + "/releases/"
+ GetReleaseName(request_context);
}

void AuthzChecker::HttpFetch(
const std::string &url, const std::string &request_body,
const std::string &url, const std::string &method,
const std::string &request_body,
std::function<void(Status, std::string &&)> continuation) {

env_->LogInfo(std::string("Issue HTTP Request to url :") + url +
" method : " + method + " body: " + request_body);

std::unique_ptr<HTTPRequest> request(new HTTPRequest([continuation](
Status status, std::map<std::string, std::string> &&,
std::string &&body) { continuation(status, std::move(body)); }));

if (!request) {
continuation(Status(Code::INTERNAL, "Out of memory"), "");
return;
}

request->set_method("POST")
std::string token = GetAuthToken();
request->set_method(method)
.set_url(url)
.set_auth_token(GetAuthToken())
.set_header("Content-Type", "application/json")
.set_auth_token(token);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

set_auth_token(GetAuthToken()). Then you don't need "token" variable.

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.

Done.

if (method != "GET") {
request->set_header("Content-Type", "application/json")
.set_body(request_body);
}

env_->RunHTTPRequest(std::move(request));
}

} // namespace

void CheckSecurityRules(std::shared_ptr<context::RequestContext> context,
std::function<void(Status status)> continuation) {
std::shared_ptr<AuthzChecker> authzChecker =
std::make_shared<AuthzChecker>(context, continuation);
authzChecker->Check();
std::shared_ptr<AuthzChecker> checker(new AuthzChecker(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

why don't you use make_shared?

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.

Done

context->service_context()->env(),
context->service_context()->service_account_token()));
checker->Check(context, continuation);
}

} // namespace api_manager
Expand Down
Loading