use lib 'inc'; use Test::More; use strict; use IO::String; use LWP::UserAgent; use LWP::Protocol::PSGI; use MIME::Base64; BEGIN { require 't/test-lib.pm'; require 't/oidc-lib.pm'; } my $debug = 'error'; my ( $op, $rp, $res ); LWP::Protocol::PSGI->register( sub { my $req = Plack::Request->new(@_); ok( $req->uri =~ m#http://auth.((?:o|r)p).com(.*)#, ' REST request' ); my $host = $1; my $url = $2; my ( $res, $client ); count(1); if ( $host eq 'op' ) { pass(" Request from RP to OP, endpoint $url"); $client = $op; } elsif ( $host eq 'rp' ) { pass(' Request from OP to RP'); $client = $rp; } else { fail(' Aborting REST request (external)'); return [ 500, [], [] ]; } if ( $req->method =~ /^post$/i ) { my $s = $req->content; ok( $res = $client->_post( $url, IO::String->new($s), length => length($s), type => $req->header('Content-Type'), ), ' Execute request' ); } else { ok( $res = $client->_get( $url, custom => { HTTP_AUTHORIZATION => $req->header('Authorization'), } ), ' Execute request' ); } ok( $res->[0] == 200, ' Response is 200' ); ok( getHeader( $res, 'Content-Type' ) =~ m#^application/json#, ' Content is JSON' ) or explain( $res->[1], 'Content-Type => application/json' ); count(4); return $res; } ); # Initialization ok( $op = op(), 'OP portal' ); ok( $res = $op->_get('/oauth2/jwks'), 'Get JWKS, endpoint /oauth2/jwks' ); expectOK($res); my $jwks = $res->[2]->[0]; ok( $res = $op->_get('/.well-known/openid-configuration'), 'Get metadata, endpoint /.well-known/openid-configuration' ); expectOK($res); my $metadata = $res->[2]->[0]; count(3); switch ('rp'); &Lemonldap::NG::Handler::Main::cfgNum( 0, 0 ); ok( $rp = rp( $jwks, $metadata ), 'RP portal' ); count(1); # Query RP for auth ok( $res = $rp->_get( '/', accept => 'text/html' ), 'Unauth SP request' ); count(1); my ( $url, $query ) = expectRedirection( $res, qr#http://auth.op.com(/oauth2/authorize)\?(.*)$# ); # Rewrite response_type to use implicit $query =~ s/response_type=code/response_type=code%20id_token%20token/; # Push request to OP switch ('op'); ok( $res = $op->_get( $url, query => $query, accept => 'text/html' ), "Push request to OP, endpoint $url" ); count(1); expectOK($res); # Try to authenticate with an unauthorized user to IdP $query = "user=french&password=french&$query&nonce=qwerty"; ok( $res = $op->_post( $url, IO::String->new($query), accept => 'text/html', length => length($query), ), "Post authentication, endpoint $url" ); count(1); ok( $res->[2]->[0] =~ /trmsg="90"/, 'Reject reason is 90' ) or print STDERR Dumper( $res->[2]->[0] ); count(1); # Initialization ok( $op = op(), 'OP portal' ); ok( $res = $op->_get('/oauth2/jwks'), 'Get JWKS, endpoint /oauth2/jwks' ); expectOK($res); $jwks = $res->[2]->[0]; ok( $res = $op->_get('/.well-known/openid-configuration'), 'Get metadata, endpoint /.well-known/openid-configuration' ); expectOK($res); $metadata = $res->[2]->[0]; count(3); switch ('rp'); &Lemonldap::NG::Handler::Main::cfgNum( 0, 0 ); ok( $rp = rp( $jwks, $metadata ), 'RP portal' ); count(1); # Query RP for auth ok( $res = $rp->_get( '/', accept => 'text/html' ), 'Unauth SP request' ); count(1); ( $url, $query ) = expectRedirection( $res, qr#http://auth.op.com(/oauth2/authorize)\?(.*)$# ); # Rewrite response_type to use implicit $query =~ s/response_type=code/response_type=code%20id_token%20token/; # Push request to OP switch ('op'); ok( $res = $op->_get( $url, query => $query, accept => 'text/html' ), "Push request to OP, endpoint $url" ); count(1); expectOK($res); # Try to authenticate with an authorized user to IdP $query = "user=dwho&password=dwho&$query&nonce=qwerty"; ok( $res = $op->_post( $url, IO::String->new($query), accept => 'text/html', length => length($query), ), "Post authentication, endpoint $url" ); count(1); my $idpId = expectCookie($res); ## Bypass consent # my ( $host, $tmp ); # ( $host, $tmp, $query ) = expectForm( $res, '#', undef, 'confirm' ); # expectRedirection( $res, # qr#^http://auth.rp.com/?\?openidconnectcallback=1\#(.*)$# ); # ok( # $res = $op->_post( # $url, # IO::String->new($query), # accept => 'text/html', # cookie => "lemonldap=$idpId", # length => length($query), # ), # "Post confirmation, endpoint $url" # ); # count(1); ($query) = expectRedirection( $res, qr#^http://auth.rp.com/?\?openidconnectcallback=1\#(.*)$# ); my %prms = map { split /=/, $_ } split /&/, $query; ok( $prms{token_type}, ' token_type found' ); ok( $prms{session_state}, ' session_state found' ); ok( $prms{access_token}, ' access_token found' ); ok( $prms{id_token}, ' id_token found' ); ok( $prms{state}, ' state found' ); ok( $prms{session_state}, ' session_state found' ); count(6); my $id_token_payload = id_token_payload( $prms{id_token} ); ok( $id_token_payload->{c_hash}, "ID token contains c_hash" ); ok( $id_token_payload->{at_hash}, "ID token contains at_hash" ); is( $id_token_payload->{nonce}, "qwerty", "ID token contains nonce" ); count(3); my $at; ok( $at = $rp->p->_userDB->getUserInfo( 'op', $prms{access_token} ), 'Get access token' ); ok( $at->{name} eq 'Doctor Who', ' Get name' ); ok( $at->{family_name} eq 'Doctor Who', ' Get family_name' ); ok( $at->{sub} eq 'dwho', ' Get sub' ); count(4); #print STDERR Dumper($at); clean_sessions(); done_testing( count() ); sub op { return LLNG::Manager::Test->new( { ini => { logLevel => $debug, domain => 'idp.com', portal => 'http://auth.op.com', authentication => 'Demo', userDB => 'Same', issuerDBOpenIDConnectActivation => "1", issuerDBOpenIDConnectRule => '$uid eq "dwho"', oidcRPMetaDataExportedVars => { rp => { email => "mail", family_name => "cn", name => "cn" } }, oidcServiceAllowHybridFlow => 1, oidcServiceAllowImplicitFlow => 1, oidcServiceAllowAuthorizationCodeFlow => 1, oidcRPMetaDataOptions => { rp => { oidcRPMetaDataOptionsDisplayName => "RP", oidcRPMetaDataOptionsIDTokenExpiration => 3600, oidcRPMetaDataOptionsClientID => "rpid", oidcRPMetaDataOptionsIDTokenSignAlg => "HS512", oidcRPMetaDataOptionsBypassConsent => 1, oidcRPMetaDataOptionsClientSecret => "rpsecret", oidcRPMetaDataOptionsUserIDAttr => "", oidcRPMetaDataOptionsAccessTokenExpiration => 3600 } }, oidcOPMetaDataOptions => {}, oidcOPMetaDataJSON => {}, oidcOPMetaDataJWKS => {}, oidcServiceMetaDataAuthnContext => { 'loa-4' => 4, 'loa-1' => 1, 'loa-5' => 5, 'loa-2' => 2, 'loa-3' => 3 }, oidcServicePrivateKeySig => oidc_key_op_private_sig, oidcServicePublicKeySig => oidc_key_op_public_sig, } } ); } sub rp { my ( $jwks, $metadata ) = @_; return LLNG::Manager::Test->new( { ini => { logLevel => $debug, domain => 'rp.com', portal => 'http://auth.rp.com', authentication => 'OpenIDConnect', userDB => 'Same', oidcOPMetaDataExportedVars => { op => { cn => "name", uid => "sub", sn => "family_name", mail => "email" } }, oidcOPMetaDataOptions => { op => { oidcOPMetaDataOptionsCheckJWTSignature => 1, oidcOPMetaDataOptionsJWKSTimeout => 0, oidcOPMetaDataOptionsClientSecret => "rpsecret", oidcOPMetaDataOptionsScope => "openid profile", oidcOPMetaDataOptionsStoreIDToken => 0, oidcOPMetaDataOptionsDisplay => "", oidcOPMetaDataOptionsClientID => "rpid", oidcOPMetaDataOptionsConfigurationURI => "https://auth.op.com/.well-known/openid-configuration" } }, oidcOPMetaDataJWKS => { op => $jwks, }, oidcOPMetaDataJSON => { op => $metadata, } } } ); }