Commit e477a1ce authored by Christophe Maudoux's avatar Christophe Maudoux 🐛
Browse files

Append unit test & Doc (#2458)

parent 110974cc
Check DevOps plugin
===================
This plugin can be used to check the :doc:`DevOps<devopshandler>` file.
Configuration
-------------
Just enable it in the manager (section “plugins”).
Usage
-----
When enabled, ``/checkdevops`` URL path is handled by this plugin.
Then, you can paste a file to test your rules and headers.
Example
~~~~~~~
DevOps handler requires a rules.json file to define
access rules and headers:
.. code-block:: json
{
"rules": {
"^/admin": "$uid eq 'admin'",
"default": "accept"
},
"headers": {
"Auth-User": "$uid"
}
}
.. note::
This plugin displays ALL user session attributes except
the hidden ones.
You have to restrict access to specific users like DevOps teams
by setting an access rule like other VirtualHosts.
By example: ``$groups =~ /\bdevops\b/``
.. attention::
Be careful to not display secret attributes.
checkDevOps plugin uses hidden attributes option.
\ No newline at end of file
......@@ -8,6 +8,7 @@ Plugins
autosignin
bruteforceprotection
cda
checkdevops
checkstate
checkuser
viewer
......
......@@ -271,24 +271,25 @@ Name Description
:doc:`Auto Signin<autosignin>` |new| Auto Signin Addon
:doc:`Brute Force protection<bruteforceprotection>` |new| User must wait to log in after some failed login attempts
:doc:`CDA<cda>` Cross Domain Authentication
:doc:`Check DevOps<checkdevops>` [5]_ |new| Check DevOps handler file plugin
:doc:`Check state<checkstate>` |new| Check state plugin (test page)
:doc:`Check user<checkuser>` [5]_ |new| Check access rights, transmitted headers and session attibutes for a specific user and URL
:doc:`Check user<checkuser>` [6]_ |new| Check access rights, transmitted headers and session attibutes for a specific user and URL
:doc:`Configuration viewer<viewer>` |new| Edit WebSSO configuration in Read Only mode
:doc:`Context switching<contextswitching>` [6]_\ |new| Switch context other users
:doc:`Context switching<contextswitching>` [7]_\ |new| Switch context other users
:doc:`Custom<plugincustom>` Write a custom plugin
:doc:`Decrypt value<decryptvalue>` [7]_\ |image35| Decrypt ciphered values
:doc:`Display login history<loginhistory>`
:doc:`Decrypt value<decryptvalue>` [8]_\ |image35| Decrypt ciphered values
:doc:`Display login history<loginhistory>` Display Success/Fails logins
:doc:`Force Authentication<forcereauthn>` Force authentication to access to Portal
:doc:`Global Logout<globallogout>` [8]_ Suggest to close all opened sessions at logout
:doc:`Global Logout<globallogout>` [9]_ Suggest to close all opened sessions at logout
:doc:`Grant Sessions<grantsession>` Rules to apply before allowing a user to open a session
:doc:`Impersonation<impersonation>` [9]_\ |new| Allow users to use another identity
:doc:`Find user<finduser>` [10]_\ |new| Search for user account
:doc:`Notifications system<notifications>`
:doc:`Impersonation<impersonation>` [10]_\ |new| Allow users to use another identity
:doc:`Find user<finduser>` [11]_\ |new| Search for user account
:doc:`Notifications system<notifications>` DIsplay a message during log in process
:doc:`Portal Status<status>` Experimental portal status page
:doc:`Public pages<public_pages>` Enable public pages system
:doc:`Refresh session API<refreshsessionapi>` [11]_ Plugin that provides an API to refresh a user session
:doc:`Reset password by mail<resetpassword>`
:doc:`Reset certificate by mail<resetcertificate>` [12]_\ |image37| Allow users to reset their certificate
:doc:`Refresh session API<refreshsessionapi>` [12]_ Plugin that provides an API to refresh a user session
:doc:`Reset password by mail<resetpassword>` Send a mail to reset its password
:doc:`Reset certificate by mail<resetcertificate>` [13]_\ |image37| Allow users to reset their certificate
:doc:`REST services<restservices>` |new| REST server for :doc:`Proxy<authproxy>`
:doc:`SOAP services<soapservices>` |deprecated| SOAP server for :doc:`Proxy<authproxy>`
:doc:`Stay connected<stayconnected>` |new| Enable persistent connection on same browser
......@@ -306,12 +307,12 @@ Handlers are software control agents to be installed on your web servers
==================================================================== ========== ============================================================= =========================================== ================================================================================== =============================================== ======================================================================================================================
Handler type Apache LLNG FastCGI/uWSGI server (Nginx, or :doc:`SSOaaS<ssoaas>`) `Plack servers <https://plackperl.org>`__ Node.js ( `express apps <http://expressjs.com/>`__\ or :doc:`SSOaaS<ssoaas>`) :doc:`Self protected apps<selfmadeapplication>` Comment
==================================================================== ========== ============================================================= =========================================== ================================================================================== =============================================== ======================================================================================================================
Main *(default handler)* ✔ ✔ ✔ :doc:`Partial<nodehandler>` ** [13]_ ** ✔
Main *(default handler)* ✔ ✔ ✔ :doc:`Partial<nodehandler>` ** [14]_ ** ✔
:doc:`AuthBasic<handlerauthbasic>` ✔ ✔ ✔ ✔ Designed for some server-to-server applications
:doc:`CDA<cda>` ✔ ✔ ✔ ✔ For Cross Domain Authentication
:doc:`DevOps<devopshandler>` (:doc:`SSOaaS<ssoaas>`) |new| ✔ ✔ ✔ ✔ Allows application developers to define their own rules and headers inside their applications
:doc:`DevOpsST<devopssthandler>` (:doc:`SSOaaS<ssoaas>`) |new| ✔ ✔ ✔ ✔ Enables both :doc:`DevOps<devopshandler>` and :doc:`Service Token<servertoserver>`
:doc:`OAuth2<oauth2handler>` [14]_\ |new| ✔ ✔ ✔ ✔ Uses OpenID Connect/OAuth2 access token to check authentication and authorization, can be used to protect Web Services
:doc:`OAuth2<oauth2handler>` [15]_\ |new| ✔ ✔ ✔ ✔ Uses OpenID Connect/OAuth2 access token to check authentication and authorization, can be used to protect Web Services
:doc:`Secure Token<securetoken>` ✔ ✔ ✔ Designed to secure exchanges between a LLNG reverse-proxy and a remote app
:doc:`Service Token<servertoserver>` |new| *(Server-to-Server)* ✔ ✔ ✔ ✔ ✔ Designed to permit underlying requests *(API-Based Infrastructure)*
:doc:`Zimbra PreAuth<applications/zimbra>` ✔ ✔ ✔
......@@ -562,45 +563,49 @@ by your language code):
:doc:`Radius second factor<radius2f>` is available with LLNG ≥ 2.0.6
.. [4]
:doc:`Check DevOps file plugin<checkdevops>` are available with LLNG ≥
2.0.12
.. [5]
:doc:`Additional second factors<sfextra>` are available with LLNG ≥
2.0.6
.. [5]
.. [6]
:doc:`Check user plugin<checkuser>` is available with LLNG ≥ 2.0.3
.. [6]
.. [7]
:doc:`Context switching plugin<contextswitching>` is available with
LLNG ≥ 2.0.6
.. [7]
.. [8]
:doc:`Decrypt value plugin<decryptvalue>` is available with LLNG ≥
2.0.7
.. [8]
.. [9]
:doc:`Global Logout plugin<globallogout>` is available with LLNG ≥
2.0.7
.. [9]
.. [10]
:doc:`Impersonation plugin<impersonation>` is available with LLNG ≥
2.0.3
.. [10]
.. [11]
:doc:`Find user plugin<finduser>` is available with LLNG ≥
2.0.11
.. [11]
.. [12]
:doc:`Refresh session API plugin<refreshsessionapi>` is available
with LLNG ≥ 2.0.7
.. [12]
.. [13]
:doc:`Reset certificate by mail plugin<resetcertificate>` is
available with LLNG ≥ 2.0.7
.. [13]
.. [14]
:doc:`Node.js handler<nodehandler>` has not yet reached the same
level of functionalities
.. [14]
.. [15]
:doc:`OAuth2 Handler<oauth2handler>` is available with LLNG ≥ 2.0.4
.. |image0| image:: /icons/kthememgr.png
......
......@@ -107,6 +107,7 @@ lib/Lemonldap/NG/Portal/Plugins/AutoSignin.pm
lib/Lemonldap/NG/Portal/Plugins/BruteForceProtection.pm
lib/Lemonldap/NG/Portal/Plugins/CDA.pm
lib/Lemonldap/NG/Portal/Plugins/CertificateResetByMail.pm
lib/Lemonldap/NG/Portal/Plugins/CheckDevOps.pm
lib/Lemonldap/NG/Portal/Plugins/CheckState.pm
lib/Lemonldap/NG/Portal/Plugins/CheckUser.pm
lib/Lemonldap/NG/Portal/Plugins/ContextSwitching.pm
......@@ -389,6 +390,7 @@ site/templates/bootstrap/2fregisters.tpl
site/templates/bootstrap/captcha.tpl
site/templates/bootstrap/casBack2Url.tpl
site/templates/bootstrap/certificateReset.tpl
site/templates/bootstrap/checkdevops.tpl
site/templates/bootstrap/checklogins.tpl
site/templates/bootstrap/checkuser.tpl
site/templates/bootstrap/confirm.tpl
......@@ -632,6 +634,7 @@ t/43-MailPasswordReset.t
t/44-CertificateResetByMail-Demo.t
t/44-CertificateResetByMail-LDAP.t
t/50-IssuerGet.t
t/56-CheckDevOps.t
t/57-GlobalLogout-with-Double-cookies-Single-session.t
t/57-GlobalLogout-with-Double-cookies.t
t/57-GlobalLogout-without-Timer.t
......
......@@ -33,8 +33,8 @@ has ott => (
sub init {
my ($self) = @_;
$self->addAuthRoute( checkdevops => 'run', ['POST'] )
->addAuthRoute( checkdevops => 'display', ['GET'] );
$self->addAuthRoute( checkdevops => 'run', ['POST'] )
->addAuthRouteWithRedirect( checkdevops => 'display', ['GET'] );
return 1;
}
......@@ -148,7 +148,7 @@ sub run {
? 'allowed'
: 'forbidden'
}
} keys %{ $json->{rules} };
} sort keys %{ $json->{rules} };
my $rules_list = join ', ', map { "$_->{uri}:$_->{access}" } @$rules;
$self->logger->debug("CheckDevOps compiled rules: $rules_list");
......
use Test::More;
use strict;
use IO::String;
use JSON;
BEGIN {
require 't/test-lib.pm';
}
my $res;
my $file = '{
"rules": {
"^/deny": "deny",
"^/testno": "$uid ne qq{dwho}",
"^/testyes": "$uid eq qq{dwho}",
"default": "accept"
},
"headers": {
"User": "$uid",
"Mail": "$mail",
"Name": "$cn",
"UA": "$UA"
}
}';
my $bad_file = '{
"rules": {
"^/testno": "$uid ne qq{dwho}"
"default": "accept"
},
"headers": {
"User": "$uid",
}
}';
my $client = LLNG::Manager::Test->new( {
ini => {
logLevel => 'error',
authentication => 'Demo',
userDB => 'Same',
requireToken => 1,
checkDevOps => 1,
hiddenAttributes => 'mail UA'
}
}
);
## Try to authenticate
ok( $res = $client->_get( '/', accept => 'text/html' ), 'Get Menu', );
count(1);
my ( $host, $url, $query ) =
expectForm( $res, '#', undef, 'user', 'password', 'token' );
$query =~ s/user=/user=dwho/;
$query =~ s/password=/password=dwho/;
ok(
$res = $client->_post(
'/',
IO::String->new($query),
length => length($query),
accept => 'text/html',
),
'Auth query'
);
count(1);
my $id = expectCookie($res);
expectRedirection( $res, 'http://auth.example.com/' );
# CheckDevOps form
# ----------------
ok(
$res = $client->_get(
'/checkdevops',
cookie => "lemonldap=$id",
accept => 'text/html'
),
'CheckDevOps form',
);
( $host, $url, $query ) =
expectForm( $res, undef, '/checkdevops', 'checkDevOpsFile', 'token' );
ok( $res->[2]->[0] =~ m%<span trspan="checkDevOps">%,
'Found trspan="checkDevOps"' )
or explain( $res->[2]->[0], 'trspan="checkDevOps"' );
count(2);
# POST without token
# ------------------
ok(
$res = $client->_post(
'/checkdevops',
IO::String->new(''),
cookie => "lemonldap=$id",
length => 0,
accept => 'text/html'
),
'POST checkdevops without token'
);
ok( $res->[2]->[0] =~ m%<span trspan="PE81"></span%, 'Found PE_NOTOKEN' )
or explain( $res->[2]->[0], 'trspan="PE81"' );
count(2);
( $host, $url, $query ) =
expectForm( $res, undef, '/checkdevops', 'checkDevOpsFile', 'token' );
# POST bad file
# -------------
$query .= "&checkDevOpsFile=$bad_file";
ok(
$res = $client->_post(
'/checkdevops',
IO::String->new($query),
cookie => "lemonldap=$id",
length => length($query),
accept => 'text/html'
),
'POST checkdevops with bad file'
);
ok( $res->[2]->[0] =~ m%<span trspan="PE104"></span>%,
'Found PE_BAD_DEVOPS_FILE' )
or explain( $res->[2]->[0], 'trspan="PE104"' );
count(2);
( $host, $url, $query ) =
expectForm( $res, undef, '/checkdevops', 'checkDevOpsFile', 'token' );
# POST file
# ---------
$query .= "&checkDevOpsFile=$file";
ok(
$res = $client->_post(
'/checkdevops',
IO::String->new($query),
cookie => "lemonldap=$id",
length => length($query),
accept => 'text/html'
),
'POST checkdevops with file'
);
# Headers
ok( $res->[2]->[0] =~ m%<b><span trspan="headers">HEADERS</span></b>%,
'HEADERS' )
or explain( $res->[2]->[0], 'HEADERS' );
ok( $res->[2]->[0] =~ m%Name: Doctor Who<br/>%, 'Hearder Name found' )
or explain( $res->[2]->[0], 'Hearder Name' );
ok( $res->[2]->[0] =~ m%User: dwho<br/>%, 'Hearder User found' )
or explain( $res->[2]->[0], 'Hearder User' );
# Rules
ok( $res->[2]->[0] =~ m%<b><span trspan="rules">RULES</span></b>%, 'RULES' )
or explain( $res->[2]->[0], 'RULES' );
ok( $res->[2]->[0] =~ m%\^/testno: <span trspan="forbidden">%, 'testno' )
or explain( $res->[2]->[0], 'testno' );
ok( $res->[2]->[0] =~ m%default: <span trspan="allowed">%, 'default' )
or explain( $res->[2]->[0], 'default' );
ok( $res->[2]->[0] =~ m%\^/testyes: <span trspan="allowed">%, 'testyes' )
or explain( $res->[2]->[0], 'testyes' );
ok( $res->[2]->[0] =~ m%\^/deny: <span trspan="forbidden">%, 'deny' )
or explain( $res->[2]->[0], 'deny' );
ok( $res->[2]->[0] =~ m%\$uid eq qq{dwho}"%, 'file' )
or explain( $res->[2]->[0], 'file' );
count(10);
( $host, $url, $query ) =
expectForm( $res, undef, '/checkdevops', 'checkDevOpsFile', 'token' );
# POST file (json)
# ----------------
$query .= "&checkDevOpsFile=$file";
ok(
$res = $client->_post(
'/checkdevops',
IO::String->new($query),
cookie => "lemonldap=$id",
length => length($query),
),
'POST checkdevops with file'
);
ok( $res = eval { from_json( $res->[2]->[0] ) }, 'Response is JSON' )
or print STDERR "$@\n" . Dumper($res);
ok( $res->{ALERTE} eq 'alert-info', 'alert-info found' )
or print STDERR Dumper($res);
ok( $res->{FILE} =~ /headers/, 'headers found' )
or print STDERR Dumper($res);
ok( $res->{FILE} =~ /rules/, 'rules found' )
or print STDERR Dumper($res);
ok( $res->{FILE} =~ /"\$uid ne qq{dwho}"/, 'rule found' )
or print STDERR Dumper($res);
count(6);
# POST bad file (json)
# --------------------
ok(
$res = $client->_get(
'/checkdevops',
cookie => "lemonldap=$id",
accept => 'text/html'
),
'CheckDevOps form',
);
( $host, $url, $query ) =
expectForm( $res, undef, '/checkdevops', 'checkDevOpsFile', 'token' );
$query .= "&checkDevOpsFile=$bad_file";
ok(
$res = $client->_post(
'/checkdevops',
IO::String->new($query),
cookie => "lemonldap=$id",
length => length($query),
),
'POST checkdevops with file'
);
ok( $res = eval { from_json( $res->[2]->[0] ) }, 'Response is JSON' )
or print STDERR "$@\n" . Dumper($res);
ok( $res->{ALERTE} eq 'alert-danger', 'alert-danger found' )
or print STDERR Dumper($res);
ok( $res->{FILE} eq '', 'No file found' )
or print STDERR Dumper($res);
ok( $res->{MSG} eq 'PE104', 'PE104 found' )
or print STDERR Dumper($res);
ok( $res->{TOKEN} =~ /^\d{10}_\d+$/, 'Token found' )
or print STDERR Dumper($res);
count(7);
# POST with an expired token (json)
# ---------------------------------
ok(
$res = $client->_post(
'/checkdevops',
IO::String->new($query),
cookie => "lemonldap=$id",
length => length($query),
),
'POST checkdevops without token'
);
ok( $res = eval { from_json( $res->[2]->[0] ) }, 'Response is JSON' )
or print STDERR "$@\n" . Dumper($res);
ok( $res->{ALERTE} eq 'alert-warning', 'alert-warning found' )
or print STDERR Dumper($res);
ok( $res->{TOKEN} =~ /^\d{10}_\d+$/, 'Token found' )
or print STDERR Dumper($res);
ok( $res->{FILE} eq '', 'No file found' )
or print STDERR Dumper($res);
ok( $res->{MSG} eq 'PE82', 'PE82 found' )
or print STDERR Dumper($res);
count(6);
# POST without token (json)
# -------------------------
ok(
$res = $client->_post(
'/checkdevops',
IO::String->new(''),
cookie => "lemonldap=$id",
length => 0,
),
'POST checkdevops without token'
);
ok( $res = eval { from_json( $res->[2]->[0] ) }, 'Response is JSON' )
or print STDERR "$@\n" . Dumper($res);
ok( $res->{ALERTE} eq 'alert-warning', 'alert-warning found' )
or print STDERR Dumper($res);
ok( $res->{TOKEN} =~ /^\d{10}_\d+$/, 'Token found' )
or print STDERR Dumper($res);
ok( $res->{MSG} eq 'PE81', 'PE81 found' )
or print STDERR Dumper($res);
count(5);
$client->logout($id);
clean_sessions();
done_testing( count() );
......@@ -51,7 +51,7 @@ my $id = expectCookie($res);
expectRedirection( $res, 'http://auth.example.com/' );
# CheckUser form
# ------------------------
# --------------
ok(
$res = $client->_get(
'/checkuser',
......
......@@ -309,6 +309,12 @@ m@<form.+?action="(?:(?:http://([^/]+))?(/.*?)?|(#))".+method="(post|get)"@is,
m#<input.+?name="([^"]+)"[^>]+(?:value="([^"]*?)")?#gs,
%fields
);
# Add textarea
%fields = (
$res->[2]->[0] =~
m#<textarea.+?name="([^"]+)"[^>]+(?:value="([^"]*?)")?#gs,
%fields
);
my $query = buildForm( \%fields );
foreach my $f (@requiredFields) {
ok( exists $fields{$f}, qq{ Field "$f" is defined} );
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment