Commit 93cb619c authored by Xavier Guimard's avatar Xavier Guimard

Merge branch 'v2.0' into master

parents ba638e50 f1e2848e
Pipeline #9965 failed with stages
in 24 minutes and 43 seconds
### Summary
Summarize the proposed new feature concisely
Summarize the desired feature concisely
### Design proposition
Detail your proposed implementation (interface design, architecture, impact on
current behavior,…)
Describe here the proposed implementation of this feature (interface design suggestions, general architecture, pseudo-code,…)
......@@ -31,7 +31,10 @@ Restrictions: superficial, skippable
# Use pkg-perl-autopkgtest test for runtime-deps-and-recommends
# Some portal suggested dependencies are added here
Test-Command: /usr/share/pkg-perl-autopkgtest/runner runtime-deps-and-recommends
Depends: @, @builddeps@, pkg-perl-autopkgtest, libyaml-perl, liblog-log4perl-perl, libauthen-pam-perl, libauthen-radius-perl, libweb-id-perl, libdatetime-format-rfc3339-perl
Depends: @, @builddeps@, pkg-perl-autopkgtest
, libyaml-perl, liblog-log4perl-perl
, libauthen-pam-perl, libauthen-radius-perl
, libweb-id-perl, libio-socket-timeout-perl
Restrictions: superficial
#Test-Command: ./debian/tests/runner heavy-deps
......
......@@ -34,6 +34,7 @@ and configure the following parameters:
Kerberos code to validate Kerberos ticket
- **Remove domain in username**: set to "enabled" to strip username
value and remove the '@domain'.
- **Allowed domains**: if set, tickets will only be accepted if they come from one of the domains listed here. This is a space-separated list. This feature can be useful when using :doc:`combination<authcombination>` and cross-realm Kerberos trusts.
.. attention::
......
......@@ -98,12 +98,12 @@ Exported attributes
For each attribute, you can set:
- **Key name**: name of the key in LemonLDAP::NG session (for example
"uid" will then be used as $uid in access rules)
- **Variable name**: name of the variable in LemonLDAP::NG session that will contain this attribute. For example
"uid" will then be used as $uid in access rules
- **Attribute name**: name of the SAML attribute coming from the remote IDP
- **Friendly Name**: optional, SAML attribute friendly name.
- **Mandatory**: if set to On, then session will not open if this
attribute is not given by IDP.
- **Name**: SAML attribute name.
- **Friendly Name**: optional, SAML attribute friendly name.
- **Format** (optional): SAML attribute format.
|image1|
......
......@@ -166,11 +166,16 @@ claim <http://openid.net/specs/openid-connect-core-1_0.html#StandardClaims>`__.
.. include:: openidconnectclaims.rst
So you can define for example:
For each OpenID Connect claim you want to release to applications, you can define:
- name => cn
- family_name => sn
- email => mail
* **Claim name**: the name of the claim as it will appear in Userinfo responses
* **Variable name**: the name of the LemonLDAP::NG session variable containing the claim value
* **Type**: the data type of the attribute. By default, a string. Choosing integer or boolean will make the claim appear as the corresponding JSON type.
* **Array**: choose how to process multi-valued attributes
* **Auto**: If the session key contains a single value, it will be released as a JSON number, string or boolean, depending on the previously specified type. If the session key contains multiple values, it will be released as an array of numbers, strings or booleans.
* **Always**: Return an array even if the attribute only contains one value
* **Never**: If the session key contains a single value, it will be released as a JSON number, string or boolean. If the session key contains multiple values, it will be released as a single string with a separator character.
.. attention::
......@@ -178,6 +183,7 @@ So you can define for example:
The specific ``sub`` attribute is not defined here, but
in User attribute parameter (see below).
Extra Claims
^^^^^^^^^^^^
......
......@@ -89,9 +89,9 @@ Exported attributes
For each attribute, you can set:
- **Key name**: name of the key in LemonLDAP::NG session
- **Name**: SAML attribute name.
- **Friendly Name**: optional, SAML attribute friendly name.
- **Variable name**: name of the variable in LemonLDAP::NG session
- **Attribute name**: name of the SAML attribute that will be seen by applications
- **Friendly Name**: optional, friendly name of the SAML attribute seen by applications
- **Mandatory**: if set to "On", then this attribute is required to
build the SAML response, an error will displayed if there is no value
for it. Optional attribute will be sent only if there is a value
......
......@@ -27,3 +27,4 @@ Plugins
resetcertificate
restservices
soapservices
stayconnected
......@@ -133,7 +133,7 @@ You have to run this command on Active Directory:
::
ktpass -princ HTTP/auth.example.com@EXAMPLE.COM -mapuser KERB_AUTH@EXAMPLE.COM -crypto DES-CBC-MD5 -ptype KRB5_NT_PRINCIPAL -mapOp set -pass <PASSWORD> -out c:\auth.keytab
ktpass -princ HTTP/auth.example.com@EXAMPLE.COM -mapuser KERB_AUTH@EXAMPLE.COM -crypto All -ptype KRB5_NT_PRINCIPAL -mapOp set -pass <PASSWORD> -out c:\auth.keytab
.. attention::
......
......@@ -25,7 +25,7 @@ authentication process, then use ``afterSub``, for example:
use constant afterSub => {
getUser => 'mysub',
}
};
sub mysub {
my ( $self ,$req ) = @_;
# Do something
......
......@@ -286,7 +286,7 @@ Name Description
:doc:`Reset certificate by mail<resetcertificate>` [11]_\ |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>`
Stay connected |new| Enable persistent connection on same browser
:doc:`Stay connected<stayconnected>` |new| Enable persistent connection on same browser
Upgrade session |new| This plugin explains to an already authenticated user that a higher authentication level is required to access the URL instead of reject him
==================================================================== ============================================================================================================================================
......
Stay connected plugin
=====================
This plugin enables persistent connection. It allows us to connect
automatically from the same browser.
Configuration
-------------
Just enable it in the manager (section “plugins”).
- **Parameters**:
- **Activation**: Enable / Disable this plugin
- **Expiration time**: Persistent session connection and cookie timeout
- **Cookie name**: Persistent connection cookie name
\ No newline at end of file
......@@ -17,6 +17,11 @@ backups and a rollback plan ready!
update.
2.0.10
------
- New dependency: IO::Socket::Timeout
2.0.9
-----
......
......@@ -349,6 +349,8 @@ sub defaultValues {
'SSLAuthnLevel' => 5,
'SSLVar' => 'SSL_CLIENT_S_DN_Email',
'SSLVarIf' => {},
'stayConnectedCookieName' => 'llngconnection',
'stayConnectedTimeout' => 2592000,
'successLoginNumber' => 5,
'timeout' => 72000,
'timeoutActivity' => 0,
......
......@@ -393,8 +393,31 @@ sub _oidcMetaDataNodes {
my ( $id, $resp ) = ( 1, [] );
# Handle RP Attributes
if ($query eq "oidcRPMetaDataExportedVars") {
my $pk = eval { $self->getConfKey( $req, $query )->{$partner} } // {};
return $self->sendError( $req, undef, 400 ) if ( $req->error );
foreach my $h ( sort keys %$pk ) {
# Set default values for type and array
my $data = [ split /;/, $pk->{$h} ];
unless ( $data->[1]) {
$data->[1] = "string";
}
unless ( $data->[2]) {
$data->[2] = "auto";
}
push @$resp,
{
id => "oidc${type}MetaDataNodes/$partner/$query/" . $id++,
title => $h,
data => $data,
type => 'oidcAttribute',
};
}
return $self->sendJSONresponse( $req, $resp );
}
# Return all exported attributes if asked
if ( $query =~
elsif ( $query =~
/^(?:oidc${type}MetaDataExportedVars|oidcRPMetaDataOptionsExtraClaims|oidcRPMetaDataMacros)$/
)
{
......
......@@ -44,7 +44,7 @@ our $authParameters = {
facebookParams => [qw(facebookAuthnLevel facebookExportedVars facebookAppId facebookAppSecret facebookUserField)],
githubParams => [qw(githubAuthnLevel githubClientID githubClientSecret githubUserField githubScope)],
gpgParams => [qw(gpgAuthnLevel gpgDb)],
kerberosParams => [qw(krbAuthnLevel krbKeytab krbByJs krbRemoveDomain)],
kerberosParams => [qw(krbAuthnLevel krbKeytab krbByJs krbRemoveDomain krbAllowedDomains)],
ldapParams => [qw(ldapAuthnLevel ldapExportedVars ldapServer ldapPort ldapVerify ldapBase managerDn managerPassword ldapTimeout ldapIOTimeout ldapVersion ldapRaw ldapCAFile ldapCAPath LDAPFilter AuthLDAPFilter mailLDAPFilter ldapSearchDeref ldapGroupBase ldapGroupObjectClass ldapGroupAttributeName ldapGroupAttributeNameUser ldapGroupAttributeNameSearch ldapGroupDecodeSearchedValue ldapGroupRecursive ldapGroupAttributeNameGroup ldapPpolicyControl ldapSetPassword ldapChangePasswordAsUser ldapPwdEnc ldapUsePasswordResetAttribute ldapPasswordResetAttribute ldapPasswordResetAttributeValue ldapAllowResetExpiredPassword ldapITDS)],
linkedinParams => [qw(linkedInAuthnLevel linkedInClientID linkedInClientSecret linkedInFields linkedInUserField linkedInScope)],
nullParams => [qw(nullAuthnLevel)],
......
......@@ -192,6 +192,8 @@ sub sendError {
: $code == 400 ? 'Bad request'
: 'Error'
);
# TODO: this should probably use a template instead
my $s = "<html><head><title>$title</title>
<style>
body{background:#000;color:#fff;padding:10px 50px;font-family:sans-serif;}a{text-decoration:none;color:#fff;}h1{text-align:center;}
......@@ -203,18 +205,25 @@ body{background:#000;color:#fff;padding:10px 50px;font-family:sans-serif;}a{text
<center><a href=\"https://lemonldap-ng.org\">LemonLDAP::NG</a></center>
</body>
</html>";
return [
$code,
[
'Content-Type' => 'text/html; charset=utf-8',
'Content-Length' => length($s),
$req->spliceHdrs,
],
[$s]
];
return $self->sendRawHtml( $req, $s, code => $code );
}
}
sub sendRawHtml {
my ( $self, $req, $s, %args ) = @_;
my $code = $args{code} || 200;
my $headers = $args{headers} || [ $req->spliceHdrs ];
return [
$code,
[
'Content-Type' => 'text/html; charset=utf-8',
'Content-Length' => length($s),
@{$headers},
],
[$s]
];
}
sub abort {
my ( $self, $err ) = @_;
eval { $self->logger->error($err) };
......@@ -250,7 +259,7 @@ sub sendJs {
return [
200,
[
'Content-Type' => 'application/javascript',
'Content-Type' => 'application/json',
'Content-Length' => length($s),
'Cache-Control' => 'public,max-age=2592000',
],
......
......@@ -197,9 +197,14 @@ sub custom {
# @return user identifier to log
sub userId {
my ( $self, $req ) = @_;
return $req->userData->{ $Lemonldap::NG::Handler::Main::tsv->{whatToTrace}
my $userId =
$req->userData->{ $Lemonldap::NG::Handler::Main::tsv->{whatToTrace}
|| '_whatToTrace' }
|| $req->userData->{'_user'} # Fix 2377
|| 'anonymous';
$self->logger->debug("Returned userId: $userId");
return $userId;
}
## @method boolean group(string group)
......
......@@ -624,7 +624,7 @@ sub substitute {
$expr =~ s/\bskip\b/q\{999_SKIP\}/g;
# handle inGroup
$expr =~ s/\binGroup\(([^)]*)\)/listMatch(\$s->{'hGroups'},$1,1),/g;
$expr =~ s/\binGroup\(([^)]*)\)/listMatch(\$s->{'hGroups'},$1,1)/g;
return $expr;
}
......
......@@ -128,6 +128,8 @@ site/htdocs/static/forms/longtext.html
site/htdocs/static/forms/menuApp.html
site/htdocs/static/forms/menuCat.html
site/htdocs/static/forms/mini.html
site/htdocs/static/forms/oidcAttribute.html
site/htdocs/static/forms/oidcAttributeContainer.html
site/htdocs/static/forms/oidcOPMetaDataNode.html
site/htdocs/static/forms/oidcOPMetaDataNodeContainer.html
site/htdocs/static/forms/oidcRPMetaDataNode.html
......
......@@ -90,8 +90,7 @@ qr/^(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:[a-zA-Z][-a-
'form' => 'text',
'test' => sub {
my ( $val, $conf ) = @_;
return 1
if defined $conf->{'macros'}{$val} or $val eq '_timezone';
return 1 if defined $conf->{'macros'}{$val} or $val =~ /^_/;
foreach $_ ( keys %$conf ) {
return 1
if $_ =~ /exportedvars$/i and defined $conf->{$_}{$val};
......@@ -114,6 +113,11 @@ qr/^(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:[a-zA-Z][-a-
1;
}
},
'oidcAttribute' => {
'test' => sub {
1;
}
},
'oidcmetadatajson' => {
'test' => sub {
1;
......@@ -1589,6 +1593,9 @@ qr/^(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:[a-zA-Z][-a-zA-Z0-
'key' => {
'type' => 'password'
},
'krbAllowedDomains' => {
'type' => 'text'
},
'krbAuthnLevel' => {
'default' => 3,
'type' => 'int'
......@@ -2183,7 +2190,9 @@ m[^(?:ldapi://[^/]*/?|\w[\w\-\.]*(?::\d{1,5})?|ldap(?:s|\+tls)?://\w[\w\-\.]*(?:
'family_name' => 'sn',
'name' => 'cn'
},
'type' => 'keyTextContainer'
'keyTest' => qr/\w/,
'test' => qr/\w/,
'type' => 'oidcAttributeContainer'
},
'oidcRPMetaDataMacros' => {
'default' => {},
......@@ -3923,6 +3932,16 @@ qr/^(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:[a-zA-Z][-a-
'default' => 0,
'type' => 'bool'
},
'stayConnectedCookieName' => {
'default' => 'llngconnection',
'msgFail' => '__badCookieName__',
'test' => qr/^[a-zA-Z][a-zA-Z0-9_-]*$/,
'type' => 'text'
},
'stayConnectedTimeout' => {
'default' => 2592000,
'type' => 'int'
},
'storePassword' => {
'default' => 0,
'type' => 'bool'
......
......@@ -433,7 +433,8 @@ sub buildZeroConf {
open( F, '>', $self->firstLmConfFile ) or die($!);
my $tmp = Lemonldap::NG::Manager::Conf::Zero::zeroConf(
'__DNSDOMAIN__', '__SESSIONDIR__',
'__PSESSIONDIR__', '__NOTIFICATIONDIR__', '__CACHEDIR__'
'__PSESSIONDIR__', '__NOTIFICATIONDIR__',
'__CACHEDIR__'
);
$tmp->{cfgNum} = 1;
print F $jsonEnc->encode($tmp);
......@@ -624,10 +625,10 @@ sub scanTree {
# Get data type and build tree
#
# Types : PerlModule bool boolOrExpr catAndAppList file hostname int
# keyTextContainer lmAttrOrMacro longtext openidServerList pcre
# rulesContainer samlAssertion samlAttributeContainer samlService
# select text trool url virtualHostContainer word
# password
# keyTextContainer lmAttrOrMacro longtext openidServerList
# oidcAttributeContainer pcre rulesContainer samlAssertion
# samlAttributeContainer samlService select text trool url
# virtualHostContainer word password
if ( $leaf =~ s/^\*// ) {
push @angularScopeVars, [ $leaf, "$path._nodes[$ord]" ];
......@@ -659,12 +660,18 @@ sub scanTree {
my $type = $attr->{type};
$type =~ s/Container//;
foreach my $k ( sort keys( %{ $attr->{default} } ) ) {
# Special handling for oidcAttribute
my $default = $attr->{default}->{$k};
if ( $attr->{type} eq 'oidcAttributeContainer' ) {
$default = [ $default, "string", "auto" ];
}
push @{ $jleaf->{default} },
{
id => "$prefix$leaf/$k",
title => $k,
type => $type,
data => $attr->{default}->{$k},
data => $default,
(
$type eq 'rule'
? ( re => $k )
......
......@@ -81,7 +81,7 @@ sub types {
my ( $val, $conf ) = @_;
return 1
if ( defined $conf->{macros}->{$val}
or $val eq '_timezone' );
or $val =~ m/^_/ );
foreach ( keys %$conf ) {
return 1
if ( $_ =~ /exportedvars$/i
......@@ -184,6 +184,9 @@ sub types {
menuCat => {
test => sub { 1 }
},
oidcAttribute => {
test => sub { 1 }
},
oidcOPMetaDataNode => {
test => sub { 1 }
},
......@@ -431,12 +434,25 @@ sub attributes {
flags => 'hmp',
},
stayConnected => {
type => 'bool',
#help => 'stayconnected.html',
type => 'bool',
default => 0,
documentation => 'Enable StayConnected plugin',
},
stayConnectedTimeout => {
type => 'int',
default => 2592000,
documentation =>
'StayConnected persistent connexion session timeout',
flags => 'm',
},
stayConnectedCookieName => {
type => 'text',
test => qr/^[a-zA-Z][a-zA-Z0-9_-]*$/,
msgFail => '__badCookieName__',
default => 'llngconnection',
documentation => 'Name of the stayConnected plugin cookie',
flags => 'p',
},
checkState => {
type => 'bool',
default => 0,
......@@ -3684,6 +3700,10 @@ m{^(?:ldapi://[^/]*/?|\w[\w\-\.]*(?::\d{1,5})?|ldap(?:s|\+tls)?://\w[\w\-\.]*(?:
default => 1,
documentation => 'Remove domain in Kerberos username',
},
krbAllowedDomains => {
type => 'text',
documentation => 'Allowed domains',
},
# Slave
slaveAuthnLevel => {
......@@ -4096,11 +4116,13 @@ m{^(?:ldapi://[^/]*/?|\w[\w\-\.]*(?::\d{1,5})?|ldap(?:s|\+tls)?://\w[\w\-\.]*(?:
# OpenID Connect relying parties
oidcRPMetaDataExportedVars => {
type => 'keyTextContainer',
type => 'oidcAttributeContainer',
keyTest => qr/\w/,
test => qr/\w/,
default => {
'name' => 'cn',
'family_name' => 'sn',
'email' => 'mail'
'email' => 'mail',
}
},
oidcRPMetaDataOptionsClientID => { type => 'text', },
......
......@@ -251,7 +251,8 @@ sub tree {
help => 'authkerberos.html',
nodes => [
'krbAuthnLevel', 'krbKeytab',
'krbByJs', 'krbRemoveDomain'
'krbByJs', 'krbRemoveDomain',
'krbAllowedDomains',
]
},
{
......@@ -616,11 +617,20 @@ sub tree {
title => 'plugins',
help => 'start.html#plugins',
nodes => [
'stayConnected',
'portalStatus',
'upgradeSession',
'refreshSessions',
'adaptativeAuthenticationLevelRules',
{
title => 'stayConnect',
help => 'stayconnected.html',
form => 'simpleInputContainer',
nodes => [
'stayConnected',
'stayConnectedTimeout',
'stayConnectedCookieName'
],
},
{
title => 'portalServers',
help => 'portalservers.html',
......
......@@ -407,6 +407,37 @@ sub _scanNodes {
hdebug(" $target");
$self->set( $target, $key, $leaf->{data} );
}
elsif ( $target =~ /^oidcRPMetaDataExportedVars$/ ) {
hdebug(" $target");
if ( $leaf->{cnodes} ) {
hdebug(' unopened');
$self->newConf->{$target}->{$key} =
$self->refConf->{$target}->{$oldName} // {};
}
elsif ($h) {
hdebug(' opened');
$self->confChanged(1);
my $tmp = $leaf->{data};
if ( ref( $leaf->{data} ) eq 'ARRAY' ) {
# Forward compatibility. If Type and Array have
# default values, store in old format
if ( $leaf->{data}->[1] eq "string"
and $leaf->{data}->[2] eq "auto" )
{
$tmp = $leaf->{data}->[0];
}
else {
$tmp = join ';', @{ $leaf->{data} };
}
}
$self->set( $target, $key, $leaf->{title}, $tmp );
}
else {
hdebug(" $target: looking for subnodes");
$self->_scanNodes($subNodes);
}
}
elsif (
$target =~ /^oidc(?:O|R)PMetaData(?:ExportedVars|Macros)$/ )
{
......
......@@ -377,6 +377,15 @@ llapp.controller 'TreeCtrl', [
type: 'samlAttribute'
data: ['0', 'New', '', '']
# OIDC attribute entry
$scope.addOidcAttribute = ->
node = $scope._findContainer()
node.nodes.push
id: "#{node.id}/n#{idinc++}"
title: 'new'
type: 'oidcAttribute'
data: ['', 'string', 'auto']
# Nodes with template
$scope.addVhost = ->
name = if $scope.domain then ".#{$scope.domain.data}" else '.example.com'
......
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title" trspan="oidcAttribute"></h3>
</div>
<table class="table">
<!-- Key Name -->
<tr>
<th><span trspan="claimName"></span></th>
<td><input id="oakinput" class="form-control" ng-model="currentNode.title"/></td>
</tr>
<!-- Name -->
<tr>
<th><span trspan="variableName"></span></th>