From 36d188d7b59962f9edb8846e400b1d6636878688 Mon Sep 17 00:00:00 2001 From: hangy Date: Wed, 28 Sep 2016 15:51:34 +0200 Subject: [PATCH 01/36] WIP Open ID Connect Server based on OIDC::Lite. --- cgi/oidc/authorize.pl | 207 ++++++++++++++++ cgi/oidc/clients.pl | 52 ++++ cgi/oidc/token.pl | 52 ++++ cpanfile | 6 + lib/ProductOpener/Config2_sample.pm | 45 +++- lib/ProductOpener/Config_obf.pm | 3 + lib/ProductOpener/Config_off.pm | 4 + lib/ProductOpener/OIDC/Server/DataHandler.pm | 223 +++++++++++++++++ lib/ProductOpener/OIDC/Server/README.md | 1 + .../OIDC/Server/Web/M/AccessToken.pm | 64 +++++ .../OIDC/Server/Web/M/AuthInfo.pm | 187 +++++++++++++++ lib/ProductOpener/OIDC/Server/Web/M/Client.pm | 224 ++++++++++++++++++ .../OIDC/Server/Web/M/ResourceOwner.pm | 53 +++++ 13 files changed, 1120 insertions(+), 1 deletion(-) create mode 100644 cgi/oidc/authorize.pl create mode 100644 cgi/oidc/clients.pl create mode 100644 cgi/oidc/token.pl create mode 100644 lib/ProductOpener/OIDC/Server/DataHandler.pm create mode 100644 lib/ProductOpener/OIDC/Server/README.md create mode 100644 lib/ProductOpener/OIDC/Server/Web/M/AccessToken.pm create mode 100644 lib/ProductOpener/OIDC/Server/Web/M/AuthInfo.pm create mode 100644 lib/ProductOpener/OIDC/Server/Web/M/Client.pm create mode 100644 lib/ProductOpener/OIDC/Server/Web/M/ResourceOwner.pm diff --git a/cgi/oidc/authorize.pl b/cgi/oidc/authorize.pl new file mode 100644 index 0000000000000..42a82520b1d31 --- /dev/null +++ b/cgi/oidc/authorize.pl @@ -0,0 +1,207 @@ +#!/usr/bin/perl -W + +# This file is part of Product Opener. +# +# Product Opener +# Copyright (C) 2011-2016 Association Open Food Facts +# Contact: contact@openfoodfacts.org +# Address: 21 rue des Iles, 94100 Saint-Maur des Fossés, France +# +# Product Opener is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +use CGI::Carp qw(fatalsToBrowser); + +use strict; +use warnings; +use utf8; + +use ProductOpener::Config qw/:all/; +use ProductOpener::Store qw/:all/; +use ProductOpener::Index qw/:all/; +use ProductOpener::Display qw/:all/; +use ProductOpener::Tags qw/:all/; +use ProductOpener::Users qw/:all/; +use ProductOpener::Images qw/:all/; +use ProductOpener::Lang qw/:all/; +use ProductOpener::Mail qw/:all/; +use ProductOpener::Products qw/:all/; +use ProductOpener::Food qw/:all/; +use ProductOpener::Ingredients qw/:all/; +use ProductOpener::Images qw/:all/; + +use Apache2::RequestRec (); +use Apache2::Const (); + +use CGI qw/:cgi :form escapeHTML/; +use URI::Escape::XS; +use Storable qw/dclone/; +use Encode; +use JSON::PP; + +use OAuth::Lite2::Util qw/build_content/; +use ProductOpener::OIDC::Server::DataHandler; +#use ProductOpener::OIDC::Server::Web::M::ResourceOwner; +use OIDC::Lite::Server::AuthorizationHandler; +use OIDC::Lite::Server::Scope; +use OIDC::Lite::Model::IDToken; + +ProductOpener::Display::init(); + +my $RESPONSE_TYPES = [ + q{code}, q{id_token}, q{token}, + q{code id_token}, q{code token}, q{id_token token}, + q{code id_token token} +]; + +my $request = Apache2::RequestUtil->request(); +my $method = $request->method(); + +my $title = 'OpenFoodFacts OpenID Connect'; +my $html = ''; +my $status = undef; + +if ($method eq 'GET') { + if ((not $User_id) or ($User_id eq 'unwanted-bot-id')) { + ($html, $status) = _add_login(); + } + else { + ($html, $status) = _show_authorize(); + } +} +elsif ($method eq 'POST') { + if ((not $User_id) or ($User_id eq 'unwanted-bot-id')) { + ($html, $status) = _add_login(); + } + else { + ($html, $status) = _redirect(); + } +} +else { + print header ( -status => '405 Method Not Allowed' ); + print header ( -allow => 'GET, POST' ); + exit(0); +} + +display_new( { + title => $title, + content_ref => \$html, + status => $status, + full_width => 1, +}); +exit(0); + +sub _add_login() { + + my $status = '401 Unauthorized'; + + my $html = < +
+
+ +
+
+ +
+
+ +
+
+ + + +HTML +; + + return ($html, $status); +} + +sub _show_authorize() { + + my $request_uri = $request->uri; +# my $params = $request->query_parameters->as_hashref; + my $dh = ProductOpener::OIDC::Server::DataHandler->new( + request => $request, + ); + my $ah = OIDC::Lite::Server::AuthorizationHandler->new( + data_handler => $dh, + response_types => $RESPONSE_TYPES, + ); + + eval { + $ah->handle_request(); + }; + my $error; + my $client_info = $dh->get_client_info(); + if ($error = $@) { +# return $c->render( +# "authorize/confirm.tt" => { +# status => $error, +# request_uri => $request_uri, +# params => $params, +# client_info => $client_info, +# } +# ); + } + + # create array ref of returned user claims for display + my $resource_owner_id = $dh->get_user_id_for_authorization; + my @scopes = split(/\s/, $request->param('scope')); +# my $claims = $class->_get_resource_owner_claims($resource_owner_id, @scopes); + + # confirm screen +# return $c->render( +# "authorize/confirm.tt" => { +# status => q{valid}, +# scopes => \@scopes, +# request_uri => $request_uri, +# params => $params, +# client_info => $client_info, +# claims => $claims, +# } +# ); + +} + +sub _redirect() { + + my $html = 'Feel redirected. ^_^'; + + return ($html, undef); + +} + +sub _get_resource_owner_claims { + my ($class, $resource_owner_id, @scopes) = @_; + + my $resource_owner_info = + ProductOpener::OIDC::Server::Web::M::ResourceOwner->find_by_id($resource_owner_id); + my $requested_claims = OIDC::Lite::Server::Scope->to_normal_claims(\@scopes); + my @claims; + foreach my $claim (@{$requested_claims}) { + if ($claim eq q{address}) { + push (@claims, "$claim : ". encode_json($resource_owner_info->{$claim})); + } else { + push (@claims, "$claim : $resource_owner_info->{$claim}"); + } + } + return \@claims; +} diff --git a/cgi/oidc/clients.pl b/cgi/oidc/clients.pl new file mode 100644 index 0000000000000..4f09d929bed8f --- /dev/null +++ b/cgi/oidc/clients.pl @@ -0,0 +1,52 @@ +#!/usr/bin/perl -W + +# This file is part of Product Opener. +# +# Product Opener +# Copyright (C) 2011-2016 Association Open Food Facts +# Contact: contact@openfoodfacts.org +# Address: 21 rue des Iles, 94100 Saint-Maur des Fossés, France +# +# Product Opener is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +use CGI::Carp qw(fatalsToBrowser); + +use strict; +use warnings; +use utf8; + +use ProductOpener::Config qw/:all/; +use ProductOpener::Store qw/:all/; +use ProductOpener::Index qw/:all/; +use ProductOpener::Display qw/:all/; +use ProductOpener::Tags qw/:all/; +use ProductOpener::Users qw/:all/; +use ProductOpener::Images qw/:all/; +use ProductOpener::Lang qw/:all/; +use ProductOpener::Mail qw/:all/; +use ProductOpener::Products qw/:all/; +use ProductOpener::Food qw/:all/; +use ProductOpener::Ingredients qw/:all/; +use ProductOpener::Images qw/:all/; + +use Apache2::RequestRec (); +use Apache2::Const (); + +use CGI qw/:cgi :form escapeHTML/; +use URI::Escape::XS; +use Storable qw/dclone/; +use Encode; +use JSON::PP; + +ProductOpener::Display::init(); diff --git a/cgi/oidc/token.pl b/cgi/oidc/token.pl new file mode 100644 index 0000000000000..4f09d929bed8f --- /dev/null +++ b/cgi/oidc/token.pl @@ -0,0 +1,52 @@ +#!/usr/bin/perl -W + +# This file is part of Product Opener. +# +# Product Opener +# Copyright (C) 2011-2016 Association Open Food Facts +# Contact: contact@openfoodfacts.org +# Address: 21 rue des Iles, 94100 Saint-Maur des Fossés, France +# +# Product Opener is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +use CGI::Carp qw(fatalsToBrowser); + +use strict; +use warnings; +use utf8; + +use ProductOpener::Config qw/:all/; +use ProductOpener::Store qw/:all/; +use ProductOpener::Index qw/:all/; +use ProductOpener::Display qw/:all/; +use ProductOpener::Tags qw/:all/; +use ProductOpener::Users qw/:all/; +use ProductOpener::Images qw/:all/; +use ProductOpener::Lang qw/:all/; +use ProductOpener::Mail qw/:all/; +use ProductOpener::Products qw/:all/; +use ProductOpener::Food qw/:all/; +use ProductOpener::Ingredients qw/:all/; +use ProductOpener::Images qw/:all/; + +use Apache2::RequestRec (); +use Apache2::Const (); + +use CGI qw/:cgi :form escapeHTML/; +use URI::Escape::XS; +use Storable qw/dclone/; +use Encode; +use JSON::PP; + +ProductOpener::Display::init(); diff --git a/cpanfile b/cpanfile index 6c4190c9f7973..31741f079c09b 100644 --- a/cpanfile +++ b/cpanfile @@ -36,6 +36,12 @@ requires 'DateTime::Locale'; requires 'Math::Random::Secure'; requires 'Crypt::ScryptKDF'; +# OIDC +requires 'OAuth::Lite2', '0.11'; +requires 'OIDC::Lite', '0.10'; +requires 'Crypt::OpenSSL::Random'; +requires 'Digest::SHA'; + on 'test' => sub { requires 'Test::More', '>= 1.302049, < 2.0'; }; diff --git a/lib/ProductOpener/Config2_sample.pm b/lib/ProductOpener/Config2_sample.pm index 1c4e894ddc6ca..c2240712f74a9 100644 --- a/lib/ProductOpener/Config2_sample.pm +++ b/lib/ProductOpener/Config2_sample.pm @@ -13,7 +13,7 @@ BEGIN $mongodb $facebook_app_id $facebook_app_secret - + $oidc ); %EXPORT_TAGS = (all => [@EXPORT_OK]); } @@ -33,4 +33,47 @@ $mongodb = "off"; $facebook_app_id = ""; $facebook_app_secret = ""; +$oidc = { + 'id_token' => { + 'iss' => $server_domain, + 'expires_in' => 3600, + 'pub_key' => "-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5XxKc3Rz/8EakvZG+Ez9 +nCpdn2HGVq0CRD1GZ/fEuM7nHfmy1LzC0VyNa8YkU7Qrb4s/BgSxjFrLvbpFHcUo +o+D3W5TvCR+8RLJLISmUoo4jMb2wDq35DEaJa3j1lGb7o93msFwlSRLwbmcYw08F +H3NEB1IQxkfAv6Z/ddzjDzV1nhbQO/RnO4v4JJ8wR4xxLmo00AJJ7fr8oL51aF9S +kjHU8cjvKFjGW4kcNxxm3+pMCzNtbJvUFQFeTQNBkWt9k83yKA5bhNwK1W4otRPs +l2bP0AyuJtA3tFdWSD1fvIA+l8rywrVom/RbDRUZkm1k+YgbyqyUgJbM5oJkGhwP +fwIDAQAB +-----END PUBLIC KEY-----", + 'priv_key' => "-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEA5XxKc3Rz/8EakvZG+Ez9nCpdn2HGVq0CRD1GZ/fEuM7nHfmy +1LzC0VyNa8YkU7Qrb4s/BgSxjFrLvbpFHcUoo+D3W5TvCR+8RLJLISmUoo4jMb2w +Dq35DEaJa3j1lGb7o93msFwlSRLwbmcYw08FH3NEB1IQxkfAv6Z/ddzjDzV1nhbQ +O/RnO4v4JJ8wR4xxLmo00AJJ7fr8oL51aF9SkjHU8cjvKFjGW4kcNxxm3+pMCzNt +bJvUFQFeTQNBkWt9k83yKA5bhNwK1W4otRPsl2bP0AyuJtA3tFdWSD1fvIA+l8ry +wrVom/RbDRUZkm1k+YgbyqyUgJbM5oJkGhwPfwIDAQABAoIBADYW6Jlz7k9u3Wuc +PrgRtYkUd0K00gHl/23EH48r2CNTKShojV0VLLoaHX80kaVlBwPghzdM7ehOEk2i +1N2ideTChqsAXKMC5uYuPAUR/uWdqO/1bMTY/qWFDqjVNtUGvPMvv0r8PRGPNDph +dHW8b1GtYnBzSF7j1KuXe9109dPEbLQHpOuZijPOGSupJ+pXRc4nRTSasJXLhtVC +2FAhyK/U8vXQbtSLfcsldK0oWCwmTi/OVLYioz4JulyDz6P98M2AZ+WG7OptNcFi +nt5WbX/1JUKpHsPMQhMv/UVVk9hihx0j7qhQw0iDEdx4Por4+sYLeqKLEFIYrf1f +KvAUwgkCgYEA9I7r2x7fxIH9tQczPBa053Nyp5kFgS5BFgXPWUHDPl0+WACDVpih +/w7YCZmdOYc3a2UncOaK1/G9Nh0BogCfl3kMTgDeuS810dB9vM2K684Ay/hnQhTL +vsLnVflFKbIeZWywLxq7/Rzi/gmrK+YoeLJpv2xdAxJQ4gHIfPJC2s0CgYEA8DjX +tpBWN6zn2ui/eKHhwtEKbA4BMHySQ8wtWQ5lLR9DbA8Hs9LxyeQUfvEBu2728/h8 +56acCn7D4H1VCBfwIWDKIu+jEqi+RE/kRPnCpV1iBHE0EuB/YN89UGIDpLKfgebK +bDbGbpuEeMfIedvO1FRn4g+P1vNzvWq28qcuq3sCgYEA7eLcT9//YIHlzSK81rVr +sTweiiKSNS9OBmMOZ89NYSuISkfted2srpK82NHBG0WJRgE2VV8cLaQrHikm/nPG +yavoqTO1csMWggphVLdHa8qOAdqWbrQV4HBsYLfBbCaj5JrN4nQJ6tMfhmbXRzNx +qL47mQWKkENPxBhh8hAhsf0CgYEAwrVQIynauEXtqAH/MEgGNWI6kFrJnANcipd0 +KjsAxxIQFAYauCbC1GGKO1odjU7j29wNYbYpxFf7bHop8eV1PZi2Ppr+EqGzlqsq +2r2Wh3Kpf/BBxQsyM9K+X+kSCuy9XQ00BYJgVEa5mSxV0m/XtUK08QasEA5EQcO9 +hfD8YwECgYEAyHiQ1yuMzVzNKDCUVo2UpiFiVIqOg0Yjoa/tt9MbBwAukYMhRkvU +2CI8jKu8KugD7NArWMpoPcIECk64roYZR0RH4MKWK46OHg2vwPGiOUaWjlsoUcNc +eH+MQwdClxQ8rTr2CxXZKffji8I2Vs9FUE+pep0s3gR072kix3EFdZc= +-----END RSA PRIVATE KEY-----", + }, +}; + 1; diff --git a/lib/ProductOpener/Config_obf.pm b/lib/ProductOpener/Config_obf.pm index 2810bcf4f3f6f..4bdf442138189 100644 --- a/lib/ProductOpener/Config_obf.pm +++ b/lib/ProductOpener/Config_obf.pm @@ -16,6 +16,7 @@ BEGIN $contact_email $admin_email + $oidc $mongodb @@ -80,6 +81,8 @@ $mongodb = $ProductOpener::Config2::mongodb; $www_root = $ProductOpener::Config2::www_root; $data_root = $ProductOpener::Config2::data_root; +$oidc = $ProductOpener::Config2::oidc; + $reference_timezone = 'Europe/Paris'; $contact_email = 'contact@openbeautyfacts.org'; diff --git a/lib/ProductOpener/Config_off.pm b/lib/ProductOpener/Config_off.pm index ed2c099bb75a9..498aeec34bcb0 100644 --- a/lib/ProductOpener/Config_off.pm +++ b/lib/ProductOpener/Config_off.pm @@ -19,6 +19,8 @@ BEGIN $facebook_app_id $facebook_app_secret + $oidc + $mongodb $google_analytics @@ -85,6 +87,8 @@ $data_root = $ProductOpener::Config2::data_root; $facebook_app_id = $ProductOpener::Config2::facebook_app_id; $facebook_app_secret = $ProductOpener::Config2::facebook_app_secret; +$oidc = $ProductOpener::Config2::oidc; + $reference_timezone = 'Europe/Paris'; $contact_email = 'contact@openfoodfacts.org'; diff --git a/lib/ProductOpener/OIDC/Server/DataHandler.pm b/lib/ProductOpener/OIDC/Server/DataHandler.pm new file mode 100644 index 0000000000000..6447019c5839d --- /dev/null +++ b/lib/ProductOpener/OIDC/Server/DataHandler.pm @@ -0,0 +1,223 @@ +package ProductOpener::OIDC::Server::DataHandler; +use strict; +use warnings; +use utf8; +use parent 'OIDC::Lite::Server::DataHandler'; + +use OIDC::Lite::Server::Scope; +use OIDC::Lite::Model::IDToken; +#use ProductOpener::OIDC::Server; +use ProductOpener::OIDC::Server::Web::M::AccessToken; +use ProductOpener::OIDC::Server::Web::M::AuthInfo; +use ProductOpener::OIDC::Server::Web::M::Client; + +use ProductOpener::Config qw/:all/; +use ProductOpener::Users qw/:all/; + +my $client; +my $c = ProductOpener::OIDC::Server->new; + +sub get_client_info { + my $self = shift; + # return hash ref of client info + return $client; +} + +sub validate_client_by_id { + my ($self, $client_id) = @_; + return unless $client_id; + + # obtain Client info + unless ( $client && $client->{client_id} eq $client_id ) { + $client = undef; + $client = ProductOpener::OIDC::Server::Web::M::Client->find_by_client_id($c->db, $client_id) + or return; + } + + return ($client); +} + +sub validate_client_for_authorization { + my ($self, $client_id, $response_type) = @_; + return unless ( $client_id && $response_type ); + return unless $self->validate_client_by_id($client_id); + + ## TODO: return $client->is_allowed_response_type($response_type) + my %allowed_response_type_hash; + $allowed_response_type_hash{$_}++ foreach @{$client->{allowed_response_types}}; + + return (exists $allowed_response_type_hash{$response_type}); +} + +sub validate_redirect_uri { + my ($self, $client_id, $redirect_uri) = @_; + return unless ( $client_id && $redirect_uri ); + return unless $self->validate_client_by_id($client_id); + + ## TODO: return $client->is_valid_redirect_uri($redirect_uri) + my %redirect_uri_hash; + $redirect_uri_hash{$_}++ foreach @{$client->{redirect_uris}}; + + return (exists $redirect_uri_hash{$redirect_uri}); +} + +sub validate_scope { + my ($self, $client_id, $scope) = @_; + return unless ( $client_id && $scope ); + return unless $self->validate_client_by_id($client_id); + + # Only OpenID Connect Scope are allowed + my @scopes = split(/\s/, $scope); + return unless OIDC::Lite::Server::Scope->is_openid_request(\@scopes); + + ## TODO: return $client->is_allowed_scope($scope) + return 1; +} + +# Optional request param are not validated +sub validate_display { + my ($self, $display) = @_; + return 1; +} + +sub validate_prompt { + my ($self, $prompt) = @_; + return 1; +} + +sub validate_max_age { + my ($self, $param) = @_; + return 1; +} + +sub validate_ui_locales { + my ($self, $ui_locales) = @_; + return 1; +} + +sub validate_claims_locales { + my ($self, $claims_locales) = @_; + return 1; +} + +sub validate_id_token_hint { + my ($self, $param) = @_; + return 1; +} + +sub validate_login_hint { + my ($self, $param) = @_; + return 1; +} + +sub validate_request { + my ($self, $param) = @_; + return 1; +} + +sub validate_request_uri { + my ($self, $param) = @_; + return 1; +} + +sub validate_acr_values { + my ($self, $param) = @_; + return 1; +} + +sub get_user_id_for_authorization { + my ($self, $session) = @_; + return $User_id; +} + +sub create_id_token { + my ($self) = @_; + return unless ( $self->{request} && $oidc ); + + my $oidc_config = $oidc; + my $ts = time(); + my $payload = { + sub => $self->get_user_id_for_authorization(), + iss => $oidc_config->{id_token}->{iss}, + iat => $ts, + exp => $ts + $oidc_config->{id_token}->{expires_in}, + aud => $client->{client_id}, + }; + $payload->{nonce} = $self->{request}->param('nonce') if $self->{request}->param('nonce'); + + return OIDC::Lite::Model::IDToken->new( + header => { + typ => q{JOSE}, + alg => q{RS256}, + kid => 1, + }, + payload => $payload, + key => $oidc_config->{id_token}->{priv_key}, + ); +} + +sub create_or_update_auth_info { + my ($self, %args) = @_; + return unless ( %args && + $self->{request} && + $self->{request}->param('redirect_uri')); + + $args{redirect_uri} = $self->{request}->param('redirect_uri'); + # create AuthInfo Object + my $info = ProductOpener::OIDC::Server::Web::M::AuthInfo->create(%args); + $info->set_code; + $info->save($c->db); + return $info; +} + +sub create_or_update_access_token { + my ($self, %args) = @_; + return unless $args{auth_info}; + + my $auth_info = $args{auth_info}; + # If the request is for token endpoint, the code in AuthInfo is deleted + if ($self->{request}->param('grant_type') && + $self->{request}->param('grant_type') eq q{authorization_code}) { + $auth_info->set_refresh_token($c->db); + $auth_info->unset_code($c->db); + $auth_info->save($c->db); + } + return ProductOpener::OIDC::Server::Web::M::AccessToken->create($args{auth_info}); +} + +sub validate_client { + my ($self, $client_id, $client_secret, $grant_type) = @_; + return unless ( $client_id && $grant_type ); + return unless $self->validate_client_by_id($client_id); + + # verify client_secret + return unless $client->{client_secret} eq $client_secret; + + ## TODO: return $client->is_allowed_grant_type($response_type) + my %allowed_grant_type_hash; + $allowed_grant_type_hash{$_}++ foreach @{$client->{allowed_grant_types}}; + + return (exists $allowed_grant_type_hash{$grant_type}); +} + +sub get_auth_info_by_code { + my ($self, $code) = @_; + return ProductOpener::OIDC::Server::Web::M::AuthInfo->find_by_code($c->db, $code); +} + +sub get_auth_info_by_refresh_token { + my ($self, $refresh_token) = @_; + return ProductOpener::OIDC::Server::Web::M::AuthInfo->find_by_refresh_token($c->db, $refresh_token); +} + +sub get_access_token { + my ($self, $token) = @_; + return ProductOpener::OIDC::Server::Web::M::AccessToken->validate($token); +} + +sub get_auth_info_by_id { + my ($self, $id) = @_; + return ProductOpener::OIDC::Server::Web::M::AuthInfo->find_by_id($c->db, $id); +} + +1; diff --git a/lib/ProductOpener/OIDC/Server/README.md b/lib/ProductOpener/OIDC/Server/README.md new file mode 100644 index 0000000000000..f092f64d1a439 --- /dev/null +++ b/lib/ProductOpener/OIDC/Server/README.md @@ -0,0 +1 @@ +Adapted from https://github.com/ritou/p5-oidc-lite-demo-server diff --git a/lib/ProductOpener/OIDC/Server/Web/M/AccessToken.pm b/lib/ProductOpener/OIDC/Server/Web/M/AccessToken.pm new file mode 100644 index 0000000000000..04514392908f7 --- /dev/null +++ b/lib/ProductOpener/OIDC/Server/Web/M/AccessToken.pm @@ -0,0 +1,64 @@ +package ProductOpener::OIDC::Server::Web::M::AccessToken; +use strict; +use warnings; +use utf8; +use parent 'OAuth::Lite2::Model::AccessToken'; + +use JSON::WebToken qw/ + encode_jwt + decode_jwt +/; + +my $ACCESS_TOKEN_EXPIRATION = 24*60*60; +my $ACCESS_TOKEN_HMAC_KEY = q{DUMMY_HMAC_KEY_FOR_ACCESS_TOKEN}; + +sub create { + my ($class, $auth_info) = @_; + return unless $auth_info; + + # generate token string + my $ts = time(); + my $token = encode_jwt ( + { + auth_id => $auth_info->id, + expired_on => $ts + $ACCESS_TOKEN_EXPIRATION, + }, + $ACCESS_TOKEN_HMAC_KEY, + ); + + # return instance + return $class->new({ + auth_id => $auth_info->id, + token => $token, + expires_in => $ACCESS_TOKEN_EXPIRATION, + created_on => $ts, + }); +} + +sub validate { + my ($class, $token) = @_; + + my $decoded; + # if signature is invalid, JSON::WebToken cause error + eval { + $decoded = decode_jwt( + $token, + $ACCESS_TOKEN_HMAC_KEY + ) or return; + }; + return if $@; + + # verify expiration + return unless ( $decoded->{expired_on} && + $decoded->{expired_on} >= time() ); + + # return instance + return $class->new({ + auth_id => $decoded->{auth_id}, + token => $token, + expires_in => $ACCESS_TOKEN_EXPIRATION, + created_on => $decoded->{expired_on} - $ACCESS_TOKEN_EXPIRATION, + }); +} + +1; diff --git a/lib/ProductOpener/OIDC/Server/Web/M/AuthInfo.pm b/lib/ProductOpener/OIDC/Server/Web/M/AuthInfo.pm new file mode 100644 index 0000000000000..16dd71b4484f0 --- /dev/null +++ b/lib/ProductOpener/OIDC/Server/Web/M/AuthInfo.pm @@ -0,0 +1,187 @@ +package ProductOpener::OIDC::Server::Web::M::AuthInfo; +use strict; +use warnings; +use utf8; +use parent 'OIDC::Lite::Model::AuthInfo'; +__PACKAGE__->mk_accessors(qw( + code_expired_on + refresh_token_expired_on +)); + +use JSON::PP qw/ + decode_json + encode_json +/; +use Digest::SHA qw/ + hmac_sha256_base64 +/; +use Crypt::OpenSSL::Random qw/ + random_pseudo_bytes +/; +use OIDC::Lite::Server::Scope; + +use ProductOpener::Config qw/:all/; +use ProductOpener::Store qw/:all/; + +my $CODE_EXPIRATION = 5*60; +my $CODE_HMAC_KEY = q{DUMMY_HMAC_KEY_FOR_AUTHRIZATION_CODE}; +my $REFRESH_TOKEN_EXPIRATION = 30*24*60*60; +my $REFRESH_TOKEN_HMAC_KEY = q{DUMMY_HMAC_KEY_FOR_AUTHRIZATION_CODE}; + +sub create { + my ($class, %args) = @_; + return unless %args; + + $args{id} = 0; + $args{code} = q{}; + $args{code_expired_on} = 0; + $args{refresh_token} = q{}; + $args{refresh_token_expired_on} = 0; + my @scopes = split(/\s/, $args{scope}); + $args{userinfo_claims} = OIDC::Lite::Server::Scope->to_normal_claims(\@scopes); + return $class->new(\%args); +} + +sub set_code { + my $self = shift; + + $self->code_expired_on(time() + $CODE_EXPIRATION); + my $code = hmac_sha256_base64( + $self->client_id. + $self->code_expired_on. + unpack('H*', random_pseudo_bytes(32)), + $CODE_HMAC_KEY); + $self->code($code); +} + +sub unset_code { + my $self = shift; + + $self->code_expired_on(0); + $self->code(q{}); +} + +sub set_refresh_token { + my $self = shift; + + $self->refresh_token_expired_on(time() + $REFRESH_TOKEN_EXPIRATION); + my $refresh_token = hmac_sha256_base64( + $self->client_id. + $self->refresh_token_expired_on. + unpack('H*', random_pseudo_bytes(32)), + $REFRESH_TOKEN_HMAC_KEY); + $self->refresh_token($refresh_token); +} + +sub save { + my ($self, $db) = @_; + + my @scopes = split(/\s/, $self->scope); + $self->userinfo_claims(OIDC::Lite::Server::Scope->to_normal_claims(\@scopes)); + + store($self, "$data_root/oidc/authinfo/" . $self->id . ".sto"); + + my $row; + if ($self->id == 0) { + $row = $db->insert( + 'auth_info' => { + client_id => $self->client_id, + user_id => $self->user_id, + scope => $self->scope, + refresh_token => $self->refresh_token, + code => $self->code, + redirect_uri => $self->redirect_uri, + id_token => $self->id_token, + userinfo_claims => encode_json($self->userinfo_claims), + code_expired_on => $self->code_expired_on, + refresh_token_expired_on => $self->refresh_token_expired_on, + } + ); + $self->id($row->{row_data}->{id}); + } else { + $row = $db->update( + 'auth_info', + { + client_id => $self->client_id, + user_id => $self->user_id, + scope => $self->scope, + refresh_token => $self->refresh_token, + code => $self->code, + redirect_uri => $self->redirect_uri, + id_token => $self->id_token, + userinfo_claims => encode_json($self->userinfo_claims), + code_expired_on => $self->code_expired_on, + refresh_token_expired_on => $self->refresh_token_expired_on, + }, + { + id => $self->id, + } + ); + } +} + +sub find_by_code { + my ($self, $db, $code) = @_; + return unless ($db && $code); + + # fetch from DB + my $row = $db->single( + 'auth_info', + { + code => $code, + } + ) or return; + # verify expiration + return unless $row->code_expired_on >= time(); + return $self->_row_to_obj($row); +} + +sub find_by_refresh_token { + my ($self, $db, $refresh_token) = @_; + return unless ($db && $refresh_token); + + # fetch from DB + my $row = $db->single( + 'auth_info', + { + refresh_token => $refresh_token, + } + ) or return; + # verify expiration + return unless $row->refresh_token_expired_on >= time(); + return $self->_row_to_obj($row); +} + +sub find_by_id { + my ($self, $db, $id) = @_; + return unless ($db && $id); + + # fetch from DB + my $row = $db->single( + 'auth_info', + { + id => $id, + } + ) or return; + return $self->_row_to_obj($row); +} + +sub _row_to_obj { + my ($class, $row) = @_; + + return $class->new({ + id => $row->id, + client_id => $row->client_id, + user_id => $row->user_id, + scope => $row->scope, + refresh_token => $row->refresh_token, + code => $row->code, + redirect_uri => $row->redirect_uri, + id_token => $row->id_token, + userinfo_claims => decode_json($row->userinfo_claims), + code_expired_on => $row->code_expired_on, + refresh_token_expired_on => $row->refresh_token_expired_on, + }); +} + +1; diff --git a/lib/ProductOpener/OIDC/Server/Web/M/Client.pm b/lib/ProductOpener/OIDC/Server/Web/M/Client.pm new file mode 100644 index 0000000000000..4a3387fe4efec --- /dev/null +++ b/lib/ProductOpener/OIDC/Server/Web/M/Client.pm @@ -0,0 +1,224 @@ +package ProductOpener::OIDC::Server::Web::M::Client; +use strict; +use warnings; +use utf8; + +use JSON::PP qw/ + encode_json + decode_json +/; +use Crypt::OpenSSL::Random qw/ + random_bytes + random_pseudo_bytes +/; + +use ProductOpener::OIDC::Server; +my $c = ProductOpener::OIDC::Server->new; + +# sample client data +our $SAMPLE_CLIENTS = { + 'sample_client_id' => { + 'id' => 0, + 'name' => q{Sample Client}, + 'client_id' => q{sample_client_id}, + 'client_secret' => q{sample_client_secret}, + 'redirect_uris' => [ + $c->config->{SampleClient}->{redirect_uri}, + ], + 'allowed_response_types' => [ + q{code}, q{id_token}, q{token}, + q{code id_token}, q{id_token token}, q{code token}, + q{code id_token token}, + ], + 'allowed_grant_types' => [ + q{authorization_code}, + q{refresh_token}, + ], + 'client_type' => 4, + 'is_disabled' => 0, + }, +}; + +my $CLIENT_TYPES = { + '1' => { + 'display' => 'Web Client', + 'allowed_response_types' => [ + q{code}, q{code id_token}, + ], + 'allowed_grant_types' => [ + q{authorization_code}, + q{refresh_token}, + ], + }, + '2' => { + 'display' => 'JavaScript Client', + 'allowed_response_types' => [ + q{id_token}, + q{id_token token}, + ], + 'allowed_grant_types' => [], + }, + '3' => { + 'display' => 'Mobile App', + 'allowed_response_types' => [ + q{code}, + q{id_token}, + q{id_token token}, + q{code id_token token}, + ], + 'allowed_grant_types' => [ + q{authorization_code}, + q{refresh_token}, + ], + }, + '4' => { + 'display' => 'Full Spec Client', + 'allowed_response_types' => [ + q{code}, q{id_token}, q{token}, + q{code id_token}, q{id_token token}, q{code token}, + q{code id_token token}, + ], + 'allowed_grant_types' => [ + q{authorization_code}, + q{refresh_token}, + ], + }, +}; + +sub create { + my ($class, $args) = @_; + + return unless( + $args->{name} && + $args->{redirect_uris} + ); + + $args->{client_type} = 4; + my $credentials = $class->_gen_credentials(); + + return { + 'id' => 0, + 'name' => $args->{name}, + 'client_id' => $credentials->{client_id}, + 'client_secret' => $credentials->{client_secret}, + 'redirect_uris' => $args->{redirect_uris}, + 'client_type' => $args->{client_type}, + 'allowed_response_types' => $CLIENT_TYPES->{$args->{client_type}}->{allowed_response_types}, + 'allowed_grant_types' => $CLIENT_TYPES->{$args->{client_type}}->{allowed_grant_types}, + 'is_disabled' => $args->{is_disabled}, + }; +} + +sub insert { + my ($class, $db, $args) = @_; + + $args->{is_disabled} = 0 unless defined $args->{is_disabled}; + + my $row = $db->insert( + 'clients' => { + name => $args->{name}, + client_id => $args->{client_id}, + client_secret => $args->{client_secret}, + redirect_uris => encode_json($args->{redirect_uris}), + client_type => $args->{client_type}, + is_disabled => $args->{is_disabled}, + } + ); + return $class->_row_to_hash_ref($row); +} + +sub update { + my ($class, $db, $args) = @_; + + my $row = $db->update( + 'clients', + { + name => $args->{name}, + client_id => $args->{client_id}, + client_secret => $args->{client_secret}, + redirect_uris => encode_json($args->{redirect_uris}), + client_type => $args->{client_type}, + is_disabled => $args->{is_disabled} || 0, + }, + { + id => $args->{id}, + } + ); +} + +sub find_by_client_id { + my ($class, $db, $client_id) = @_; + return unless $client_id; + + # find from sample Clients + return $SAMPLE_CLIENTS->{$client_id} if $SAMPLE_CLIENTS->{$client_id}; + + # find from DB + my $row = $db->single( + 'clients', + { + client_id => $client_id, + is_disabled => 0, + } + ) or return; + return $class->_row_to_hash_ref($row); +} + +sub find_by_id { + my ($class, $db, $id) = @_; + return unless ($db && $id); + + return $SAMPLE_CLIENTS->{sample_client_id} if $id == 0; + + # fetch from DB + my $row = $db->single( + 'clients', + { + id => $id, + is_disabled => 0, + } + ) or return; + return $class->_row_to_hash_ref($row); +} + +sub find_all { + my ($class, $db) = @_; + return unless $db; + + my @rows = $db->search( + 'clients', + { is_disabled => 0 }, + { order_by => 'id' } + ); + + my @clients; + foreach my $row (@rows) { + push(@clients, $class->_row_to_hash_ref($row)); + } + return \@clients; +} + +sub _gen_credentials { + my $class = shift; + return { + client_id => unpack('H*', random_pseudo_bytes(32)), + client_secret => unpack('H*', random_bytes(32)), + }; +} + +sub _row_to_hash_ref { + my ($class, $row) = @_; + return { + 'id' => $row->id, + 'name' => $row->name, + 'client_id' => $row->client_id, + 'client_secret' => $row->client_secret, + 'redirect_uris' => decode_json($row->redirect_uris), + 'allowed_response_types' => $CLIENT_TYPES->{$row->client_type}->{allowed_response_types}, + 'allowed_grant_types' => $CLIENT_TYPES->{$row->client_type}->{allowed_grant_types}, + 'client_type' => $row->client_type, + 'is_disabled' => $row->is_disabled, + }; +} + +1; diff --git a/lib/ProductOpener/OIDC/Server/Web/M/ResourceOwner.pm b/lib/ProductOpener/OIDC/Server/Web/M/ResourceOwner.pm new file mode 100644 index 0000000000000..50a92bc7cd858 --- /dev/null +++ b/lib/ProductOpener/OIDC/Server/Web/M/ResourceOwner.pm @@ -0,0 +1,53 @@ +package ProductOpener::OIDC::Server::Web::M::ResourceOwner; +use strict; +use warnings; +use utf8; + +use JSON::PP; + +# sample user data +our $RESOURCE_OWNERS = { + 1 => { + sub => 1, + name => q{Taro "Testuser" Tokyo}, + given_name => q{Taro}, + family_name => q{Tokyo}, + middle_name => q{Testuser}, + nickname => q{Testuser}, + preferred_username => q{Testuser}, + profile => q{https://profile.example.com/users/1}, + picture => q{https://profile.example.com/users/1/image.png}, + website => q{http://testuser.example.com}, + email => q{user_1@example.com}, +# email_verified => JSON::PP:true, + gender => q{male}, + birthdate => q{2000-12-31}, + zoneinfo => q{Asia/Tokyo}, + locale => q{jp-JP}, + phone_number => q{+81 (90) 0000 0000}, +# phone_number_verified => JSON::PP:true, + address => { + formatted => q{2400 Camino Ramon, Suite 375 San Ramon, CA 94583 United States}, + street_address => q{2400 Camino Ramon, Suite 375}, + locality => q{San Ramon}, + region =>q{CA}, + postal_code => q{94853}, + country => q{United States} + }, + updated_at => 1356966000, + }, +}; + +sub find_by_id { + my ($class, $id) = @_; + return unless $id; + + # find from sample user data + return $RESOURCE_OWNERS->{$id} if $RESOURCE_OWNERS->{$id}; + + # find from DB + # TBD + return; +} + +1; From 90af3c6eba34af2bd2728ec59c2f36add6b5d0ec Mon Sep 17 00:00:00 2001 From: hangy Date: Wed, 28 Sep 2016 17:42:50 +0200 Subject: [PATCH 02/36] Expose new MongoDB connection via the OIDC Server class. --- lib/ProductOpener/OIDC/Server.pm | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 lib/ProductOpener/OIDC/Server.pm diff --git a/lib/ProductOpener/OIDC/Server.pm b/lib/ProductOpener/OIDC/Server.pm new file mode 100644 index 0000000000000..9cab5bfd6caeb --- /dev/null +++ b/lib/ProductOpener/OIDC/Server.pm @@ -0,0 +1,25 @@ +package ProductOpener::OIDC::Server; +use strict; +use warnings; +use utf8; + +sub new { + + my $class = shift; + return bless {}, $class; + +} + +sub db { + + my $self = shift; + if ( !defined $self->{db} ) { + my $oidc_collection = ProductOpener::Display::database->get_collection('oidc'); + $self->{db} = $oidc_collection; + } + + return $self->{db}; + +} + +1; From f7a0cec7e37231579ca6449aa30fb656be4d763e Mon Sep 17 00:00:00 2001 From: hangy Date: Wed, 28 Sep 2016 17:43:17 +0200 Subject: [PATCH 03/36] Use params method (Apache/CGI mod_perl?). --- cgi/oidc/authorize.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cgi/oidc/authorize.pl b/cgi/oidc/authorize.pl index 42a82520b1d31..9e52a75349b22 100644 --- a/cgi/oidc/authorize.pl +++ b/cgi/oidc/authorize.pl @@ -164,7 +164,7 @@ () # create array ref of returned user claims for display my $resource_owner_id = $dh->get_user_id_for_authorization; - my @scopes = split(/\s/, $request->param('scope')); + my @scopes = split(/\s/, param('scope')); # my $claims = $class->_get_resource_owner_claims($resource_owner_id, @scopes); # confirm screen From 8078a6e99aafb33fa00e873948f2af0b404de274 Mon Sep 17 00:00:00 2001 From: hangy Date: Wed, 28 Sep 2016 18:20:57 +0200 Subject: [PATCH 04/36] Implement AuthInfo using MongoDB. --- .../OIDC/Server/Web/M/AuthInfo.pm | 272 +++++++++--------- 1 file changed, 137 insertions(+), 135 deletions(-) diff --git a/lib/ProductOpener/OIDC/Server/Web/M/AuthInfo.pm b/lib/ProductOpener/OIDC/Server/Web/M/AuthInfo.pm index 16dd71b4484f0..63d8bd5e92adb 100644 --- a/lib/ProductOpener/OIDC/Server/Web/M/AuthInfo.pm +++ b/lib/ProductOpener/OIDC/Server/Web/M/AuthInfo.pm @@ -4,24 +4,23 @@ use warnings; use utf8; use parent 'OIDC::Lite::Model::AuthInfo'; __PACKAGE__->mk_accessors(qw( - code_expired_on - refresh_token_expired_on + code_expired_on + refresh_token_expired_on )); use JSON::PP qw/ - decode_json - encode_json + decode_json + encode_json /; use Digest::SHA qw/ - hmac_sha256_base64 + hmac_sha256_base64 /; use Crypt::OpenSSL::Random qw/ - random_pseudo_bytes + random_pseudo_bytes /; use OIDC::Lite::Server::Scope; use ProductOpener::Config qw/:all/; -use ProductOpener::Store qw/:all/; my $CODE_EXPIRATION = 5*60; my $CODE_HMAC_KEY = q{DUMMY_HMAC_KEY_FOR_AUTHRIZATION_CODE}; @@ -29,159 +28,162 @@ my $REFRESH_TOKEN_EXPIRATION = 30*24*60*60; my $REFRESH_TOKEN_HMAC_KEY = q{DUMMY_HMAC_KEY_FOR_AUTHRIZATION_CODE}; sub create { - my ($class, %args) = @_; - return unless %args; - - $args{id} = 0; - $args{code} = q{}; - $args{code_expired_on} = 0; - $args{refresh_token} = q{}; - $args{refresh_token_expired_on} = 0; - my @scopes = split(/\s/, $args{scope}); - $args{userinfo_claims} = OIDC::Lite::Server::Scope->to_normal_claims(\@scopes); - return $class->new(\%args); + my ($class, %args) = @_; + return unless %args; + + $args{id} = undef; + $args{code} = q{}; + $args{code_expired_on} = 0; + $args{refresh_token} = q{}; + $args{refresh_token_expired_on} = 0; + my @scopes = split(/\s/, $args{scope}); + $args{userinfo_claims} = OIDC::Lite::Server::Scope->to_normal_claims(\@scopes); + return $class->new(\%args); } sub set_code { - my $self = shift; - - $self->code_expired_on(time() + $CODE_EXPIRATION); - my $code = hmac_sha256_base64( - $self->client_id. - $self->code_expired_on. - unpack('H*', random_pseudo_bytes(32)), - $CODE_HMAC_KEY); - $self->code($code); + my $self = shift; + + $self->code_expired_on(time() + $CODE_EXPIRATION); + my $code = hmac_sha256_base64( + $self->client_id. + $self->code_expired_on. + unpack('H*', random_pseudo_bytes(32)), + $CODE_HMAC_KEY); + $self->code($code); } sub unset_code { - my $self = shift; + my $self = shift; - $self->code_expired_on(0); - $self->code(q{}); + $self->code_expired_on(0); + $self->code(q{}); } sub set_refresh_token { - my $self = shift; - - $self->refresh_token_expired_on(time() + $REFRESH_TOKEN_EXPIRATION); - my $refresh_token = hmac_sha256_base64( - $self->client_id. - $self->refresh_token_expired_on. - unpack('H*', random_pseudo_bytes(32)), - $REFRESH_TOKEN_HMAC_KEY); - $self->refresh_token($refresh_token); + my $self = shift; + + $self->refresh_token_expired_on(time() + $REFRESH_TOKEN_EXPIRATION); + my $refresh_token = hmac_sha256_base64( + $self->client_id. + $self->refresh_token_expired_on. + unpack('H*', random_pseudo_bytes(32)), + $REFRESH_TOKEN_HMAC_KEY); + $self->refresh_token($refresh_token); } sub save { - my ($self, $db) = @_; - - my @scopes = split(/\s/, $self->scope); - $self->userinfo_claims(OIDC::Lite::Server::Scope->to_normal_claims(\@scopes)); - - store($self, "$data_root/oidc/authinfo/" . $self->id . ".sto"); - - my $row; - if ($self->id == 0) { - $row = $db->insert( - 'auth_info' => { - client_id => $self->client_id, - user_id => $self->user_id, - scope => $self->scope, - refresh_token => $self->refresh_token, - code => $self->code, - redirect_uri => $self->redirect_uri, - id_token => $self->id_token, - userinfo_claims => encode_json($self->userinfo_claims), - code_expired_on => $self->code_expired_on, - refresh_token_expired_on => $self->refresh_token_expired_on, - } - ); - $self->id($row->{row_data}->{id}); - } else { - $row = $db->update( - 'auth_info', - { - client_id => $self->client_id, - user_id => $self->user_id, - scope => $self->scope, - refresh_token => $self->refresh_token, - code => $self->code, - redirect_uri => $self->redirect_uri, - id_token => $self->id_token, - userinfo_claims => encode_json($self->userinfo_claims), - code_expired_on => $self->code_expired_on, - refresh_token_expired_on => $self->refresh_token_expired_on, - }, - { - id => $self->id, - } - ); - } + my ($self, $db) = @_; + + my @scopes = split(/\s/, $self->scope); + $self->userinfo_claims(OIDC::Lite::Server::Scope->to_normal_claims(\@scopes)); + + my $row; + if (undef $self->id) { + my $id = $db->insert_one( + { + client_id => $self->client_id, + user_id => $self->user_id, + scope => $self->scope, + refresh_token => $self->refresh_token, + code => $self->code, + redirect_uri => $self->redirect_uri, + id_token => $self->id_token, + userinfo_claims => encode_json($self->userinfo_claims), + code_expired_on => $self->code_expired_on, + refresh_token_expired_on => $self->refresh_token_expired_on, + } + ); + $self->id($id); + } else { + $row = $db->find_one_and_update( + { + _id => $self->id, + }, + { + client_id => $self->client_id, + user_id => $self->user_id, + scope => $self->scope, + refresh_token => $self->refresh_token, + code => $self->code, + redirect_uri => $self->redirect_uri, + id_token => $self->id_token, + userinfo_claims => encode_json($self->userinfo_claims), + code_expired_on => $self->code_expired_on, + refresh_token_expired_on => $self->refresh_token_expired_on, + } + ); + } } sub find_by_code { - my ($self, $db, $code) = @_; - return unless ($db && $code); - - # fetch from DB - my $row = $db->single( - 'auth_info', - { - code => $code, - } - ) or return; - # verify expiration - return unless $row->code_expired_on >= time(); - return $self->_row_to_obj($row); + my ($self, $db, $code) = @_; + return unless ($db && $code); + + # fetch from DB + my $row; + eval { + $row = $db->find_one({ code => $code }); + }; + if ($@) { + return; + } + + # verify expiration + return unless $row->code_expired_on >= time(); + return $self->_row_to_obj($row); } sub find_by_refresh_token { - my ($self, $db, $refresh_token) = @_; - return unless ($db && $refresh_token); - - # fetch from DB - my $row = $db->single( - 'auth_info', - { - refresh_token => $refresh_token, - } - ) or return; - # verify expiration - return unless $row->refresh_token_expired_on >= time(); - return $self->_row_to_obj($row); + my ($self, $db, $refresh_token) = @_; + return unless ($db && $refresh_token); + + # fetch from DB + my $row; + eval { + $row = $db->find_one({ refresh_token => $refresh_token }); + }; + if ($@) { + return; + } + + # verify expiration + return unless $row->refresh_token_expired_on >= time(); + return $self->_row_to_obj($row); } sub find_by_id { - my ($self, $db, $id) = @_; - return unless ($db && $id); - - # fetch from DB - my $row = $db->single( - 'auth_info', - { - id => $id, - } - ) or return; - return $self->_row_to_obj($row); + my ($self, $db, $id) = @_; + return unless ($db && $id); + + # fetch from DB + my $row; + eval { + $row = $db->find_one({ id => $id }); + }; + if ($@) { + return; + } + + return $self->_row_to_obj($row); } sub _row_to_obj { - my ($class, $row) = @_; - - return $class->new({ - id => $row->id, - client_id => $row->client_id, - user_id => $row->user_id, - scope => $row->scope, - refresh_token => $row->refresh_token, - code => $row->code, - redirect_uri => $row->redirect_uri, - id_token => $row->id_token, - userinfo_claims => decode_json($row->userinfo_claims), - code_expired_on => $row->code_expired_on, - refresh_token_expired_on => $row->refresh_token_expired_on, - }); + my ($class, $row) = @_; + + return $class->new({ + id => $row->_id, + client_id => $row->client_id, + user_id => $row->user_id, + scope => $row->scope, + refresh_token => $row->refresh_token, + code => $row->code, + redirect_uri => $row->redirect_uri, + id_token => $row->id_token, + userinfo_claims => decode_json($row->userinfo_claims), + code_expired_on => $row->code_expired_on, + refresh_token_expired_on => $row->refresh_token_expired_on, + }); } 1; From 1f861d3816840ceff4752c64d03b6806bff154e6 Mon Sep 17 00:00:00 2001 From: hangy Date: Wed, 28 Sep 2016 18:47:14 +0200 Subject: [PATCH 05/36] View claims form --- cgi/oidc/authorize.pl | 66 ++++++++++++++++++++++++++++++------------- 1 file changed, 46 insertions(+), 20 deletions(-) diff --git a/cgi/oidc/authorize.pl b/cgi/oidc/authorize.pl index 9e52a75349b22..3dccb6b301b72 100644 --- a/cgi/oidc/authorize.pl +++ b/cgi/oidc/authorize.pl @@ -137,7 +137,6 @@ () sub _show_authorize() { my $request_uri = $request->uri; -# my $params = $request->query_parameters->as_hashref; my $dh = ProductOpener::OIDC::Server::DataHandler->new( request => $request, ); @@ -152,32 +151,31 @@ () my $error; my $client_info = $dh->get_client_info(); if ($error = $@) { -# return $c->render( -# "authorize/confirm.tt" => { -# status => $error, -# request_uri => $request_uri, -# params => $params, -# client_info => $client_info, -# } -# ); + my $html =_render_authorize({ + status => $error, + request_uri => $request_uri, + params => param(), + client_info => $client_info, + }); + return ($html, undef); } # create array ref of returned user claims for display my $resource_owner_id = $dh->get_user_id_for_authorization; my @scopes = split(/\s/, param('scope')); -# my $claims = $class->_get_resource_owner_claims($resource_owner_id, @scopes); + my $claims = _get_resource_owner_claims($resource_owner_id, @scopes); # confirm screen -# return $c->render( -# "authorize/confirm.tt" => { -# status => q{valid}, -# scopes => \@scopes, -# request_uri => $request_uri, -# params => $params, -# client_info => $client_info, -# claims => $claims, -# } -# ); + my $html = _render_authorize({ + status => q{valid}, + scopes => \@scopes, + request_uri => $request_uri, + params => param(), + client_info => $client_info, + claims => $claims, + }); + + return ($html, undef); } @@ -205,3 +203,31 @@ sub _get_resource_owner_claims { } return \@claims; } + +sub _render_authorize($) { + + my (%data) = @_; + + my $html = '

Authorization Endpoint (Confirm)

'; + if ($data{status} and $data{status} ne q{valid}) { + $html .= '
' . $data{status} . '
'; + } + + if ($data{scopes}) { + $html .= 'This client would to access your claims by following scopes.
    '; + for my $scope ($data{scopes}) { + $html .= '
  • ' . $scope . '
  • '; + } + + $html .= '
'; + } + + $html .= start_form() + . '' + . '' + . submit() + . end_form(); + + return $html; + +} From eb0725845f0ed2f112c0887df23abe0afdba4103 Mon Sep 17 00:00:00 2001 From: hangy Date: Wed, 28 Sep 2016 18:54:26 +0200 Subject: [PATCH 06/36] WIP authorize --- cgi/oidc/authorize.pl | 65 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 63 insertions(+), 2 deletions(-) diff --git a/cgi/oidc/authorize.pl b/cgi/oidc/authorize.pl index 3dccb6b301b72..411f4fb8fe397 100644 --- a/cgi/oidc/authorize.pl +++ b/cgi/oidc/authorize.pl @@ -181,9 +181,70 @@ () sub _redirect() { - my $html = 'Feel redirected. ^_^'; + my $request_uri = $request->request_uri; + my $dh = OIDC::Lite::Demo::Server::DataHandler->new( + request => $request, + ); + my $ah = OIDC::Lite::Server::AuthorizationHandler->new( + data_handler => $dh, + response_types => $RESPONSE_TYPES, + ); - return ($html, undef); + my $res; + eval { + $ah->handle_request(); + if( $c->validate_csrf() && + param('user_action') ){ + if( $param('user_action') eq q{accept} ){ + $res = $ah->allow(); + }else{ + $res = $ah->deny(); + } + }else{ + $class->confirm($c); + } + }; + my $error; + my $client_info = $dh->get_client_info(); + if ($error = $@) { + # return $c->render( + # "authorize/accept.tt" => { + # status => $error, + # request_uri => $request_uri, + # params => param(), + # client_info => $client_info, + # } + # ); + } + + # create array ref of returned user claims for display + my $resource_owner_id = $dh->get_user_id_for_authorization; + my @scopes = split(/\s/, param('scope')); + my $claims = get_resource_owner_claims($resource_owner_id, @scopes); + + $res->{query_string} = build_content($res->{query}); + $res->{fragment_string} = build_content($res->{fragment}); + $res->{uri} = $res->{redirect_uri}; + if ($res->{query_string}) { + $res->{uri} .= ($res->{uri} =~ /\?/) ? q{&} : q{?}; + $res->{uri} .= $res->{query_string}; + } + if ($res->{fragment_string}) { + $res->{uri} .= q{#}.$res->{fragment_string}; + } + + # confirm screen +# return $c->render( +# "authorize/accept.tt" => { +# status => q{valid}, +# scopes => \@scopes, +# request_uri => $request_uri, +# params => $params, +# client_info => $client_info, +# claims => $claims, +# response_info => $res, +# } +# ); } From b43b27998a5e2a1e89afd4386dc518603ef1a564 Mon Sep 17 00:00:00 2001 From: hangy Date: Sun, 29 Nov 2015 07:28:38 -0500 Subject: [PATCH 07/36] Added WWW::CSRF to user related methods. Cherry-pick c5898692cbe9264e47fefb77d132c3f7345bfae9 Conflicts: cgi/Users.pm cgi/reset_password.pl --- cpanfile | 1 + lib/ProductOpener/Config2_sample.pm | 3 +++ lib/ProductOpener/Config_obf.pm | 2 ++ lib/ProductOpener/Config_off.pm | 2 ++ lib/ProductOpener/Lang.pm | 3 +++ lib/ProductOpener/Users.pm | 26 ++++++++++++++++++++------ 6 files changed, 31 insertions(+), 6 deletions(-) diff --git a/cpanfile b/cpanfile index 31741f079c09b..1310f3b005df7 100644 --- a/cpanfile +++ b/cpanfile @@ -20,6 +20,7 @@ requires 'XML::FeedPP'; # libxml-feedpp-perl requires 'URI::Find'; # liburi-find-perl requires 'XML::Simple'; # libxml-simple-perl requires 'experimental'; # libexperimental-perl +requires 'WWW::CSRF'; # libwww-csrf-perl # Probably not available as Debian packages requires 'MongoDB', '>= 1.4.5'; # libmongodb-perl has an older version diff --git a/lib/ProductOpener/Config2_sample.pm b/lib/ProductOpener/Config2_sample.pm index c2240712f74a9..93345c1bf13fa 100644 --- a/lib/ProductOpener/Config2_sample.pm +++ b/lib/ProductOpener/Config2_sample.pm @@ -14,6 +14,7 @@ BEGIN $facebook_app_id $facebook_app_secret $oidc + $csrf_secret ); %EXPORT_TAGS = (all => [@EXPORT_OK]); } @@ -76,4 +77,6 @@ eH+MQwdClxQ8rTr2CxXZKffji8I2Vs9FUE+pep0s3gR072kix3EFdZc= }, }; +$csrf_secret = "SWMAqq4znqqaHN9q7UWM5xQ5aJqKqPsekcwSuvjkkTmTtTXvPpyZxXkY25kqgaXQbLFVEaqZ"; + 1; diff --git a/lib/ProductOpener/Config_obf.pm b/lib/ProductOpener/Config_obf.pm index 4bdf442138189..980b821df5c4c 100644 --- a/lib/ProductOpener/Config_obf.pm +++ b/lib/ProductOpener/Config_obf.pm @@ -17,6 +17,7 @@ BEGIN $admin_email $oidc + $csrf_secret $mongodb @@ -82,6 +83,7 @@ $www_root = $ProductOpener::Config2::www_root; $data_root = $ProductOpener::Config2::data_root; $oidc = $ProductOpener::Config2::oidc; +$csrf_secret = $ProductOpener::Config2::csrf_secret; $reference_timezone = 'Europe/Paris'; diff --git a/lib/ProductOpener/Config_off.pm b/lib/ProductOpener/Config_off.pm index 498aeec34bcb0..f38cda7d5b9a2 100644 --- a/lib/ProductOpener/Config_off.pm +++ b/lib/ProductOpener/Config_off.pm @@ -20,6 +20,7 @@ BEGIN $facebook_app_secret $oidc + $csrf_secret $mongodb @@ -88,6 +89,7 @@ $facebook_app_id = $ProductOpener::Config2::facebook_app_id; $facebook_app_secret = $ProductOpener::Config2::facebook_app_secret; $oidc = $ProductOpener::Config2::oidc; +$csrf_secret = $ProductOpener::Config2::csrf_secret; $reference_timezone = 'Europe/Paris'; diff --git a/lib/ProductOpener/Lang.pm b/lib/ProductOpener/Lang.pm index 45b51a394333a..eb4240f690ff2 100644 --- a/lib/ProductOpener/Lang.pm +++ b/lib/ProductOpener/Lang.pm @@ -3000,6 +3000,9 @@ error_invalid_address => { nl_be => 'Ongeldig adres', }, +error_invalid_csrf_token => { + en => "Invalid CSRF token.", +}, name => { fr => "Nom", diff --git a/lib/ProductOpener/Users.pm b/lib/ProductOpener/Users.pm index 4ec92764b6cd6..9080ea41f4702 100644 --- a/lib/ProductOpener/Users.pm +++ b/lib/ProductOpener/Users.pm @@ -51,6 +51,8 @@ BEGIN &check_session + &generate_po_csrf_token + &check_po_csrf_token &generate_token ); # symbols to export on request @@ -77,6 +79,8 @@ use Crypt::PasswdMD5 qw(unix_md5_crypt); use Math::Random::Secure qw(irand); use Crypt::ScryptKDF qw(scrypt_hash scrypt_hash_verify); +use WWW::CSRF qw(generate_csrf_token check_csrf_token CSRF_OK); + my @salt = ( '.', '/', 0 .. 9, 'A' .. 'Z', 'a' .. 'z' ); # uses global @salt to construct salt string of requested length @@ -594,23 +598,23 @@ sub init_user() { # If we don't have a user id, check if there is a browser id cookie, or assign one - if (not ((defined cookie('b')) and (cookie('b') =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)_(\d+)$/))) - { + if (not ((defined cookie('b')) and (cookie('b') =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)_(\d+)$/))) + { my $b = remote_addr() . '_' . time(); # $Visitor_id = $b; # don't set $Visitor_id unless we get the cookie back # Set a cookie if (not defined $cookie) { - $cookie = cookie (-name=>'b', -value=>$b, -path=>'/', -expires=>'+86400000s') ; - print STDERR "Users.pm - setting b cookie: $cookie\n"; + $cookie = cookie (-name=>'b', -value=>$b, -path=>'/', -expires=>'+86400000s') ; + print STDERR "Users.pm - setting b cookie: $cookie\n"; } } else { - $Visitor_id = cookie('b'); + $Visitor_id = cookie('b'); $user_ref = retrieve("$data_root/virtual_users/$Visitor_id.sto"); print STDERR "Users.pm - got b cookie: $Visitor_id\n"; - } + } } @@ -704,4 +708,14 @@ sub save_user() { } } +sub generate_po_csrf_token($) { + my ( $user_id ) = @_; + generate_csrf_token($user_id, $csrf_secret); +} + +sub check_po_csrf_token($$) { + my ( $user_id, $csrf_token) = @_; + check_csrf_token($user_id, $csrf_secret, $csrf_token); +} + 1; From 1b6081a380bdc7ab852e86ea46c94bb2de5091ab Mon Sep 17 00:00:00 2001 From: hangy Date: Wed, 28 Sep 2016 21:36:37 +0200 Subject: [PATCH 08/36] Mocked Request class --- cgi/oidc/authorize.pl | 29 ++++++++++++++---------- lib/ProductOpener/OIDC/Server/Request.pm | 28 +++++++++++++++++++++++ 2 files changed, 45 insertions(+), 12 deletions(-) create mode 100644 lib/ProductOpener/OIDC/Server/Request.pm diff --git a/cgi/oidc/authorize.pl b/cgi/oidc/authorize.pl index 411f4fb8fe397..57d5dcda5cf2c 100644 --- a/cgi/oidc/authorize.pl +++ b/cgi/oidc/authorize.pl @@ -48,10 +48,12 @@ use Storable qw/dclone/; use Encode; use JSON::PP; +use WWW::CSRF qw(CSRF_OK); use OAuth::Lite2::Util qw/build_content/; +use ProductOpener::OIDC::Server::Request; use ProductOpener::OIDC::Server::DataHandler; -#use ProductOpener::OIDC::Server::Web::M::ResourceOwner; +use ProductOpener::OIDC::Server::Web::M::ResourceOwner; use OIDC::Lite::Server::AuthorizationHandler; use OIDC::Lite::Server::Scope; use OIDC::Lite::Model::IDToken; @@ -138,7 +140,7 @@ () my $request_uri = $request->uri; my $dh = ProductOpener::OIDC::Server::DataHandler->new( - request => $request, + request => ProductOpener::OIDC::Server::Request->new(), ); my $ah = OIDC::Lite::Server::AuthorizationHandler->new( data_handler => $dh, @@ -154,7 +156,7 @@ () my $html =_render_authorize({ status => $error, request_uri => $request_uri, - params => param(), + params => url_param(), client_info => $client_info, }); return ($html, undef); @@ -181,9 +183,9 @@ () sub _redirect() { - my $request_uri = $request->request_uri; - my $dh = OIDC::Lite::Demo::Server::DataHandler->new( - request => $request, + my $request_uri = $request->uri; + my $dh = ProductOpener::OIDC::Server::DataHandler->new( + request => ProductOpener::OIDC::Server::Request->new(), ); my $ah = OIDC::Lite::Server::AuthorizationHandler->new( data_handler => $dh, @@ -193,20 +195,21 @@ () my $res; eval { $ah->handle_request(); - if( $c->validate_csrf() && - param('user_action') ){ - if( $param('user_action') eq q{accept} ){ + my $csrf_token_status = CSRF_OK; #check_po_csrf_token(cookie('b'), param('csrf')); + if (($csrf_token_status eq CSRF_OK) and param('user_action')) { + if( param('user_action') eq q{accept} ){ $res = $ah->allow(); }else{ $res = $ah->deny(); } }else{ - $class->confirm($c); + return _show_authorize(); } }; my $error; my $client_info = $dh->get_client_info(); if ($error = $@) { + return ($error, undef); # return $c->render( # "authorize/accept.tt" => { # status => $error, @@ -220,7 +223,7 @@ () # create array ref of returned user claims for display my $resource_owner_id = $dh->get_user_id_for_authorization; my @scopes = split(/\s/, param('scope')); - my $claims = get_resource_owner_claims($resource_owner_id, @scopes); + my $claims = _get_resource_owner_claims($resource_owner_id, @scopes); $res->{query_string} = build_content($res->{query}); $res->{fragment_string} = build_content($res->{fragment}); @@ -234,6 +237,7 @@ () } # confirm screen + return ('valid', undef); # return $c->render( # "authorize/accept.tt" => { # status => q{valid}, @@ -276,7 +280,7 @@ ($) if ($data{scopes}) { $html .= 'This client would to access your claims by following scopes.
    '; - for my $scope ($data{scopes}) { + for my $scope (%data{scopes}) { $html .= '
  • ' . $scope . '
  • '; } @@ -286,6 +290,7 @@ ($) $html .= start_form() . '' . '' + #. hidden(-name=>'csrf', -value=>generate_po_csrf_token(cookie('b')), -override=>1) . submit() . end_form(); diff --git a/lib/ProductOpener/OIDC/Server/Request.pm b/lib/ProductOpener/OIDC/Server/Request.pm new file mode 100644 index 0000000000000..746cd48bd4182 --- /dev/null +++ b/lib/ProductOpener/OIDC/Server/Request.pm @@ -0,0 +1,28 @@ +package ProductOpener::OIDC::Server::Request; +use strict; +use warnings; +use utf8; + +sub new { + + my $class = shift; + return bless {}, $class; + +} + +sub param($) { + + my ($self, $name) = @_; + + CGI::url_param($name); + +} + + +sub parameters() { + + CGI::url_param(); + +} + +1; From 1c24b711a4d27dce925632a8dcf84793d95c17ea Mon Sep 17 00:00:00 2001 From: hangy Date: Wed, 28 Sep 2016 22:19:50 +0200 Subject: [PATCH 09/36] WIPFIXES --- lib/ProductOpener/OIDC/Server.pm | 4 +++- lib/ProductOpener/OIDC/Server/Web/M/AuthInfo.pm | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/ProductOpener/OIDC/Server.pm b/lib/ProductOpener/OIDC/Server.pm index 9cab5bfd6caeb..94bd8dfaa96f8 100644 --- a/lib/ProductOpener/OIDC/Server.pm +++ b/lib/ProductOpener/OIDC/Server.pm @@ -3,6 +3,8 @@ use strict; use warnings; use utf8; +use ProductOpener::Display qw/:all/; + sub new { my $class = shift; @@ -14,7 +16,7 @@ sub db { my $self = shift; if ( !defined $self->{db} ) { - my $oidc_collection = ProductOpener::Display::database->get_collection('oidc'); + my $oidc_collection = $database->get_collection('oidc'); $self->{db} = $oidc_collection; } diff --git a/lib/ProductOpener/OIDC/Server/Web/M/AuthInfo.pm b/lib/ProductOpener/OIDC/Server/Web/M/AuthInfo.pm index 63d8bd5e92adb..7acaf408d8f48 100644 --- a/lib/ProductOpener/OIDC/Server/Web/M/AuthInfo.pm +++ b/lib/ProductOpener/OIDC/Server/Web/M/AuthInfo.pm @@ -79,8 +79,8 @@ sub save { $self->userinfo_claims(OIDC::Lite::Server::Scope->to_normal_claims(\@scopes)); my $row; - if (undef $self->id) { - my $id = $db->insert_one( + if (undef $self{id}) { + my $res = $db->insert_one( { client_id => $self->client_id, user_id => $self->user_id, @@ -94,7 +94,7 @@ sub save { refresh_token_expired_on => $self->refresh_token_expired_on, } ); - $self->id($id); + $self{id} = $res->inserted_id; } else { $row = $db->find_one_and_update( { From 2d39e7de7fcdbe9291d59311d4bc7a906cfd61ec Mon Sep 17 00:00:00 2001 From: hangy Date: Thu, 29 Sep 2016 10:03:13 +0200 Subject: [PATCH 10/36] Use CGI::Plus as a built-in CSRF protection. --- cgi/oidc/authorize.pl | 10 ++++++---- cpanfile | 2 +- lib/ProductOpener/Config2_sample.pm | 3 --- lib/ProductOpener/Config_obf.pm | 4 +--- lib/ProductOpener/Config_off.pm | 2 -- lib/ProductOpener/OIDC/Server/DataHandler.pm | 2 +- lib/ProductOpener/OIDC/Server/Web/M/AuthInfo.pm | 4 ++-- lib/ProductOpener/OIDC/Server/Web/M/Client.pm | 3 ++- lib/ProductOpener/Users.pm | 14 -------------- 9 files changed, 13 insertions(+), 31 deletions(-) diff --git a/cgi/oidc/authorize.pl b/cgi/oidc/authorize.pl index 57d5dcda5cf2c..ea3b8e91205eb 100644 --- a/cgi/oidc/authorize.pl +++ b/cgi/oidc/authorize.pl @@ -44,11 +44,11 @@ use Apache2::Const (); use CGI qw/:cgi :form escapeHTML/; +use CGI::Plus; use URI::Escape::XS; use Storable qw/dclone/; use Encode; use JSON::PP; -use WWW::CSRF qw(CSRF_OK); use OAuth::Lite2::Util qw/build_content/; use ProductOpener::OIDC::Server::Request; @@ -66,6 +66,9 @@ q{code id_token token} ]; +my $cgi = CGI::Plus->new(); +$cgi->csrf(1); + my $request = Apache2::RequestUtil->request(); my $method = $request->method(); @@ -195,8 +198,7 @@ () my $res; eval { $ah->handle_request(); - my $csrf_token_status = CSRF_OK; #check_po_csrf_token(cookie('b'), param('csrf')); - if (($csrf_token_status eq CSRF_OK) and param('user_action')) { + if (($cgi->csrf_check) and param('user_action')) { if( param('user_action') eq q{accept} ){ $res = $ah->allow(); }else{ @@ -290,7 +292,7 @@ ($) $html .= start_form() . '' . '' - #. hidden(-name=>'csrf', -value=>generate_po_csrf_token(cookie('b')), -override=>1) + . $cgi->csrf_field . submit() . end_form(); diff --git a/cpanfile b/cpanfile index 1310f3b005df7..6d0d05d677388 100644 --- a/cpanfile +++ b/cpanfile @@ -20,7 +20,6 @@ requires 'XML::FeedPP'; # libxml-feedpp-perl requires 'URI::Find'; # liburi-find-perl requires 'XML::Simple'; # libxml-simple-perl requires 'experimental'; # libexperimental-perl -requires 'WWW::CSRF'; # libwww-csrf-perl # Probably not available as Debian packages requires 'MongoDB', '>= 1.4.5'; # libmongodb-perl has an older version @@ -36,6 +35,7 @@ requires 'DateTime::Format::CLDR'; requires 'DateTime::Locale'; requires 'Math::Random::Secure'; requires 'Crypt::ScryptKDF'; +requires 'CGI::Plus', '>= 0.15'; # OIDC requires 'OAuth::Lite2', '0.11'; diff --git a/lib/ProductOpener/Config2_sample.pm b/lib/ProductOpener/Config2_sample.pm index 93345c1bf13fa..c2240712f74a9 100644 --- a/lib/ProductOpener/Config2_sample.pm +++ b/lib/ProductOpener/Config2_sample.pm @@ -14,7 +14,6 @@ BEGIN $facebook_app_id $facebook_app_secret $oidc - $csrf_secret ); %EXPORT_TAGS = (all => [@EXPORT_OK]); } @@ -77,6 +76,4 @@ eH+MQwdClxQ8rTr2CxXZKffji8I2Vs9FUE+pep0s3gR072kix3EFdZc= }, }; -$csrf_secret = "SWMAqq4znqqaHN9q7UWM5xQ5aJqKqPsekcwSuvjkkTmTtTXvPpyZxXkY25kqgaXQbLFVEaqZ"; - 1; diff --git a/lib/ProductOpener/Config_obf.pm b/lib/ProductOpener/Config_obf.pm index 980b821df5c4c..bfb19ab47a0bd 100644 --- a/lib/ProductOpener/Config_obf.pm +++ b/lib/ProductOpener/Config_obf.pm @@ -17,8 +17,7 @@ BEGIN $admin_email $oidc - $csrf_secret - + $mongodb $google_analytics @@ -83,7 +82,6 @@ $www_root = $ProductOpener::Config2::www_root; $data_root = $ProductOpener::Config2::data_root; $oidc = $ProductOpener::Config2::oidc; -$csrf_secret = $ProductOpener::Config2::csrf_secret; $reference_timezone = 'Europe/Paris'; diff --git a/lib/ProductOpener/Config_off.pm b/lib/ProductOpener/Config_off.pm index f38cda7d5b9a2..498aeec34bcb0 100644 --- a/lib/ProductOpener/Config_off.pm +++ b/lib/ProductOpener/Config_off.pm @@ -20,7 +20,6 @@ BEGIN $facebook_app_secret $oidc - $csrf_secret $mongodb @@ -89,7 +88,6 @@ $facebook_app_id = $ProductOpener::Config2::facebook_app_id; $facebook_app_secret = $ProductOpener::Config2::facebook_app_secret; $oidc = $ProductOpener::Config2::oidc; -$csrf_secret = $ProductOpener::Config2::csrf_secret; $reference_timezone = 'Europe/Paris'; diff --git a/lib/ProductOpener/OIDC/Server/DataHandler.pm b/lib/ProductOpener/OIDC/Server/DataHandler.pm index 6447019c5839d..139fba33979e6 100644 --- a/lib/ProductOpener/OIDC/Server/DataHandler.pm +++ b/lib/ProductOpener/OIDC/Server/DataHandler.pm @@ -6,7 +6,7 @@ use parent 'OIDC::Lite::Server::DataHandler'; use OIDC::Lite::Server::Scope; use OIDC::Lite::Model::IDToken; -#use ProductOpener::OIDC::Server; +use ProductOpener::OIDC::Server; use ProductOpener::OIDC::Server::Web::M::AccessToken; use ProductOpener::OIDC::Server::Web::M::AuthInfo; use ProductOpener::OIDC::Server::Web::M::Client; diff --git a/lib/ProductOpener/OIDC/Server/Web/M/AuthInfo.pm b/lib/ProductOpener/OIDC/Server/Web/M/AuthInfo.pm index 7acaf408d8f48..6601471bbc5d9 100644 --- a/lib/ProductOpener/OIDC/Server/Web/M/AuthInfo.pm +++ b/lib/ProductOpener/OIDC/Server/Web/M/AuthInfo.pm @@ -79,7 +79,7 @@ sub save { $self->userinfo_claims(OIDC::Lite::Server::Scope->to_normal_claims(\@scopes)); my $row; - if (undef $self{id}) { + if (undef $self->id) { my $res = $db->insert_one( { client_id => $self->client_id, @@ -94,7 +94,7 @@ sub save { refresh_token_expired_on => $self->refresh_token_expired_on, } ); - $self{id} = $res->inserted_id; + $self->id = $res->inserted_id; } else { $row = $db->find_one_and_update( { diff --git a/lib/ProductOpener/OIDC/Server/Web/M/Client.pm b/lib/ProductOpener/OIDC/Server/Web/M/Client.pm index 4a3387fe4efec..b45b57ea7d768 100644 --- a/lib/ProductOpener/OIDC/Server/Web/M/Client.pm +++ b/lib/ProductOpener/OIDC/Server/Web/M/Client.pm @@ -23,7 +23,8 @@ our $SAMPLE_CLIENTS = { 'client_id' => q{sample_client_id}, 'client_secret' => q{sample_client_secret}, 'redirect_uris' => [ - $c->config->{SampleClient}->{redirect_uri}, + 'www.hangy.de' +# $c->config->{SampleClient}->{redirect_uri}, ], 'allowed_response_types' => [ q{code}, q{id_token}, q{token}, diff --git a/lib/ProductOpener/Users.pm b/lib/ProductOpener/Users.pm index 9080ea41f4702..a8521a349060d 100644 --- a/lib/ProductOpener/Users.pm +++ b/lib/ProductOpener/Users.pm @@ -51,8 +51,6 @@ BEGIN &check_session - &generate_po_csrf_token - &check_po_csrf_token &generate_token ); # symbols to export on request @@ -79,8 +77,6 @@ use Crypt::PasswdMD5 qw(unix_md5_crypt); use Math::Random::Secure qw(irand); use Crypt::ScryptKDF qw(scrypt_hash scrypt_hash_verify); -use WWW::CSRF qw(generate_csrf_token check_csrf_token CSRF_OK); - my @salt = ( '.', '/', 0 .. 9, 'A' .. 'Z', 'a' .. 'z' ); # uses global @salt to construct salt string of requested length @@ -708,14 +704,4 @@ sub save_user() { } } -sub generate_po_csrf_token($) { - my ( $user_id ) = @_; - generate_csrf_token($user_id, $csrf_secret); -} - -sub check_po_csrf_token($$) { - my ( $user_id, $csrf_token) = @_; - check_csrf_token($user_id, $csrf_secret, $csrf_token); -} - 1; From bb2b7317aff9d8c609732bf03f35f962c9dbfff4 Mon Sep 17 00:00:00 2001 From: hangy Date: Thu, 29 Sep 2016 11:19:07 +0200 Subject: [PATCH 11/36] Use the correct url_param sub instead of param. --- cgi/oidc/authorize.pl | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/cgi/oidc/authorize.pl b/cgi/oidc/authorize.pl index ea3b8e91205eb..a3b2c5c788d66 100644 --- a/cgi/oidc/authorize.pl +++ b/cgi/oidc/authorize.pl @@ -27,18 +27,9 @@ use utf8; use ProductOpener::Config qw/:all/; -use ProductOpener::Store qw/:all/; -use ProductOpener::Index qw/:all/; use ProductOpener::Display qw/:all/; -use ProductOpener::Tags qw/:all/; use ProductOpener::Users qw/:all/; -use ProductOpener::Images qw/:all/; use ProductOpener::Lang qw/:all/; -use ProductOpener::Mail qw/:all/; -use ProductOpener::Products qw/:all/; -use ProductOpener::Food qw/:all/; -use ProductOpener::Ingredients qw/:all/; -use ProductOpener::Images qw/:all/; use Apache2::RequestRec (); use Apache2::Const (); @@ -159,7 +150,6 @@ () my $html =_render_authorize({ status => $error, request_uri => $request_uri, - params => url_param(), client_info => $client_info, }); return ($html, undef); @@ -167,7 +157,7 @@ () # create array ref of returned user claims for display my $resource_owner_id = $dh->get_user_id_for_authorization; - my @scopes = split(/\s/, param('scope')); + my @scopes = split(/\s/, url_param('scope')); my $claims = _get_resource_owner_claims($resource_owner_id, @scopes); # confirm screen @@ -175,7 +165,6 @@ () status => q{valid}, scopes => \@scopes, request_uri => $request_uri, - params => param(), client_info => $client_info, claims => $claims, }); @@ -224,7 +213,7 @@ () # create array ref of returned user claims for display my $resource_owner_id = $dh->get_user_id_for_authorization; - my @scopes = split(/\s/, param('scope')); + my @scopes = split(/\s/, url_param('scope')); my $claims = _get_resource_owner_claims($resource_owner_id, @scopes); $res->{query_string} = build_content($res->{query}); From 722ca4cf757a9e12c990be4d25be84ac65b4a96a Mon Sep 17 00:00:00 2001 From: hangy Date: Thu, 29 Sep 2016 11:20:57 +0200 Subject: [PATCH 12/36] Removed CGI::Plus. Does not seem to set the cookie correctly in our envirmonment setup. --- cgi/oidc/authorize.pl | 8 ++------ cpanfile | 1 - 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/cgi/oidc/authorize.pl b/cgi/oidc/authorize.pl index a3b2c5c788d66..4541e101f1d12 100644 --- a/cgi/oidc/authorize.pl +++ b/cgi/oidc/authorize.pl @@ -35,7 +35,6 @@ use Apache2::Const (); use CGI qw/:cgi :form escapeHTML/; -use CGI::Plus; use URI::Escape::XS; use Storable qw/dclone/; use Encode; @@ -57,9 +56,6 @@ q{code id_token token} ]; -my $cgi = CGI::Plus->new(); -$cgi->csrf(1); - my $request = Apache2::RequestUtil->request(); my $method = $request->method(); @@ -187,7 +183,7 @@ () my $res; eval { $ah->handle_request(); - if (($cgi->csrf_check) and param('user_action')) { + if (param('user_action')) { # TODO: CSRF if( param('user_action') eq q{accept} ){ $res = $ah->allow(); }else{ @@ -281,7 +277,7 @@ ($) $html .= start_form() . '' . '' - . $cgi->csrf_field +# TODO: CSRF . submit() . end_form(); diff --git a/cpanfile b/cpanfile index 6d0d05d677388..31741f079c09b 100644 --- a/cpanfile +++ b/cpanfile @@ -35,7 +35,6 @@ requires 'DateTime::Format::CLDR'; requires 'DateTime::Locale'; requires 'Math::Random::Secure'; requires 'Crypt::ScryptKDF'; -requires 'CGI::Plus', '>= 0.15'; # OIDC requires 'OAuth::Lite2', '0.11'; From 86539430e3815d2666b40478f36948774daa9e68 Mon Sep 17 00:00:00 2001 From: hangy Date: Thu, 29 Sep 2016 12:06:15 +0200 Subject: [PATCH 13/36] Fix MongoDB insert/id. --- lib/ProductOpener/OIDC/Server/Web/M/AuthInfo.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/ProductOpener/OIDC/Server/Web/M/AuthInfo.pm b/lib/ProductOpener/OIDC/Server/Web/M/AuthInfo.pm index 6601471bbc5d9..39d4b0230c8c0 100644 --- a/lib/ProductOpener/OIDC/Server/Web/M/AuthInfo.pm +++ b/lib/ProductOpener/OIDC/Server/Web/M/AuthInfo.pm @@ -79,7 +79,7 @@ sub save { $self->userinfo_claims(OIDC::Lite::Server::Scope->to_normal_claims(\@scopes)); my $row; - if (undef $self->id) { + if (!$self->id) { my $res = $db->insert_one( { client_id => $self->client_id, @@ -94,7 +94,7 @@ sub save { refresh_token_expired_on => $self->refresh_token_expired_on, } ); - $self->id = $res->inserted_id; + $self->id($res->inserted_id->value); } else { $row = $db->find_one_and_update( { From 61789c619aeb947726e992a29979a5cfd9139e1a Mon Sep 17 00:00:00 2001 From: hangy Date: Thu, 29 Sep 2016 12:31:06 +0200 Subject: [PATCH 14/36] Add redirect to client application. --- cgi/oidc/authorize.pl | 29 ++++++++--------------------- 1 file changed, 8 insertions(+), 21 deletions(-) diff --git a/cgi/oidc/authorize.pl b/cgi/oidc/authorize.pl index 4541e101f1d12..f67e08662c94c 100644 --- a/cgi/oidc/authorize.pl +++ b/cgi/oidc/authorize.pl @@ -196,15 +196,12 @@ () my $error; my $client_info = $dh->get_client_info(); if ($error = $@) { - return ($error, undef); - # return $c->render( - # "authorize/accept.tt" => { - # status => $error, - # request_uri => $request_uri, - # params => param(), - # client_info => $client_info, - # } - # ); + my $html =_render_authorize({ + status => $error, + request_uri => $request_uri, + client_info => $client_info, + }); + return ($html, undef); } # create array ref of returned user claims for display @@ -224,18 +221,8 @@ () } # confirm screen - return ('valid', undef); -# return $c->render( -# "authorize/accept.tt" => { -# status => q{valid}, -# scopes => \@scopes, -# request_uri => $request_uri, -# params => $params, -# client_info => $client_info, -# claims => $claims, -# response_info => $res, -# } -# ); + print header ( -location => $res->{uri} ); + return ('valid', 302); } From a0dbc9216d32514f39626d86775416b77d3b00cf Mon Sep 17 00:00:00 2001 From: hangy Date: Thu, 29 Sep 2016 13:21:27 +0200 Subject: [PATCH 15/36] Use Foundation 6 style for the buttons. --- cgi/oidc/authorize.pl | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/cgi/oidc/authorize.pl b/cgi/oidc/authorize.pl index f67e08662c94c..bfc370d6b690a 100644 --- a/cgi/oidc/authorize.pl +++ b/cgi/oidc/authorize.pl @@ -262,10 +262,9 @@ ($) } $html .= start_form() - . '' - . '' + . '' + . '' # TODO: CSRF - . submit() . end_form(); return $html; From 5f4fa47a9b1cc468356b7e0511a9fdb032cec732 Mon Sep 17 00:00:00 2001 From: hangy Date: Thu, 29 Sep 2016 13:49:16 +0200 Subject: [PATCH 16/36] Use MongoDB for OpenID Connect clients, too. --- lib/ProductOpener/OIDC/Server.pm | 22 +- lib/ProductOpener/OIDC/Server/DataHandler.pm | 16 +- lib/ProductOpener/OIDC/Server/Web/M/Client.pm | 363 +++++++++--------- 3 files changed, 208 insertions(+), 193 deletions(-) diff --git a/lib/ProductOpener/OIDC/Server.pm b/lib/ProductOpener/OIDC/Server.pm index 94bd8dfaa96f8..7d8f962bd4437 100644 --- a/lib/ProductOpener/OIDC/Server.pm +++ b/lib/ProductOpener/OIDC/Server.pm @@ -12,15 +12,27 @@ sub new { } -sub db { +sub auth { my $self = shift; - if ( !defined $self->{db} ) { - my $oidc_collection = $database->get_collection('oidc'); - $self->{db} = $oidc_collection; + if ( !defined $self->{auth} ) { + my $oidc_collection = $database->get_collection('oidc_auth'); + $self->{auth} = $oidc_collection; } - return $self->{db}; + return $self->{auth}; + +} + +sub clients { + + my $self = shift; + if ( !defined $self->{clients} ) { + my $oidc_collection = $database->get_collection('oidc_clients'); + $self->{clients} = $oidc_collection; + } + + return $self->{clients}; } diff --git a/lib/ProductOpener/OIDC/Server/DataHandler.pm b/lib/ProductOpener/OIDC/Server/DataHandler.pm index 139fba33979e6..b56dc4a7d853a 100644 --- a/lib/ProductOpener/OIDC/Server/DataHandler.pm +++ b/lib/ProductOpener/OIDC/Server/DataHandler.pm @@ -30,7 +30,7 @@ sub validate_client_by_id { # obtain Client info unless ( $client && $client->{client_id} eq $client_id ) { $client = undef; - $client = ProductOpener::OIDC::Server::Web::M::Client->find_by_client_id($c->db, $client_id) + $client = ProductOpener::OIDC::Server::Web::M::Client->find_by_client_id($c->clients, $client_id) or return; } @@ -166,7 +166,7 @@ sub create_or_update_auth_info { # create AuthInfo Object my $info = ProductOpener::OIDC::Server::Web::M::AuthInfo->create(%args); $info->set_code; - $info->save($c->db); + $info->save($c->auth); return $info; } @@ -178,9 +178,9 @@ sub create_or_update_access_token { # If the request is for token endpoint, the code in AuthInfo is deleted if ($self->{request}->param('grant_type') && $self->{request}->param('grant_type') eq q{authorization_code}) { - $auth_info->set_refresh_token($c->db); - $auth_info->unset_code($c->db); - $auth_info->save($c->db); + $auth_info->set_refresh_token($c->auth); + $auth_info->unset_code($c->auth); + $auth_info->save($c->auth); } return ProductOpener::OIDC::Server::Web::M::AccessToken->create($args{auth_info}); } @@ -202,12 +202,12 @@ sub validate_client { sub get_auth_info_by_code { my ($self, $code) = @_; - return ProductOpener::OIDC::Server::Web::M::AuthInfo->find_by_code($c->db, $code); + return ProductOpener::OIDC::Server::Web::M::AuthInfo->find_by_code($c->auth, $code); } sub get_auth_info_by_refresh_token { my ($self, $refresh_token) = @_; - return ProductOpener::OIDC::Server::Web::M::AuthInfo->find_by_refresh_token($c->db, $refresh_token); + return ProductOpener::OIDC::Server::Web::M::AuthInfo->find_by_refresh_token($c->auth, $refresh_token); } sub get_access_token { @@ -217,7 +217,7 @@ sub get_access_token { sub get_auth_info_by_id { my ($self, $id) = @_; - return ProductOpener::OIDC::Server::Web::M::AuthInfo->find_by_id($c->db, $id); + return ProductOpener::OIDC::Server::Web::M::AuthInfo->find_by_id($c->auth, $id); } 1; diff --git a/lib/ProductOpener/OIDC/Server/Web/M/Client.pm b/lib/ProductOpener/OIDC/Server/Web/M/Client.pm index b45b57ea7d768..b0f56c8323503 100644 --- a/lib/ProductOpener/OIDC/Server/Web/M/Client.pm +++ b/lib/ProductOpener/OIDC/Server/Web/M/Client.pm @@ -4,12 +4,12 @@ use warnings; use utf8; use JSON::PP qw/ - encode_json - decode_json + encode_json + decode_json /; use Crypt::OpenSSL::Random qw/ - random_bytes - random_pseudo_bytes + random_bytes + random_pseudo_bytes /; use ProductOpener::OIDC::Server; @@ -17,209 +17,212 @@ my $c = ProductOpener::OIDC::Server->new; # sample client data our $SAMPLE_CLIENTS = { - 'sample_client_id' => { - 'id' => 0, - 'name' => q{Sample Client}, - 'client_id' => q{sample_client_id}, - 'client_secret' => q{sample_client_secret}, - 'redirect_uris' => [ - 'www.hangy.de' -# $c->config->{SampleClient}->{redirect_uri}, - ], - 'allowed_response_types' => [ - q{code}, q{id_token}, q{token}, - q{code id_token}, q{id_token token}, q{code token}, - q{code id_token token}, - ], - 'allowed_grant_types' => [ - q{authorization_code}, - q{refresh_token}, - ], - 'client_type' => 4, - 'is_disabled' => 0, - }, + 'sample_client_id' => { + 'id' => 0, + 'name' => q{Sample Client}, + 'client_id' => q{sample_client_id}, + 'client_secret' => q{sample_client_secret}, + 'redirect_uris' => [ + 'https://www.hangy.de/' +# $c->config->{SampleClient}->{redirect_uri}, + ], + 'allowed_response_types' => [ + q{code}, q{id_token}, q{token}, + q{code id_token}, q{id_token token}, q{code token}, + q{code id_token token}, + ], + 'allowed_grant_types' => [ + q{authorization_code}, + q{refresh_token}, + ], + 'client_type' => 4, + 'is_disabled' => 0, + }, }; my $CLIENT_TYPES = { - '1' => { - 'display' => 'Web Client', - 'allowed_response_types' => [ - q{code}, q{code id_token}, - ], - 'allowed_grant_types' => [ - q{authorization_code}, - q{refresh_token}, - ], - }, - '2' => { - 'display' => 'JavaScript Client', - 'allowed_response_types' => [ - q{id_token}, - q{id_token token}, - ], - 'allowed_grant_types' => [], - }, - '3' => { - 'display' => 'Mobile App', - 'allowed_response_types' => [ - q{code}, - q{id_token}, - q{id_token token}, - q{code id_token token}, - ], - 'allowed_grant_types' => [ - q{authorization_code}, - q{refresh_token}, - ], - }, - '4' => { - 'display' => 'Full Spec Client', - 'allowed_response_types' => [ - q{code}, q{id_token}, q{token}, - q{code id_token}, q{id_token token}, q{code token}, - q{code id_token token}, - ], - 'allowed_grant_types' => [ - q{authorization_code}, - q{refresh_token}, - ], - }, + '1' => { + 'display' => 'Web Client', + 'allowed_response_types' => [ + q{code}, q{code id_token}, + ], + 'allowed_grant_types' => [ + q{authorization_code}, + q{refresh_token}, + ], + }, + '2' => { + 'display' => 'JavaScript Client', + 'allowed_response_types' => [ + q{id_token}, + q{id_token token}, + ], + 'allowed_grant_types' => [], + }, + '3' => { + 'display' => 'Mobile App', + 'allowed_response_types' => [ + q{code}, + q{id_token}, + q{id_token token}, + q{code id_token token}, + ], + 'allowed_grant_types' => [ + q{authorization_code}, + q{refresh_token}, + ], + }, + '4' => { + 'display' => 'Full Spec Client', + 'allowed_response_types' => [ + q{code}, q{id_token}, q{token}, + q{code id_token}, q{id_token token}, q{code token}, + q{code id_token token}, + ], + 'allowed_grant_types' => [ + q{authorization_code}, + q{refresh_token}, + ], + }, }; sub create { - my ($class, $args) = @_; - - return unless( - $args->{name} && - $args->{redirect_uris} - ); - - $args->{client_type} = 4; - my $credentials = $class->_gen_credentials(); - - return { - 'id' => 0, - 'name' => $args->{name}, - 'client_id' => $credentials->{client_id}, - 'client_secret' => $credentials->{client_secret}, - 'redirect_uris' => $args->{redirect_uris}, - 'client_type' => $args->{client_type}, - 'allowed_response_types' => $CLIENT_TYPES->{$args->{client_type}}->{allowed_response_types}, - 'allowed_grant_types' => $CLIENT_TYPES->{$args->{client_type}}->{allowed_grant_types}, - 'is_disabled' => $args->{is_disabled}, - }; + my ($class, $args) = @_; + + return unless( + $args->{name} && + $args->{redirect_uris} + ); + + $args->{client_type} = 4; + my $credentials = $class->_gen_credentials(); + + return { + 'id' => undef, + 'name' => $args->{name}, + 'client_id' => $credentials->{client_id}, + 'client_secret' => $credentials->{client_secret}, + 'redirect_uris' => $args->{redirect_uris}, + 'client_type' => $args->{client_type}, + 'allowed_response_types' => $CLIENT_TYPES->{$args->{client_type}}->{allowed_response_types}, + 'allowed_grant_types' => $CLIENT_TYPES->{$args->{client_type}}->{allowed_grant_types}, + 'is_disabled' => $args->{is_disabled}, + }; } sub insert { - my ($class, $db, $args) = @_; - - $args->{is_disabled} = 0 unless defined $args->{is_disabled}; - - my $row = $db->insert( - 'clients' => { - name => $args->{name}, - client_id => $args->{client_id}, - client_secret => $args->{client_secret}, - redirect_uris => encode_json($args->{redirect_uris}), - client_type => $args->{client_type}, - is_disabled => $args->{is_disabled}, - } - ); - return $class->_row_to_hash_ref($row); + my ($class, $db, $args) = @_; + + $args->{is_disabled} = 0 unless defined $args->{is_disabled}; + + my $res = $db->insert_one( + { + name => $args->{name}, + client_id => $args->{client_id}, + client_secret => $args->{client_secret}, + redirect_uris => encode_json($args->{redirect_uris}), + client_type => $args->{client_type}, + is_disabled => $args->{is_disabled}, + } + ); + $args->id($res->inserted_id->value); + return $args; } sub update { - my ($class, $db, $args) = @_; - - my $row = $db->update( - 'clients', - { - name => $args->{name}, - client_id => $args->{client_id}, - client_secret => $args->{client_secret}, - redirect_uris => encode_json($args->{redirect_uris}), - client_type => $args->{client_type}, - is_disabled => $args->{is_disabled} || 0, - }, - { - id => $args->{id}, - } - ); + my ($class, $db, $args) = @_; + + my $row = $db->find_one_and_update( + { + _id => $args->{id}, + }, + { + name => $args->{name}, + client_id => $args->{client_id}, + client_secret => $args->{client_secret}, + redirect_uris => encode_json($args->{redirect_uris}), + client_type => $args->{client_type}, + is_disabled => $args->{is_disabled} || 0, + } + ); } sub find_by_client_id { - my ($class, $db, $client_id) = @_; - return unless $client_id; - - # find from sample Clients - return $SAMPLE_CLIENTS->{$client_id} if $SAMPLE_CLIENTS->{$client_id}; - - # find from DB - my $row = $db->single( - 'clients', - { - client_id => $client_id, - is_disabled => 0, - } - ) or return; - return $class->_row_to_hash_ref($row); + my ($class, $db, $client_id) = @_; + return unless $client_id; + + # find from sample Clients + return $SAMPLE_CLIENTS->{$client_id} if $SAMPLE_CLIENTS->{$client_id}; + + # find from DB + my $row; + eval { + $row = $db->find_one({ client_id => $client_id }); + }; + if ($@) { + return; + } + return $class->_row_to_hash_ref($row); } sub find_by_id { - my ($class, $db, $id) = @_; - return unless ($db && $id); - - return $SAMPLE_CLIENTS->{sample_client_id} if $id == 0; - - # fetch from DB - my $row = $db->single( - 'clients', - { - id => $id, - is_disabled => 0, - } - ) or return; - return $class->_row_to_hash_ref($row); + my ($class, $db, $id) = @_; + return unless ($db && $id); + + return $SAMPLE_CLIENTS->{sample_client_id} if $id == 0; + + # find from DB + my $row; + eval { + $row = $db->find_one({ _id => $id, is_disabled => 0 }); + }; + if ($@) { + return; + } + return $class->_row_to_hash_ref($row); } sub find_all { - my ($class, $db) = @_; - return unless $db; - - my @rows = $db->search( - 'clients', - { is_disabled => 0 }, - { order_by => 'id' } - ); - - my @clients; - foreach my $row (@rows) { - push(@clients, $class->_row_to_hash_ref($row)); - } - return \@clients; + my ($class, $db) = @_; + return unless $db; + + # find from DB + my @rows; + eval { + @rows = $db->find({ is_disabled => 0 })->sort( { _id => 1 }); + }; + if ($@) { + return; + } + + my @clients; + foreach my $row (@rows) { + push(@clients, $class->_row_to_hash_ref($row)); + } + return \@clients; } sub _gen_credentials { - my $class = shift; - return { - client_id => unpack('H*', random_pseudo_bytes(32)), - client_secret => unpack('H*', random_bytes(32)), - }; + my $class = shift; + return { + client_id => unpack('H*', random_pseudo_bytes(32)), + client_secret => unpack('H*', random_bytes(32)), + }; } sub _row_to_hash_ref { - my ($class, $row) = @_; - return { - 'id' => $row->id, - 'name' => $row->name, - 'client_id' => $row->client_id, - 'client_secret' => $row->client_secret, - 'redirect_uris' => decode_json($row->redirect_uris), - 'allowed_response_types' => $CLIENT_TYPES->{$row->client_type}->{allowed_response_types}, - 'allowed_grant_types' => $CLIENT_TYPES->{$row->client_type}->{allowed_grant_types}, - 'client_type' => $row->client_type, - 'is_disabled' => $row->is_disabled, - }; + my ($class, $row) = @_; + return { + 'id' => $row->_id, + 'name' => $row->name, + 'client_id' => $row->client_id, + 'client_secret' => $row->client_secret, + 'redirect_uris' => decode_json($row->redirect_uris), + 'allowed_response_types' => $CLIENT_TYPES->{$row->client_type}->{allowed_response_types}, + 'allowed_grant_types' => $CLIENT_TYPES->{$row->client_type}->{allowed_grant_types}, + 'client_type' => $row->client_type, + 'is_disabled' => $row->is_disabled, + }; } 1; From f3725311bf73085e6b773556fb18d40d05b4e1e8 Mon Sep 17 00:00:00 2001 From: hangy Date: Thu, 29 Sep 2016 19:49:18 +0200 Subject: [PATCH 17/36] WIP client editing --- cgi/oidc/clients.pl | 139 ++++++++++++++++-- lib/ProductOpener/OIDC/Server/Web/M/Client.pm | 52 +++---- 2 files changed, 152 insertions(+), 39 deletions(-) diff --git a/cgi/oidc/clients.pl b/cgi/oidc/clients.pl index 4f09d929bed8f..b6270521b2f9b 100644 --- a/cgi/oidc/clients.pl +++ b/cgi/oidc/clients.pl @@ -27,26 +27,143 @@ use utf8; use ProductOpener::Config qw/:all/; -use ProductOpener::Store qw/:all/; -use ProductOpener::Index qw/:all/; use ProductOpener::Display qw/:all/; -use ProductOpener::Tags qw/:all/; use ProductOpener::Users qw/:all/; -use ProductOpener::Images qw/:all/; use ProductOpener::Lang qw/:all/; -use ProductOpener::Mail qw/:all/; -use ProductOpener::Products qw/:all/; -use ProductOpener::Food qw/:all/; -use ProductOpener::Ingredients qw/:all/; -use ProductOpener::Images qw/:all/; use Apache2::RequestRec (); use Apache2::Const (); use CGI qw/:cgi :form escapeHTML/; use URI::Escape::XS; -use Storable qw/dclone/; use Encode; -use JSON::PP; + +use ProductOpener::OIDC::Server; +use ProductOpener::OIDC::Server::Web::M::Client; + +my $c = ProductOpener::OIDC::Server->new; ProductOpener::Display::init(); + +my $RESPONSE_TYPES = [ + q{code}, q{id_token}, q{token}, + q{code id_token}, q{code token}, q{id_token token}, + q{code id_token token} +]; + +my $request = Apache2::RequestUtil->request(); +my $method = $request->method(); + +my $title = 'OpenFoodFacts OpenID Clients'; +my $html = ''; +my $status = undef; + +if ($method eq 'GET') { + if ((not $User_id) or ($User_id eq 'unwanted-bot-id')) { + ($html, $status) = _add_login(); + } + elsif (not $admin) { + display_error($Lang{error_no_permission}{$lang}, 403); + exit(0); + } + elsif (not param('id')) { + ($html, $status) = _show_list(); + } + else { + ($html, $status) = _show_client(param('id')); + } +} +elsif ($method eq 'POST') { + if ((not $User_id) or ($User_id eq 'unwanted-bot-id')) { + ($html, $status) = _add_login(); + } + elsif (not $admin) { + display_error($Lang{error_no_permission}{$lang}, 403); + exit(0); + } + elsif (not param('id')) { + print header ( -status => '302 Moved Temporarily' ); + print header ( -location => '/cgi/oidc/clients.pl' ); + exit(); + } + else { + ($html, $status) = _update_client(param('id')); + } +} +else { + print header ( -status => '405 Method Not Allowed' ); + print header ( -allow => 'GET, POST' ); + exit(0); +} + +display_new( { + title => $title, + content_ref => \$html, + status => $status, + full_width => 1, +}); +exit(0); + +sub _add_login() { + + my $status = '401 Unauthorized'; + + my $html = < +
    +
    + +
    +
    + +
    +
    + +
    +
    + + + +HTML +; + + return ($html, $status); +} + +sub _show_list { + + my $html = ''; + my $clients = ProductOpener::OIDC::Server::Web::M::Client->find_all($c->clients); + +use Data::Dumper; + for my $client ($clients) { + $html .= Dumper($client); + } + + return ($html, undef); + +} + +sub _show_client { + + my ($client) = @_; + + return ('show client ' + $client, undef); + +} + +sub _update_client { + + my ($client) = @_; + + return ('update client ' + $client, undef); + +} + diff --git a/lib/ProductOpener/OIDC/Server/Web/M/Client.pm b/lib/ProductOpener/OIDC/Server/Web/M/Client.pm index b0f56c8323503..3c941e340b382 100644 --- a/lib/ProductOpener/OIDC/Server/Web/M/Client.pm +++ b/lib/ProductOpener/OIDC/Server/Web/M/Client.pm @@ -16,29 +16,29 @@ use ProductOpener::OIDC::Server; my $c = ProductOpener::OIDC::Server->new; # sample client data -our $SAMPLE_CLIENTS = { - 'sample_client_id' => { - 'id' => 0, - 'name' => q{Sample Client}, - 'client_id' => q{sample_client_id}, - 'client_secret' => q{sample_client_secret}, - 'redirect_uris' => [ - 'https://www.hangy.de/' +#our $SAMPLE_CLIENTS = { +# 'sample_client_id' => { +# 'id' => 0, +# 'name' => q{Sample Client}, +# 'client_id' => q{sample_client_id}, +# 'client_secret' => q{sample_client_secret}, +# 'redirect_uris' => [ +# 'https://www.hangy.de/' # $c->config->{SampleClient}->{redirect_uri}, - ], - 'allowed_response_types' => [ - q{code}, q{id_token}, q{token}, - q{code id_token}, q{id_token token}, q{code token}, - q{code id_token token}, - ], - 'allowed_grant_types' => [ - q{authorization_code}, - q{refresh_token}, - ], - 'client_type' => 4, - 'is_disabled' => 0, - }, -}; +# ], +# 'allowed_response_types' => [ +# q{code}, q{id_token}, q{token}, +## q{code id_token}, q{id_token token}, q{code token}, +# q{code id_token token}, +# ], +# 'allowed_grant_types' => [ +# q{authorization_code}, +# q{refresh_token}, +# ], +# 'client_type' => 4, +# 'is_disabled' => 0, +# }, +#}; my $CLIENT_TYPES = { '1' => { @@ -151,9 +151,6 @@ sub find_by_client_id { my ($class, $db, $client_id) = @_; return unless $client_id; - # find from sample Clients - return $SAMPLE_CLIENTS->{$client_id} if $SAMPLE_CLIENTS->{$client_id}; - # find from DB my $row; eval { @@ -169,8 +166,6 @@ sub find_by_id { my ($class, $db, $id) = @_; return unless ($db && $id); - return $SAMPLE_CLIENTS->{sample_client_id} if $id == 0; - # find from DB my $row; eval { @@ -189,9 +184,10 @@ sub find_all { # find from DB my @rows; eval { - @rows = $db->find({ is_disabled => 0 })->sort( { _id => 1 }); + @rows = $db->find({ is_disabled => 0 })->sort( { _id => 1 })->all;; }; if ($@) { +print STDERR "ERROR: " . $@; return; } From 0f0dacdafe25b51fc551914d472043102e8c9eb6 Mon Sep 17 00:00:00 2001 From: hangy Date: Sat, 1 Oct 2016 16:45:07 +0200 Subject: [PATCH 18/36] WIP OpenID Connect Discovery --- cgi/oidc/openid-configuration.pl | 72 ++++++++++++++++++++++++++++++++ conf/nginx/off | 2 + 2 files changed, 74 insertions(+) create mode 100644 cgi/oidc/openid-configuration.pl diff --git a/cgi/oidc/openid-configuration.pl b/cgi/oidc/openid-configuration.pl new file mode 100644 index 0000000000000..564ca7e810dbc --- /dev/null +++ b/cgi/oidc/openid-configuration.pl @@ -0,0 +1,72 @@ +#!/usr/bin/perl -W + +# This file is part of Product Opener. +# +# Product Opener +# Copyright (C) 2011-2016 Association Open Food Facts +# Contact: contact@openfoodfacts.org +# Address: 21 rue des Iles, 94100 Saint-Maur des Fossés, France +# +# Product Opener is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +use CGI::Carp qw(fatalsToBrowser); + +use strict; +use warnings; +use utf8; + +use ProductOpener::Config qw/:all/; +use ProductOpener::Store qw/:all/; +use ProductOpener::Index qw/:all/; +use ProductOpener::Display qw/:all/; +use ProductOpener::Tags qw/:all/; +use ProductOpener::Users qw/:all/; +use ProductOpener::Images qw/:all/; +use ProductOpener::Lang qw/:all/; +use ProductOpener::Mail qw/:all/; +use ProductOpener::Products qw/:all/; +use ProductOpener::Food qw/:all/; +use ProductOpener::Ingredients qw/:all/; +use ProductOpener::Images qw/:all/; + +use Apache2::RequestRec (); +use Apache2::Const (); + +use CGI qw/:cgi :form escapeHTML/; +use URI::Escape::XS; +use Storable qw/dclone/; +use Encode; +use JSON::PP; + +ProductOpener::Display::init(); + +my $domain = 'accounts.' . $server_domain; +my $uri = 'https://' . $domain; + +my %result = ( + + issuer => $uri, + authorization_endpoint => $uri . '/cgi/oidc/authorize.pl', + token_endpoint => $uri . '/cgi/oidc/token.pl', + jwks_uri => $uri . '/cgi/oidc/jwks.pl', + scopes_supported => [ 'openid', 'profile', 'email', 'api' ], + subject_types_supported => [ 'public' ], + token_endpoint_auth_methods_supported => [ 'client_secret_basic' ], + response_modes_supported => [ 'query', 'fragment' ], + +); + +my $data = encode_json(\%result); + +print "Content-Type: application/json; charset=UTF-8\r\nAccess-Control-Allow-Origin: *\r\n\r\n" . $data; diff --git a/conf/nginx/off b/conf/nginx/off index 5bc1ecb7c8c1b..a7dffdf57c4a5 100644 --- a/conf/nginx/off +++ b/conf/nginx/off @@ -63,6 +63,8 @@ server { proxy_pass http://127.0.0.1:8001; } + rewrite ^/\.well-known/openid-configuration$ /cgi/oidc/openid-configuration.pl last; + # deny access to .htaccess files, if Apache's document root # concurs with nginx's one # From d8114170d0e6e26166dd1dfe20aefdaa2f38ed82 Mon Sep 17 00:00:00 2001 From: hangy Date: Sat, 1 Oct 2016 21:49:53 +0200 Subject: [PATCH 19/36] WIP OpenID Connect userinfo endpoint --- cgi/oidc/openid-configuration.pl | 1 + cgi/oidc/userinfo.pl | 134 +++++++++++++++++++++++++++++++ 2 files changed, 135 insertions(+) create mode 100644 cgi/oidc/userinfo.pl diff --git a/cgi/oidc/openid-configuration.pl b/cgi/oidc/openid-configuration.pl index 564ca7e810dbc..7c03feb5d7f06 100644 --- a/cgi/oidc/openid-configuration.pl +++ b/cgi/oidc/openid-configuration.pl @@ -59,6 +59,7 @@ issuer => $uri, authorization_endpoint => $uri . '/cgi/oidc/authorize.pl', token_endpoint => $uri . '/cgi/oidc/token.pl', + userinfo_endpoint => $uri . '/cgi/oidc/userinfo.pl', jwks_uri => $uri . '/cgi/oidc/jwks.pl', scopes_supported => [ 'openid', 'profile', 'email', 'api' ], subject_types_supported => [ 'public' ], diff --git a/cgi/oidc/userinfo.pl b/cgi/oidc/userinfo.pl new file mode 100644 index 0000000000000..83a1011bb0097 --- /dev/null +++ b/cgi/oidc/userinfo.pl @@ -0,0 +1,134 @@ +#!/usr/bin/perl -W + +# This file is part of Product Opener. +# +# Product Opener +# Copyright (C) 2011-2016 Association Open Food Facts +# Contact: contact@openfoodfacts.org +# Address: 21 rue des Iles, 94100 Saint-Maur des Fossés, France +# +# Product Opener is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +use CGI::Carp qw(fatalsToBrowser); + +use strict; +use warnings; +use utf8; + +use ProductOpener::Config qw/:all/; +use ProductOpener::Store qw/:all/; +use ProductOpener::Index qw/:all/; +use ProductOpener::Display qw/:all/; +use ProductOpener::Tags qw/:all/; +use ProductOpener::Users qw/:all/; +use ProductOpener::Images qw/:all/; +use ProductOpener::Lang qw/:all/; +use ProductOpener::Mail qw/:all/; +use ProductOpener::Products qw/:all/; +use ProductOpener::Food qw/:all/; +use ProductOpener::Ingredients qw/:all/; +use ProductOpener::Images qw/:all/; + +use Apache2::RequestRec (); +use Apache2::Const (); + +use CGI qw/:cgi :form escapeHTML/; +use URI::Escape::XS; +use Storable qw/dclone/; +use Encode; +use JSON::PP; + +use OAuth::Lite2::Util qw(encode_param decode_param build_content); + +ProductOpener::Display::init(); + +try { +my $r = shift; +my $header = $r->headers_in->get('Authentication'); +if (!$header) { + # 401? +} + +# https://github.com/lyokato/p5-oauth-lite2/blob/master/lib/OAuth/Lite2/ParamMethod/AuthHeader.pm +$header =~ s/^\s*(Bearer)\s+([^\s\,]*)//; +my $token = $2; +my $params = Hash::MultiValue->new; +$header =~ s/^\s*(Bearer)\s*([^\s\,]*)//; + +if ($header) { + $header =~ s/^\s*\,\s*//; + for my $attr (split /,\s*/, $header) { + my ($key, $val) = split /=/, $attr, 2; + $val =~ s/^"//; + $val =~ s/"$//; + $params->add($key, decode_param($val)); + } +} + +# https://github.com/ritou/p5-oidc-lite/blob/master/lib/Plack/Middleware/Auth/OIDC/ProtectedResource.pm +OAuth::Lite2::Server::Error::InvalidRequest->throw unless $token; + +my $dh = ProductOpener::OIDC::Server::DataHandler->new(); +my $access_token = $dh->get_access_token($token); + +OAuth::Lite2::Server::Error::InvalidToken->throw unless $access_token; + +Carp::croak "OIDC::Lite::Server::DataHandler::get_access_token doesn't return OAuth::Lite2::Model::AccessToken" unless $access_token->isa("OAuth::Lite2::Model::AccessToken"); + +unless ($access_token->created_on + $access_token->expires_in > time()) { + OAuth::Lite2::Server::Error::ExpiredToken->throw; +} + +my $auth_info = $dh->get_auth_info_by_id($access_token->auth_id); +OAuth::Lite2::Server::Error::InvalidToken->throw unless $auth_info; +Carp::croak "OIDC::Lite::Server::DataHandler::get_auth_info_by_id doesn't return OIDC::Lite::Model::AuthInfo" unless $auth_info->isa("OIDC::Lite::Model::AuthInfo"); + +$dh->validate_client_by_id($auth_info->client_id) or OAuth::Lite2::Server::Error::InvalidToken->throw; + +$dh->validate_user_by_id($auth_info->user_id) or OAuth::Lite2::Server::Error::InvalidToken->throw; + +my $domain = 'accounts.' . $server_domain; +my $uri = 'https://' . $domain; + +my %result = ( + + iss => $uri, + authorization_endpoint => $uri . '/cgi/oidc/authorize.pl', + token_endpoint => $uri . '/cgi/oidc/token.pl', + userinfo_endpoint => $uri . '/cgi/oidc/userinfo.pl', + jwks_uri => $uri . '/cgi/oidc/jwks.pl', + scopes_supported => [ 'openid', 'profile', 'email', 'api' ], + subject_types_supported => [ 'public' ], + token_endpoint_auth_methods_supported => [ 'client_secret_basic' ], + response_modes_supported => [ 'query', 'fragment' ], + +); + +my $data = encode_json(\%result); + +print "Content-Type: application/json; charset=UTF-8\r\nAccess-Control-Allow-Origin: *\r\n\r\n" . $data; +return; +} +catch { +if ($_->isa("OAuth::Lite2::Server::Error")) { + my @params; + push(@params, sprintf(q{error="%s"}, $_->type)); + push(@params, sprintf(q{error_description="%s"}, $_->description)) if $_->description; + return [ $_->code, [ "WWW-Authenticate" => "Bearer " . join(', ', @params) ], [ ] ]; + +} else { + # rethrow + die $_; +} +} From 13264fa1af8ae000813840903479b8f949be6c73 Mon Sep 17 00:00:00 2001 From: hangy Date: Thu, 6 Oct 2016 18:44:20 +0200 Subject: [PATCH 20/36] Preload OIDC packages on startup. Fixed userinfo (missing use statement). Implemented jwks.pl. Don't redirect from 'accounts' subdomain (to be used as the OpenID Connect Issuer URL). --- cgi/oidc/jwks.pl | 66 ++++++++++++++++++++++++++++++++++++ cgi/oidc/userinfo.pl | 1 + cpanfile | 1 + lib/ProductOpener/Display.pm | 4 +-- lib/startup.pl | 10 ++++++ lib/startup_apache2.pl | 10 ++++++ 6 files changed, 90 insertions(+), 2 deletions(-) create mode 100644 cgi/oidc/jwks.pl diff --git a/cgi/oidc/jwks.pl b/cgi/oidc/jwks.pl new file mode 100644 index 0000000000000..95753fae22e5d --- /dev/null +++ b/cgi/oidc/jwks.pl @@ -0,0 +1,66 @@ +#!/usr/bin/perl -W + +# This file is part of Product Opener. +# +# Product Opener +# Copyright (C) 2011-2016 Association Open Food Facts +# Contact: contact@openfoodfacts.org +# Address: 21 rue des Iles, 94100 Saint-Maur des Fossés, France +# +# Product Opener is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +use CGI::Carp qw(fatalsToBrowser); + +use strict; +use warnings; +use utf8; + +use ProductOpener::Config qw/:all/; +use ProductOpener::Store qw/:all/; +use ProductOpener::Index qw/:all/; +use ProductOpener::Display qw/:all/; +use ProductOpener::Tags qw/:all/; +use ProductOpener::Users qw/:all/; +use ProductOpener::Images qw/:all/; +use ProductOpener::Lang qw/:all/; +use ProductOpener::Mail qw/:all/; +use ProductOpener::Products qw/:all/; +use ProductOpener::Food qw/:all/; +use ProductOpener::Ingredients qw/:all/; +use ProductOpener::Images qw/:all/; + +use Apache2::RequestRec (); +use Apache2::Const (); + +use CGI qw/:cgi :form escapeHTML/; +use URI::Escape::XS; +use Storable qw/dclone/; +use Encode; +use JSON::PP; +use Crypt::PK::RSA; + +ProductOpener::Display::init(); + +my $pk = Crypt::PK::RSA->new(); +$pk->import_key(\$oidc->{id_token}->{priv_key}); + +my $jwk_hash = $pk->export_key_jwk('public', 1); +my @hashes = ( $jwk_hash ); +my %result = ( + keys => \@hashes +); + +my $data = encode_json(\%result); + +print "Content-Type: application/json; charset=UTF-8\r\nAccess-Control-Allow-Origin: *\r\n\r\n" . $data; diff --git a/cgi/oidc/userinfo.pl b/cgi/oidc/userinfo.pl index 83a1011bb0097..ec3f25cb2b510 100644 --- a/cgi/oidc/userinfo.pl +++ b/cgi/oidc/userinfo.pl @@ -50,6 +50,7 @@ use JSON::PP; use OAuth::Lite2::Util qw(encode_param decode_param build_content); +use OAuth::Lite2::Server::Error; ProductOpener::Display::init(); diff --git a/cpanfile b/cpanfile index 31741f079c09b..55a12f6e8b622 100644 --- a/cpanfile +++ b/cpanfile @@ -41,6 +41,7 @@ requires 'OAuth::Lite2', '0.11'; requires 'OIDC::Lite', '0.10'; requires 'Crypt::OpenSSL::Random'; requires 'Digest::SHA'; +requires 'CryptX', '>= 0.022'; on 'test' => sub { requires 'Test::More', '>= 1.302049, < 2.0'; diff --git a/lib/ProductOpener/Display.pm b/lib/ProductOpener/Display.pm index d5322263de548..21ac53781516a 100644 --- a/lib/ProductOpener/Display.pm +++ b/lib/ProductOpener/Display.pm @@ -232,7 +232,7 @@ sub init() print STDERR "Display::init - country_name($subdomain) - ip: " . remote_addr() . " - hostname: " . $hostname . "query_string: " . $ENV{QUERY_STRING} . " subdomain: $subdomain - lc: $lc - cc: $cc - country: $country - 2\n"; } - elsif ($ENV{QUERY_STRING} !~ /cgi/) { + elsif (($ENV{QUERY_STRING} !~ /cgi/) and ($subdomain ne 'accounts')) { # redirect print STDERR "Display::init - ip: " . remote_addr() . " - hostname: " . $hostname . "query_string: " . $ENV{QUERY_STRING} . " subdomain: $subdomain - lc: $lc - cc: $cc - country: $country - redirect to world.${server_domain}\n"; $r->headers_out->set(Location => "http://world.${server_domain}/" . $ENV{QUERY_STRING}); @@ -252,7 +252,7 @@ sub init() $lang = $lc; # If the language is equal to the first language of the country, but we are on a different subdomain, redirect to the main country subdomain. (fr-fr => fr) - if ((defined $lc) and (defined $cc) and (defined $country_languages{$cc}[0]) and ($country_languages{$cc}[0] eq $lc) and ($subdomain ne $cc) and ($subdomain !~ /^ssl-api/) and ($r->method() eq 'GET')) { + if ((defined $lc) and (defined $cc) and (defined $country_languages{$cc}[0]) and ($country_languages{$cc}[0] eq $lc) and ($subdomain ne $cc) and ($subdomain !~ /^ssl-api/) and ($subdomain ne 'accounts') and ($r->method() eq 'GET')) { # redirect print STDERR "Display::init - ip: " . remote_addr() . " - hostname: " . $hostname . "query_string: " . $ENV{QUERY_STRING} . " subdomain: $subdomain - lc: $lc - cc: $cc - country: $country - redirect to $cc.${server_domain}\n"; $r->headers_out->set(Location => "http://$cc.${server_domain}/" . $ENV{QUERY_STRING}); diff --git a/lib/startup.pl b/lib/startup.pl index b4f78aacf2b82..7eed4760b34bd 100755 --- a/lib/startup.pl +++ b/lib/startup.pl @@ -51,6 +51,7 @@ # Needs to be configured use lib "/home/off/lib"; +# Preload ProductOpener use ProductOpener::Store qw/:all/; use ProductOpener::Config qw/:all/; use ProductOpener::Display qw/:all/; @@ -60,6 +61,15 @@ use ProductOpener::Index qw/:all/; use ProductOpener::Version qw/:all/; +# Preload OIDC +use ProductOpener::OIDC::Server qw/:all/; +use ProductOpener::OIDC::Server::DataHandler qw/:all/; +use ProductOpener::OIDC::Server::Request qw/:all/; +use ProductOpener::OIDC::Server::Web::M::AccessToken qw/:all/; +use ProductOpener::OIDC::Server::Web::M::AuthInfo qw/:all/; +use ProductOpener::OIDC::Server::Web::M::Client qw/:all/; +use ProductOpener::OIDC::Server::Web::M::ResourceOwner qw/:all/; + use Apache2::Const -compile => qw(OK); use Apache2::Connection (); use Apache2::RequestRec (); diff --git a/lib/startup_apache2.pl b/lib/startup_apache2.pl index 4a57b2c528f68..1a972cff3e037 100755 --- a/lib/startup_apache2.pl +++ b/lib/startup_apache2.pl @@ -47,6 +47,7 @@ use Cache::Memcached::Fast (); use URI::Escape::XS (); +# Preload ProductOpener use ProductOpener::Lang qw/:all/; use ProductOpener::Store qw/:all/; @@ -58,6 +59,15 @@ use ProductOpener::Index qw/:all/; use ProductOpener::Version qw/:all/; +# Preload OIDC +use ProductOpener::OIDC::Server qw/:all/; +use ProductOpener::OIDC::Server::DataHandler qw/:all/; +use ProductOpener::OIDC::Server::Request qw/:all/; +use ProductOpener::OIDC::Server::Web::M::AccessToken qw/:all/; +use ProductOpener::OIDC::Server::Web::M::AuthInfo qw/:all/; +use ProductOpener::OIDC::Server::Web::M::Client qw/:all/; +use ProductOpener::OIDC::Server::Web::M::ResourceOwner qw/:all/; + use Apache2::Const -compile => qw(OK); use Apache2::Connection (); use Apache2::RequestRec (); From 2378ba48e48a3c495ee0c68f53ee141e6f00425b Mon Sep 17 00:00:00 2001 From: hangy Date: Sat, 15 Oct 2016 10:34:50 +0200 Subject: [PATCH 21/36] [OIDC] Fix some perl errors. --- lib/ProductOpener/OIDC/Server/DataHandler.pm | 7 ++----- lib/ProductOpener/OIDC/Server/Web/M/Client.pm | 18 +++++++++--------- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/lib/ProductOpener/OIDC/Server/DataHandler.pm b/lib/ProductOpener/OIDC/Server/DataHandler.pm index b56dc4a7d853a..c41821d9c836b 100644 --- a/lib/ProductOpener/OIDC/Server/DataHandler.pm +++ b/lib/ProductOpener/OIDC/Server/DataHandler.pm @@ -2,6 +2,7 @@ package ProductOpener::OIDC::Server::DataHandler; use strict; use warnings; use utf8; +use experimental 'smartmatch'; use parent 'OIDC::Lite::Server::DataHandler'; use OIDC::Lite::Server::Scope; @@ -54,11 +55,7 @@ sub validate_redirect_uri { return unless ( $client_id && $redirect_uri ); return unless $self->validate_client_by_id($client_id); - ## TODO: return $client->is_valid_redirect_uri($redirect_uri) - my %redirect_uri_hash; - $redirect_uri_hash{$_}++ foreach @{$client->{redirect_uris}}; - - return (exists $redirect_uri_hash{$redirect_uri}); + return $redirect_uri ~~ $client->{redirect_uris}; } sub validate_scope { diff --git a/lib/ProductOpener/OIDC/Server/Web/M/Client.pm b/lib/ProductOpener/OIDC/Server/Web/M/Client.pm index 3c941e340b382..76f4fa972c1f0 100644 --- a/lib/ProductOpener/OIDC/Server/Web/M/Client.pm +++ b/lib/ProductOpener/OIDC/Server/Web/M/Client.pm @@ -209,15 +209,15 @@ sub _gen_credentials { sub _row_to_hash_ref { my ($class, $row) = @_; return { - 'id' => $row->_id, - 'name' => $row->name, - 'client_id' => $row->client_id, - 'client_secret' => $row->client_secret, - 'redirect_uris' => decode_json($row->redirect_uris), - 'allowed_response_types' => $CLIENT_TYPES->{$row->client_type}->{allowed_response_types}, - 'allowed_grant_types' => $CLIENT_TYPES->{$row->client_type}->{allowed_grant_types}, - 'client_type' => $row->client_type, - 'is_disabled' => $row->is_disabled, + 'id' => $row->{_id}, + 'name' => $row->{name}, + 'client_id' => $row->{client_id}, + 'client_secret' => $row->{client_secret}, + 'redirect_uris' => $row->{redirect_uris}, + 'allowed_response_types' => $CLIENT_TYPES->{$row->{client_type}}->{allowed_response_types}, + 'allowed_grant_types' => $CLIENT_TYPES->{$row->{client_type}}->{allowed_grant_types}, + 'client_type' => $row->{client_type}, + 'is_disabled' => $row->{is_disabled}, }; } From 9d5cf25209c7695504b656758e783561349e10b5 Mon Sep 17 00:00:00 2001 From: hangy Date: Thu, 3 Nov 2016 15:13:10 +0100 Subject: [PATCH 22/36] Support OIDC form_post method. --- cgi/oidc/authorize.pl | 36 +++++++++++++++++++++++++++++--- cgi/oidc/openid-configuration.pl | 2 +- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/cgi/oidc/authorize.pl b/cgi/oidc/authorize.pl index bfc370d6b690a..d624ae2f8f838 100644 --- a/cgi/oidc/authorize.pl +++ b/cgi/oidc/authorize.pl @@ -216,13 +216,21 @@ () $res->{uri} .= ($res->{uri} =~ /\?/) ? q{&} : q{?}; $res->{uri} .= $res->{query_string}; } + if ($res->{fragment_string}) { $res->{uri} .= q{#}.$res->{fragment_string}; } - # confirm screen - print header ( -location => $res->{uri} ); - return ('valid', 302); + if (url_param('response_mode') && url_param('response_mode') eq 'form_post') { + print header( -cache-control => 'no-cache, no-store' ); + print header( -pragma => 'no-cache'); + my $html = _render_post_form($res); + return ($html, undef); + } else { + # confirm screen + print header ( -location => $res->{uri} ); + return ('valid', 302); + } } @@ -270,3 +278,25 @@ ($) return $html; } + +sub _render_post_form($) { + + my ($res) = @_; + + my $html = '

    Authorization Success

    '; + $html .= "
    "; + my $data; + if ($res->{query}) { + $data = $res->{query}; + } + else { + $data = $res->{fragment}; + } + + $html .= ""; + $html .= ""; + $html .= "
    "; + $html .= ''; + return $html; + +} diff --git a/cgi/oidc/openid-configuration.pl b/cgi/oidc/openid-configuration.pl index 7c03feb5d7f06..afabaf404c84d 100644 --- a/cgi/oidc/openid-configuration.pl +++ b/cgi/oidc/openid-configuration.pl @@ -64,7 +64,7 @@ scopes_supported => [ 'openid', 'profile', 'email', 'api' ], subject_types_supported => [ 'public' ], token_endpoint_auth_methods_supported => [ 'client_secret_basic' ], - response_modes_supported => [ 'query', 'fragment' ], + response_modes_supported => [ 'query', 'fragment', 'form_post' ], ); From 1e67e700d31d96015a8fa17d33fbbe7fc82d1374 Mon Sep 17 00:00:00 2001 From: hangy Date: Thu, 3 Nov 2016 15:55:31 +0100 Subject: [PATCH 23/36] Use content negotiation for the UI language for the 'accounts' subdomain. --- cpanfile | 1 + lib/ProductOpener/Display.pm | 12 +++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/cpanfile b/cpanfile index 55a12f6e8b622..ecf64483a3d44 100644 --- a/cpanfile +++ b/cpanfile @@ -35,6 +35,7 @@ requires 'DateTime::Format::CLDR'; requires 'DateTime::Locale'; requires 'Math::Random::Secure'; requires 'Crypt::ScryptKDF'; +requires 'HTTP::AcceptLanguage', '>=0.02'; # OIDC requires 'OAuth::Lite2', '0.11'; diff --git a/lib/ProductOpener/Display.pm b/lib/ProductOpener/Display.pm index 21ac53781516a..e3d32bb55cdaf 100644 --- a/lib/ProductOpener/Display.pm +++ b/lib/ProductOpener/Display.pm @@ -113,6 +113,7 @@ use Encode; use URI::Escape::XS; use CGI qw/:cgi :form escapeHTML/; use HTML::Entities; +use HTTP::AcceptLanguage; use DateTime; use DateTime::Format::Mail; use DateTime::Format::CLDR; @@ -239,7 +240,16 @@ sub init() $r->status(301); return 301; } - + + if ($subdomain eq 'accounts') { + # For the accounts subdomain, try to use the user's browser locale to display texts. + my $headerLang = HTTP::AcceptLanguage->new($ENV{HTTP_ACCEPT_LANGUAGE})->match(@Langs); + print STDERR "Display::init - Accept-Language for " . $ENV{HTTP_ACCEPT_LANGUAGE} . " => $headerLang\n"; + if ($headerLang and not ($headerLang eq $lc)) { + $lc = $headerLang; + } + } + $lclc = $lc; $langlang = $lc; $lc =~ s/_.*//; # PT_PT doest not work yet: categories From fd43f40c9a807e8e34fdda1cf1330bcceb08ef05 Mon Sep 17 00:00:00 2001 From: hangy Date: Thu, 3 Nov 2016 16:20:59 +0100 Subject: [PATCH 24/36] Support OIDC 'ui_locales'? --- cgi/oidc/openid-configuration.pl | 1 + lib/ProductOpener/Display.pm | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/cgi/oidc/openid-configuration.pl b/cgi/oidc/openid-configuration.pl index afabaf404c84d..b268735330957 100644 --- a/cgi/oidc/openid-configuration.pl +++ b/cgi/oidc/openid-configuration.pl @@ -65,6 +65,7 @@ subject_types_supported => [ 'public' ], token_endpoint_auth_methods_supported => [ 'client_secret_basic' ], response_modes_supported => [ 'query', 'fragment', 'form_post' ], + ui_locales_supported => \@Langs ); diff --git a/lib/ProductOpener/Display.pm b/lib/ProductOpener/Display.pm index e3d32bb55cdaf..e97ffaef438c7 100644 --- a/lib/ProductOpener/Display.pm +++ b/lib/ProductOpener/Display.pm @@ -248,6 +248,29 @@ sub init() if ($headerLang and not ($headerLang eq $lc)) { $lc = $headerLang; } + + my $ui_locales = url_param('ui_locales'); + if ($ui_locales) { + my @locales = split(/ /, $ui_locales); + $ui_locales = ''; + my $coherence = 1.0; + foreach my $l (@locales) { + if ($coherence >= 1.0) { + $ui_locales .= $l; + } + else { + $ui_locales .= ',' . $l . ';q=' . $coherence; + } + + $coherence = $coherence * 0.99; + } + + my $paramLang = HTTP::AcceptLanguage->new($ui_locales)->match(@Langs); + print STDERR "Display::init - OIDC lc for $ui_locales => $paramLang\n"; + if ($paramLang and not ($paramLang eq $lc)) { + $lc = $paramLang; + } + } } $lclc = $lc; From c8ea63d6fde40cd5edde0aa33e6cb352e99eab38 Mon Sep 17 00:00:00 2001 From: hangy Date: Sat, 12 Nov 2016 10:36:39 +0100 Subject: [PATCH 25/36] Check CSRF in OIDC authorize form. --- cgi/oidc/authorize.pl | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/cgi/oidc/authorize.pl b/cgi/oidc/authorize.pl index d624ae2f8f838..7ddba099a52b5 100644 --- a/cgi/oidc/authorize.pl +++ b/cgi/oidc/authorize.pl @@ -40,6 +40,8 @@ use Encode; use JSON::PP; +use WWW::CSRF qw(CSRF_OK); + use OAuth::Lite2::Util qw/build_content/; use ProductOpener::OIDC::Server::Request; use ProductOpener::OIDC::Server::DataHandler; @@ -183,7 +185,12 @@ () my $res; eval { $ah->handle_request(); - if (param('user_action')) { # TODO: CSRF + my $csrf_token_status = check_po_csrf_token($User_id, param('csrf')); + if (not ($csrf_token_status eq CSRF_OK)) { + display_error(lang("error_invalid_csrf_token"), 403); + } + + if (param('user_action')) { if( param('user_action') eq q{accept} ){ $res = $ah->allow(); }else{ @@ -272,7 +279,7 @@ ($) $html .= start_form() . '' . '' -# TODO: CSRF + . hidden(-name=>'csrf', -value=>generate_po_csrf_token($User_id), -override=>1) . end_form(); return $html; From 08ccb8338bded3580a6dd3ddaa7711d67aba1c2b Mon Sep 17 00:00:00 2001 From: hangy Date: Wed, 28 Dec 2016 12:28:21 +0100 Subject: [PATCH 26/36] Enable Modern::Perl for OIDC stuff. --- cgi/oidc/authorize.pl | 3 +-- cgi/oidc/clients.pl | 3 +-- cgi/oidc/jwks.pl | 3 +-- cgi/oidc/openid-configuration.pl | 3 +-- cgi/oidc/token.pl | 3 +-- cgi/oidc/userinfo.pl | 3 +-- lib/ProductOpener/OIDC/Server.pm | 24 +++++++++++++++++-- lib/ProductOpener/OIDC/Server/DataHandler.pm | 24 +++++++++++++++++-- lib/ProductOpener/OIDC/Server/Request.pm | 24 +++++++++++++++++-- .../OIDC/Server/Web/M/AccessToken.pm | 24 +++++++++++++++++-- .../OIDC/Server/Web/M/AuthInfo.pm | 24 +++++++++++++++++-- lib/ProductOpener/OIDC/Server/Web/M/Client.pm | 24 +++++++++++++++++-- .../OIDC/Server/Web/M/ResourceOwner.pm | 24 +++++++++++++++++-- 13 files changed, 160 insertions(+), 26 deletions(-) diff --git a/cgi/oidc/authorize.pl b/cgi/oidc/authorize.pl index 7ddba099a52b5..dec9738c3c0c2 100644 --- a/cgi/oidc/authorize.pl +++ b/cgi/oidc/authorize.pl @@ -22,8 +22,7 @@ use CGI::Carp qw(fatalsToBrowser); -use strict; -use warnings; +use Modern::Perl '2012'; use utf8; use ProductOpener::Config qw/:all/; diff --git a/cgi/oidc/clients.pl b/cgi/oidc/clients.pl index b6270521b2f9b..e1addb0b53d3a 100644 --- a/cgi/oidc/clients.pl +++ b/cgi/oidc/clients.pl @@ -22,8 +22,7 @@ use CGI::Carp qw(fatalsToBrowser); -use strict; -use warnings; +use Modern::Perl '2012'; use utf8; use ProductOpener::Config qw/:all/; diff --git a/cgi/oidc/jwks.pl b/cgi/oidc/jwks.pl index 95753fae22e5d..d06608ad04169 100644 --- a/cgi/oidc/jwks.pl +++ b/cgi/oidc/jwks.pl @@ -22,8 +22,7 @@ use CGI::Carp qw(fatalsToBrowser); -use strict; -use warnings; +use Modern::Perl '2012'; use utf8; use ProductOpener::Config qw/:all/; diff --git a/cgi/oidc/openid-configuration.pl b/cgi/oidc/openid-configuration.pl index b268735330957..64f11e299a41a 100644 --- a/cgi/oidc/openid-configuration.pl +++ b/cgi/oidc/openid-configuration.pl @@ -22,8 +22,7 @@ use CGI::Carp qw(fatalsToBrowser); -use strict; -use warnings; +use Modern::Perl '2012'; use utf8; use ProductOpener::Config qw/:all/; diff --git a/cgi/oidc/token.pl b/cgi/oidc/token.pl index 4f09d929bed8f..4f7c9ba0008a0 100644 --- a/cgi/oidc/token.pl +++ b/cgi/oidc/token.pl @@ -22,8 +22,7 @@ use CGI::Carp qw(fatalsToBrowser); -use strict; -use warnings; +use Modern::Perl '2012'; use utf8; use ProductOpener::Config qw/:all/; diff --git a/cgi/oidc/userinfo.pl b/cgi/oidc/userinfo.pl index ec3f25cb2b510..6cd1744279bfe 100644 --- a/cgi/oidc/userinfo.pl +++ b/cgi/oidc/userinfo.pl @@ -22,8 +22,7 @@ use CGI::Carp qw(fatalsToBrowser); -use strict; -use warnings; +use Modern::Perl '2012'; use utf8; use ProductOpener::Config qw/:all/; diff --git a/lib/ProductOpener/OIDC/Server.pm b/lib/ProductOpener/OIDC/Server.pm index 7d8f962bd4437..2dfcb0c89d387 100644 --- a/lib/ProductOpener/OIDC/Server.pm +++ b/lib/ProductOpener/OIDC/Server.pm @@ -1,7 +1,27 @@ +# This file is part of Product Opener. +# +# Product Opener +# Copyright (C) 2011-2016 Association Open Food Facts +# Contact: contact@openfoodfacts.org +# Address: 21 rue des Iles, 94100 Saint-Maur des Fossés, France +# +# Product Opener is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + package ProductOpener::OIDC::Server; -use strict; -use warnings; + use utf8; +use Modern::Perl '2012'; use ProductOpener::Display qw/:all/; diff --git a/lib/ProductOpener/OIDC/Server/DataHandler.pm b/lib/ProductOpener/OIDC/Server/DataHandler.pm index c41821d9c836b..4bcc5a1c6cbd7 100644 --- a/lib/ProductOpener/OIDC/Server/DataHandler.pm +++ b/lib/ProductOpener/OIDC/Server/DataHandler.pm @@ -1,7 +1,27 @@ +# This file is part of Product Opener. +# +# Product Opener +# Copyright (C) 2011-2016 Association Open Food Facts +# Contact: contact@openfoodfacts.org +# Address: 21 rue des Iles, 94100 Saint-Maur des Fossés, France +# +# Product Opener is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + package ProductOpener::OIDC::Server::DataHandler; -use strict; -use warnings; + use utf8; +use Modern::Perl '2012'; use experimental 'smartmatch'; use parent 'OIDC::Lite::Server::DataHandler'; diff --git a/lib/ProductOpener/OIDC/Server/Request.pm b/lib/ProductOpener/OIDC/Server/Request.pm index 746cd48bd4182..e6e5d1ca6987d 100644 --- a/lib/ProductOpener/OIDC/Server/Request.pm +++ b/lib/ProductOpener/OIDC/Server/Request.pm @@ -1,7 +1,27 @@ +# This file is part of Product Opener. +# +# Product Opener +# Copyright (C) 2011-2016 Association Open Food Facts +# Contact: contact@openfoodfacts.org +# Address: 21 rue des Iles, 94100 Saint-Maur des Fossés, France +# +# Product Opener is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + package ProductOpener::OIDC::Server::Request; -use strict; -use warnings; + use utf8; +use Modern::Perl '2012'; sub new { diff --git a/lib/ProductOpener/OIDC/Server/Web/M/AccessToken.pm b/lib/ProductOpener/OIDC/Server/Web/M/AccessToken.pm index 04514392908f7..30efc73d3b7c6 100644 --- a/lib/ProductOpener/OIDC/Server/Web/M/AccessToken.pm +++ b/lib/ProductOpener/OIDC/Server/Web/M/AccessToken.pm @@ -1,7 +1,27 @@ +# This file is part of Product Opener. +# +# Product Opener +# Copyright (C) 2011-2016 Association Open Food Facts +# Contact: contact@openfoodfacts.org +# Address: 21 rue des Iles, 94100 Saint-Maur des Fossés, France +# +# Product Opener is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + package ProductOpener::OIDC::Server::Web::M::AccessToken; -use strict; -use warnings; + use utf8; +use Modern::Perl '2012'; use parent 'OAuth::Lite2::Model::AccessToken'; use JSON::WebToken qw/ diff --git a/lib/ProductOpener/OIDC/Server/Web/M/AuthInfo.pm b/lib/ProductOpener/OIDC/Server/Web/M/AuthInfo.pm index 39d4b0230c8c0..352e6923be220 100644 --- a/lib/ProductOpener/OIDC/Server/Web/M/AuthInfo.pm +++ b/lib/ProductOpener/OIDC/Server/Web/M/AuthInfo.pm @@ -1,7 +1,27 @@ +# This file is part of Product Opener. +# +# Product Opener +# Copyright (C) 2011-2016 Association Open Food Facts +# Contact: contact@openfoodfacts.org +# Address: 21 rue des Iles, 94100 Saint-Maur des Fossés, France +# +# Product Opener is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + package ProductOpener::OIDC::Server::Web::M::AuthInfo; -use strict; -use warnings; + use utf8; +use Modern::Perl '2012'; use parent 'OIDC::Lite::Model::AuthInfo'; __PACKAGE__->mk_accessors(qw( code_expired_on diff --git a/lib/ProductOpener/OIDC/Server/Web/M/Client.pm b/lib/ProductOpener/OIDC/Server/Web/M/Client.pm index 76f4fa972c1f0..a83862873d0a5 100644 --- a/lib/ProductOpener/OIDC/Server/Web/M/Client.pm +++ b/lib/ProductOpener/OIDC/Server/Web/M/Client.pm @@ -1,7 +1,27 @@ +# This file is part of Product Opener. +# +# Product Opener +# Copyright (C) 2011-2016 Association Open Food Facts +# Contact: contact@openfoodfacts.org +# Address: 21 rue des Iles, 94100 Saint-Maur des Fossés, France +# +# Product Opener is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + package ProductOpener::OIDC::Server::Web::M::Client; -use strict; -use warnings; + use utf8; +use Modern::Perl '2012'; use JSON::PP qw/ encode_json diff --git a/lib/ProductOpener/OIDC/Server/Web/M/ResourceOwner.pm b/lib/ProductOpener/OIDC/Server/Web/M/ResourceOwner.pm index 50a92bc7cd858..c03f0f49e91fc 100644 --- a/lib/ProductOpener/OIDC/Server/Web/M/ResourceOwner.pm +++ b/lib/ProductOpener/OIDC/Server/Web/M/ResourceOwner.pm @@ -1,7 +1,27 @@ +# This file is part of Product Opener. +# +# Product Opener +# Copyright (C) 2011-2016 Association Open Food Facts +# Contact: contact@openfoodfacts.org +# Address: 21 rue des Iles, 94100 Saint-Maur des Fossés, France +# +# Product Opener is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + package ProductOpener::OIDC::Server::Web::M::ResourceOwner; -use strict; -use warnings; + use utf8; +use Modern::Perl '2012'; use JSON::PP; From d8a766016b3001f6fb688a641cf0c5fe779d5904 Mon Sep 17 00:00:00 2001 From: hangy Date: Wed, 28 Dec 2016 12:33:19 +0100 Subject: [PATCH 27/36] Fix Perl::Critic::Policy::TestingAndDebugging::RequireUseStrict --- lib/ProductOpener/URL.pm | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/ProductOpener/URL.pm b/lib/ProductOpener/URL.pm index eea8589c9f406..a6ff32a5c3fc2 100644 --- a/lib/ProductOpener/URL.pm +++ b/lib/ProductOpener/URL.pm @@ -21,11 +21,13 @@ package ProductOpener::URL; +use utf8; +use Modern::Perl '2012'; +use Exporter qw< import >; + BEGIN { use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS); - require Exporter; - @ISA = qw(Exporter); @EXPORT = qw(); # symbols to export by default @EXPORT_OK = qw( &format_subdomain @@ -36,9 +38,6 @@ BEGIN } use vars @EXPORT_OK ; -use strict; -use warnings; -use utf8; use experimental 'smartmatch'; From d383fdb67d45034c1610e54a7f7652612ba27be0 Mon Sep 17 00:00:00 2001 From: hangy Date: Wed, 2 May 2018 22:37:23 +0200 Subject: [PATCH 28/36] Upgrade some OIDC crypto dependencies. --- cpanfile | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cpanfile b/cpanfile index e532afbdba9c1..8000b584dd42b 100644 --- a/cpanfile +++ b/cpanfile @@ -47,11 +47,11 @@ requires 'Data::Dumper::AutoEncode'; requires 'XML::Rules'; # OIDC -requires 'OAuth::Lite2', '0.11'; -requires 'OIDC::Lite', '0.10'; -requires 'Crypt::OpenSSL::Random'; -requires 'Digest::SHA'; -requires 'CryptX', '>= 0.022'; +requires 'OAuth::Lite2', '>= 0.11'; +requires 'OIDC::Lite', '>= 0.10'; +requires 'Crypt::OpenSSL::Random', '>= 0.15'; +requires 'Digest::SHA', '>= 6.02'; +requires 'CryptX', '>= 0.060'; on 'test' => sub { requires 'Test::More', '>= 1.302049, < 2.0'; From 468b721ba54a384e1a5ab2f6bad78a6818a0eafc Mon Sep 17 00:00:00 2001 From: hangy Date: Mon, 4 Jun 2018 00:30:55 +0200 Subject: [PATCH 29/36] Use alpine based images for some Docker images. --- docker/backend-base-cpan/Dockerfile | 8 ++--- docker/backend-base/Dockerfile | 46 +++++++---------------------- docker/backend-dev/Dockerfile | 14 +++------ docker/backend-git/Dockerfile | 14 +++------ 4 files changed, 21 insertions(+), 61 deletions(-) diff --git a/docker/backend-base-cpan/Dockerfile b/docker/backend-base-cpan/Dockerfile index 1fbb539f8c7b8..a139e533475a2 100644 --- a/docker/backend-base-cpan/Dockerfile +++ b/docker/backend-base-cpan/Dockerfile @@ -1,14 +1,10 @@ ARG BRANCH=master FROM productopener/backend-base -# https://github.com/tianon/docker-brew-ubuntu-core/issues/59 -ARG DEBIAN_FRONTEND=noninteractive -RUN apt-get update && apt-get install --assume-yes apt-utils - ARG BRANCH ADD https://raw.githubusercontent.com/openfoodfacts/openfoodfacts-server/${BRANCH}/cpanfile /tmp/cpanfile WORKDIR "/tmp" -# Add ProductOpener runtime dependencies cpanm -RUN cpanm --quiet --installdeps --notest --skip-satisfied . \ No newline at end of file +# Add ProductOpener runtime dependencies from the git repositories' cpanfile +RUN cpm install -g diff --git a/docker/backend-base/Dockerfile b/docker/backend-base/Dockerfile index 4efb6eb61def6..5e5781d2582c0 100644 --- a/docker/backend-base/Dockerfile +++ b/docker/backend-base/Dockerfile @@ -1,41 +1,17 @@ -FROM httpd:latest - -# https://github.com/tianon/docker-brew-ubuntu-core/issues/59 -ARG DEBIAN_FRONTEND=noninteractive -RUN apt-get update && apt-get install --assume-yes apt-utils - -RUN set -x \ - && apt-get install -y --no-install-recommends git ca-certificates libfile-spec-native-perl make gcc libperl-dev g++ \ - libapache2-request-perl \ - libimage-magick-perl \ - libbarcode-zbar-perl \ - tesseract-ocr \ - graphviz \ - imagemagick \ - geoip-database \ - cpanminus \ - && ln -s /usr/share/GeoIP /usr/local/share/GeoIP - -# Fetch mod_perl source, build and install it -# Note: The fetch URL should be adjusted to point to a local mirror -ADD http://www.eu.apache.org/dist/perl/mod_perl-2.0.10.tar.gz mod_perl-2.0.10.tar.gz -RUN set -x \ - && ln -s /usr/lib/x86_64-linux-gnu/libgdbm.so.3.0.0 /usr/lib/libgdbm.so \ - && tar -zxvf mod_perl-2.0.10.tar.gz \ - && rm mod_perl-2.0.10.tar.gz \ - && cd mod_perl-2.0.10 \ - && perl Makefile.PL MP_AP_PREFIX=/usr/local/apache2 \ - && make \ - && make install \ - && cd .. \ - && rm -r mod_perl-2.0.10 +FROM hangy/httpd-alpine-perl-modperl # Prepare Apache to include our custom config RUN set -x \ && mkdir -p /usr/local/apache2/conf/sites-enabled \ && echo 'IncludeOptional conf/sites-enabled/*.conf' >> /usr/local/apache2/conf/httpd.conf -# Remove mod_perl build dependencies -RUN set -x \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* +RUN apk add --update alpine-sdk imagemagick6 imagemagick6-dev graphviz graphviz-dev tesseract-ocr tesseract-ocr-dev \ + && rm -rf /var/cache/apk/* + +# As of Alpine 3.7, zbar is only available in testing. +RUN echo -e '@edge http://dl-cdn.alpinelinux.org/alpine/edge/main\n@edgecommunity http://dl-cdn.alpinelinux.org/alpine/edge/community\n@testing http://dl-cdn.alpinelinux.org/alpine/edge/testing' >> /etc/apk/repositories +RUN apk add -U imagemagick@edge zbar@testing zbar-dev@testing \ + && rm -rf /var/cache/apk/* + +# Dependency of libapreq2-2.13, which is not installed automatically. +RUN cpm install ExtUtils::XSBuilder::ParseSource -g diff --git a/docker/backend-dev/Dockerfile b/docker/backend-dev/Dockerfile index 09b729606c70a..0f8e2037a3442 100644 --- a/docker/backend-dev/Dockerfile +++ b/docker/backend-dev/Dockerfile @@ -1,17 +1,11 @@ FROM productopener/backend-base-cpan -# https://github.com/tianon/docker-brew-ubuntu-core/issues/59 -ARG DEBIAN_FRONTEND=noninteractive -RUN apt-get update && apt-get install --assume-yes apt-utils - ADD . "/opt/product-opener/" WORKDIR "/opt/product-opener" -# Add ProductOpener runtime dependencies cpanm -RUN cpanm --quiet --installdeps --notest --skip-satisfied . +# Add ProductOpener runtime dependencies from the local cpanfile +RUN cpm install -g # Remove build dependencies -RUN set -x \ - && apt-get purge -y --auto-remove make gcc libperl-dev g++ git \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* \ No newline at end of file +RUN apk del -U alpine-sdk imagemagick6-dev graphviz-dev tesseract-ocr-dev zbar-dev \ + && rm -rf /var/cache/apk/* diff --git a/docker/backend-git/Dockerfile b/docker/backend-git/Dockerfile index d1c06f8fe215d..0a9e3871d4af3 100644 --- a/docker/backend-git/Dockerfile +++ b/docker/backend-git/Dockerfile @@ -1,20 +1,14 @@ ARG BRANCH=master FROM productopener/backend-base-cpan -# https://github.com/tianon/docker-brew-ubuntu-core/issues/59 -ARG DEBIAN_FRONTEND=noninteractive -RUN apt-get update && apt-get install --assume-yes apt-utils - ARG BRANCH ADD https://api.github.com/repos/openfoodfacts/openfoodfacts-server/compare/${BRANCH}...HEAD /dev/null RUN git clone --depth 1 https://github.com/openfoodfacts/openfoodfacts-server.git -b ${BRANCH} /opt/product-opener WORKDIR "/opt/product-opener" -# Add ProductOpener runtime dependencies cpanm -RUN cpanm --quiet --installdeps --notest --skip-satisfied . +# Add ProductOpener runtime dependencies from the git repositories' cpanfile +RUN cpm install -g # Remove build dependencies -RUN set -x \ - && apt-get purge -y --auto-remove make gcc libperl-dev g++ git \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* +RUN apk del -U alpine-sdk imagemagick6-dev graphviz-dev tesseract-ocr-dev zbar-dev \ + && rm -rf /var/cache/apk/* From 3adfcad6968d33530242c56cc4a7ae18df4ba0a2 Mon Sep 17 00:00:00 2001 From: hangy Date: Mon, 4 Jun 2018 08:17:15 +0200 Subject: [PATCH 30/36] Remove *-git Dockerfiles, unify *base* Dockefiles into -dev. --- docker/README.md | 32 +--------- docker/backend-base-cpan/Dockerfile | 10 --- docker/backend-base/Dockerfile | 17 ----- docker/backend-dev/Dockerfile | 29 ++++++++- docker/backend-dev/conf | 1 - .../conf/Config.pm | 0 .../conf/Config2.pm | 0 .../conf/apache.conf | 0 .../conf/po-foreground.sh | 0 docker/backend-git/Dockerfile | 14 ----- docker/docker-compose-git.yml | 63 ------------------- docker/frontend-git/Dockerfile | 27 -------- 12 files changed, 30 insertions(+), 163 deletions(-) delete mode 100644 docker/backend-base-cpan/Dockerfile delete mode 100644 docker/backend-base/Dockerfile delete mode 120000 docker/backend-dev/conf rename docker/{backend-git => backend-dev}/conf/Config.pm (100%) rename docker/{backend-git => backend-dev}/conf/Config2.pm (100%) rename docker/{backend-git => backend-dev}/conf/apache.conf (100%) rename docker/{backend-git => backend-dev}/conf/po-foreground.sh (100%) delete mode 100644 docker/backend-git/Dockerfile delete mode 100644 docker/docker-compose-git.yml delete mode 100644 docker/frontend-git/Dockerfile diff --git a/docker/README.md b/docker/README.md index 29aa8e054ddc2..e51c78b59df09 100644 --- a/docker/README.md +++ b/docker/README.md @@ -24,29 +24,9 @@ docker-compose -f docker-compose-aio.yml up ### Split Containers ### -Uses different base images that don't need to be rebuilt often, and separate containers for application layer (Apache) and the reverse proxy (nginx). +Uses separate containers for application layer (Apache) and the reverse proxy (nginx). -#### Base image #### - -In order to reducing the amout of time and work needed to rebuild the program image, we have two base images that the actual images are built on. Build them using - -``` -docker build -t productopener/backend-base backend-base -docker build -t productopener/backend-base-cpan backend-base-cpan -``` - -#### Source from Git #### - -Use this if you want to run Product Opener with the source code from Git. - -``` -docker build -t productopener/backend-git backend-git -docker build -t productopener/frontend-git frontend-git -``` - -#### Source from local directory #### - -Use this for local development. +#### Building the Container #### ``` docker build -t productopener/backend-dev -f backend-dev/Dockerfile .. @@ -56,14 +36,6 @@ docker build -t productopener/backend-dev -f backend-dev/Dockerfile .. In this repository, the service dependencies are expressed in [compose](https://docs.docker.com/compose/compose-file/) files. -#### Source from Git #### - -If you built `productopener/backend-git` and `productopener/frontend-git` above, run - -``` -docker stack deploy --compose-file=docker-compose-git.yml po -``` - #### Source from local directory #### If you built `productopener/backend-dev`, run diff --git a/docker/backend-base-cpan/Dockerfile b/docker/backend-base-cpan/Dockerfile deleted file mode 100644 index a139e533475a2..0000000000000 --- a/docker/backend-base-cpan/Dockerfile +++ /dev/null @@ -1,10 +0,0 @@ -ARG BRANCH=master -FROM productopener/backend-base - -ARG BRANCH -ADD https://raw.githubusercontent.com/openfoodfacts/openfoodfacts-server/${BRANCH}/cpanfile /tmp/cpanfile - -WORKDIR "/tmp" - -# Add ProductOpener runtime dependencies from the git repositories' cpanfile -RUN cpm install -g diff --git a/docker/backend-base/Dockerfile b/docker/backend-base/Dockerfile deleted file mode 100644 index 5e5781d2582c0..0000000000000 --- a/docker/backend-base/Dockerfile +++ /dev/null @@ -1,17 +0,0 @@ -FROM hangy/httpd-alpine-perl-modperl - -# Prepare Apache to include our custom config -RUN set -x \ - && mkdir -p /usr/local/apache2/conf/sites-enabled \ - && echo 'IncludeOptional conf/sites-enabled/*.conf' >> /usr/local/apache2/conf/httpd.conf - -RUN apk add --update alpine-sdk imagemagick6 imagemagick6-dev graphviz graphviz-dev tesseract-ocr tesseract-ocr-dev \ - && rm -rf /var/cache/apk/* - -# As of Alpine 3.7, zbar is only available in testing. -RUN echo -e '@edge http://dl-cdn.alpinelinux.org/alpine/edge/main\n@edgecommunity http://dl-cdn.alpinelinux.org/alpine/edge/community\n@testing http://dl-cdn.alpinelinux.org/alpine/edge/testing' >> /etc/apk/repositories -RUN apk add -U imagemagick@edge zbar@testing zbar-dev@testing \ - && rm -rf /var/cache/apk/* - -# Dependency of libapreq2-2.13, which is not installed automatically. -RUN cpm install ExtUtils::XSBuilder::ParseSource -g diff --git a/docker/backend-dev/Dockerfile b/docker/backend-dev/Dockerfile index 0f8e2037a3442..a177bb097bfe5 100644 --- a/docker/backend-dev/Dockerfile +++ b/docker/backend-dev/Dockerfile @@ -1,5 +1,32 @@ -FROM productopener/backend-base-cpan +ARG BRANCH=master +FROM hangy/httpd-alpine-perl-modperl +# Prepare Apache to include our custom config +RUN set -x \ + && mkdir -p /usr/local/apache2/conf/sites-enabled \ + && echo 'IncludeOptional conf/sites-enabled/*.conf' >> /usr/local/apache2/conf/httpd.conf + +RUN apk add --update alpine-sdk imagemagick6 imagemagick6-dev graphviz graphviz-dev tesseract-ocr tesseract-ocr-dev \ + && rm -rf /var/cache/apk/* + +# As of Alpine 3.7, zbar is only available in testing. +RUN echo -e '@edge http://dl-cdn.alpinelinux.org/alpine/edge/main\n@edgecommunity http://dl-cdn.alpinelinux.org/alpine/edge/community\n@testing http://dl-cdn.alpinelinux.org/alpine/edge/testing' >> /etc/apk/repositories +RUN apk add -U imagemagick@edge zbar@testing zbar-dev@testing \ + && rm -rf /var/cache/apk/* + +# Dependency of libapreq2-2.13, which is not installed automatically. +RUN cpm install ExtUtils::XSBuilder::ParseSource -g + +# Builder image stage +ARG BRANCH +ADD https://raw.githubusercontent.com/openfoodfacts/openfoodfacts-server/${BRANCH}/cpanfile /tmp/cpanfile + +WORKDIR "/tmp" + +# Add ProductOpener runtime dependencies from the git repositories' cpanfile +RUN cpm install -g + +# Install Product Opener from the workdir. ADD . "/opt/product-opener/" WORKDIR "/opt/product-opener" diff --git a/docker/backend-dev/conf b/docker/backend-dev/conf deleted file mode 120000 index a5872174986b8..0000000000000 --- a/docker/backend-dev/conf +++ /dev/null @@ -1 +0,0 @@ -../backend-git/conf \ No newline at end of file diff --git a/docker/backend-git/conf/Config.pm b/docker/backend-dev/conf/Config.pm similarity index 100% rename from docker/backend-git/conf/Config.pm rename to docker/backend-dev/conf/Config.pm diff --git a/docker/backend-git/conf/Config2.pm b/docker/backend-dev/conf/Config2.pm similarity index 100% rename from docker/backend-git/conf/Config2.pm rename to docker/backend-dev/conf/Config2.pm diff --git a/docker/backend-git/conf/apache.conf b/docker/backend-dev/conf/apache.conf similarity index 100% rename from docker/backend-git/conf/apache.conf rename to docker/backend-dev/conf/apache.conf diff --git a/docker/backend-git/conf/po-foreground.sh b/docker/backend-dev/conf/po-foreground.sh similarity index 100% rename from docker/backend-git/conf/po-foreground.sh rename to docker/backend-dev/conf/po-foreground.sh diff --git a/docker/backend-git/Dockerfile b/docker/backend-git/Dockerfile deleted file mode 100644 index 0a9e3871d4af3..0000000000000 --- a/docker/backend-git/Dockerfile +++ /dev/null @@ -1,14 +0,0 @@ -ARG BRANCH=master -FROM productopener/backend-base-cpan - -ARG BRANCH -ADD https://api.github.com/repos/openfoodfacts/openfoodfacts-server/compare/${BRANCH}...HEAD /dev/null -RUN git clone --depth 1 https://github.com/openfoodfacts/openfoodfacts-server.git -b ${BRANCH} /opt/product-opener -WORKDIR "/opt/product-opener" - -# Add ProductOpener runtime dependencies from the git repositories' cpanfile -RUN cpm install -g - -# Remove build dependencies -RUN apk del -U alpine-sdk imagemagick6-dev graphviz-dev tesseract-ocr-dev zbar-dev \ - && rm -rf /var/cache/apk/* diff --git a/docker/docker-compose-git.yml b/docker/docker-compose-git.yml deleted file mode 100644 index 375f11875196a..0000000000000 --- a/docker/docker-compose-git.yml +++ /dev/null @@ -1,63 +0,0 @@ -version: "3.3" -services: - mongodb: - image: mongo:latest - volumes: - - dbdata:/var/lib/mongodb - ports: - - 27017:27017 - networks: - - webnet - backend: - image: productopener/backend-git:latest - depends_on: - - mongodb - volumes: - - podata:/mnt/podata - - type: tmpfs - target: /mnt/podata/mnt - - product_images:/opt/product-opener/html/images/products - configs: - - source: poconfig - target: /opt/product-opener/lib/ProductOpener/Config.pm - - source: poconfig2 - target: /opt/product-opener/lib/ProductOpener/Config2.pm - - source: apacheconfig - target: /usr/local/apache2/conf/sites-enabled/product-opener.conf - - source: starthttpd - target: /usr/local/bin/po-foreground.sh - command: ["/bin/sh", "/usr/local/bin/po-foreground.sh"] - ports: - - 80 - networks: - - webnet - frontend: - image: productopener/frontend-git:latest - depends_on: - - backend - volumes: - - product_images:/opt/product-opener/html/images/products - configs: - - source: nginxconfig - target: /etc/nginx/conf.d/default.conf - ports: - - 80:80 - networks: - - webnet -networks: - webnet: -volumes: - dbdata: - podata: - product_images: -configs: - poconfig: - file: ./backend-git/conf/Config.pm - poconfig2: - file: ./backend-git/conf/Config2.pm - apacheconfig: - file: ./backend-git/conf/apache.conf - starthttpd: - file: ./backend-git/conf/po-foreground.sh - nginxconfig: - file: ./frontend-git/conf/nginx.conf diff --git a/docker/frontend-git/Dockerfile b/docker/frontend-git/Dockerfile deleted file mode 100644 index d4d9dd6cd2532..0000000000000 --- a/docker/frontend-git/Dockerfile +++ /dev/null @@ -1,27 +0,0 @@ -ARG BRANCH=master -FROM nginx:latest - -# https://github.com/tianon/docker-brew-ubuntu-core/issues/59 -ARG DEBIAN_FRONTEND=noninteractive -RUN apt-get update && apt-get install --assume-yes apt-utils - -RUN set -x \ - && apt-get install -y --no-install-recommends git ca-certificates curl gnupg2 \ - && curl -sL https://deb.nodesource.com/setup_8.x | bash - \ - && apt-get install -y --no-install-recommends nodejs \ - && curl -o- -L https://yarnpkg.com/install.sh | bash - -ARG BRANCH -ADD https://api.github.com/repos/openfoodfacts/openfoodfacts-server/compare/${BRANCH}...HEAD /dev/null -RUN git clone --depth 1 https://github.com/openfoodfacts/openfoodfacts-server.git -b ${BRANCH} /opt/product-opener - -WORKDIR "/opt/product-opener" - -# Add ProductOpener runtime dependencies from yarn -RUN /root/.yarn/bin/yarn install - -# Remove build dependencies -RUN set -x \ - && apt-get purge -y --auto-remove git ca-certificates curl gnupg2 nodejs \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* From 8a073e341888103343c9ff946f3bf35b8511ec57 Mon Sep 17 00:00:00 2001 From: hangy Date: Mon, 4 Jun 2018 23:25:10 +0200 Subject: [PATCH 31/36] Remove HTML::Defang. --- cpanfile | 1 - lib/ProductOpener/Index.pm | 1 - 2 files changed, 2 deletions(-) diff --git a/cpanfile b/cpanfile index 9f37dea5f8f88..29d4564ae641e 100644 --- a/cpanfile +++ b/cpanfile @@ -28,7 +28,6 @@ requires 'MongoDB', '>= 1.4.5'; # libmongodb-perl has an older version requires 'URI::Escape::XS'; requires 'Encode::Punycode'; requires 'GraphViz2'; -requires 'HTML::Defang'; requires 'Algorithm::CheckDigits'; requires 'Geo::IP'; requires 'Image::OCR::Tesseract'; diff --git a/lib/ProductOpener/Index.pm b/lib/ProductOpener/Index.pm index 7c46586be27fd..9b97036970f18 100644 --- a/lib/ProductOpener/Index.pm +++ b/lib/ProductOpener/Index.pm @@ -60,7 +60,6 @@ use Cache::Memcached::Fast; use Digest::MD5 qw(md5); use URI::Escape; use URI::Escape::XS; -use HTML::Defang; #use Text::Unaccent::PurePerl "unac_string"; use Text::Unaccent "unac_string"; use DateTime; From e931e0444fbc9fea06a6966944659174a18a6541 Mon Sep 17 00:00:00 2001 From: hangy Date: Mon, 4 Jun 2018 23:55:20 +0200 Subject: [PATCH 32/36] Dereference scalars for some operations - http://www.perlmonks.org/?node_id=1190789 - https://stackoverflow.com/a/41192741/11963 --- cgi/product_jqm_multilingual.pl | 2 +- lib/ProductOpener/Display.pm | 2 +- lib/ProductOpener/SiteQuality_off.pm | 74 ++++++++++++++-------------- 3 files changed, 39 insertions(+), 39 deletions(-) diff --git a/cgi/product_jqm_multilingual.pl b/cgi/product_jqm_multilingual.pl index 6bcf3ad3ef4f6..94fbd63335b8a 100644 --- a/cgi/product_jqm_multilingual.pl +++ b/cgi/product_jqm_multilingual.pl @@ -306,7 +306,7 @@ if ($no_nutrition_data) { # Delete all non-carbon-footprint nids. - foreach my $key (keys $product_ref->{nutriments}) { + foreach my $key (keys %{$product_ref->{nutriments}}) { next if $key =~ /_/; next if $key eq 'carbon-footprint'; diff --git a/lib/ProductOpener/Display.pm b/lib/ProductOpener/Display.pm index 2a8ce82912b0d..613e9521cde33 100644 --- a/lib/ProductOpener/Display.pm +++ b/lib/ProductOpener/Display.pm @@ -7980,7 +7980,7 @@ sub add_images_urls_to_product($) { } if (defined $product_ref->{languages_codes}) { - foreach my $key (keys $product_ref->{languages_codes}) { + foreach my $key (keys %{$product_ref->{languages_codes}}) { my $id = $imagetype . '_' . $key; if ((defined $product_ref->{images}) and (defined $product_ref->{images}{$id}) and (defined $product_ref->{images}{$id}{sizes}) and (defined $product_ref->{images}{$id}{sizes}{$size})) { diff --git a/lib/ProductOpener/SiteQuality_off.pm b/lib/ProductOpener/SiteQuality_off.pm index e883f742d14b0..ebffc447cec78 100644 --- a/lib/ProductOpener/SiteQuality_off.pm +++ b/lib/ProductOpener/SiteQuality_off.pm @@ -374,14 +374,14 @@ sub detect_categories ($) { if ($match_fr =~ /lait ([^,-]* )?(suite|croissance|infantile|bébé|bebe|nourrisson|nourisson|age|maternise|maternisé)/i) { if (not has_tag($product_ref, "categories", "en:baby-milks")) { - push $product_ref->{quality_tags}, "detected-category-from-name-ingredients-en-baby-milks"; + push @{$product_ref->{quality_tags}}, "detected-category-from-name-ingredients-en-baby-milks"; } } if (defined $product_ref->{brands_tags}) { foreach my $brandid (@{$product_ref->{brands_tags}}) { if (defined $baby_food_brands{$brandid}) { - push $product_ref->{quality_tags}, "detected-category-from-brand-en-baby-foods"; + push @{$product_ref->{quality_tags}}, "detected-category-from-brand-en-baby-foods"; last; } } @@ -390,7 +390,7 @@ sub detect_categories ($) { if (defined $product_ref->{brands_tags}) { foreach my $brandid (@{$product_ref->{brands_tags}}) { if (defined $cigarette_brands{$brandid}) { - push $product_ref->{quality_tags}, "detected-category-from-brand-en-cigarettes"; + push @{$product_ref->{quality_tags}}, "detected-category-from-brand-en-cigarettes"; last; } } @@ -398,7 +398,7 @@ sub detect_categories ($) { # Plant milks should probably not be dairies https://github.com/openfoodfacts/openfoodfacts-server/issues/73 if (has_tag($product_ref, "categories", "en:plant-milks") and has_tag($product_ref, "categories", "en:dairies")) { - push $product_ref->{quality_tags}, "plant-milk-also-is-dairy"; + push @{$product_ref->{quality_tags}}, "plant-milk-also-is-dairy"; } } @@ -412,26 +412,26 @@ sub check_nutrition_data($) { if ((defined $product_ref->{multiple_nutrition_data}) and ($product_ref->{multiple_nutrition_data} eq 'on')) { - push $product_ref->{quality_tags}, "multiple-nutrition-data"; + push @{$product_ref->{quality_tags}}, "multiple-nutrition-data"; if ((defined $product_ref->{not_comparable_nutrition_data}) and $product_ref->{not_comparable_nutrition_data}) { - push $product_ref->{quality_tags}, "not-comparable-nutrition-data"; + push @{$product_ref->{quality_tags}}, "not-comparable-nutrition-data"; } } if ((defined $product_ref->{no_nutrition_data}) and ($product_ref->{no_nutrition_data} eq 'on')) { - push $product_ref->{quality_tags}, "no-nutrition-data"; + push @{$product_ref->{quality_tags}}, "no-nutrition-data"; } if (defined $product_ref->{nutriments}) { if ((defined $product_ref->{nutrition_data_prepared}) and ($product_ref->{nutrition_data_prepared} eq 'on')) { - push $product_ref->{quality_tags}, "nutrition-data-prepared"; + push @{$product_ref->{quality_tags}}, "nutrition-data-prepared"; if (not has_tag($product_ref, "categories", "en:dried-products-to-be-rehydrated")) { - push $product_ref->{quality_tags}, "nutrition-data-prepared-without-category-dried-products-to-be-rehydrated"; + push @{$product_ref->{quality_tags}}, "nutrition-data-prepared-without-category-dried-products-to-be-rehydrated"; } } @@ -439,10 +439,10 @@ sub check_nutrition_data($) { if ((defined $product_ref->{nutrition_data_per}) and ($product_ref->{nutrition_data_per} eq 'serving')) { if ((not defined $product_ref->{serving_size}) or ($product_ref->{serving_size} eq '')) { - push $product_ref->{quality_tags}, "nutrition-data-per-serving-missing-serving-size"; + push @{$product_ref->{quality_tags}}, "nutrition-data-per-serving-missing-serving-size"; } elsif ($product_ref->{serving_quantity} == 0) { - push $product_ref->{quality_tags}, "nutrition-data-per-serving-missing-serving-quantity-is-zero"; + push @{$product_ref->{quality_tags}}, "nutrition-data-per-serving-missing-serving-quantity-is-zero"; } } @@ -456,7 +456,7 @@ sub check_nutrition_data($) { if (($nid !~ /energy/) and ($nid !~ /footprint/) and ($product_ref->{nutriments}{$nid . "_100g"} > 105)) { - push $product_ref->{quality_tags}, "nutrition-value-over-105-for-$nid"; + push @{$product_ref->{quality_tags}}, "nutrition-value-over-105-for-$nid"; } if ($product_ref->{nutriments}{$nid . "_100g"} == 0) { @@ -466,18 +466,18 @@ sub check_nutrition_data($) { } if (($nid_n >= 1) and ($nid_zero == $nid_n)) { - push $product_ref->{quality_tags}, "nutrition-all-values-zero"; + push @{$product_ref->{quality_tags}}, "nutrition-all-values-zero"; } if ((defined $product_ref->{nutriments}{"carbohydrates_100g"}) and (($product_ref->{nutriments}{"sugars_100g"} + $product_ref->{nutriments}{"starch_100g"}) > ($product_ref->{nutriments}{"carbohydrates_100g"}) + 0.001)) { - push $product_ref->{quality_tags}, "nutrition-sugars-plus-starch-greater-than-carbohydrates"; + push @{$product_ref->{quality_tags}}, "nutrition-sugars-plus-starch-greater-than-carbohydrates"; } if (($product_ref->{nutriments}{"saturated-fat_100g"} > ($product_ref->{nutriments}{"fat_100g"}) + 0.001)) { - push $product_ref->{quality_tags}, "nutrition-saturated-fat-greater-than-fat"; + push @{$product_ref->{quality_tags}}, "nutrition-saturated-fat-greater-than-fat"; } } @@ -507,10 +507,10 @@ sub check_ingredients($) { if ($nb_languages > 1) { foreach my $max (5, 4, 3, 2, 1) { if ($nb_languages > $max) { - push $product_ref->{quality_tags}, "ingredients-number-of-languages-above-$max"; + push @{$product_ref->{quality_tags}}, "ingredients-number-of-languages-above-$max"; } } - push $product_ref->{quality_tags}, "ingredients-number-of-languages-$nb_languages"; + push @{$product_ref->{quality_tags}}, "ingredients-number-of-languages-$nb_languages"; } if ((defined $product_ref->{ingredients_n}) and ( $product_ref->{ingredients_n} > 0)) { @@ -519,14 +519,14 @@ sub check_ingredients($) { foreach my $max (50, 40, 30, 20, 10, 5, 0) { if ($score > $max) { - push $product_ref->{quality_tags}, "ingredients-unknown-score-above-$max"; + push @{$product_ref->{quality_tags}}, "ingredients-unknown-score-above-$max"; last; } } foreach my $max (100, 90, 80, 70, 60, 50) { if (($product_ref->{unknown_ingredients_n} / $product_ref->{ingredients_n}) >= ($max / 100)) { - push $product_ref->{quality_tags}, "ingredients-$max-percent-unknown"; + push @{$product_ref->{quality_tags}}, "ingredients-$max-percent-unknown"; last; } } @@ -547,7 +547,7 @@ sub check_ingredients($) { if ($max_length > $max_length_threshold) { - push $product_ref->{quality_tags}, "ingredients-ingredient-tag-length-greater-than-" . $max_length_threshold; + push @{$product_ref->{quality_tags}}, "ingredients-ingredient-tag-length-greater-than-" . $max_length_threshold; } } @@ -566,42 +566,42 @@ sub check_ingredients($) { if ($product_ref->{$ingredients_text_lc} =~ /,(\s*)$/is) { - push $product_ref->{quality_tags}, "ingredients-" . $display_lc . "-ending-comma"; + push @{$product_ref->{quality_tags}}, "ingredients-" . $display_lc . "-ending-comma"; } if ($product_ref->{$ingredients_text_lc} =~ /[aeiouy]{5}/is) { - push $product_ref->{quality_tags}, "ingredients-" . $display_lc . "-5-vowels"; + push @{$product_ref->{quality_tags}}, "ingredients-" . $display_lc . "-5-vowels"; } if ($product_ref->{$ingredients_text_lc} =~ /[bcdfghjklmnpqrstvwxz]{4}/is) { - push $product_ref->{quality_tags}, "ingredients-" . $display_lc . "-4-consonants"; + push @{$product_ref->{quality_tags}}, "ingredients-" . $display_lc . "-4-consonants"; } if ($product_ref->{$ingredients_text_lc} =~ /(.)\1{4,}/is) { - push $product_ref->{quality_tags}, "ingredients-" . $display_lc . "-4-repeated-chars"; + push @{$product_ref->{quality_tags}}, "ingredients-" . $display_lc . "-4-repeated-chars"; } if ($product_ref->{$ingredients_text_lc} =~ /[\$\€\£\¥\₩]/is) { - push $product_ref->{quality_tags}, "ingredients-" . $display_lc . "-unexpected-chars-currencies"; + push @{$product_ref->{quality_tags}}, "ingredients-" . $display_lc . "-unexpected-chars-currencies"; } if ($product_ref->{$ingredients_text_lc} =~ /[\@]/is) { - push $product_ref->{quality_tags}, "ingredients-" . $display_lc . "-unexpected-chars-arobase"; + push @{$product_ref->{quality_tags}}, "ingredients-" . $display_lc . "-unexpected-chars-arobase"; } if ($product_ref->{$ingredients_text_lc} =~ /[\!]/is) { - push $product_ref->{quality_tags}, "ingredients-" . $display_lc . "-unexpected-chars-exclamation-mark"; + push @{$product_ref->{quality_tags}}, "ingredients-" . $display_lc . "-unexpected-chars-exclamation-mark"; } if ($product_ref->{$ingredients_text_lc} =~ /[\?]/is) { - push $product_ref->{quality_tags}, "ingredients-" . $display_lc . "-unexpected-chars-question-mark"; + push @{$product_ref->{quality_tags}}, "ingredients-" . $display_lc . "-unexpected-chars-question-mark"; } @@ -610,12 +610,12 @@ sub check_ingredients($) { if ($product_ref->{$ingredients_text_lc} =~ /kcal|glucides|(dont sucres)|(dont acides gras)|(valeurs nutri)/is) { - push $product_ref->{quality_tags}, "ingredients-" . $display_lc . "-includes-fr-nutrition-facts"; + push @{$product_ref->{quality_tags}}, "ingredients-" . $display_lc . "-includes-fr-nutrition-facts"; } if ($product_ref->{$ingredients_text_lc} =~ /(à conserver)|(conditions de )|(à consommer )|(plus d'info)|consigne/is) { - push $product_ref->{quality_tags}, "ingredients-" . $display_lc . "-includes-fr-instructions"; + push @{$product_ref->{quality_tags}}, "ingredients-" . $display_lc . "-includes-fr-instructions"; } #} } @@ -635,11 +635,11 @@ sub check_quantity($) { if ((defined $product_ref->{quantity}) and ($product_ref->{quantity} =~ /(?:.*e$)|(?:[0-9]+\s*[kmc]?[gl]?\s*e)/i) and (not ($product_ref->{quantity} =~ /\N{U+212E}/i))) { - push $product_ref->{quality_tags}, "quantity-contains-e"; + push @{$product_ref->{quality_tags}}, "quantity-contains-e"; } if ((defined $product_ref->{quantity}) and (not defined $product_ref->{product_quantity})) { - push $product_ref->{quality_tags}, "quantity-not-recognized"; + push @{$product_ref->{quality_tags}}, "quantity-not-recognized"; } } @@ -659,13 +659,13 @@ sub check_bug_code_missing($) { # https://github.com/openfoodfacts/openfoodfacts-server/issues/185#issuecomment-364653043 if ((not (defined $product_ref->{code}))) { - push $product_ref->{quality_tags}, "code-missing"; + push @{$product_ref->{quality_tags}}, "code-missing"; } elsif ($product_ref->{code} eq '') { - push $product_ref->{quality_tags}, "code-empty"; + push @{$product_ref->{quality_tags}}, "code-empty"; } elsif ($product_ref->{code} == 0) { - push $product_ref->{quality_tags}, "code-zero"; + push @{$product_ref->{quality_tags}}, "code-zero"; } } @@ -676,10 +676,10 @@ sub check_bug_created_t_missing($) { # https://github.com/openfoodfacts/openfoodfacts-server/issues/185 if ((not (defined $product_ref->{created_t}))) { - push $product_ref->{quality_tags}, "created-missing"; + push @{$product_ref->{quality_tags}}, "created-missing"; } elsif ($product_ref->{created_t} == 0) { - push $product_ref->{quality_tags}, "created-zero"; + push @{$product_ref->{quality_tags}}, "created-zero"; } } From 5ab601d27d95e08bfa1076374ca8f4e04f90dbd9 Mon Sep 17 00:00:00 2001 From: hangy Date: Tue, 5 Jun 2018 18:36:49 +0200 Subject: [PATCH 33/36] Add OIDC to docker configs. --- docker/backend-dev/conf/Config.pm | 4 ++- docker/backend-dev/conf/Config2.pm | 44 ++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/docker/backend-dev/conf/Config.pm b/docker/backend-dev/conf/Config.pm index efdabaefa2d8b..1804e69cf9fc9 100644 --- a/docker/backend-dev/conf/Config.pm +++ b/docker/backend-dev/conf/Config.pm @@ -42,6 +42,7 @@ BEGIN $facebook_app_id $facebook_app_secret + $oidc $csrf_secret $mongodb @@ -161,7 +162,8 @@ $data_root = $ProductOpener::Config2::data_root; $facebook_app_id = $ProductOpener::Config2::facebook_app_id; $facebook_app_secret = $ProductOpener::Config2::facebook_app_secret; -$csrf_secret = $Blogs::Config2::csrf_secret; +$oidc = $ProductOpener::Config2::oidc; +$csrf_secret = $ProductOpener::Config2::csrf_secret; $reference_timezone = 'Europe/Paris'; diff --git a/docker/backend-dev/conf/Config2.pm b/docker/backend-dev/conf/Config2.pm index 577cab44980d5..67f79e8b74869 100644 --- a/docker/backend-dev/conf/Config2.pm +++ b/docker/backend-dev/conf/Config2.pm @@ -37,6 +37,7 @@ BEGIN $mongodb_host $facebook_app_id $facebook_app_secret + $oidc $csrf_secret ); @@ -59,6 +60,49 @@ $mongodb_host = "mongodb://mongodb:27017"; $facebook_app_id = ""; $facebook_app_secret = ""; +$oidc = { + 'id_token' => { + 'iss' => $server_domain, + 'expires_in' => 3600, + 'pub_key' => "-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5XxKc3Rz/8EakvZG+Ez9 +nCpdn2HGVq0CRD1GZ/fEuM7nHfmy1LzC0VyNa8YkU7Qrb4s/BgSxjFrLvbpFHcUo +o+D3W5TvCR+8RLJLISmUoo4jMb2wDq35DEaJa3j1lGb7o93msFwlSRLwbmcYw08F +H3NEB1IQxkfAv6Z/ddzjDzV1nhbQO/RnO4v4JJ8wR4xxLmo00AJJ7fr8oL51aF9S +kjHU8cjvKFjGW4kcNxxm3+pMCzNtbJvUFQFeTQNBkWt9k83yKA5bhNwK1W4otRPs +l2bP0AyuJtA3tFdWSD1fvIA+l8rywrVom/RbDRUZkm1k+YgbyqyUgJbM5oJkGhwP +fwIDAQAB +-----END PUBLIC KEY-----", + 'priv_key' => "-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEA5XxKc3Rz/8EakvZG+Ez9nCpdn2HGVq0CRD1GZ/fEuM7nHfmy +1LzC0VyNa8YkU7Qrb4s/BgSxjFrLvbpFHcUoo+D3W5TvCR+8RLJLISmUoo4jMb2w +Dq35DEaJa3j1lGb7o93msFwlSRLwbmcYw08FH3NEB1IQxkfAv6Z/ddzjDzV1nhbQ +O/RnO4v4JJ8wR4xxLmo00AJJ7fr8oL51aF9SkjHU8cjvKFjGW4kcNxxm3+pMCzNt +bJvUFQFeTQNBkWt9k83yKA5bhNwK1W4otRPsl2bP0AyuJtA3tFdWSD1fvIA+l8ry +wrVom/RbDRUZkm1k+YgbyqyUgJbM5oJkGhwPfwIDAQABAoIBADYW6Jlz7k9u3Wuc +PrgRtYkUd0K00gHl/23EH48r2CNTKShojV0VLLoaHX80kaVlBwPghzdM7ehOEk2i +1N2ideTChqsAXKMC5uYuPAUR/uWdqO/1bMTY/qWFDqjVNtUGvPMvv0r8PRGPNDph +dHW8b1GtYnBzSF7j1KuXe9109dPEbLQHpOuZijPOGSupJ+pXRc4nRTSasJXLhtVC +2FAhyK/U8vXQbtSLfcsldK0oWCwmTi/OVLYioz4JulyDz6P98M2AZ+WG7OptNcFi +nt5WbX/1JUKpHsPMQhMv/UVVk9hihx0j7qhQw0iDEdx4Por4+sYLeqKLEFIYrf1f +KvAUwgkCgYEA9I7r2x7fxIH9tQczPBa053Nyp5kFgS5BFgXPWUHDPl0+WACDVpih +/w7YCZmdOYc3a2UncOaK1/G9Nh0BogCfl3kMTgDeuS810dB9vM2K684Ay/hnQhTL +vsLnVflFKbIeZWywLxq7/Rzi/gmrK+YoeLJpv2xdAxJQ4gHIfPJC2s0CgYEA8DjX +tpBWN6zn2ui/eKHhwtEKbA4BMHySQ8wtWQ5lLR9DbA8Hs9LxyeQUfvEBu2728/h8 +56acCn7D4H1VCBfwIWDKIu+jEqi+RE/kRPnCpV1iBHE0EuB/YN89UGIDpLKfgebK +bDbGbpuEeMfIedvO1FRn4g+P1vNzvWq28qcuq3sCgYEA7eLcT9//YIHlzSK81rVr +sTweiiKSNS9OBmMOZ89NYSuISkfted2srpK82NHBG0WJRgE2VV8cLaQrHikm/nPG +yavoqTO1csMWggphVLdHa8qOAdqWbrQV4HBsYLfBbCaj5JrN4nQJ6tMfhmbXRzNx +qL47mQWKkENPxBhh8hAhsf0CgYEAwrVQIynauEXtqAH/MEgGNWI6kFrJnANcipd0 +KjsAxxIQFAYauCbC1GGKO1odjU7j29wNYbYpxFf7bHop8eV1PZi2Ppr+EqGzlqsq +2r2Wh3Kpf/BBxQsyM9K+X+kSCuy9XQ00BYJgVEa5mSxV0m/XtUK08QasEA5EQcO9 +hfD8YwECgYEAyHiQ1yuMzVzNKDCUVo2UpiFiVIqOg0Yjoa/tt9MbBwAukYMhRkvU +2CI8jKu8KugD7NArWMpoPcIECk64roYZR0RH4MKWK46OHg2vwPGiOUaWjlsoUcNc +eH+MQwdClxQ8rTr2CxXZKffji8I2Vs9FUE+pep0s3gR072kix3EFdZc= +-----END RSA PRIVATE KEY-----", + }, +}; + $csrf_secret = "EYvfj3GDJnc2UPVqTwTGPgWC"; 1; From 6e0c1402d63f051b1165fbbc81b6d08c94c691f5 Mon Sep 17 00:00:00 2001 From: hangy Date: Tue, 5 Jun 2018 19:08:54 +0200 Subject: [PATCH 34/36] Update copyrights. --- cgi/oidc/authorize.pl | 2 +- cgi/oidc/clients.pl | 2 +- cgi/oidc/jwks.pl | 2 +- cgi/oidc/openid-configuration.pl | 2 +- cgi/oidc/token.pl | 2 +- cgi/oidc/userinfo.pl | 2 +- lib/ProductOpener/OIDC/Server.pm | 2 +- lib/ProductOpener/OIDC/Server/DataHandler.pm | 2 +- lib/ProductOpener/OIDC/Server/Request.pm | 2 +- lib/ProductOpener/OIDC/Server/Web/M/AccessToken.pm | 2 +- lib/ProductOpener/OIDC/Server/Web/M/AuthInfo.pm | 2 +- lib/ProductOpener/OIDC/Server/Web/M/Client.pm | 2 +- lib/ProductOpener/OIDC/Server/Web/M/ResourceOwner.pm | 2 +- 13 files changed, 13 insertions(+), 13 deletions(-) diff --git a/cgi/oidc/authorize.pl b/cgi/oidc/authorize.pl index dec9738c3c0c2..5add0ad94f298 100644 --- a/cgi/oidc/authorize.pl +++ b/cgi/oidc/authorize.pl @@ -3,7 +3,7 @@ # This file is part of Product Opener. # # Product Opener -# Copyright (C) 2011-2016 Association Open Food Facts +# Copyright (C) 2011-2018 Association Open Food Facts # Contact: contact@openfoodfacts.org # Address: 21 rue des Iles, 94100 Saint-Maur des Fossés, France # diff --git a/cgi/oidc/clients.pl b/cgi/oidc/clients.pl index e1addb0b53d3a..ceb3e05361878 100644 --- a/cgi/oidc/clients.pl +++ b/cgi/oidc/clients.pl @@ -3,7 +3,7 @@ # This file is part of Product Opener. # # Product Opener -# Copyright (C) 2011-2016 Association Open Food Facts +# Copyright (C) 2011-2018 Association Open Food Facts # Contact: contact@openfoodfacts.org # Address: 21 rue des Iles, 94100 Saint-Maur des Fossés, France # diff --git a/cgi/oidc/jwks.pl b/cgi/oidc/jwks.pl index d06608ad04169..9bb763061f70e 100644 --- a/cgi/oidc/jwks.pl +++ b/cgi/oidc/jwks.pl @@ -3,7 +3,7 @@ # This file is part of Product Opener. # # Product Opener -# Copyright (C) 2011-2016 Association Open Food Facts +# Copyright (C) 2011-2018 Association Open Food Facts # Contact: contact@openfoodfacts.org # Address: 21 rue des Iles, 94100 Saint-Maur des Fossés, France # diff --git a/cgi/oidc/openid-configuration.pl b/cgi/oidc/openid-configuration.pl index 64f11e299a41a..691231490b59c 100644 --- a/cgi/oidc/openid-configuration.pl +++ b/cgi/oidc/openid-configuration.pl @@ -3,7 +3,7 @@ # This file is part of Product Opener. # # Product Opener -# Copyright (C) 2011-2016 Association Open Food Facts +# Copyright (C) 2011-2018 Association Open Food Facts # Contact: contact@openfoodfacts.org # Address: 21 rue des Iles, 94100 Saint-Maur des Fossés, France # diff --git a/cgi/oidc/token.pl b/cgi/oidc/token.pl index 4f7c9ba0008a0..e02141a4477e4 100644 --- a/cgi/oidc/token.pl +++ b/cgi/oidc/token.pl @@ -3,7 +3,7 @@ # This file is part of Product Opener. # # Product Opener -# Copyright (C) 2011-2016 Association Open Food Facts +# Copyright (C) 2011-2018 Association Open Food Facts # Contact: contact@openfoodfacts.org # Address: 21 rue des Iles, 94100 Saint-Maur des Fossés, France # diff --git a/cgi/oidc/userinfo.pl b/cgi/oidc/userinfo.pl index 6cd1744279bfe..ffe8c01413ad5 100644 --- a/cgi/oidc/userinfo.pl +++ b/cgi/oidc/userinfo.pl @@ -3,7 +3,7 @@ # This file is part of Product Opener. # # Product Opener -# Copyright (C) 2011-2016 Association Open Food Facts +# Copyright (C) 2011-2018 Association Open Food Facts # Contact: contact@openfoodfacts.org # Address: 21 rue des Iles, 94100 Saint-Maur des Fossés, France # diff --git a/lib/ProductOpener/OIDC/Server.pm b/lib/ProductOpener/OIDC/Server.pm index 2dfcb0c89d387..c3b35fe11bb58 100644 --- a/lib/ProductOpener/OIDC/Server.pm +++ b/lib/ProductOpener/OIDC/Server.pm @@ -1,7 +1,7 @@ # This file is part of Product Opener. # # Product Opener -# Copyright (C) 2011-2016 Association Open Food Facts +# Copyright (C) 2011-2018 Association Open Food Facts # Contact: contact@openfoodfacts.org # Address: 21 rue des Iles, 94100 Saint-Maur des Fossés, France # diff --git a/lib/ProductOpener/OIDC/Server/DataHandler.pm b/lib/ProductOpener/OIDC/Server/DataHandler.pm index 4bcc5a1c6cbd7..091f887c94a44 100644 --- a/lib/ProductOpener/OIDC/Server/DataHandler.pm +++ b/lib/ProductOpener/OIDC/Server/DataHandler.pm @@ -1,7 +1,7 @@ # This file is part of Product Opener. # # Product Opener -# Copyright (C) 2011-2016 Association Open Food Facts +# Copyright (C) 2011-2018 Association Open Food Facts # Contact: contact@openfoodfacts.org # Address: 21 rue des Iles, 94100 Saint-Maur des Fossés, France # diff --git a/lib/ProductOpener/OIDC/Server/Request.pm b/lib/ProductOpener/OIDC/Server/Request.pm index e6e5d1ca6987d..68d8c928833d9 100644 --- a/lib/ProductOpener/OIDC/Server/Request.pm +++ b/lib/ProductOpener/OIDC/Server/Request.pm @@ -1,7 +1,7 @@ # This file is part of Product Opener. # # Product Opener -# Copyright (C) 2011-2016 Association Open Food Facts +# Copyright (C) 2011-2018 Association Open Food Facts # Contact: contact@openfoodfacts.org # Address: 21 rue des Iles, 94100 Saint-Maur des Fossés, France # diff --git a/lib/ProductOpener/OIDC/Server/Web/M/AccessToken.pm b/lib/ProductOpener/OIDC/Server/Web/M/AccessToken.pm index 30efc73d3b7c6..682907d628f7e 100644 --- a/lib/ProductOpener/OIDC/Server/Web/M/AccessToken.pm +++ b/lib/ProductOpener/OIDC/Server/Web/M/AccessToken.pm @@ -1,7 +1,7 @@ # This file is part of Product Opener. # # Product Opener -# Copyright (C) 2011-2016 Association Open Food Facts +# Copyright (C) 2011-2018 Association Open Food Facts # Contact: contact@openfoodfacts.org # Address: 21 rue des Iles, 94100 Saint-Maur des Fossés, France # diff --git a/lib/ProductOpener/OIDC/Server/Web/M/AuthInfo.pm b/lib/ProductOpener/OIDC/Server/Web/M/AuthInfo.pm index 352e6923be220..1c0f372420bcc 100644 --- a/lib/ProductOpener/OIDC/Server/Web/M/AuthInfo.pm +++ b/lib/ProductOpener/OIDC/Server/Web/M/AuthInfo.pm @@ -1,7 +1,7 @@ # This file is part of Product Opener. # # Product Opener -# Copyright (C) 2011-2016 Association Open Food Facts +# Copyright (C) 2011-2018 Association Open Food Facts # Contact: contact@openfoodfacts.org # Address: 21 rue des Iles, 94100 Saint-Maur des Fossés, France # diff --git a/lib/ProductOpener/OIDC/Server/Web/M/Client.pm b/lib/ProductOpener/OIDC/Server/Web/M/Client.pm index a83862873d0a5..e9f38f5773330 100644 --- a/lib/ProductOpener/OIDC/Server/Web/M/Client.pm +++ b/lib/ProductOpener/OIDC/Server/Web/M/Client.pm @@ -1,7 +1,7 @@ # This file is part of Product Opener. # # Product Opener -# Copyright (C) 2011-2016 Association Open Food Facts +# Copyright (C) 2011-2018 Association Open Food Facts # Contact: contact@openfoodfacts.org # Address: 21 rue des Iles, 94100 Saint-Maur des Fossés, France # diff --git a/lib/ProductOpener/OIDC/Server/Web/M/ResourceOwner.pm b/lib/ProductOpener/OIDC/Server/Web/M/ResourceOwner.pm index c03f0f49e91fc..6e4a1a9e9fede 100644 --- a/lib/ProductOpener/OIDC/Server/Web/M/ResourceOwner.pm +++ b/lib/ProductOpener/OIDC/Server/Web/M/ResourceOwner.pm @@ -1,7 +1,7 @@ # This file is part of Product Opener. # # Product Opener -# Copyright (C) 2011-2016 Association Open Food Facts +# Copyright (C) 2011-2018 Association Open Food Facts # Contact: contact@openfoodfacts.org # Address: 21 rue des Iles, 94100 Saint-Maur des Fossés, France # From af8966ff8ed489ff8ca06623bce3c28cbeb30628 Mon Sep 17 00:00:00 2001 From: hangy Date: Tue, 5 Jun 2018 18:36:49 +0200 Subject: [PATCH 35/36] Add OIDC to docker configs. --- docker/backend-git/conf/Config.pm | 4 ++- docker/backend-git/conf/Config2.pm | 44 ++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/docker/backend-git/conf/Config.pm b/docker/backend-git/conf/Config.pm index efdabaefa2d8b..1804e69cf9fc9 100644 --- a/docker/backend-git/conf/Config.pm +++ b/docker/backend-git/conf/Config.pm @@ -42,6 +42,7 @@ BEGIN $facebook_app_id $facebook_app_secret + $oidc $csrf_secret $mongodb @@ -161,7 +162,8 @@ $data_root = $ProductOpener::Config2::data_root; $facebook_app_id = $ProductOpener::Config2::facebook_app_id; $facebook_app_secret = $ProductOpener::Config2::facebook_app_secret; -$csrf_secret = $Blogs::Config2::csrf_secret; +$oidc = $ProductOpener::Config2::oidc; +$csrf_secret = $ProductOpener::Config2::csrf_secret; $reference_timezone = 'Europe/Paris'; diff --git a/docker/backend-git/conf/Config2.pm b/docker/backend-git/conf/Config2.pm index 577cab44980d5..67f79e8b74869 100644 --- a/docker/backend-git/conf/Config2.pm +++ b/docker/backend-git/conf/Config2.pm @@ -37,6 +37,7 @@ BEGIN $mongodb_host $facebook_app_id $facebook_app_secret + $oidc $csrf_secret ); @@ -59,6 +60,49 @@ $mongodb_host = "mongodb://mongodb:27017"; $facebook_app_id = ""; $facebook_app_secret = ""; +$oidc = { + 'id_token' => { + 'iss' => $server_domain, + 'expires_in' => 3600, + 'pub_key' => "-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5XxKc3Rz/8EakvZG+Ez9 +nCpdn2HGVq0CRD1GZ/fEuM7nHfmy1LzC0VyNa8YkU7Qrb4s/BgSxjFrLvbpFHcUo +o+D3W5TvCR+8RLJLISmUoo4jMb2wDq35DEaJa3j1lGb7o93msFwlSRLwbmcYw08F +H3NEB1IQxkfAv6Z/ddzjDzV1nhbQO/RnO4v4JJ8wR4xxLmo00AJJ7fr8oL51aF9S +kjHU8cjvKFjGW4kcNxxm3+pMCzNtbJvUFQFeTQNBkWt9k83yKA5bhNwK1W4otRPs +l2bP0AyuJtA3tFdWSD1fvIA+l8rywrVom/RbDRUZkm1k+YgbyqyUgJbM5oJkGhwP +fwIDAQAB +-----END PUBLIC KEY-----", + 'priv_key' => "-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEA5XxKc3Rz/8EakvZG+Ez9nCpdn2HGVq0CRD1GZ/fEuM7nHfmy +1LzC0VyNa8YkU7Qrb4s/BgSxjFrLvbpFHcUoo+D3W5TvCR+8RLJLISmUoo4jMb2w +Dq35DEaJa3j1lGb7o93msFwlSRLwbmcYw08FH3NEB1IQxkfAv6Z/ddzjDzV1nhbQ +O/RnO4v4JJ8wR4xxLmo00AJJ7fr8oL51aF9SkjHU8cjvKFjGW4kcNxxm3+pMCzNt +bJvUFQFeTQNBkWt9k83yKA5bhNwK1W4otRPsl2bP0AyuJtA3tFdWSD1fvIA+l8ry +wrVom/RbDRUZkm1k+YgbyqyUgJbM5oJkGhwPfwIDAQABAoIBADYW6Jlz7k9u3Wuc +PrgRtYkUd0K00gHl/23EH48r2CNTKShojV0VLLoaHX80kaVlBwPghzdM7ehOEk2i +1N2ideTChqsAXKMC5uYuPAUR/uWdqO/1bMTY/qWFDqjVNtUGvPMvv0r8PRGPNDph +dHW8b1GtYnBzSF7j1KuXe9109dPEbLQHpOuZijPOGSupJ+pXRc4nRTSasJXLhtVC +2FAhyK/U8vXQbtSLfcsldK0oWCwmTi/OVLYioz4JulyDz6P98M2AZ+WG7OptNcFi +nt5WbX/1JUKpHsPMQhMv/UVVk9hihx0j7qhQw0iDEdx4Por4+sYLeqKLEFIYrf1f +KvAUwgkCgYEA9I7r2x7fxIH9tQczPBa053Nyp5kFgS5BFgXPWUHDPl0+WACDVpih +/w7YCZmdOYc3a2UncOaK1/G9Nh0BogCfl3kMTgDeuS810dB9vM2K684Ay/hnQhTL +vsLnVflFKbIeZWywLxq7/Rzi/gmrK+YoeLJpv2xdAxJQ4gHIfPJC2s0CgYEA8DjX +tpBWN6zn2ui/eKHhwtEKbA4BMHySQ8wtWQ5lLR9DbA8Hs9LxyeQUfvEBu2728/h8 +56acCn7D4H1VCBfwIWDKIu+jEqi+RE/kRPnCpV1iBHE0EuB/YN89UGIDpLKfgebK +bDbGbpuEeMfIedvO1FRn4g+P1vNzvWq28qcuq3sCgYEA7eLcT9//YIHlzSK81rVr +sTweiiKSNS9OBmMOZ89NYSuISkfted2srpK82NHBG0WJRgE2VV8cLaQrHikm/nPG +yavoqTO1csMWggphVLdHa8qOAdqWbrQV4HBsYLfBbCaj5JrN4nQJ6tMfhmbXRzNx +qL47mQWKkENPxBhh8hAhsf0CgYEAwrVQIynauEXtqAH/MEgGNWI6kFrJnANcipd0 +KjsAxxIQFAYauCbC1GGKO1odjU7j29wNYbYpxFf7bHop8eV1PZi2Ppr+EqGzlqsq +2r2Wh3Kpf/BBxQsyM9K+X+kSCuy9XQ00BYJgVEa5mSxV0m/XtUK08QasEA5EQcO9 +hfD8YwECgYEAyHiQ1yuMzVzNKDCUVo2UpiFiVIqOg0Yjoa/tt9MbBwAukYMhRkvU +2CI8jKu8KugD7NArWMpoPcIECk64roYZR0RH4MKWK46OHg2vwPGiOUaWjlsoUcNc +eH+MQwdClxQ8rTr2CxXZKffji8I2Vs9FUE+pep0s3gR072kix3EFdZc= +-----END RSA PRIVATE KEY-----", + }, +}; + $csrf_secret = "EYvfj3GDJnc2UPVqTwTGPgWC"; 1; From eddf3a4a9454fc429bb6c8aea4c7eddaa1529de1 Mon Sep 17 00:00:00 2001 From: hangy Date: Tue, 5 Jun 2018 19:08:54 +0200 Subject: [PATCH 36/36] Update copyrights. --- cgi/oidc/authorize.pl | 2 +- cgi/oidc/clients.pl | 2 +- cgi/oidc/jwks.pl | 2 +- cgi/oidc/openid-configuration.pl | 2 +- cgi/oidc/token.pl | 2 +- cgi/oidc/userinfo.pl | 2 +- lib/ProductOpener/OIDC/Server.pm | 2 +- lib/ProductOpener/OIDC/Server/DataHandler.pm | 2 +- lib/ProductOpener/OIDC/Server/Request.pm | 2 +- lib/ProductOpener/OIDC/Server/Web/M/AccessToken.pm | 2 +- lib/ProductOpener/OIDC/Server/Web/M/AuthInfo.pm | 2 +- lib/ProductOpener/OIDC/Server/Web/M/Client.pm | 2 +- lib/ProductOpener/OIDC/Server/Web/M/ResourceOwner.pm | 2 +- 13 files changed, 13 insertions(+), 13 deletions(-) diff --git a/cgi/oidc/authorize.pl b/cgi/oidc/authorize.pl index dec9738c3c0c2..5add0ad94f298 100644 --- a/cgi/oidc/authorize.pl +++ b/cgi/oidc/authorize.pl @@ -3,7 +3,7 @@ # This file is part of Product Opener. # # Product Opener -# Copyright (C) 2011-2016 Association Open Food Facts +# Copyright (C) 2011-2018 Association Open Food Facts # Contact: contact@openfoodfacts.org # Address: 21 rue des Iles, 94100 Saint-Maur des Fossés, France # diff --git a/cgi/oidc/clients.pl b/cgi/oidc/clients.pl index e1addb0b53d3a..ceb3e05361878 100644 --- a/cgi/oidc/clients.pl +++ b/cgi/oidc/clients.pl @@ -3,7 +3,7 @@ # This file is part of Product Opener. # # Product Opener -# Copyright (C) 2011-2016 Association Open Food Facts +# Copyright (C) 2011-2018 Association Open Food Facts # Contact: contact@openfoodfacts.org # Address: 21 rue des Iles, 94100 Saint-Maur des Fossés, France # diff --git a/cgi/oidc/jwks.pl b/cgi/oidc/jwks.pl index d06608ad04169..9bb763061f70e 100644 --- a/cgi/oidc/jwks.pl +++ b/cgi/oidc/jwks.pl @@ -3,7 +3,7 @@ # This file is part of Product Opener. # # Product Opener -# Copyright (C) 2011-2016 Association Open Food Facts +# Copyright (C) 2011-2018 Association Open Food Facts # Contact: contact@openfoodfacts.org # Address: 21 rue des Iles, 94100 Saint-Maur des Fossés, France # diff --git a/cgi/oidc/openid-configuration.pl b/cgi/oidc/openid-configuration.pl index 64f11e299a41a..691231490b59c 100644 --- a/cgi/oidc/openid-configuration.pl +++ b/cgi/oidc/openid-configuration.pl @@ -3,7 +3,7 @@ # This file is part of Product Opener. # # Product Opener -# Copyright (C) 2011-2016 Association Open Food Facts +# Copyright (C) 2011-2018 Association Open Food Facts # Contact: contact@openfoodfacts.org # Address: 21 rue des Iles, 94100 Saint-Maur des Fossés, France # diff --git a/cgi/oidc/token.pl b/cgi/oidc/token.pl index 4f7c9ba0008a0..e02141a4477e4 100644 --- a/cgi/oidc/token.pl +++ b/cgi/oidc/token.pl @@ -3,7 +3,7 @@ # This file is part of Product Opener. # # Product Opener -# Copyright (C) 2011-2016 Association Open Food Facts +# Copyright (C) 2011-2018 Association Open Food Facts # Contact: contact@openfoodfacts.org # Address: 21 rue des Iles, 94100 Saint-Maur des Fossés, France # diff --git a/cgi/oidc/userinfo.pl b/cgi/oidc/userinfo.pl index 6cd1744279bfe..ffe8c01413ad5 100644 --- a/cgi/oidc/userinfo.pl +++ b/cgi/oidc/userinfo.pl @@ -3,7 +3,7 @@ # This file is part of Product Opener. # # Product Opener -# Copyright (C) 2011-2016 Association Open Food Facts +# Copyright (C) 2011-2018 Association Open Food Facts # Contact: contact@openfoodfacts.org # Address: 21 rue des Iles, 94100 Saint-Maur des Fossés, France # diff --git a/lib/ProductOpener/OIDC/Server.pm b/lib/ProductOpener/OIDC/Server.pm index 2dfcb0c89d387..c3b35fe11bb58 100644 --- a/lib/ProductOpener/OIDC/Server.pm +++ b/lib/ProductOpener/OIDC/Server.pm @@ -1,7 +1,7 @@ # This file is part of Product Opener. # # Product Opener -# Copyright (C) 2011-2016 Association Open Food Facts +# Copyright (C) 2011-2018 Association Open Food Facts # Contact: contact@openfoodfacts.org # Address: 21 rue des Iles, 94100 Saint-Maur des Fossés, France # diff --git a/lib/ProductOpener/OIDC/Server/DataHandler.pm b/lib/ProductOpener/OIDC/Server/DataHandler.pm index 4bcc5a1c6cbd7..091f887c94a44 100644 --- a/lib/ProductOpener/OIDC/Server/DataHandler.pm +++ b/lib/ProductOpener/OIDC/Server/DataHandler.pm @@ -1,7 +1,7 @@ # This file is part of Product Opener. # # Product Opener -# Copyright (C) 2011-2016 Association Open Food Facts +# Copyright (C) 2011-2018 Association Open Food Facts # Contact: contact@openfoodfacts.org # Address: 21 rue des Iles, 94100 Saint-Maur des Fossés, France # diff --git a/lib/ProductOpener/OIDC/Server/Request.pm b/lib/ProductOpener/OIDC/Server/Request.pm index e6e5d1ca6987d..68d8c928833d9 100644 --- a/lib/ProductOpener/OIDC/Server/Request.pm +++ b/lib/ProductOpener/OIDC/Server/Request.pm @@ -1,7 +1,7 @@ # This file is part of Product Opener. # # Product Opener -# Copyright (C) 2011-2016 Association Open Food Facts +# Copyright (C) 2011-2018 Association Open Food Facts # Contact: contact@openfoodfacts.org # Address: 21 rue des Iles, 94100 Saint-Maur des Fossés, France # diff --git a/lib/ProductOpener/OIDC/Server/Web/M/AccessToken.pm b/lib/ProductOpener/OIDC/Server/Web/M/AccessToken.pm index 30efc73d3b7c6..682907d628f7e 100644 --- a/lib/ProductOpener/OIDC/Server/Web/M/AccessToken.pm +++ b/lib/ProductOpener/OIDC/Server/Web/M/AccessToken.pm @@ -1,7 +1,7 @@ # This file is part of Product Opener. # # Product Opener -# Copyright (C) 2011-2016 Association Open Food Facts +# Copyright (C) 2011-2018 Association Open Food Facts # Contact: contact@openfoodfacts.org # Address: 21 rue des Iles, 94100 Saint-Maur des Fossés, France # diff --git a/lib/ProductOpener/OIDC/Server/Web/M/AuthInfo.pm b/lib/ProductOpener/OIDC/Server/Web/M/AuthInfo.pm index 352e6923be220..1c0f372420bcc 100644 --- a/lib/ProductOpener/OIDC/Server/Web/M/AuthInfo.pm +++ b/lib/ProductOpener/OIDC/Server/Web/M/AuthInfo.pm @@ -1,7 +1,7 @@ # This file is part of Product Opener. # # Product Opener -# Copyright (C) 2011-2016 Association Open Food Facts +# Copyright (C) 2011-2018 Association Open Food Facts # Contact: contact@openfoodfacts.org # Address: 21 rue des Iles, 94100 Saint-Maur des Fossés, France # diff --git a/lib/ProductOpener/OIDC/Server/Web/M/Client.pm b/lib/ProductOpener/OIDC/Server/Web/M/Client.pm index a83862873d0a5..e9f38f5773330 100644 --- a/lib/ProductOpener/OIDC/Server/Web/M/Client.pm +++ b/lib/ProductOpener/OIDC/Server/Web/M/Client.pm @@ -1,7 +1,7 @@ # This file is part of Product Opener. # # Product Opener -# Copyright (C) 2011-2016 Association Open Food Facts +# Copyright (C) 2011-2018 Association Open Food Facts # Contact: contact@openfoodfacts.org # Address: 21 rue des Iles, 94100 Saint-Maur des Fossés, France # diff --git a/lib/ProductOpener/OIDC/Server/Web/M/ResourceOwner.pm b/lib/ProductOpener/OIDC/Server/Web/M/ResourceOwner.pm index c03f0f49e91fc..6e4a1a9e9fede 100644 --- a/lib/ProductOpener/OIDC/Server/Web/M/ResourceOwner.pm +++ b/lib/ProductOpener/OIDC/Server/Web/M/ResourceOwner.pm @@ -1,7 +1,7 @@ # This file is part of Product Opener. # # Product Opener -# Copyright (C) 2011-2016 Association Open Food Facts +# Copyright (C) 2011-2018 Association Open Food Facts # Contact: contact@openfoodfacts.org # Address: 21 rue des Iles, 94100 Saint-Maur des Fossés, France #