Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[WIP] OpenID Connect #1230

Closed
wants to merge 52 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
36d188d
WIP Open ID Connect Server based on OIDC::Lite.
hangy Sep 28, 2016
90af3c6
Expose new MongoDB connection via the OIDC Server class.
hangy Sep 28, 2016
f7a0cec
Use params method (Apache/CGI mod_perl?).
hangy Sep 28, 2016
8078a6e
Implement AuthInfo using MongoDB.
hangy Sep 28, 2016
1f861d3
View claims form
hangy Sep 28, 2016
eb07258
WIP authorize
hangy Sep 28, 2016
b43b279
Added WWW::CSRF to user related methods.
hangy Nov 29, 2015
1b6081a
Mocked Request class
hangy Sep 28, 2016
1c24b71
WIPFIXES
hangy Sep 28, 2016
2d39e7d
Use CGI::Plus as a built-in CSRF protection.
hangy Sep 29, 2016
bb2b731
Use the correct url_param sub instead of param.
hangy Sep 29, 2016
722ca4c
Removed CGI::Plus. Does not seem to set the cookie correctly in our e…
hangy Sep 29, 2016
8653943
Fix MongoDB insert/id.
hangy Sep 29, 2016
61789c6
Add redirect to client application.
hangy Sep 29, 2016
a0dbc92
Use Foundation 6 style for the buttons.
hangy Sep 29, 2016
5f4fa47
Use MongoDB for OpenID Connect clients, too.
hangy Sep 29, 2016
f372531
WIP client editing
hangy Sep 29, 2016
0f0dacd
WIP OpenID Connect Discovery
hangy Oct 1, 2016
d811417
WIP OpenID Connect userinfo endpoint
hangy Oct 1, 2016
13264fa
Preload OIDC packages on startup.
hangy Oct 6, 2016
2378ba4
[OIDC] Fix some perl errors.
hangy Oct 15, 2016
9d5cf25
Support OIDC form_post method.
hangy Nov 3, 2016
1e67e70
Use content negotiation for the UI language for the 'accounts' subdom…
hangy Nov 3, 2016
fd43f40
Support OIDC 'ui_locales'?
hangy Nov 3, 2016
a74d5ef
Merge branch 'csrf' into oidc
hangy Nov 12, 2016
c8ea63d
Check CSRF in OIDC authorize form.
hangy Nov 12, 2016
30d1dbf
Merge branch 'csrf' into oidc
hangy Nov 12, 2016
2fa0708
Merge branch 'master' into oidc
hangy Nov 26, 2016
dfabc18
Merge branch 'master' into oidc
hangy Dec 28, 2016
083b5f0
Merge branch 'issue/580-fix-perlcritic-issues' into oidc
hangy Dec 28, 2016
1a469c2
Merge branch 'fix-some-perl-warnings' into oidc
hangy Dec 28, 2016
fb98e3f
Merge branch 'ssl-subdomains' into oidc
hangy Dec 28, 2016
08ccb83
Enable Modern::Perl for OIDC stuff.
hangy Dec 28, 2016
d8a7660
Fix Perl::Critic::Policy::TestingAndDebugging::RequireUseStrict
hangy Dec 28, 2016
ec5655b
Merge branch 'issue/580-fix-perlcritic-issues' into oidc
hangy Dec 28, 2016
54be17b
Merge remote-tracking branch 'github/master' into oidc
hangy Feb 11, 2017
9288507
Merge branch 'master' into oidc
hangy Feb 9, 2018
7d0c8f3
Merge branch 'master' into oidc
hangy May 2, 2018
d383fdb
Upgrade some OIDC crypto dependencies.
hangy May 2, 2018
468b721
Use alpine based images for some Docker images.
hangy Jun 3, 2018
3adfcad
Remove *-git Dockerfiles, unify *base* Dockefiles into -dev.
hangy Jun 4, 2018
b3e75ef
Merge branch 'alpine' into oidc-alpine
hangy Jun 4, 2018
8a073e3
Remove HTML::Defang.
hangy Jun 4, 2018
3de4506
Merge branch 'no-defang' into alpine
hangy Jun 4, 2018
e931e04
Dereference scalars for some operations
hangy Jun 4, 2018
ed78a7e
Merge branch 'dereference-scalars' into alpine
hangy Jun 4, 2018
fdef941
Merge branch 'alpine' into oidc-alpine
hangy Jun 4, 2018
5ab601d
Add OIDC to docker configs.
hangy Jun 5, 2018
6e0c140
Update copyrights.
hangy Jun 5, 2018
af8966f
Add OIDC to docker configs.
hangy Jun 5, 2018
eddf3a4
Update copyrights.
hangy Jun 5, 2018
8ea1bd2
Merge branch 'oidc-alpine' into oidc
hangy Jun 5, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
308 changes: 308 additions & 0 deletions cgi/oidc/authorize.pl
Original file line number Diff line number Diff line change
@@ -0,0 +1,308 @@
#!/usr/bin/perl -W

# This file is part of Product Opener.
#
# Product Opener
# Copyright (C) 2011-2018 Association Open Food Facts
# Contact: [email protected]
# 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 <http://www.gnu.org/licenses/>.

use CGI::Carp qw(fatalsToBrowser);

use Modern::Perl '2012';
use utf8;

use ProductOpener::Config qw/:all/;
use ProductOpener::Display qw/:all/;
use ProductOpener::Users qw/:all/;
use ProductOpener::Lang 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 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 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
<form method="post" action="/cgi/session.pl">
<div class="row">
<div class="small-12 columns">
<label>$Lang{login_username_email}{$lc}
<input type="text" name="user_id" />
</label>
</div>
<div class="small-12 columns">
<label>$Lang{password}{$lc}
<input type="password" name="password" />
</label>
</div>
<div class="small-12 columns">
<label>
<input type="checkbox" name="remember_me" value="on" />
$Lang{remember_me}{$lc}
</label>
</div>
</div>
<input type="submit" name=".submit" value="$Lang{login_register_title}{$lc}" class="button small" />
</form>

HTML
;

return ($html, $status);
}

sub _show_authorize() {

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,
response_types => $RESPONSE_TYPES,
);

eval {
$ah->handle_request();
};
my $error;
my $client_info = $dh->get_client_info();
if ($error = $@) {
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
my $resource_owner_id = $dh->get_user_id_for_authorization;
my @scopes = split(/\s/, url_param('scope'));
my $claims = _get_resource_owner_claims($resource_owner_id, @scopes);

# confirm screen
my $html = _render_authorize({
status => q{valid},
scopes => \@scopes,
request_uri => $request_uri,
client_info => $client_info,
claims => $claims,
});

return ($html, undef);

}

sub _redirect() {

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,
response_types => $RESPONSE_TYPES,
);

my $res;
eval {
$ah->handle_request();
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{
$res = $ah->deny();
}
}else{
return _show_authorize();
}
};
my $error;
my $client_info = $dh->get_client_info();
if ($error = $@) {
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
my $resource_owner_id = $dh->get_user_id_for_authorization;
my @scopes = split(/\s/, url_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};
}

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);
}

}

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;
}

sub _render_authorize($) {

my (%data) = @_;

my $html = '<h2>Authorization Endpoint (Confirm)</h2>';
if ($data{status} and $data{status} ne q{valid}) {
$html .= '<div data-alert class="alert-box warning radius">' . $data{status} . '</div>';
}

if ($data{scopes}) {
$html .= 'This client would to access your claims by following scopes.<ul>';
for my $scope (%data{scopes}) {
$html .= '<li>' . $scope . '</li>';
}

$html .= '</ul>';
}

$html .= start_form()
. '<input class="alter button" type="submit" name="user_action" value="cancel">'
. '<input class="success button" type="submit" name="user_action" value="accept">'
. hidden(-name=>'csrf', -value=>generate_po_csrf_token($User_id), -override=>1)
. end_form();

return $html;

}

sub _render_post_form($) {

my ($res) = @_;

my $html = '<h2>Authorization Success</h2>';
$html .= "<form id='oidc' method='post' action='" . escapeHTML($res->{redirect_uri}) . "'>";
my $data;
if ($res->{query}) {
$data = $res->{query};
}
else {
$data = $res->{fragment};
}

$html .= "<input type='hidden' name='state' value='" . escapeHTML($data->{state}) . "'/>";
$html .= "<input type='hidden' name='id_token' value='" . escapeHTML($data->{id_token}) . "'/>";
$html .= "</form>";
$html .= '<script>document.addEventListener("DOMContentLoaded", function(event) { document.getElementById("oidc").submit(); });</script>';
return $html;

}
Loading