Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: added a geoip api endpoint #10648

Merged
merged 5 commits into from
Aug 7, 2024
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
10 changes: 7 additions & 3 deletions cgi/display.pl
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,10 @@

# Special behaviors for API v3 requests

if ($env_query_string =~ /^\/?api\/v(3(\.\d+)?)\//) {
my $api_pattern = qr/^\/?api\/v(3(\.\d+)?)\//;
my $method_pattern = qr/^(POST|PUT|PATCH)$/;

if ($env_query_string =~ $api_pattern) {

# Record that we have an API v3 request, as errors (e.g. bad userid and password) will be handled differently
# (through API::process_api_request instead of returning an error page in HTML)
Expand All @@ -65,13 +68,14 @@
# if we have such a request, we need to read the body before CGI.pm tries to read it to get multipart/form-data parameters
# We also need to do this before the call to init_request() which calls init_user()
# so that authentification credentials user_id and password from the JSON body can be used to authenticate the user
if ($request_ref->{method} =~ /^(POST|PUT|PATCH)$/) {
if ($request_ref->{method} =~ $method_pattern) {
read_request_body($request_ref);
decode_json_request_body($request_ref);
}

}

if (($env_query_string !~ /^\/?api\/v(3(\.\d+)?)\//) or ($request_ref->{method} !~ /^(POST|PUT|PATCH)$/)) {
if (($env_query_string !~ $api_pattern) or ($request_ref->{method} !~ $method_pattern)) {
# Not an API v3 POST/PUT/PATCH request: we will use CGI.pm param() method to access query string or multipart/form-data parameters

# The nginx reverse proxy turns /somepath?someparam=somevalue to /cgi/display.pl?/somepath?someparam=somevalue
Expand Down
102 changes: 42 additions & 60 deletions lib/ProductOpener/API.pm
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ use ProductOpener::KnowledgePanels qw/create_knowledge_panels initialize_knowled
use ProductOpener::Ecoscore qw/localize_ecoscore/;
use ProductOpener::Packaging qw/%packaging_taxonomies/;
use ProductOpener::Permissions qw/has_permission/;
use ProductOpener::GeoIP qw/get_country_for_ip_api/;

use ProductOpener::APIProductRead qw/read_product_api/;
use ProductOpener::APIProductWrite qw/write_product_api/;
Expand Down Expand Up @@ -384,6 +385,43 @@ Reference to the request object.

=cut

# Dipatch table for API actions
my $dispatch_table = {
Copy link
Member

Choose a reason for hiding this comment

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

Great refactor 👍

# Product read or write
product => {
GET => \&read_product_api,
HEAD => \&read_product_api,
OPTIONS => sub {return;}, # Just return CORS headers
PATCH => \&write_product_api,
},
# Product revert
product_revert => {
# Check that the method is POST (GET may be dangerous: it would allow to revert a product by just clicking or loading a link)
POST => \&revert_product_api,
},
# Product services
product_services => {
POST => \&product_services_api,
OPTIONS => sub {return;}, # Just return CORS headers
},
# Taxonomy suggestions
taxonomy_suggestions => {
GET => \&taxonomy_suggestions_api,
HEAD => \&taxonomy_suggestions_api,
OPTIONS => sub {return;}, # Just return CORS headers
},
# Tag read
tag => {
GET => \&read_tag_api,
HEAD => \&read_tag_api,
OPTIONS => sub {return;}, # Just return CORS headers
},
geoip => {
GET => \&get_country_for_ip_api,
}

};

sub process_api_request ($request_ref) {

$log->debug("process_api_request - start", {request => $request_ref}) if $log->is_debug();
Expand All @@ -396,70 +434,16 @@ sub process_api_request ($request_ref) {
if $log->is_warn();
}
else {

# Route the API request to the right processing function, based on API action (from path) and method

# Product read or write
if ($request_ref->{api_action} eq "product") {

if ($request_ref->{api_method} eq "OPTIONS") {
# Just return CORS headers
}
elsif ($request_ref->{api_method} eq "PATCH") {
write_product_api($request_ref);
}
elsif ($request_ref->{api_method} =~ /^(GET|HEAD)$/) {
read_product_api($request_ref);
}
else {
add_invalid_method_error($response_ref, $request_ref);
}
}
# Product revert
elsif ($request_ref->{api_action} eq "product_revert") {

# Check that the method is POST (GET may be dangerous: it would allow to revert a product by just clicking or loading a link)
if ($request_ref->{api_method} eq "POST") {
revert_product_api($request_ref);
if (exists $dispatch_table->{$request_ref->{api_action}}) {
my $action_dispatch_ref = $dispatch_table->{$request_ref->{api_action}};
if (exists $action_dispatch_ref->{$request_ref->{api_method}}) {
$action_dispatch_ref->{$request_ref->{api_method}}->($request_ref);
}
else {
add_invalid_method_error($response_ref, $request_ref);
}
}
# Product services
elsif ($request_ref->{api_action} eq "product_services") {

if ($request_ref->{api_method} eq "OPTIONS") {
# Just return CORS headers
}
elsif ($request_ref->{api_method} eq "POST") {
product_services_api($request_ref);
}
else {
add_invalid_method_error($response_ref, $request_ref);
}
}
# Taxonomy suggestions
elsif ($request_ref->{api_action} eq "taxonomy_suggestions") {

if ($request_ref->{api_method} =~ /^(GET|HEAD|OPTIONS)$/) {
taxonomy_suggestions_api($request_ref);
}
else {
add_invalid_method_error($response_ref, $request_ref);
}
}
# Tag read
elsif ($request_ref->{api_action} eq "tag") {

if ($request_ref->{api_method} =~ /^(GET|HEAD|OPTIONS)$/) {
read_tag_api($request_ref);
}
else {
add_invalid_method_error($response_ref, $request_ref);
}
}
# Unknown action
else {
$log->warn("process_api_request - unknown action", {request => $request_ref}) if $log->is_warn();
add_error(
Expand All @@ -474,9 +458,7 @@ sub process_api_request ($request_ref) {
}

determine_response_result($response_ref);

add_localized_messages_to_api_response($request_ref->{lc}, $response_ref);

send_api_response($request_ref);

$log->debug("process_api_request - stop", {request => $request_ref}) if $log->is_debug();
Expand Down
30 changes: 30 additions & 0 deletions lib/ProductOpener/GeoIP.pm
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ BEGIN {
@EXPORT_OK = qw(
&get_country_for_ip
&get_country_code_for_ip
&get_country_for_ip_api

); # symbols to export on request
%EXPORT_TAGS = (all => [@EXPORT_OK]);
Expand Down Expand Up @@ -138,4 +139,33 @@ sub get_country_code_for_ip ($ip) {
return $country;
}

sub get_country_for_ip_api ($request_ref) {
my $response_ref = $request_ref->{api_response};

my $error_id;

if (not defined $request_ref->{ip}) {
$error_id = "missing_field";
}
else {
$response_ref->{country} = get_country_for_ip($request_ref->{ip});
$response_ref->{cc} = get_country_code_for_ip($request_ref->{ip});
if (not defined $response_ref->{country}) {
$error_id = "invalid_field";
}
}

if (defined $error_id) {
push @{$response_ref->{errors}},
{
message => {id => $error_id},
field => {id => "ip"},
impact => {id => "failure"},
};
$response_ref->{status_code} = $400;
}

return;
}

1;
25 changes: 9 additions & 16 deletions lib/ProductOpener/Routing.pm
Original file line number Diff line number Diff line change
Expand Up @@ -185,10 +185,6 @@ Sometimes we modify request parameters (param) to correspond to request_ref:

sub analyze_request($request_ref) {
sanitize_request($request_ref);
return _analyze_request_impl($request_ref);
}

sub _analyze_request_impl($request_ref) {

$log->debug("analyze_request", {components => $request_ref->{components},}) if $log->is_debug();

Expand Down Expand Up @@ -289,25 +285,17 @@ sub org_route($request_ref) {
$log->debug("org route", {orgid => $orgid, components => $request_ref->{components}}) if $log->is_debug();
# /search
# /product/[code]
return _analyze_request_impl($request_ref);
return match_route($request_ref);
}

# api/v0/product(s)/[code]
# api/v0/search
sub api_route($request_ref) {
my @components = @{$request_ref->{components}};
my $api = $components[1]; # v0
my $api_version = $api =~ /v(\d+)/ ? $1 : 0;
my $api_action = $components[2]; # product

my $api_version = $api;
($api_version) = $api =~ /v(\d+)/;
$api_version //= 0;

# Also support "products" in order not to break apps that were using it
if ($api_action eq 'products') {
$api_action = 'product';
}

# If the api_action is different than "search", check if it is the local path for "product"
# so that urls like https://fr.openfoodfacts.org/api/v3/produit/4324232423 work (produit instead of product)
# this is so that we can quickly add /api/v3/ to get the API
Expand All @@ -319,15 +307,20 @@ sub api_route($request_ref) {
}

# some API actions have an associated object
if ($api_action eq "product") { # api/v3/product/[code]

# Also support "products" in order not to break apps that were using it
if ($api_action =~ /^products?/) { # api/v3/product/[code]
param("code", $components[3]);
$request_ref->{code} = $components[3];
}
elsif ($api_action eq "tag") { # api/v3/tag/[type]/[tagid]
param("tagtype", $components[3]);
$request_ref->{tagtype} = $components[3];
param("tagid", $components[4]);
$request_ref->{tagid} = $components[5];
$request_ref->{tagid} = $components[4];
}
elsif ($api_action eq "geoip") { # api/v3/geoip/[ip]
$request_ref->{ip} = $components[3];
}

# If return format is not xml or jqm or jsonp, default to json
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"errors" : [
{
"field" : {
"error" : "'null' expected, at character offset 0 (before \"not json\") at /opt/product-opener/lib/ProductOpener/API.pm line 224.\n",
"error" : "'null' expected, at character offset 0 (before \"not json\") at /opt/product-opener/lib/ProductOpener/API.pm line 225.\n",
"id" : "body",
"value" : "not json"
},
Expand Down
Loading